Deluge & Private Internet Access

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:

  1. 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 than tun0.
  2. It has to be ran as root/sudo due to using ifconfig 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

 

This entry was posted in Programming, Raspberry Pi, Shell Scripting. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *