Bash: VPN Config Manager

January 14, 2018

Recently hopped on the VPN train and needed a better way to manage my settings via the command-line. Cobbled together a bash script to make controlling OpenVPN a little more convenient.

The script gets a list of all your configs, then lets you enable or disable individual or multiple instances of OpenVPN using the selected config file(s).

I’ve only used it on Ubuntu 16.04, so it relies on systemd for starting and stopping the OpenVPN service. Take a look through the file to see how it works and what settings you need to tweak. I tried to comment everything to make modifying easier.

#!/bin/bash
## OpenVPN Config Manager
## Script to help manage multiple configuration files for OpenVPN
##
## Last Updated: April 23 2018
## - Found a different way to get external ip
##
## January 12 2018
## Notes/Issues/Things to fix:
## - Only tested on Ubuntu 16.04
## - Uses systemd to control services
## - Also uses proxychains-ng to check proxy/vpn ip
##   but can be edited to use curl, wget, etc.
## - Multiple VPN shows "ready" message even though
##   second, third, etc interfaces are still starting up
## - Use the option to shutdown all VPNs to start from
##   a clean slate if it feels like nothing is happening
##
##
################################################
## Settings                                   ##
################################################

# Set directory that stores your config files
CONFIGDIR=/etc/openvpn/

# Change extension to .conf or .ovpn based on your setup
CONFIGS=($CONFIGDIR*.conf)

################################################
## Colors!                                    ##
################################################
c1='\e[0;32m'   c1b='\e[1;32m'  #green bold
c2='\e[0;31m'   c2b='\e[1;31m'  #red bold
c3='\e[0;34m'   c3b='\e[1;34m'  #blue bold
c4='\e[0;37m'   c4b='\e[1;37m'  #white bold
endc='\e[0m'                    #end color

################################################
## Activate Single VPN                        ##
################################################

# Ask user to select a config, list files or exit
function confSelect {

  while [[ ! $USRCONF =~ ^[0-9]+$ ]] || [[ "$USRCONF" -ge ${#CONFIGS[@]} ]];
  do
    printf "${c4}Input Config Number %s\n(L)ist configs (M)ain Menu (Q)uit):${endc} "
    read USRCONF

    if [[ "$USRCONF" =~ ^[Ll]+$ ]]
    then
      confList
    elif [[ "$USRCONF" =~ ^[Mm]+$ ]]
    then
      menuMain
    elif [[ "$USRCONF" =~ ^[Qq]+$ ]]
    then
      exit
    elif
    ! [[ "$USRCONF" =~ ^[0-9]+$ ]] && [[ ! "$USRCONF" =~ ^[Ll]+$ ]]
    then
      echo -e "${c2b}--- Invalid input: numbers only ---${endc}"
    elif [[ "$USRCONF" -ge ${#CONFIGS[@]} ]];
    then
      echo -e "${c2b}--- Invalid input: config not found ---${endc}"
    fi
  done

  echo -e "${c4b}You selected:${endc} ${c1b} ${CONFIGS[$USRCONF]##*/} ${endc}\n"
}

# Launch OpenVPN with the selected config
function confRun {
  printf "${c3b}Running${endc} OpenVPN with selected config... %s\t%s\n\n"

# Stop active instances of OpenVPN
  printf "${c2b}Stopping${endc} active process... %s\t%s\n\n"
  sudo systemctl stop openvpn
  sleep 2

  printf "${c1}Starting${endc} service using ${c1b}${CONFIGS[$USRCONF]##*/}${endc}...
  %s\t%s\n\n"

# Filter out file extension for use in openvpn@ command
  CONFSEL=${CONFIGS[$USRCONF]##*/}

# Start OpenVPN service using the selected config file
  sudo systemctl start openvpn@${CONFSEL%.*}

}

################################################
## Activate Multiple VPNs                     ##
################################################

# Ask user to select multiple configs, list files or exit
function confMultiSel {

  while [[ ! "$usrinput" == "D" ]]
  do
    printf "${c4}Input Config Number (press enter after each input) %s\n(L)ist configs (D)one Adding Configs (R)eset selection (M)ain menu (Q)uit):${endc} "
    read usrinput
# Check if user wants to list configs, quit, or reset selection
# Then validate input for numbers or valid configs
    if [[ "$usrinput" =~ ^[Ll]+$ ]]
    then
      confList
    elif [[ "$usrinput" =~ ^[Qq]+$ ]]
    then
      exit
    elif [[ "$usrinput" =~ ^[Mm]+$ ]]
    then
      menuMain
    elif [[ "$usrinput" =~ ^[Rr]+$ ]]
    then
      unset 'USRCONF[@]'
    elif ! [[ "$usrinput" =~ ^[0-9]+$ ]] && [[ ! "$usrinput" =~ ^[Dd]+$  ]]
      then
      echo -e "${c2b}--- Invalid input: numbers only ---${endc}"
    elif [[ "$usrinput" -ge "${#CONFIGS[@]}" ]]
    then
      echo -e "${c2b}--- Invalid input: config not found ---${endc}"
    else
      USRCONF+=($usrinput)
    fi

  done
# Remove end command (D) from array, leaving only selected conigs
  unset 'USRCONF[-1]'

# Output list of selected configs
  printf "%s\n${c4b}You selected the following configs:${endc} "
  for x in "${USRCONF[@]}"
  do
    printf "${c1b}${CONFIGS[$x]##*/}${endc} "
    CONFSEL+="${CONFIGS[$x]##*/} "
  done
  printf "\n"

}

# Launch OpenVPN using the selected configs
function confMultiRun {
  printf "${c3b}Running${endc} OpenVPN using selected configs... %s\t%s\n\n"

# Stop active instances of OpenVPN
  printf "${c2b}Stopping${endc} active processes... %s\t%s\n\n"
  sudo systemctl stop openvpn
  sleep 2

  printf "${c1}Starting${endc} service using ${c1b}${CONFSEL}${endc}...
  %s\t%s\n\n"

# Filter out file extension for use in openvpn@ command
  #CONFSEL=${CONFIGS[$USRCONF]##*/}

# Start OpenVPN services using the selected config files
# Edit filename to remove path, then remove extension
  for z in "${USRCONF[@]}"
  do
    CONFSEL="${CONFIGS[$z]##*/}"
    SELMOD=${CONFSEL%.*}
    sudo systemctl start openvpn@${SELMOD%.*}
  done
}

################################################
## Loading Feedback                           ##
################################################
# Display feedack while waiting for network adapter(s) to activate
function adapterLoading {
  spin='-\|/'
  sp=0

  while ! (ip link | grep 'tun\|tap' > /dev/null)
  do
    sp=$(( (sp+1) %4 ))
    printf "\r[${spin:$sp:1}] Waiting for network adapter(s)..."
    sleep .1
  done
  printf "%s\n\n${c1b}VPN is ready!${endc}%s\n"
}

################################################
## Disable Individual VPN                     ##
################################################

# Display list of running vpn instances and configs
function vpnList {
# Get list of running openvpn processes and add to array
  VPNIDS=( $(ps -fe | grep [o]penvpn | awk '{print $2}') )

# Get info on configs used on running processes
  for o in "${!VPNIDS[@]}"
  do
    VPNCFG+=( $(cat /proc/${VPNIDS[$o]}/cmdline | strings -1 | grep $CONFIGDIR\.*conf) )
  done

# Print active vpn list for user to choose from
  printf "${c4b}[#] %s\t[PID] %s\t[Config]${endc} %s\n"
  for p in "${!VPNIDS[@]}";
  do
    printf "%s\t%s\t%s\n" "[$p]" "${VPNIDS[$p]}" "${VPNCFG[$p]}"
  done
}

# Get user input for which service to disable
function vpnOffSelect {

  while [[ ! "$vpnInput" =~ ^[0-9]+$ ]]
  do
    printf "${c4}Input VPN Number to disable %s\n(L)ist active vpn (M)ain menu (Q)uit):${endc} "
  read vpnInput
  # Check if user wants to list processes, go to main menu or quit
  # Then validate input for numbers or valid running configs
    if [[ "$vpnInput" =~ ^[Ll]+$ ]]
    then
      vpnList
    elif [[ "$vpnInput" =~ ^[Qq]+$ ]]
    then
      exit
    elif [[ "$vpnInput" =~ ^[Mm]+$ ]]
    then
      menuMain
    elif ! [[ "$vpnInput" =~ ^[0-9]+$ ]] && [[ ! "$vpnInput" =~ ^[Ll]+$  ]]
      then
      echo -e "${c2b}--- Invalid input: numbers only ---${endc}"
    elif [[ "$vpnInput" -ge "${#VPNIDS[@]}" ]]
    then
      echo -e "${c2b}--- Invalid input: config not found ---${endc}"
    fi
  done
}

# Disable the service selected by the user
function vpnOffRun {
    printf "${c2b}Stopping${endc} service running ${c1b}${VPNCFG[$vpnInput]##*/}${endc}...
    %s\t%s\n\n"

  # Filter out file extension for use in openvpn@ command
    STOPSEL=${VPNCFG[$vpnInput]##*/}

  # Stop service using the selected config file
    sudo systemctl stop openvpn@${STOPSEL%.*}
}


################################################
## Disable All VPN                            ##
################################################
function vpnAllOff {
  # Stop active instances of OpenVPN
    printf "${c2b}Stopping${endc} active processes... %s\t%s\n\n"
    sudo systemctl stop openvpn
  }

################################################
## Show Adapter Status                        ##
################################################
function adapterStatus {
  printf "${c1b}Running OpenVPN processes${endc}%s\n"
  vpnList

  printf "${c1b}Default IP Address  : ${endc}"
  ##curl v4.ifconfig.co
  curl -s checkip.dyndns.org | sed -e 's/.*Current IP Address: //' -e 's/<.*$//'

  printf "${c1b}VPN/Proxy IP Address: ${endc}"
  ##proxychains4 -q curl v4.ifconfig.co
  proxychains4 -q curl -s checkip.dyndns.org | sed -e 's/.*Current IP Address: //' -e 's/<.*$//'
}

################################################
## confList                                   ##
################################################
# Output list of configuration files as an array
function confList {
  echo "+--------------------------------------+"
  echo "|             Config List              |"
  echo "+--------------------------------------+"
  printf "${c4b}[${#CONFIGS[@]} Available Configuration Files] ${endc}%s\n"
  printf "${c4b}[#]%-2s[Filename]${endc} %s\n"
  for i in "${!CONFIGS[@]}";
  do
    printf "%-4s %s\n" "[$i]" "${CONFIGS[$i]##*/}"
  done | column
  printf "%s\n"
}

################################################
## Menus                                      ##
################################################

# Main Menu
function menuMainDisplay {
# Obligatory ascii art
  clear
  echo "+-----------------------------+"
  echo "| ██╗   ██╗██████╗ ███╗   ██╗ |"
  echo "| ██║   ██║██╔══██╗████╗  ██║ |"
  echo "| ██║   ██║██████╔╝██╔██╗ ██║ |"
  echo "| ╚██╗ ██╔╝██╔═══╝ ██║╚██╗██║ |"
  echo "|  ╚████╔╝ ██║     ██║ ╚████║ |"
  echo "|   ╚═══╝  ╚═╝     ╚═╝  ╚═══╝ |"
  echo "+-----------------------------+"
  echo "|        Config Manager       |"
  echo "+-----------------------------+"
  echo "| [1] Activate Single VPN     |"
  echo "| [2] Activate Multiple VPNs  |"
  echo "| [3] Disable Individual VPN  |"
  echo "| [4] Disable All VPN         |"
  echo "| [5] Show Adapter Status     |"
  echo "| [Q] Exit                    |"
  echo "+-----------------------------+"

}

function menuMainSel {
  local menuSel
  read -p "Enter menu selection: " menuSel
  case $menuSel in
    1) menuActivateSingle;;
    2) menuActivateMulti;;
    3) menuDisableSingle;;
    4) menuDisableAll;;
    5) adapterStatus;;
    Q) exit 0 ;;
    q) exit 0 ;;
    *) echo -e "${c2b}Input error...${endc}"
  esac
}

# Menu Functions
function menuActivateSingle {
  confList
  confSelect
  confRun
  adapterLoading
}
function menuActivateMulti {
  confList
  confMultiSel
  confMultiRun
  adapterLoading
}
function menuDisableAll {
  vpnAllOff
}
function menuDisableSingle {
  vpnOffSelect
  vpnOffRun
}
function menuMain {
  menuMainDisplay
  menuMainSel
}


# Functions to run on start up
menuMainDisplay
menuMainSel