[Full Guide] Set up PIA on Headless Ubuntu with Transmission and Port Forwarding with Auto-Reconnect
This is my complete guide to my extremely over-complicated setup. This will be very long in an attempt to be detailed and include as much code as I can. I welcome feedback and improvements. This should be mostly portable to any other version of linux. My preference is to use vi as an editor but use whatever you want. I'll leave my username as surfrock66...any time you see that change it to your username.
Before we begin, I have some components which may set the bar too high for this to be used by most people:
Thread: https://forums.cpanel.net/threads/can-cpanel-update-dynamic-ip-information-to-dns-records.261951/
Script (must be logged in): https://forums.cpanel.net/attachments/cpanel-dynamic-dns-sh-txt.16051/
On home-server I have my apache web root exported to tunnel-server via NFS as well as my home directory. Those will be mounted on tunnel-server under /mnt/home-server. I have also configured my Transmission download directory to go to home-server for easy file manipulation, but there's many ways to handle that task.
On tunnel-server, I installed PIA and openvpn using this guide:
https://helpdesk.privateinternetaccess.com/hc/en-us/articles/219438247-Installing-OpenVPN-PIA-on-Linux
Essentially:
https://helpdesk.privateinternetaccess.com/hc/en-us/articles/219460187-How-do-I-enable-port-forwarding-on-my-VPN-
I then set up a systemd service called openvpnauto to automatically start the PIA VPN using this guide here: https://www.htpcguides.com/autoconnect-private-internet-access-vpn-boot-linux/
Essentially:
Now that the VPN works, we need to set up Transmission. As a precursor, you should know a giant pain in the ass about this whole process...transmission doesn't respect setting changes unless the daemon is stopped, and every time the daemon is started it screws with the permissions and ownership of the settings file. There is no point doing anything with it as an unprivileged user, it has to be root. Other things in here shouldn't really be done as root, so separating tasks per-user is kind of important and a little nuanced. Use the following code to install transmission daemon on ubuntu server:
Once finished, we need to do the following to start the daemon (which will obscure the password you typed), stop it again, make a copy of the settings file, and lax the permissions on it for later updating by a non-root user. I'm also adding my user account to the transmission group:
One question at this point...why all the cron, why not run that in an openvpn up script so systemd can manage the service? Honestly...I couldn't get it to work and got fed up. My googling found a lot of evidence that up scripts are unreliable, especially with weird user account stuff. The cron job is a little more versatile and easier to debug, plus the longest downtime we're talking about is 5 minutes. Because this is how we're gonna manage that openvpnauto service, we need to disable it from starting on boot:
So there it is. All the pieces here can be adapted to other solutions. I also had to do some other steps...by making the transmission download directory exist over the nfs mount i had to make sure my UID's were consistent across systems. Both of these systems have 2 1GB NICs so I bonded them for redundancy, that's out of scope here. I would appreciate feedback or suggestions...I'm sure there's a bunch of stuff that would simplify this, but it's working well. Thanks for reading!
Before we begin, I have some components which may set the bar too high for this to be used by most people:
- A hosted website which is managed by cpanel. From there, I can manage subdomains (I use 2..home.domain.com and tunnel.domain.com)
- My Primary server; this can be any linux device on your network. The important requirements: a publically accessible web server supporting php (or any other dynamic language), nfs and ssh availability with password-less keyfile authentication. I will refer to it as home-server. For me, it also runs libresonic, pydio, minidlna, ttrss, dns, dhcp, and a bunch of other stuff so it's on all the time and very robust.
- My VPN computer, running ubuntu server headless. I will refer to it as tunnel-server.
Thread: https://forums.cpanel.net/threads/can-cpanel-update-dynamic-ip-information-to-dns-records.261951/
Script (must be logged in): https://forums.cpanel.net/attachments/cpanel-dynamic-dns-sh-txt.16051/
On home-server I have my apache web root exported to tunnel-server via NFS as well as my home directory. Those will be mounted on tunnel-server under /mnt/home-server. I have also configured my Transmission download directory to go to home-server for easy file manipulation, but there's many ways to handle that task.
On tunnel-server, I installed PIA and openvpn using this guide:
https://helpdesk.privateinternetaccess.com/hc/en-us/articles/219438247-Installing-OpenVPN-PIA-on-Linux
Essentially:
sudo apt-get install openvpn cd /etc/openvpn sudo wget https://www.privateinternetaccess.com/openvpn/openvpn.zip sudo unzip openvpn.zipWhen choosing a gateway, because we'll be needing port-forwarding, you need to choose one of the ones listed here:
https://helpdesk.privateinternetaccess.com/hc/en-us/articles/219460187-How-do-I-enable-port-forwarding-on-my-VPN-
I then set up a systemd service called openvpnauto to automatically start the PIA VPN using this guide here: https://www.htpcguides.com/autoconnect-private-internet-access-vpn-boot-linux/
Essentially:
sudo vi /etc/init.d/openvpnautoThen paste this...I used "CAToronto.ovpn" (which I renamed from "CA Toronto.ovpn" for filename simplicity) but so long as you're using a port forwarding enabled gateway, you'll be fine
#!/bin/sh ### BEGIN INIT INFO # Provides: OpenVPN Autoconnect # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: OpenVPN Autoconnect # Description: OpenVPN Autoconnect ### END INIT INFO # Documentation available at # http://refspecs.linuxfoundation.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptfunc.html # Debian provides some extra functions though . /lib/lsb/init-functions DAEMON_NAME="openvpnauto" DAEMON_USER=root DAEMON_PATH="/usr/sbin/openvpn" DAEMON_OPTS="--config /etc/openvpn/CAToronto.ovpn --auth-user-pass /etc/openvpn/login.txt" DAEMON_PWD="/etc/openvpn" DAEMON_DESC=$(get_lsb_header_val $0 "Short-Description") DAEMON_PID="/var/run/${DAEMON_NAME}.pid" DAEMON_NICE=0 DAEMON_LOG='/var/log/openvpnauto.log' [ -r "/etc/default/${DAEMON_NAME}" ] && . "/etc/default/${DAEMON_NAME}" do_start() { local result pidofproc -p "${DAEMON_PID}" "${DAEMON_PATH}" > /dev/null if [ $? -eq 0 ]; then log_warning_msg "${DAEMON_NAME} is already started" result=0 else log_daemon_msg "Starting ${DAEMON_DESC}" "${DAEMON_NAME}" touch "${DAEMON_LOG}" chown $DAEMON_USER "${DAEMON_LOG}" chmod u+rw "${DAEMON_LOG}" if [ -z "${DAEMON_USER}" ]; then start-stop-daemon --start --quiet --oknodo --background \ --nicelevel $DAEMON_NICE \ --chdir "${DAEMON_PWD}" \ --pidfile "${DAEMON_PID}" --make-pidfile \ --exec "${DAEMON_PATH}" -- $DAEMON_OPTS result=$? else start-stop-daemon --start --quiet --oknodo --background \ --nicelevel $DAEMON_NICE \ --chdir "${DAEMON_PWD}" \ --pidfile "${DAEMON_PID}" --make-pidfile \ --chuid "${DAEMON_USER}" \ --exec "${DAEMON_PATH}" -- $DAEMON_OPTS result=$? fi log_end_msg $result fi return $result } do_stop() { local result pidofproc -p "${DAEMON_PID}" "${DAEMON_PATH}" > /dev/null if [ $? -ne 0 ]; then log_warning_msg "${DAEMON_NAME} is not started" result=0 else log_daemon_msg "Stopping ${DAEMON_DESC}" "${DAEMON_NAME}" killproc -p "${DAEMON_PID}" "${DAEMON_PATH}" result=$? log_end_msg $result rm "${DAEMON_PID}" fi return $result } do_restart() { local result do_stop result=$? if [ $result = 0 ]; then do_start result=$? fi return $result } do_status() { local result status_of_proc -p "${DAEMON_PID}" "${DAEMON_PATH}" "${DAEMON_NAME}" result=$? return $result } do_usage() { echo $"Usage: $0 {start | stop | restart | status}" exit 1 } case "$1" in start) do_start; exit $? ;; stop) do_stop; exit $? ;; restart) do_restart; exit $? ;; status) do_status; exit $? ;; *) do_usage; exit 1 ;; esacThen run the following to get the service up and running:
sudo chmod +x /etc/init.d/openvpnauto echo "IP Before VPN:" wget http://ipinfo.io/ip -qO - sudo systemctl daemon-reload sudo systemctl start openvpnauto.service echo "IP After VPN:" wget http://ipinfo.io/ip -qO -You SHOULD see 2 different IP's. That means the VPN is working. If it isn't, troubleshoot that before proceeding. If you want to test the VPN without all the systemd stuff, this command should work:
sudo /usr/sbin/openvpn --config /etc/openvpn/CAToronto.ovpn --auth-user-pass /etc/openvpn/login.txtThen run the IP check in another terminal session.
Now that the VPN works, we need to set up Transmission. As a precursor, you should know a giant pain in the ass about this whole process...transmission doesn't respect setting changes unless the daemon is stopped, and every time the daemon is started it screws with the permissions and ownership of the settings file. There is no point doing anything with it as an unprivileged user, it has to be root. Other things in here shouldn't really be done as root, so separating tasks per-user is kind of important and a little nuanced. Use the following code to install transmission daemon on ubuntu server:
sudo add-apt-repository ppa:transmissionbt/ppa sudo apt-get update sudo apt-get -y install transmission-cli transmission-common transmission-daemon sudo service transmission-daemon stop sudo vi /var/lib/transmission-daemon/info/settings.jsonYou need to update transmission's settings. The setting advice here is good: https://help.ubuntu.com/community/TransmissionHowTo but the important stuff is to disable whitelisting (for remote access from unknown IP's), change the umask to 2, change the username and password, and change your download directory. You can change anything else you like (except the ports!).
Once finished, we need to do the following to start the daemon (which will obscure the password you typed), stop it again, make a copy of the settings file, and lax the permissions on it for later updating by a non-root user. I'm also adding my user account to the transmission group:
sudo usermod -a -G debian-transmission surfrock66 sudo service transmission-daemon start sudo service transmission-daemon stop sudo cp /etc/transmission-daemon/settings.json /etc/transmission-daemon/settings.json.edits sudo chown surfrock66:debian-transmission /etc/transmission-daemon/settings.json.edits sudo chmod 664 /etc/transmission-daemon/settings.json.edits sudo service transmission-daemon startWe need to do some user permission nonsense. There needs to be some sudo rules in place so my unprivileged user can restart some services, and we also need to do some polkit work to enable access to the systemctl command. Here's the relevant lines from my /etc/sudoers file (to be edited with visudo):
Cmnd_Alias RESTART_VPN = /bin/systemctl restart openvpnauto.service surfrock66 ALL=(ALL) NOPASSWD: RESTART_VPNIf you run "pkaction --version" and get a value less than 0.106, you need to use localauthority syntax and create this file: /etc/polkit-1/localauthority/50-local.d/openvpnauto.pkla
[openvpnauto Permissions] Identity=unix-user:surfrock66 Action=org.freedesktop.systemd1.manage-units ResultAny=no ResultInactive=no ResultActive=auth_selfIf you have a version equal to or higher than 0.106, make this file: /etc/polkit-1/rules.d/10-manage-openvpnauto.rules:
// Allow alice to manage openvpnauto.service;
// fall back to implicit authorization otherwise.
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "openvpnauto.service" &&
subject.user == "surfrock66") {
return polkit.Result.YES;
}
});
Based on the code in this post, we need to come up with a way to grab and document a new forwarded port upon VPN connection. Once you establish a VPN connection, the API documented here will give you a single port within a 2 minute window: https://www.privateinternetaccess.com/forum/discussion/3359/port-forwarding-without-application-pia-script-advanced-users#latest. One quick task, we need to make a write-able file for storing the port that my regular user can write to:sudo touch /etc/openvpn/port sudo chmod 666 /etc/openvpn/portMy version of this (located in /etc/openvpn/openvpn.portForward.sh) does a bunch more stuff. It notifies me if there's an issue via mail with updating the port (I have exim4 set up to send mail...if you don't, you can just remove that). This code requests the new port, validates that it's there, writes it to a file, updates the STAGING VERSION of the transmission settings with the new port (both the RPC port and the peer communication port, which can be the same), invokes the cpanel dynamic dns script for the tunnel subdomain, then lastly puts the port in a file on the home-server. Because this script will be run as my regular user, this is the place to run the cpanel update script as it should NOT be run as root (and updating it within a 5 minute window triggered by a likely VPN reconnect makes more sense):
#!/usr/bin/env bash # Enable port forwarding when using Private Internet Access client_id=`head -n 100 /dev/urandom | sha256sum | tr -d " -"` json=`curl "http://209.222.18.222:2000/?client_id=$client_id" 2>/dev/null` echo "$json" if [ "$json" == "" ]; then echo "Error updating the port forward rule for tunnel-server's VPN" | mail -s "Tunel-Server VPN Port Forwarding Process Failed" [email protected] else port=$( echo "$json" | cut -c9-13 ) echo "$port" > /etc/openvpn/port sed -i -r "s|\"rpc-port\": [0-9][0-9]*,|\"rpc-port\": $port,|g" /etc/transmission-daemon/settings.json.edits sed -i -r "s|\"peer-port\": [0-9][0-9]*,|\"peer-port\": $port,|g" /etc/transmission-daemon/settings.json.edits /etc/openvpn/cpanel-dynamic-dns-all-tunnel-domain-com.sh /etc/openvpn/cpanel-dynamic-dns-tunnel-domain-com.sh echo "$port" > /mnt/home-server/.tunnel.port fi exit 0Now that we have this, we can manage the VPN connection testing and reconnect via a cronjob. I also have cronjobs for making sure tunnel.domain.com is updated every 10 minutes. The cronjob must be run as a normal user, so here is the output of my crontabs:
# DynDNS Setting */10 * * * * /bin/bash /etc/openvpn/cpanel-dynamic-dns-tunnel-domain-com.sh >/dev/null 2>&1 */10 * * * * /bin/bash /etc/openvpn/cpanel-dynamic-dns-all-tunnel-domain-com.sh >/dev/null 2>&1 # Start VPN on boot @reboot sudo /bin/systemctl restart openvpnauto.service; sleep 10; /bin/bash /etc/openvpn/openvpn.portForward.sh # Notify if VPN is down */5 * * * * localip=$(wget http://ipinfo.io/ip -qO -); remoteip=$(ssh home-server "wget http://ipinfo.io/ip -qO -"); if [ "$localip" = "$remoteip" ]; then echo -e "Remote IP: $remoteip\nLocal IP: $localip" | mail -s "Tunnel-Server VPN is Potentially Down!" [email protected]; sudo /bin/systemctl restart openvpnauto.service; sleep 10; /bin/bash /etc/openvpn/openvpn.portForward.sh; fiThis is a complex cronjob running every 5 minutes. First, it pulls the local IP and stores it in a variable (this should be a VPN-enabled IP). Then, it runs a remote ssh command on home-server to get the IP from there (non-VPN). It compares the 2. If they are different, send me an email (again, if you don't use mail/exim4, remove that) then restarts the VPN, waits 10 seconds, then refreshes the port forwarding.
One question at this point...why all the cron, why not run that in an openvpn up script so systemd can manage the service? Honestly...I couldn't get it to work and got fed up. My googling found a lot of evidence that up scripts are unreliable, especially with weird user account stuff. The cron job is a little more versatile and easier to debug, plus the longest downtime we're talking about is 5 minutes. Because this is how we're gonna manage that openvpnauto service, we need to disable it from starting on boot:
sudo systemctl disable openvpnauto.serviceSo, at this point, at will, you will get a new IP and port forwarding assignment automatically. Reboot your server, kill the service, test it out...it'll come back up (and notify you if you have that set up). Still, transmission doesn't know about the new port, and you won't really have a great way to navigate to the new URL. To update Transmission, I wrote the following script: /etc/openvpn/openvpn.refreshTransmissionSettings.sh
#!/bin/bash
sum1=$(md5sum /etc/transmission-daemon/settings.json | awk '{print $1}')
sum2=$(md5sum /etc/transmission-daemon/settings.json.edits | awk '{print $1}')
if [[ "$sum1" != "$sum2" ]]; then
systemctl stop transmission-daemon.service
cp /etc/transmission-daemon/settings.json.edits /etc/transmission-daemon/settings.json
systemctl start transmission-daemon.service
fi
Basically, this takes the editable version of the transmission settings, hashes it, if it's different than the hash of the running version, stop the daemon, copy the editable one to the real one, then restart the daemon. Due to the way transmission handles and updates the permissions of that settings file, this has to be done by root. To that end, I have a root cronjob running every 5 minutes (offset by 1) to check this:
# Refresh Transmission Settings 1-59/5 * * * * /bin/bash /etc/openvpn/openvpn.refreshTransmissionSettings.shOk, done. Tunnel-server will now keep itself up to date on all fronts. My subdomain tunnel.domain.com will update automatically, but how will I learn the new randomized port? Very simple...you'll remember we also have home.domain.com which is dynamically updated AND has a copy of the port in a file in the web root. I wrote the following php file and stuck it in there:
<?php
$lines = file('/var/www/.tunnel.port', FILE_IGNORE_NEW_LINES);
$port = $lines[0];
header('Location: http://tunnel.domain.com:' . $port);
?>
<html>
<body>
<a href="http://tunnel.domain.com:<?php echo $port; ?>">http://tunnel.domain.com:<?php echo $port; ?></a>
</body>
</html>
If I navigate to "http://home.domain.com/tunnel.php" it should immediately redirect me to my transmission instance. This has worked flawlessly since I set it up; sometimes there's a DNS update lag, but with a 5 minute TTL set for that subdomain on my hosting provider, it's never very long.So there it is. All the pieces here can be adapted to other solutions. I also had to do some other steps...by making the transmission download directory exist over the nfs mount i had to make sure my UID's were consistent across systems. Both of these systems have 2 1GB NICs so I bonded them for redundancy, that's out of scope here. I would appreciate feedback or suggestions...I'm sure there's a bunch of stuff that would simplify this, but it's working well. Thanks for reading!
Comments
Changes the IP, however starting the service doesn't change the IP. Here is status:
● openvpnauto.service - LSB: OpenVPN Autoconnect
Loaded: loaded (/etc/init.d/openvpnauto; generated; vendor preset: enabled)
Active: active (exited) since Sat 2018-02-24 19:09:20 UTC; 2min 49s ago
Docs: man:systemd-sysv-generator(8)
CGroup: /system.slice/openvpnauto.service
Feb 24 19:09:19 raspberrypi systemd[1]: Starting LSB: OpenVPN Autoconnect...
Feb 24 19:09:20 raspberrypi openvpnauto[437]: Starting OpenVPN Autoconnect: open
Feb 24 19:09:20 raspberrypi systemd[1]: Started LSB: OpenVPN Autoconnect.
Do you know why this is?
I got problems with native openvpn on trusty. used xfce4 for pia software
transmission behaves normal with permissions. I bind to ip too, tun0
/etc/init/transmission-daemon.conf user group set