I’ve been using Private Internet Access for about a year and a half now, and I’m pleased enough with the outcomes and cost. It’s simple enough to use on the Mac, and it can be run through OpenVPN. This is how I use it on my Raspberry Pis. Several of their servers offer port forwarding, which several applications need to work properly, such as Bittorrent clients, like Deluge. Finding out which port is open through the Mac application is simple (hover the mouse over the system tray icon and it appears at the end of the tool tip).
Finding it out on the Raspberry Pi requires a little bit of work. Fortunately, PIA provide an API for this purpose. The information is found out by using one’s log in details, local IP address and a curl
command:
curl -d "user=USERNAME&pass=PASSWORD&client_id=$(cat ~/.pia_client_id)&local_ip=LOCAL_IP" https://www.privateinternetaccess.com/vpninfo/port_forward_assignment
This returns the port in the following format:
{ "port": 23423 }
If we set the incoming port in Deluge to this, we find that the active port works when using the VPN. Success!
However: PIA say that we should run this at least every hour to check whether the port has changed. Manually checking the port every hour would be silly, and completely impractical if one wants an automated system. This is exactly the right situation for using cron
to run a script every hour, check the open port and change the incoming port options in Deluge if there is a discrepancy. I therefore took this opportunity to learn a little more about shell scripting and to finally get myself set up on GitHub.
About the Script
The script itself is pretty simple. It calls the PIA port forwarding API with user specific information set in the variables, cleans up the output, and compares it to the start and end ports currently in use by Deluge. If there is a mismatch, the script stops the daemon, modifies the configuration file (preserving the whitespace for the json
) and then relaunches the daemon.
There are two caveats to running the script:
- It uses
ifconfig
to find out what the local internet address is of the VPN tunnel. I provided a variable in case your relevant VPN tunnel is named something other thantun0
. - It has to be ran as
root/sudo
due to usingifconfig
and because it stops/starts a service.
Hopefully there are no security issues due to this, but please inform me if there are! Mistakes are a great way to learn, but I’d like to find out about them before running anyone’s or my own system.
I have saved the script in my /usr/local/bin folder so that I can run the script as a command from anywhere in the system. Remember to set the permissions of the script to allow it to be executed:
chmod +x deluge_check_ports
GitHub
I’d created a GitHub for myself three years ago. There I my first useful piece of Python coding, a script which edited the ID3 tags for a large number of old In Out Time podcast files I had downloaded. No one had touched that repository, including me, since I’d first uploaded it.
Anyway, the new script is held in this repository, entitled “PIA Deluge Ports” (at least for now). It contains the script, a licence and a read me. Pretty simple. Hopefully Git helps me keep track of what I’ve done (that is the point, after all), and lets other contribute if they find anything I’m doing useful.
What I’ve immediately found useful in the desktop client is that it displays the lines that have changed in red, and the new lines (with the changes) in green. This makes it very easy to comment on what the updates are, and see easily how much one has changed the file.
Set Up
In order to run the script, we have to schedule a cron
job to run every hour, and as we have to run it as root/sudo
, we should run it with the sudo
user’s crontab
:
sudo crontab -e
We then add the command we want to run, and how frequently we want it. For every hour, enter the time as every wildcards for every day, hour, day of the week, day of the month and mont, and specify a particular minute in the hour that we want it to be ran. I’ve gone for 36 here, as it’s good practice to runs scheduled tasks at random times so that they are not all running at once.
36 * * * * deluge_check_ports &> /var/log/deluge/check_ports_out.log
Note here I’ve also piped the standard out and standard error to a separate log file, just incase something else goes wrong I haven’t attempted to capture. This also means (hopefully) that the progress bar from curl will not appear on the 36th minute of every hour in my terminal window, interpreting other commands.
Edit: the PATH
variable
Unfortunately the script wasn’t running with this set up. Why? Well, as I feared but hadn’t feared enough to investigate, the sudo user's
cron
's PATH
variable did not include /usr/local/bin/
. So while I can run the script from anywhere on my system by issuing the command sudo deluge_check_ports
, this fails when running the cron job.
I followed the first post about failures of cron jobs here, which suggested adding the following line to your crontab
in order to see what environment variables are being used by cron
:
* * * * * env > /tmp/env.output
I found that my sudo crontab
was using the following PATH
variable:
PATH=/usr/bin:/bin
I decided to create a symbolic link to the file:
ln -s /usr/local/bin/deluge_check_ports deluge_check_ports
The Script
So, the script itself can be found here, but as it's not too long, here it is as well, after the fold.
#! /bin/bash # User Set Variables USER=$(awk NR==1 /etc/openvpn/pass.txt) PASS=$(awk NR==2 /etc/openvpn/pass.txt) TUN="tun0" DEL_USER_HOME="/home/pi" DEL_CONF_PATH="/home/pi/.config/deluge" DEL_CONF_BK_PATH="/home/pi/.config/deluge/config_bak" LOG_PATH="/var/log/deluge/check_ports.log" # Script Set Variables DEL_CONF="$DEL_CONF_PATH/core.conf" echo "$DEL_CONF" LOCAL_ADD=$(ifconfig "$TUN" | awk '/inet addr/ {print $2}' | awk 'BEGIN{FS=":"}{print $2}') # Find the current port FWD_PORT=$(curl -s -d "user=$USER&pass=$PASS&client_id=$(cat $DEL_USER_HOME/.pia_client_id)&local_ip=$LOCAL_ADD" https://www.privateinternetaccess.com/vpninfo/port_forward_assignment) # TO DO: fix flags on curl to stop progress info being posted to terminal PORT=$(echo ${FWD_PORT:8:5}) # Check we got something, that $PORT has a value: if [ -n $PORT ] then # Log the port this hour echo "$(date +"%D %T"): Current VPN forwarded port is: $PORT" > "$LOG_PATH" else # Stop the script & log the error echo "$(date +"%D %T"): ERROR: attempt at getting VPN forwarded port returned nothing" > "$LOG_PATH" exit 1 fi # Check the port in the current config file DELUGE_START_PORT=$(grep 'listen_ports' -A 2 "$DEL_CONF" | awk 'FNR==2 {print}') DELUGE_END_PORT=$(grep 'listen_ports' -A 2 "$DEL_CONF" | awk 'FNR==3 {print}') # The grep command returns three lines: # "listen_ports": [ # port_range_start # port_range_stop # We want to check the 2nd and 3rd lines against $PORT, and then alter the config file if they are different # Check the deluge port variables were filled by the grep commands; run them again then if they are still empty, # or not the right length (i.e. we accidently grep'd something else). # The string should be 9 characters long: 4 spaces and a 5 digit port number # (start port is 10 or 11 because of the trailing comma and (possible) space) # exit(2) = deluge_start_port is empty/not the right length # exit(3) = deluge_end_port is empty/not the right length if [ ! "${#DELUGE_START_PORT}" == 10 -a ! "${#DELUGE_START_PORT}" == 11 ] then DELUGE_START_PORT=$(grep 'listen_ports' -A 2 "$DEL_CONF" | awk 'FNR==2 {print}') if [ ! "${#DELUGE_START_PORT}" == 10 -a ! "${#DELUGE_START_PORT}" == 11 ] then echo "$(date +"%D %T"): ERROR: Could not obtain the start port of the Deluge incoming port range" >> "$LOG_PATH" exit 2 fi fi if [ ! "${#DELUGE_END_PORT}" == 9 ] then DELUGE_END_PORT=$(grep 'listen_ports' -A 2 "$DEL_CONF" | awk 'FNR==3 {print}') if [ ! "${#DELUGE_END_PORT}" == 9 ] then echo "$(date +"%D %T"): ERROR: Could not obtain the finish port of the Deluge incoming port range" >> "$LOG_PATH" exit 3 fi fi # Make sure the change variables are set to 0 each run CHANGE_START="0" CHNAGE_END="0" # Now do the comparison (and check it doesn't fail because the variable is empty) if [ " $PORT," == "$DELUGE_START_PORT" ] then echo "$(date +"%D %T"): Start port is the same" >> "$LOG_PATH" else echo "$(date +"%D %T"): Start port is: '$DELUGE_START_PORT'" >> "$LOG_PATH" echo "$(date +"%D %T"): This is different ..." >> "$LOG_PATH" CHANGE_START="1" fi if [ " $PORT" == "$DELUGE_END_PORT" ] then echo "$(date +"%D %T"): End port is the same" >> "$LOG_PATH" else echo "$(date +"%D %T"): End port is: '$DELUGE_END_PORT'" >> "$LOG_PATH" echo "$(date +"%D %T"): This is different ..." >> "$LOG_PATH" CHANGE_END="1" fi if [ "$CHANGE_START" == 1 -o "$CHANGE_END" == 1 ] then echo "$(date +"%D %T"): Changing the port" >> "$LOG_PATH" # Stop the deluge-daemon service deluge-daemon stop >> "$LOG_PATH" # Back up the config file in case something goes wrong cp --backup=t "$DEL_CONF" /home/pi/.config/deluge/config_baks/ # TO DO: add something to clean up the back ups if there's more than 10 say # Change the ports as needed: if [ "$CHANGE_START" == 1 ] then sed -i "s/$DELUGE_START_PORT/ $PORT,/" "$DEL_CONF" # Have to add the comma & preserve whitespace fi if [ "$CHANGE_END" == 1 ] then sed -i "s/$DELUGE_END_PORT/ $PORT/" "$DEL_CONF" # Preserve whitespace fi # Restart the daemon service deluge-daemon start >> "$LOG_PATH" fi