From af204615909b44c520c1bcde7861bf0acce6c884 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Wed, 27 Jul 2022 14:53:36 +0200 Subject: [PATCH 01/14] Reformatted the code --- ciscripts/startup.sh | 85 ++-- ciscripts/test.sh | 5 +- scripts/backup.sh | 156 +++--- scripts/openvpn/bash-completion | 38 +- scripts/openvpn/clientStat.sh | 139 +++--- scripts/openvpn/listOVPN.sh | 63 ++- scripts/openvpn/makeOVPN.sh | 780 ++++++++++++++++-------------- scripts/openvpn/pivpn.sh | 161 +++--- scripts/openvpn/pivpnDebug.sh | 64 ++- scripts/openvpn/removeOVPN.sh | 286 ++++++----- scripts/pivpn | 82 ++-- scripts/self_check.sh | 465 +++++++++++------- scripts/uninstall.sh | 574 +++++++++++++--------- scripts/update.sh | 199 ++++---- scripts/wireguard/bash-completion | 32 +- scripts/wireguard/clientSTAT.sh | 156 +++--- scripts/wireguard/disableCONF.sh | 196 ++++---- scripts/wireguard/enableCONF.sh | 189 ++++---- scripts/wireguard/listCONF.sh | 35 +- scripts/wireguard/makeCONF.sh | 288 +++++------ scripts/wireguard/pivpn.sh | 207 ++++---- scripts/wireguard/pivpnDEBUG.sh | 71 ++- scripts/wireguard/qrcodeCONF.sh | 129 ++--- scripts/wireguard/removeCONF.sh | 276 ++++++----- 24 files changed, 2655 insertions(+), 2021 deletions(-) diff --git a/ciscripts/startup.sh b/ciscripts/startup.sh index 33a2bc4..6f6e56d 100644 --- a/ciscripts/startup.sh +++ b/ciscripts/startup.sh @@ -1,47 +1,62 @@ -#!/bin/sh +#!/bin/bash -interface=$(ip -o link | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1 | grep -v -w 'lo' | head -1) -ipaddress=$(ip addr show "$interface" | grep -o -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}/[0-9]{2}") +interface=$(ip -o link | + awk '{print $2}' | + cut -d ':' -f 1 | + cut -d '@' -f 1 | + grep -v -w 'lo' | + head -1) +ipaddress=$(ip addr show "${interface}" | + grep -o -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}/[0-9]{2}") gateway=$(ip route show | awk '/default/ {print $3}') hostname="pivpn.test" -common(){ - sed -i "s/INTERFACE/$interface/g" "$vpnconfig" - sed -i "s|IPADDRESS|$ipaddress|g" "$vpnconfig" - sed -i "s/GATEWAY/$gateway/g" "$vpnconfig" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -openvpn(){ - vpnconfig="ciscripts/ci_openvpn.conf" - twofour=1 - common - sed -i "s/2POINT4/$twofour/g" "$vpnconfig" - cat $vpnconfig - exit 0 +common() { + sed -i "s/INTERFACE/${interface}/g" "${vpnconfig}" + sed -i "s|IPADDRESS|${ipaddress}|g" "${vpnconfig}" + sed -i "s/GATEWAY/${gateway}/g" "${vpnconfig}" } -wireguard(){ - vpnconfig="ciscripts/ci_wireguard.conf" - common - cat $vpnconfig - exit 0 +openvpn() { + vpnconfig="ciscripts/ci_openvpn.conf" + twofour=1 + common + sed -i "s/2POINT4/${twofour}/g" "${vpnconfig}" + cat "${vpnconfig}" + exit 0 } -if [ $# -lt 1 ]; then - echo "specifiy a VPN protocol to prepare" - exit 1 +wireguard() { + vpnconfig="ciscripts/ci_wireguard.conf" + common + cat "${vpnconfig}" + exit 0 +} + +if [[ "$#" -lt 1 ]]; then + err "specifiy a VPN protocol to prepare" + exit 1 else - chmod +x auto_install/install.sh - sudo hostnamectl set-hostname $hostname - cat /etc/os-release - while true; do - case "$1" in - -o | --openvpn ) openvpn - ;; - -w | --wireguard ) wireguard - ;; - * ) echo "unknown vpn protocol"; exit 1 - ;; - esac - done + chmod +x auto_install/install.sh + sudo hostnamectl set-hostname "${hostname}" + cat /etc/os-release + + while true; do + case "${1}" in + -o | --openvpn) + openvpn + ;; + -w | --wireguard) + wireguard + ;; + *) + err "unknown vpn protocol" + exit 1 + ;; + esac + done fi diff --git a/ciscripts/test.sh b/ciscripts/test.sh index c24db6f..2ba7e1f 100644 --- a/ciscripts/test.sh +++ b/ciscripts/test.sh @@ -1,10 +1,11 @@ #!/bin/bash if command -v systemctl > /dev/null; then - systemctl status openvpn + systemctl status openvpn elif command -v rc-service > /dev/null; then - rc-service openvpn status + rc-service openvpn status fi + pivpn add -n foo pivpn -qr foo pivpn -bk diff --git a/scripts/backup.sh b/scripts/backup.sh index 793a73d..dc932dd 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -2,111 +2,125 @@ # PiVPN: Backup Script # Find the rows and columns. Will default to 80x24 if it can not be detected. -screen_size=$(stty size 2>/dev/null || echo 24 80) -rows=$(echo "$screen_size" | awk '{print $1}') -columns=$(echo "$screen_size" | awk '{print $2}') +screen_size="$(stty size 2> /dev/null || echo 24 80)" +rows="$(echo "${screen_size}" | awk '{print $1}')" +columns="$(echo "${screen_size}" | awk '{print $2}')" # Divide by two so the dialogs take up half of the screen, which looks nice. -r=$(( rows / 2 )) -c=$(( columns / 2 )) +r=$((rows / 2)) +c=$((columns / 2)) # Unless the screen is tiny -r=$(( r < 20 ? 20 : r )) -c=$(( c < 70 ? 70 : c )) +r=$((r < 20 ? 20 : r)) +c=$((c < 70 ? 70 : c)) backupdir=pivpnbackup -date=$(date +%Y%m%d-%H%M%S) +date="$(date +%Y%m%d-%H%M%S)" setupVarsFile="setupVars.conf" setupConfigDir="/etc/pivpn" CHECK_PKG_INSTALLED='dpkg-query -s' -if [ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ] && [ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]; then +if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]] && + [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then + # Two protocols have been installed, check if the script has passed + # an argument, otherwise ask the user which one he wants to remove + if [[ "$#" -ge 1 ]]; then + VPN="${1}" + echo "::: Backing up VPN: ${VPN}" + else + chooseVPNCmd=(whiptail + --backtitle "Setup PiVPN" + --title "Backup" + --separate-output + --radiolist "Both OpenVPN and WireGuard are installed, choose a VPN to \ +backup (press space to select):" + "${r}" "${c}" 2) + VPNChooseOptions=(WireGuard "" on + OpenVPN "" off) - # Two protocols have been installed, check if the script has passed - # an argument, otherwise ask the user which one he wants to remove - if [ $# -ge 1 ]; then - VPN="$1" - echo "::: Backing up VPN: $VPN" - else - chooseVPNCmd=(whiptail --backtitle "Setup PiVPN" --title "Backup" --separate-output --radiolist "Both OpenVPN and WireGuard are installed, choose a VPN to backup (press space to select):" "${r}" "${c}" 2) - VPNChooseOptions=(WireGuard "" on - OpenVPN "" off) + if VPN="$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 \ + > /dev/tty)"; then + echo "::: Backing up VPN: ${VPN}" + VPN="${VPN,,}" + else + err "::: Cancel selected, exiting...." + exit 1 + fi + fi - if VPN=$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 >/dev/tty) ; then - echo "::: Backing up VPN: $VPN" - VPN="${VPN,,}" - else - echo "::: Cancel selected, exiting...." - exit 1 - fi - fi - - setupVars="${setupConfigDir}/${VPN}/${setupVarsFile}" + setupVars="${setupConfigDir}/${VPN}/${setupVarsFile}" else - - if [ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]; then - setupVars="${setupConfigDir}/wireguard/${setupVarsFile}" - elif [ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]; then - setupVars="${setupConfigDir}/openvpn/${setupVarsFile}" - fi + if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]]; then + setupVars="${setupConfigDir}/wireguard/${setupVarsFile}" + elif [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then + setupVars="${setupConfigDir}/openvpn/${setupVarsFile}" + fi fi -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -if [ "${PLAT}" == 'Alpine' ]; then - CHECK_PKG_INSTALLED='apk --no-cache info -e' +if [[ "${PLAT}" == 'Alpine' ]]; then + CHECK_PKG_INSTALLED='apk --no-cache info -e' fi -checkbackupdir(){ - - # Disabling shellcheck error $install_home sourced from $setupVars - # shellcheck disable=SC2154 - if [[ ! -d $install_home/$backupdir ]]; then - mkdir -p "$install_home"/"$backupdir" - fi - +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -backup_openvpn(){ - - openvpndir=/etc/openvpn - ovpnsdir=${install_home}/ovpns - checkbackupdir - backupzip=$date-pivpnovpnbackup.tgz - # shellcheck disable=SC2210 - tar czpf "$install_home"/"$backupdir"/"$backupzip" "$openvpndir" "$ovpnsdir" > /dev/null 2>&1 - echo -e "Backup created in $install_home/$backupdir/$backupzip \nTo restore the backup, follow instructions at:\nhttps://docs.pivpn.io/openvpn/#migrating-pivpn-openvpn\n" - +checkbackupdir() { + # Disabling shellcheck error $install_home sourced from $setupVars + # shellcheck disable=SC2154 + mkdir -p "${install_home}/${backupdir}" } -backup_wireguard(){ +backup_openvpn() { + openvpndir=/etc/openvpn + ovpnsdir="${install_home}/ovpns" + backupzip="${date}-pivpnovpnbackup.tgz" - wireguarddir=/etc/wireguard - configsdir=${install_home}/configs - checkbackupdir - backupzip=$date-pivpnwgbackup.tgz - tar czpf "$install_home"/"$backupdir"/"$backupzip" "$wireguarddir" "$configsdir" > /dev/null 2>&1 - echo -e "Backup created in $install_home/$backupdir/$backupzip \nTo restore the backup, follow instructions at:\nhttps://docs.pivpn.io/wireguard/#migrating-pivpn-wireguard\n" + checkbackupdir + # shellcheck disable=SC2210 + tar czpf "${install_home}/${backupdir}/${backupzip}" "${openvpndir}" \ + "${ovpnsdir}" > /dev/null 2>&1 + echo -e "Backup created in ${install_home}/${backupdir}/${backupzip} " + echo -e "To restore the backup, follow instructions at:" + echo -ne "https://docs.pivpn.io/openvpn/" + echo -e "#migrating-pivpn-openvpn" } -if [[ ! $EUID -eq 0 ]]; then - if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then - export SUDO="sudo" - else - echo "::: Please install sudo or run this as root." +backup_wireguard() { + wireguarddir=/etc/wireguard + configsdir="${install_home}/configs" + backupzip="${date}-pivpnwgbackup.tgz" + + checkbackupdir + tar czpf "${install_home}/${backupdir}/${backupzip}" "${wireguarddir}" \ + "${configsdir}" > /dev/null 2>&1 + + echo -e "Backup created in ${install_home}/${backupdir}/${backupzip} " + echo -e "To restore the backup, follow instructions at:" + echo -ne "https://docs.pivpn.io/openvpn/" + echo -e "wireguard/#migrating-pivpn-wireguard" +} + +if [[ "${EUID}" -ne 0 ]]; then + if ${CHECK_PKG_INSTALLED} sudo &> /dev/null; then + export SUDO="sudo" + else + err "::: Please install sudo or run this as root." exit 1 fi fi if [[ "${VPN}" == "wireguard" ]]; then - backup_wireguard + backup_wireguard else - backup_openvpn + backup_openvpn fi diff --git a/scripts/openvpn/bash-completion b/scripts/openvpn/bash-completion index 1a9cb96..775d221 100644 --- a/scripts/openvpn/bash-completion +++ b/scripts/openvpn/bash-completion @@ -1,21 +1,25 @@ #!/bin/bash -_pivpn() -{ - local cur prev opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - dashopts="-a -c -d -l -r -h -u -up -bk" - opts="debug add clients list revoke uninstall help update backup" - if [ "${#COMP_WORDS[@]}" -eq 2 ]; then - if [[ ${cur} == -* ]] ; then - COMPREPLY=( "$(compgen -W "${dashopts}" -- "${cur}")" ) - else - COMPREPLY=( "$(compgen -W "${opts}" -- "${cur}")" ) - fi - elif [[ ( "$prev" == "add" || "$prev" == "-a" ) && "${#COMP_WORDS[@]}" -eq 3 ]]; then - COMPREPLY=( "$(compgen -W "nopass" -- "${cur}")" ) + +_pivpn() { + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD - 1]}" + dashopts="-a -c -d -l -r -h -u -up -bk" + opts="debug add clients list revoke uninstall help update backup" + + if [[ "${#COMP_WORDS[@]}" -eq 2 ]]; then + if [[ "${cur}" == -* ]]; then + COMPREPLY=("$(compgen -W "${dashopts}" -- "${cur}")") + else + COMPREPLY=("$(compgen -W "${opts}" -- "${cur}")") fi - return 0 + elif [[ ("${prev}" == "add" || "${prev}" == "-a") ]] && + [[ "${#COMP_WORDS[@]}" -eq 3 ]]; then + COMPREPLY=("$(compgen -W "nopass" -- "${cur}")") + fi + + return 0 } + complete -F _pivpn pivpn diff --git a/scripts/openvpn/clientStat.sh b/scripts/openvpn/clientStat.sh index 82809f4..c810d79 100755 --- a/scripts/openvpn/clientStat.sh +++ b/scripts/openvpn/clientStat.sh @@ -1,84 +1,99 @@ -#!/usr/bin/env bash +#!/bin/bash # PiVPN: client status script STATUS_LOG="/var/log/openvpn-status.log" -if [ ! -f "${STATUS_LOG}" ]; then - echo "The file: $STATUS_LOG was not found!" - exit 1 +if [[ ! -f "${STATUS_LOG}" ]]; then + err "The file: ${STATUS_LOG} was not found!" + exit 1 fi -scriptusage(){ - echo "::: List any connected clients to the server" - echo ":::" - echo "::: Usage: pivpn <-c|clients> [-b|bytes]" - echo ":::" - echo "::: Commands:" - echo "::: [none] List clients with human readable format" - echo "::: -b, bytes List clients with dotted decimal notation" - echo "::: -h, help Show this usage dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -hr(){ - numfmt --to=iec-i --suffix=B "$1" +scriptusage() { + echo "::: List any connected clients to the server" + echo ":::" + echo "::: Usage: pivpn <-c|clients> [-b|bytes]" + echo ":::" + echo "::: Commands:" + echo "::: [none] List clients with human readable format" + echo "::: -b, bytes List clients with dotted decimal notation" + echo "::: -h, help Show this usage dialog" } +hr() { + numfmt --to=iec-i --suffix=B "${1}" +} -listClients(){ - printf ": NOTE : The output below is NOT real-time!\n" - printf ": : It may be off by a few minutes.\n" - printf "\n" - printf "\e[1m::: Client Status List :::\e[0m\n" +listClients() { + printf ": NOTE : The output below is NOT real-time!\n" + printf ": : It may be off by a few minutes.\n" + printf "\n" + printf "\e[1m::: Client Status List :::\e[0m\n" - { - printf "\e[4mName\e[0m \t \e[4mRemote IP\e[0m \t \e[4mVirtual IP\e[0m \t \e[4mBytes Received\e[0m \t \e[4mBytes Sent\e[0m \t \e[4mConnected Since\e[0m\n" + { + printf "\e[4mName\e[0m \t \e[4mRemote IP\e[0m \t " + printf "\e[4mVirtual IP\e[0m \t \e[4mBytes Received\e[0m \t " + printf "\e[4mBytes Sent\e[0m \t \e[4mConnected Since\e[0m\n" if grep -q "^CLIENT_LIST" "${STATUS_LOG}"; then - if [ -n "$(type -t numfmt)" ]; then - if [ "$HR" = 1 ]; then - while read -r line; do - read -r -a array <<< "$line" - [[ ${array[0]} = CLIENT_LIST ]] || continue - printf "%s \t %s \t %s \t %s \t %s \t %s %s %s - %s\n" "${array[1]}" "${array[2]}" "${array[3]}" "$(hr "${array[4]}")" "$(hr "${array[5]}")" "${array[7]}" "${array[8]}" "${array[10]}" "${array[9]}" - done <$STATUS_LOG - else - while read -r line; do - read -r -a array <<< "$line" - [[ ${array[0]} = CLIENT_LIST ]] || continue - printf "%s \t %s \t %s \t %'d \t %'d \t %s %s %s - %s\n" "${array[1]}" "${array[2]}" "${array[3]}" "${array[4]}" "${array[5]}" "${array[7]}" "${array[8]}" "${array[10]}" "${array[9]}" - done <$STATUS_LOG - fi - else - awk -F' ' -v s='CLIENT_LIST' '$1 == s {print $2"\t\t"$3"\t"$4"\t"$5"\t\t"$6"\t\t"$8" "$9" "$11" - "$10"\n"}' ${STATUS_LOG} - fi + if [[ -n "$(type -t numfmt)" ]]; then + while read -r line; do + read -r -a array <<< "${line}" + + [[ "${array[0]}" == 'CLIENT_LIST' ]] || continue + + printf "%s \t %s \t " "${array[1]}" "${array[2]}" + printf "%s \t " "${array[3]}" + + if [[ "${HR}" == 1 ]]; then + printf "%s \t %s" "$(hr "${array[4]}")" "$(hr "${array[5]}")" + else + printf "%'d \t %'d" "${array[4]}" "${array[5]}" + fi + + printf " \t %s %s %s " "${array[7]}" "${array[8]}" "${array[10]}" + printf "- %s\n" "${array[9]}" + done < "${STATUS_LOG}" + else + awk \ + -F' ' \ + -v s='CLIENT_LIST' \ + '$1 == s { + print $2"\t\t"$3"\t"$4"\t"$5"\t\t"$6"\t\t"$8" "$9" "$11" - "$10"\n" + }' \ + "${STATUS_LOG}" + fi else - printf "\nNo Clients Connected!\n" + printf "\nNo Clients Connected!\n" fi printf "\n" - } | column -t -s $'\t' + } | column -t -s $'\t' } -if [[ $# -eq 0 ]]; then - HR=1 - listClients +if [[ "$#" -eq 0 ]]; then + HR=1 + listClients else - while true; do - case "$1" in - -b|bytes) - HR=0 - listClients - exit 0 - ;; - -h|help) - scriptusage - exit 0 - ;; - *) - HR=0 - listClients - exit 0 - ;; - esac - done + while true; do + case "${1}" in + -b | bytes) + HR=0 + listClients + exit 0 + ;; + -h | help) + scriptusage + exit 0 + ;; + *) + HR=0 + listClients + exit 0 + ;; + esac + done fi diff --git a/scripts/openvpn/listOVPN.sh b/scripts/openvpn/listOVPN.sh index 009c82a..3257198 100755 --- a/scripts/openvpn/listOVPN.sh +++ b/scripts/openvpn/listOVPN.sh @@ -1,42 +1,57 @@ -#!/usr/bin/env bash +#!/bin/bash # PiVPN: list clients script -# Updated Script to include Expiration Dates and Clean up Escape Seq -- psgoundar +# Updated Script to include Expiration Dates and +# Clean up Escape Seq -- psgoundar INDEX="/etc/openvpn/easy-rsa/pki/index.txt" -if [ ! -f "${INDEX}" ]; then - echo "The file: $INDEX was not found!" - exit 1 + +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +if [[ ! -f "${INDEX}" ]]; then + err "The file: ${INDEX} was not found!" + exit 1 fi EASYRSA="/etc/openvpn/easy-rsa/easyrsa" -if [ ! -f "${EASYRSA}" ]; then - echo "The file: $EASYRSA was not found!" - exit 1 + +if [[ ! -f "${EASYRSA}" ]]; then + err "The file: ${EASYRSA} was not found!" + exit 1 fi -$EASYRSA update-db >> /dev/null 2>&1 +"${EASYRSA}" update-db >> /dev/null 2>&1 -printf ": NOTE : The first entry is your server, which should always be valid!\n" +printf ": NOTE : The first entry is your server, " +printf "which should always be valid!\n" printf "\\n" printf "\\e[1m::: Certificate Status List :::\\e[0m\\n" + { -printf "\\e[4mStatus\\e[0m \t \\e[4mName\\e[0m\\e[0m \t \\e[4mExpiration\\e[0m\\n" + printf "\\e[4mStatus\\e[0m \t \\e[4mName\\e[0m\\e[0m \t " + printf "\\e[4mExpiration\\e[0m\\n" -while read -r line || [ -n "$line" ]; do - STATUS=$(echo "$line" | awk '{print $1}') - NAME=$(echo "$line" | awk -FCN= '{print $2}') - EXPD=$(echo "$line" | awk '{if (length($2) == 15) print $2; else print "20"$2}' | cut -b 1-8 | date +"%b %d %Y" -f -) + while read -r line || [[ -n "${line}" ]]; do + STATUS="$(echo "${line}" | awk '{print $1}')" + NAME="$(echo "${line}" | awk -FCN= '{print $2}')" + EXPD="$(echo "${line}" | + awk '{if (length($2) == 15) print $2; else print "20"$2}' | + cut -b 1-8 | + date +"%b %d %Y" -f -)" - if [ "${STATUS}" == "V" ]; then - printf "Valid \t %s \t %s\\n" "$(echo -e "$NAME")" "$EXPD" - elif [ "${STATUS}" == "R" ]; then - printf "Revoked \t %s \t %s\\n" "$(echo -e "$NAME")" "$EXPD" - elif [ "${STATUS}" == "E" ]; then - printf "Expired \t %s \t %s\\n" "$(echo -e "$NAME")" "$EXPD" + if [[ "${STATUS}" == "V" ]]; then + printf "Valid" + elif [[ "${STATUS}" == "R" ]]; then + printf "Revoked" + elif [[ "${STATUS}" == "E" ]]; then + printf "Expired" else - printf "Unknown \t %s \t %s\\n" "$(echo -e "$NAME")" "$EXPD" + printf "Unknown" fi -done <${INDEX} -printf "\\n" + printf " \t %s \t %s\\n" "$(echo -e "${NAME}")" "${EXPD}" + done < "${INDEX}" + + printf "\\n" } | column -t -s $'\t' diff --git a/scripts/openvpn/makeOVPN.sh b/scripts/openvpn/makeOVPN.sh index 172224c..0f9558b 100755 --- a/scripts/openvpn/makeOVPN.sh +++ b/scripts/openvpn/makeOVPN.sh @@ -1,4 +1,5 @@ #!/bin/bash + # Create OVPN Client # Default Variable Declarations setupVars="/etc/pivpn/openvpn/setupVars.conf" @@ -10,429 +11,498 @@ CA="ca.crt" TA="ta.key" INDEX="/etc/openvpn/easy-rsa/pki/index.txt" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -helpFunc() { - echo "::: Create a client ovpn profile, optional nopass" - echo ":::" - echo "::: Usage: pivpn <-a|add> [-n|--name ] [-p|--password ]|[nopass] [-d|--days ] [-b|--bitwarden] [-i|--iOS] [-o|--ovpn] [-h|--help]" - echo ":::" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: nopass Create a client without a password" - echo "::: -n,--name Name for the Client (default: \"$(hostname)\")" - echo "::: -p,--password Password for the Client (no default)" - echo "::: -d,--days Expire the certificate after specified number of days (default: 1080)" - echo "::: -b,--bitwarden Create and save a client through Bitwarden" - echo "::: -i,--iOS Generate a certificate that leverages iOS keychain" - echo "::: -o,--ovpn Regenerate a .ovpn config file for an existing client" - echo "::: -h,--help Show this help dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -if [ -z "$HELP_SHOWN" ]; then - helpFunc - echo - echo "HELP_SHOWN=1" >> "$setupVars" +helpFunc() { + echo "::: Create a client ovpn profile, optional nopass" + echo ":::" + echo -n "::: Usage: pivpn <-a|add> [-n|--name ] " + echo -n "[-p|--password ]|[nopass] [-d|--days ] " + echo "[-b|--bitwarden] [-i|--iOS] [-o|--ovpn] [-h|--help]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: nopass Create a client without a password" + echo -n "::: -n,--name Name for the Client " + echo "(default: \"$(hostname)\")" + echo "::: -p,--password Password for the Client (no default)" + echo -n "::: -d,--days Expire the certificate after specified " + echo "number of days (default: 1080)" + echo "::: -b,--bitwarden Create and save a client through Bitwarden" + echo -n "::: -i,--iOS Generate a certificate that leverages iOS " + echo "keychain" + echo -n "::: -o,--ovpn Regenerate a .ovpn config file for an " + echo "existing client" + echo "::: -h,--help Show this help dialog" +} + +if [[ -z "${HELP_SHOWN}" ]]; then + helpFunc + echo + echo "HELP_SHOWN=1" >> "${setupVars}" fi # Parse input arguments -while test $# -gt 0; do - _key="$1" - case "$_key" in - -n|--name|--name=*) - _val="${_key##--name=}" - if test "$_val" = "$_key"; then - test $# -lt 2 && echo "Missing value for the optional argument '$_key'." && exit 1 - _val="$2" - shift - fi - NAME="$_val" - ;; - -p|--password|--password=*) - _val="${_key##--password=}" - if test "$_val" = "$_key"; then - test $# -lt 2 && echo "Missing value for the optional argument '$_key'." && exit 1 - _val="$2" - shift - fi - PASSWD="$_val" - ;; - -d|--days|--days=*) - _val="${_key##--days=}" - if test "$_val" = "$_key"; then - test $# -lt 2 && echo "Missing value for the optional argument '$_key'." && exit 1 - _val="$2" - shift - fi - DAYS="$_val" - ;; - -i|--iOS) - if [ "$TWO_POINT_FOUR" -ne 1 ]; then - iOS=1 - else - echo "Sorry, can't generate iOS-specific configs for ECDSA certificates" - echo "Generate traditional certificates using 'pivpn -a' or reinstall PiVPN without opting in for OpenVPN 2.4 features" - exit 1 - fi - ;; - -h|--help) - helpFunc - exit 0 - ;; - nopass) - NO_PASS="1" - ;; - -b|--bitwarden) - if command -v bw > /dev/null; then - BITWARDEN="2" - else - echo 'Bitwarden not found, please install bitwarden' +while [[ "$#" -gt 0 ]]; do + _key="${1}" - if [ "${PLAT}" == 'Alpine' ]; then - echo 'You can download it through the following commands:' - echo $'\t' 'curl -fLo bitwarden.zip --no-cache https://github.com/bitwarden/clients/releases/download/cli-v2022.6.2/bw-linux-2022.6.2.zip' - echo $'\t' 'apk --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing/ add atool' - echo $'\t' 'aunpack -F zip bitwarden.zip' - echo $'\t' 'mv bw /opt/bw' - echo $'\t' 'chmod 755 /opt/bw' - echo $'\t' 'rm bitwarden.zip' - echo $'\t' 'apk --no-cache --purge del -r atool' - fi + case "${_key}" in + -n | --name | --name=*) + _val="${_key##--name=}" - exit 1 - fi + if [[ "${_val}" == "${_key}" ]]; then + [[ "$#" -lt 2 ]] && + err "Missing value for the optional argument '${_key}'." && + exit 1 - ;; - -o|--ovpn) - GENOVPNONLY=1 - ;; - *) - echo "Error: Got an unexpected argument '$1'" - helpFunc - exit 1 - ;; - esac - shift + _val="${2}" + shift + fi + + NAME="${_val}" + ;; + -p | --password | --password=*) + _val="${_key##--password=}" + + if [[ "${_val}" == "${_key}" ]]; then + [[ "$#" -lt 2 ]] && + err "Missing value for the optional argument '${_key}'." && + exit 1 + + _val="${2}" + shift + fi + + PASSWD="${_val}" + ;; + -d | --days | --days=*) + _val="${_key##--days=}" + + if [[ "${_val}" == "${_key}" ]]; then + [[ "$#" -lt 2 ]] && + err "Missing value for the optional argument '${_key}'." && + exit 1 + + _val="${2}" + shift + fi + + DAYS="${_val}" + ;; + -i | --iOS) + if [[ "${TWO_POINT_FOUR}" -ne 1 ]]; then + iOS=1 + else + err "Sorry, can't generate iOS-specific configs for ECDSA certificates" + err "Generate traditional certificates using 'pivpn -a' or reinstall PiVPN without opting in for OpenVPN 2.4 features" + exit 1 + fi + ;; + -h | --help) + helpFunc + exit 0 + ;; + nopass) + NO_PASS="1" + ;; + -b | --bitwarden) + if command -v bw > /dev/null; then + BITWARDEN="2" + else + echo 'Bitwarden not found, please install bitwarden' + + if [[ "${PLAT}" == 'Alpine' ]]; then + echo 'You can download it through the following commands:' + echo -n $'\t''curl -fLo bitwarden.zip --no-cache https://github.com/' + echo -n 'bitwarden/clients/releases/download/cli-v2022.6.2/' + echo 'bw-linux-2022.6.2.zip' + echo $'\t''apk --no-cache unzip' + echo $'\t''unzip bitwarden.zip' + echo $'\t''mv bw /opt/bw' + echo $'\t''chmod 755 /opt/bw' + echo $'\t''rm bitwarden.zip' + echo $'\t''apk --no-cache --purge del -r unzip' + fi + + exit 1 + fi + + ;; + -o | --ovpn) + GENOVPNONLY=1 + ;; + *) + err "Error: Got an unexpected argument '${1}'" + helpFunc + exit 1 + ;; + esac + + shift done # Functions def -function keynoPASS() { - - #Build the client key - expect << EOF +keynoPASS() { + # Build the client key + expect << EOF set timeout -1 set env(EASYRSA_CERT_EXPIRE) "${DAYS}" spawn ./easyrsa build-client-full "${NAME}" nopass expect eof EOF - - cd pki || exit - + cd pki || exit } -function useBitwarden() { +useBitwarden() { + # login and unlock vault + printf "****Bitwarden Login****" + printf "\n" - # login and unlock vault - printf "****Bitwarden Login****" - printf "\n" - SESSION_KEY=$(bw login --raw) - export BW_SESSION=$SESSION_KEY - printf "Successfully Logged in!" - printf "\n" + SESSION_KEY="$(bw login --raw)" + export BW_SESSION="${SESSION_KEY}" - # ask user for username - printf "Enter the username: " + printf "Successfully Logged in!" + printf "\n" + + # ask user for username + printf "Enter the username: " + read -r NAME + + # check name + until [[ "${NAME}" =~ ^[a-zA-Z0-9.@_-]+$ ]] && + [[ "${NAME::1}" != "." ]] && + [[ "${NAME::1}" != "-" ]]; do + echo -n "Name can only contain alphanumeric characters and these " + echo -n "characters (.-@_). The name also cannot start with a dot (.)" + echo " or a dash (-). Please try again." + # ask user for username again + printf "Enter the username: " read -r NAME + done - # check name - until [[ "$NAME" =~ ^[a-zA-Z0-9.@_-]+$ && ${NAME::1} != "." && ${NAME::1} != "-" ]]; do - echo "Name can only contain alphanumeric characters and these characters (.-@_). The name also cannot start with a dot (.) or a dash (-). Please try again." - # ask user for username again - printf "Enter the username: " - read -r NAME - done - + # ask user for length of password + printf "Please enter the length of characters you want your password to be " + printf "(minimum 12): " + read -r LENGTH + # check length + until [[ "${LENGTH}" -gt 11 ]] && [[ "${LENGTH}" -lt 129 ]]; do + echo "Password must be between from 12 to 128 characters, please try again." # ask user for length of password - printf "Please enter the length of characters you want your password to be (minimum 12): " + printf "Please enter the length of characters you want your password to be " + printf "(minimum 12): " read -r LENGTH + done - # check length - until [[ "$LENGTH" -gt 11 && "$LENGTH" -lt 129 ]]; do - echo "Password must be between from 12 to 128 characters, please try again." - # ask user for length of password - printf "Enter the length of characters you want your password to be (minimum 12): " - read -r LENGTH - done - - printf "Creating a PiVPN item for your vault..." - printf "\n" - # create a new item for your PiVPN Password - PASSWD=$(bw generate -usln --length "$LENGTH") - bw get template item | jq '.login.type = "1"'| jq '.name = "PiVPN"' | jq -r --arg NAME "$NAME" '.login.username = $NAME' | jq -r --arg PASSWD "$PASSWD" '.login.password = $PASSWD' | bw encode | bw create item - bw logout + printf "Creating a PiVPN item for your vault..." + printf "\n" + # create a new item for your PiVPN Password + PASSWD="$(bw generate -usln --length "${LENGTH}")" + bw get template item | + jq '.login.type = "1"' | + jq '.name = "PiVPN"' | + jq -r --arg NAME "${NAME}" '.login.username = $NAME' | + jq -r --arg PASSWD "${PASSWD}" '.login.password = $PASSWD' | + bw encode | + bw create item + bw logout } -function keyPASS() { +keyPASS() { + if [[ -z "${PASSWD}" ]]; then + stty -echo + + while true; do + printf "Enter the password for the client: " + read -r PASSWD + printf "\n" + printf "Enter the password again to verify: " + read -r PASSWD2 + printf "\n" + + [[ "${PASSWD}" == "${PASSWD2}" ]] && break + + printf "Passwords do not match! Please try again.\n" + done + + stty echo if [[ -z "${PASSWD}" ]]; then - stty -echo - while true; do - printf "Enter the password for the client: " - read -r PASSWD - printf "\n" - printf "Enter the password again to verify: " - read -r PASSWD2 - printf "\n" - [ "${PASSWD}" = "${PASSWD2}" ] && break - printf "Passwords do not match! Please try again.\n" - done - stty echo - if [[ -z "${PASSWD}" ]]; then - echo "You left the password blank" - echo "If you don't want a password, please run:" - echo "pivpn add nopass" - exit 1 - fi - fi - if [ ${#PASSWD} -lt 4 ] || [ ${#PASSWD} -gt 1024 ]; then - echo "Password must be between from 4 to 1024 characters" - exit 1 + err "You left the password blank" + err "If you don't want a password, please run:" + err "pivpn add nopass" + exit 1 fi + fi - #Escape chars in PASSWD - PASSWD_UNESCAPED="${PASSWD}" - PASSWD=$(echo -n "${PASSWD}" | sed -E -e 's/\\/\\\\/g' -e 's/\//\\\//g' -e 's/\$/\\\$/g' -e 's/!/\\!/g' -e 's/\./\\\./g' -e "s/'/\\'/g" -e 's/"/\\"/g' -e 's/\*/\\\*/g' -e 's/@/\\@/g' -e 's/#/\\#/g' -e 's/£/\\£/g' -e 's/%/\\%/g' -e 's/\^/\\\^/g' -e 's/&/\\&/g' -e 's/\(/\\\(/g' -e 's/\)/\\\)/g' -e 's/\-/\\\-/g' -e 's/_/\\_/g' -e 's/\+/\\\+/g' -e 's/=/\\=/g' -e 's/\[/\\\[/g' -e 's/\]/\\\]/g' -e 's/;/\\;/g' -e 's/:/\\:/g' -e 's/\|/\\\|/g' -e 's/\/\\\>/g' -e 's/,/\\,/g' -e 's/\?/\\\?/g' -e 's/~/\\~/g' -e 's/\{/\\\{/g' -e 's/\}/\\\}/g') + if [[ "${#PASSWD}" -lt 4 ]] || [[ "${#PASSWD}" -gt 1024 ]]; then + err "Password must be between from 4 to 1024 characters" + exit 1 + fi - #Build the client key and then encrypt the key + # Escape chars in PASSWD + PASSWD_UNESCAPED="${PASSWD}" - expect << EOF - set timeout -1 - set env(EASYRSA_CERT_EXPIRE) "${DAYS}" - spawn ./easyrsa build-client-full "${NAME}" - expect "Enter PEM pass phrase" { sleep 0.1; send -- "${PASSWD}\r" } - expect "Verifying - Enter PEM pass phrase" { sleep 0.1; send -- "${PASSWD}\r" } - expect eof + PASSWD="${PASSWD//\\/\\\\}" + PASSWD="${PASSWD//\//\\\/}" + PASSWD="${PASSWD//\$/\\\$}" + PASSWD="${PASSWD//!/\\!}" + PASSWD="${PASSWD//\./\\\.}" + PASSWD="${PASSWD//\'/\\\'}" + PASSWD="${PASSWD//\"/\\\"}" + PASSWD="${PASSWD//*/\\*}" + PASSWD="${PASSWD//@/\\@}" + PASSWD="${PASSWD//#/\\#}" + PASSWD="${PASSWD//\£/\\\£}" + PASSWD="${PASSWD//%/\\%}" + PASSWD="${PASSWD//\^/\\\^}" + PASSWD="${PASSWD//\&/\\\&}" + PASSWD="${PASSWD//\(/\\\(}" + PASSWD="${PASSWD//\)/\\\)}" + PASSWD="${PASSWD//\-/\\\-}" + PASSWD="${PASSWD//\_/\\\_}" + PASSWD="${PASSWD//\+/\\\+}" + PASSWD="${PASSWD//\=/\\\=}" + PASSWD="${PASSWD//\[/\\\[}" + PASSWD="${PASSWD//\]/\\\]}" + PASSWD="${PASSWD//:/\\:}" + PASSWD="${PASSWD//\;/\\\;}" + PASSWD="${PASSWD//\|/\\\|}" + PASSWD="${PASSWD//\/\\\>}" + PASSWD="${PASSWD//\,/\\\,}" + PASSWD="${PASSWD//\~/\\\~}" + PASSWD="${PASSWD//\?/\\\?}" + PASSWD="${PASSWD//\{/\\\{}" + PASSWD="${PASSWD//\}/\\\}}" + + # Build the client key and then encrypt the key + expect << EOF + set timeout -1 + set env(EASYRSA_CERT_EXPIRE) "${DAYS}" + spawn ./easyrsa build-client-full "${NAME}" + expect "Enter PEM pass phrase" {sleep 0.1; send -- "${PASSWD}\r"} + expect "Verifying - Enter PEM pass phrase" {sleep 0.1; send -- "${PASSWD}\r"} + expect eof EOF - cd pki || exit - + cd pki || exit } #make sure ovpns dir exists # Disabling warning for SC2154, var sourced externaly # shellcheck disable=SC2154 -if [ ! -d "$install_home/ovpns" ]; then - mkdir "$install_home/ovpns" - chown "$install_user":"$install_user" "$install_home/ovpns" - chmod 0750 "$install_home/ovpns" +if [[ ! -d "${install_home}/ovpns" ]]; then + mkdir "${install_home}/ovpns" + chown "${install_user}:${install_user}" "${install_home}/ovpns" + chmod 0750 "${install_home}/ovpns" fi #bitWarden if [[ "${BITWARDEN}" =~ "2" ]]; then - useBitwarden -fi - -if [ -z "${NAME}" ]; then - printf "Enter a Name for the Client: " - read -r NAME -fi - -if [[ ${NAME::1} == "." ]] || [[ ${NAME::1} == "-" ]]; then - echo "Names cannot start with a dot (.) or a dash (-)." - exit 1 -fi - -if [[ "${NAME}" =~ [^a-zA-Z0-9.@_-] ]]; then - echo "Name can only contain alphanumeric characters and these characters (.-@_)." - exit 1 -fi - -if [[ "${NAME}" =~ ^[0-9]+$ ]]; then - echo "Names cannot be integers." - exit 1 + useBitwarden fi if [[ -z "${NAME}" ]]; then - echo "You cannot leave the name blank." - exit 1 + printf "Enter a Name for the Client: " + read -r NAME +elif [[ "${NAME::1}" == "." ]] || [[ "${NAME::1}" == "-" ]]; then + err "Names cannot start with a dot (.) or a dash (-)." + exit 1 +elif [[ "${NAME}" =~ [^a-zA-Z0-9.@_-] ]]; then + err "Name can only contain alphanumeric characters and these symbols (.-@_)." + exit 1 +elif [[ "${NAME}" =~ ^[0-9]+$ ]]; then + err "Names cannot be integers." + exit 1 +elif [[ -z "${NAME}" ]]; then + err "You cannot leave the name blank." + exit 1 fi -if [ "${GENOVPNONLY}" == "1" ]; then - # Generate .ovpn configuration file - cd /etc/openvpn/easy-rsa/pki || exit +if [[ "${GENOVPNONLY}" == 1 ]]; then + # Generate .ovpn configuration file + cd /etc/openvpn/easy-rsa/pki || exit else - # Check if name is already in use - while read -r line || [ -n "${line}" ]; do - STATUS=$(echo "$line" | awk '{print $1}') + # Check if name is already in use + while read -r line || [[ -n "${line}" ]]; do + STATUS=$(echo "${line}" | awk '{print $1}') - if [ "${STATUS}" == "V" ]; then - # Disabling SC2001 as ${variable//search/replace} doesn't go well with regexp - # shellcheck disable=SC2001 - CERT=$(echo "$line" | sed -e 's:.*/CN=::') - if [ "${CERT}" == "${NAME}" ]; then - INUSE="1" - break - fi - fi - done <${INDEX} + if [[ "${STATUS}" == "V" ]]; then + # Disabling SC2001 as ${variable//search/replace} + # doesn't go well with regexp + # shellcheck disable=SC2001 + CERT="$(echo "${line}" | sed -e 's:.*/CN=::')" - if [ "${INUSE}" == "1" ]; then - printf "\n!! This name is already in use by a Valid Certificate." - printf "\nPlease choose another name or revoke this certificate first.\n" - exit 1 + if [[ "${CERT}" == "${NAME}" ]]; then + INUSE="1" + break + fi fi + done < "${INDEX}" - # Check if name is reserved - if [ "${NAME}" == "ta" ] || [ "${NAME}" == "server" ] || [ "${NAME}" == "ca" ]; then - echo "Sorry, this is in use by the server and cannot be used by clients." - exit 1 - fi + if [[ "${INUSE}" == 1 ]]; then + err "!! This name is already in use by a Valid Certificate." + err "Please choose another name or revoke this certificate first." + exit 1 + # Check if name is reserved + elif [[ "${NAME}" == "ta" ]] || + [[ "${NAME}" == "server" ]] || + [[ "${NAME}" == "ca" ]]; then + err "Sorry, this is in use by the server and cannot be used by clients." + exit 1 + fi - #As of EasyRSA 3.0.6, by default certificates last 1080 days, see https://github.com/OpenVPN/easy-rsa/blob/6b7b6bf1f0d3c9362b5618ad18c66677351cacd1/easyrsa3/vars.example - if [ -z "${DAYS}" ]; then - read -r -e -p "How many days should the certificate last? " -i 1080 DAYS - fi + # As of EasyRSA 3.0.6, by default certificates last 1080 days, + # see https://github.com/OpenVPN/easy-rsa/blob/6b7b6bf1f0d3c9362b5618ad18c66677351cacd1/easyrsa3/vars.example + if [[ -z "${DAYS}" ]]; then + read -r -e -p "How many days should the certificate last? " -i 1080 DAYS + fi - if [[ ! "$DAYS" =~ ^[0-9]+$ ]] || [ "$DAYS" -lt 1 ] || [ "$DAYS" -gt 3650 ]; then - #The CRL lasts 3650 days so it doesn't make much sense that certificates would last longer - echo "Please input a valid number of days, between 1 and 3650 inclusive." - exit 1 - fi + if [[ ! "${DAYS}" =~ ^[0-9]+$ ]] || + [[ "${DAYS}" -lt 1 ]] || + [[ "${DAYS}" -gt 3650 ]]; then + # The CRL lasts 3650 days so it doesn't make much sense + # that certificates would last longer + err "Please input a valid number of days, between 1 and 3650 inclusive." + exit 1 + fi - cd /etc/openvpn/easy-rsa || exit + cd /etc/openvpn/easy-rsa || exit - if [[ "${NO_PASS}" =~ "1" ]]; then - if [[ -n "${PASSWD}" ]]; then - echo "Both nopass and password arguments passed to the script. Please use either one." - exit 1 - else - keynoPASS - fi + if [[ "${NO_PASS}" =~ "1" ]]; then + if [[ -n "${PASSWD}" ]]; then + err "Both nopass and password arguments passed to the script. Please use either one." + exit 1 else - keyPASS + keynoPASS fi + else + keyPASS + fi fi #1st Verify that clients Public Key Exists -if [ ! -f "issued/${NAME}${CRT}" ]; then - echo "[ERROR]: Client Public Key Certificate not found: $NAME$CRT" - exit +if [[ ! -f "issued/${NAME}${CRT}" ]]; then + err "[ERROR]: Client Public Key Certificate not found: ${NAME}${CRT}" + exit fi -echo "Client's cert found: $NAME$CRT" + +echo "Client's cert found: ${NAME}${CRT}" #Then, verify that there is a private key for that client -if [ ! -f "private/${NAME}${KEY}" ]; then - echo "[ERROR]: Client Private Key not found: $NAME$KEY" - exit +if [[ ! -f "private/${NAME}${KEY}" ]]; then + err "[ERROR]: Client Private Key not found: ${NAME}${KEY}" + exit fi -echo "Client's Private Key found: $NAME$KEY" + +echo "Client's Private Key found: ${NAME}${KEY}" #Confirm the CA public key exists -if [ ! -f "${CA}" ]; then - echo "[ERROR]: CA Public Key not found: $CA" - exit +if [[ ! -f "${CA}" ]]; then + err "[ERROR]: CA Public Key not found: ${CA}" + exit fi -echo "CA public Key found: $CA" + +echo "CA public Key found: ${CA}" #Confirm the tls key file exists -if [ ! -f "${TA}" ]; then - echo "[ERROR]: tls Private Key not found: $TA" - exit +if [[ ! -f "${TA}" ]]; then + err "[ERROR]: tls Private Key not found: ${TA}" + exit fi -echo "tls Private Key found: $TA" +echo "tls Private Key found: ${TA}" ## Added new step to create an .ovpn12 file that can be stored on iOS keychain -## This step is more secure method and does not require the end-user to keep entering passwords, or storing the client private cert where it can be easily tampered +## This step is more secure method and does not require the end-user to keep +## entering passwords, or storing the client private cert where it can be easily +## tampered ## https://openvpn.net/faq/how-do-i-use-a-client-certificate-and-private-key-from-the-ios-keychain/ -if [ "$iOS" = "1" ]; then - #Generates the .ovpn file WITHOUT the client private key - { - # Start by populating with the default file - cat "${DEFAULT}" - #Now, append the CA Public Cert - echo "" - cat "${CA}" - echo "" +# Generates the .ovpn file WITHOUT the client private key +{ + # Start by populating with the default file + cat "${DEFAULT}" - #Next append the client Public Cert - echo "" - sed -n -e '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' < "issued/${NAME}${CRT}" - echo "" + # Now, append the CA Public Cert + echo "" + cat "${CA}" + echo "" - #Finally, append the tls Private Key - echo "" - cat "${TA}" - echo "" + # Next append the client Public Cert + echo "" + sed \ + -n \ + -e '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' \ + < "issued/${NAME}${CRT}" + echo "" - } > "${NAME}${FILEEXT}" - - # Copy the .ovpn profile to the home directory for convenient remote access - - printf "========================================================\n" - printf "Generating an .ovpn12 file for use with iOS devices\n" - printf "Please remember the export password\n" - printf "as you will need this import the certificate on your iOS device\n" - printf "========================================================\n" - openssl pkcs12 -passin pass:"$PASSWD_UNESCAPED" -export -in "issued/${NAME}${CRT}" -inkey "private/${NAME}${KEY}" -certfile ${CA} -name "${NAME}" -out "$install_home/ovpns/$NAME.ovpn12" - chown "$install_user":"$install_user" "$install_home/ovpns/$NAME.ovpn12" - chmod 640 "$install_home/ovpns/$NAME.ovpn12" - printf "========================================================\n" - printf "\e[1mDone! %s successfully created!\e[0m \n" "$NAME.ovpn12" - printf "You will need to transfer both the .ovpn and .ovpn12 files\n" - printf "to your iOS device.\n" - printf "========================================================\n\n" -else - #This is the standard non-iOS configuration -#Ready to make a new .ovpn file - { - # Start by populating with the default file - cat "${DEFAULT}" - - #Now, append the CA Public Cert - echo "" - cat "${CA}" - echo "" - - #Next append the client Public Cert - echo "" - sed -n -e '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' < "issued/${NAME}${CRT}" - echo "" - - #Then, append the client Private Key + if [[ "${iOS}" != 1 ]]; then + # Then, append the client Private Key echo "" cat "private/${NAME}${KEY}" echo "" + fi - #Finally, append the tls Private Key - if [ "$TWO_POINT_FOUR" -eq 1 ]; then - echo "" - cat "${TA}" - echo "" - else - echo "" - cat "${TA}" - echo "" - fi + # Finally, append the tls Private Key + if [[ "${iOS}" != 1 ]] && [[ "${TWO_POINT_FOUR}" -eq 1 ]]; then + echo "" + cat "${TA}" + echo "" + else + echo "" + cat "${TA}" + echo "" + fi +} > "${NAME}${FILEEXT}" - } > "${NAME}${FILEEXT}" +if [[ "${iOS}" == 1 ]]; then + # Copy the .ovpn profile to the home directory for convenient remote access + printf "========================================================\n" + printf "Generating an .ovpn12 file for use with iOS devices\n" + printf "Please remember the export password\n" + printf "as you will need this import the certificate on your iOS device\n" + printf "========================================================\n" + openssl pkcs12 \ + -passin pass:"${PASSWD_UNESCAPED}" \ + -export \ + -in "issued/${NAME}${CRT}" \ + -inkey "private/${NAME}${KEY}" \ + -certfile "${CA}" \ + -name "${NAME}" \ + -out "${install_home}/ovpns/${NAME}.ovpn12" + + chown "${install_user}:${install_user}" "${install_home}/ovpns/${NAME}.ovpn12" + chmod 640 "${install_home}/ovpns/${NAME}.ovpn12" + + printf "========================================================\n" + printf "\e[1mDone! %s successfully created!\e[0m \n" "${NAME}.ovpn12" + printf "You will need to transfer both the .ovpn and .ovpn12 files\n" + printf "to your iOS device.\n" + printf "========================================================\n\n" fi -cidrToMask(){ - # Source: https://stackoverflow.com/a/20767392 - set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0 - shift "$1" - echo "${1-0}"."${2-0}"."${3-0}"."${4-0}" +cidrToMask() { + # Source: https://stackoverflow.com/a/20767392 + set -- $((5 - (${1} / 8))) \ + 255 255 255 255 \ + $(((255 << (8 - (${1} % 8))) & 255)) \ + 0 0 0 + shift "${1}" + echo "${1-0}.${2-0}.${3-0}.${4-0}" } #disabling SC2514, variable sourced externaly @@ -441,37 +511,45 @@ NET_REDUCED="${pivpnNET::-2}" # Find an unused number for the last octet of the client IP for i in {2..254}; do - # find returns 0 if the folder is empty, so we create the 'ls -A [...]' - # exception to stop at the first static IP (10.8.0.2). Otherwise it would - # cycle to the end without finding and available octet. - # disabling SC2514, variable sourced externaly - # shellcheck disable=SC2154 - if [ -z "$(ls -A /etc/openvpn/ccd)" ] || ! find /etc/openvpn/ccd -type f -exec grep -q "${NET_REDUCED}.${i}" {} +; then - COUNT="${i}" - echo "ifconfig-push ${NET_REDUCED}.${i} $(cidrToMask "$subnetClass")" >> /etc/openvpn/ccd/"${NAME}" - break - fi + # find returns 0 if the folder is empty, so we create the 'ls -A [...]' + # exception to stop at the first static IP (10.8.0.2). Otherwise it would + # cycle to the end without finding and available octet. + # disabling SC2514, variable sourced externaly + # shellcheck disable=SC2154 + if [[ -z "$(ls -A /etc/openvpn/ccd)" ]] || + ! find /etc/openvpn/ccd \ + -type f \ + -exec grep -q "${NET_REDUCED}.${i}" {} +; then + COUNT="${i}" + echo -n "ifconfig-push ${NET_REDUCED}.${i}" >> /etc/openvpn/ccd/"${NAME}" + cidrToMask "${subnetClass}" >> /etc/openvpn/ccd/"${NAME}" + break + fi done -if [ -f /etc/pivpn/hosts.openvpn ]; then - echo "${NET_REDUCED}.${COUNT} ${NAME}.pivpn" >> /etc/pivpn/hosts.openvpn - if killall -SIGHUP pihole-FTL; then - echo "::: Updated hosts file for Pi-hole" - else - echo "::: Failed to reload pihole-FTL configuration" - fi +if [[ -f /etc/pivpn/hosts.openvpn ]]; then + echo "${NET_REDUCED}.${COUNT} ${NAME}.pivpn" >> /etc/pivpn/hosts.openvpn + + if killall -SIGHUP pihole-FTL; then + echo "::: Updated hosts file for Pi-hole" + else + err "::: Failed to reload pihole-FTL configuration" + fi fi # Copy the .ovpn profile to the home directory for convenient remote access -cp "/etc/openvpn/easy-rsa/pki/$NAME$FILEEXT" "$install_home/ovpns/$NAME$FILEEXT" -chown "$install_user":"$install_user" "$install_home/ovpns/$NAME$FILEEXT" -chmod 640 "/etc/openvpn/easy-rsa/pki/$NAME$FILEEXT" -chmod 640 "$install_home/ovpns/$NAME$FILEEXT" +dest_path="${install_home}/ovpns/${NAME}${FILEEXT}" +cp "/etc/openvpn/easy-rsa/pki/${NAME}${FILEEXT}" "${dest_path}" +chown "${install_user}:${install_user}" "${dest_path}" +chmod 640 "/etc/openvpn/easy-rsa/pki/${NAME}${FILEEXT}" +chmod 640 "${dest_path}" +unset dest_path + printf "\n\n" printf "========================================================\n" -printf "\e[1mDone! %s successfully created!\e[0m \n" "$NAME$FILEEXT" -printf "%s was copied to:\n" "$NAME$FILEEXT" -printf " %s/ovpns\n" "$install_home" +printf "\e[1mDone! %s successfully created!\e[0m \n" "${NAME}${FILEEXT}" +printf "%s was copied to:\n" "${NAME}${FILEEXT}" +printf " %s/ovpns\n" "${install_home}" printf "for easy transfer. Please use this profile only on one\n" printf "device and create additional profiles for other devices.\n" printf "========================================================\n\n" diff --git a/scripts/openvpn/pivpn.sh b/scripts/openvpn/pivpn.sh index d8d64fd..b67321a 100755 --- a/scripts/openvpn/pivpn.sh +++ b/scripts/openvpn/pivpn.sh @@ -3,15 +3,15 @@ CHECK_PKG_INSTALLED='dpkg-query -s' if grep -qsEe "^NAME\=['\"]?Alpine[a-zA-Z ]*['\"]?$" /etc/os-release; then - CHECK_PKG_INSTALLED='apk --no-cache info -e' + CHECK_PKG_INSTALLED='apk --no-cache info -e' fi # Must be root to use this tool -if [[ ! $EUID -eq 0 ]]; then - if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then - export SUDO="sudo" +if [[ "${EUID}" -ne 0 ]]; then + if ${CHECK_PKG_INSTALLED} sudo &> /dev/null; then + export SUDO="sudo" else - echo "::: Please install sudo or run this as root." + err "::: Please install sudo or run this as root." exit 1 fi fi @@ -19,88 +19,113 @@ fi scriptDir="/opt/pivpn" vpn="openvpn" -function makeOVPNFunc { - shift - $SUDO ${scriptDir}/${vpn}/makeOVPN.sh "$@" - exit "$?" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -function listClientsFunc { - shift - $SUDO ${scriptDir}/${vpn}/clientStat.sh "$@" - exit "$?" +makeOVPNFunc() { + shift + ${SUDO} "${scriptDir}/${vpn}/makeOVPN.sh" "$@" + exit "${?}" } -function listOVPNFunc { - $SUDO ${scriptDir}/${vpn}/listOVPN.sh - exit "$?" +listClientsFunc() { + shift + ${SUDO} "${scriptDir}/${vpn}/clientStat.sh" "$@" + exit "${?}" } -function debugFunc { - echo "::: Generating Debug Output" - $SUDO ${scriptDir}/${vpn}/pivpnDebug.sh | tee /tmp/debug.log - echo "::: " - echo "::: Debug output completed above." - echo "::: Copy saved to /tmp/debug.log" - echo "::: " - exit "$?" +listOVPNFunc() { + ${SUDO} "${scriptDir}/${vpn}/listOVPN.sh" + exit "${?}" } -function removeOVPNFunc { - shift - $SUDO ${scriptDir}/${vpn}/removeOVPN.sh "$@" - exit "$?" +debugFunc() { + echo "::: Generating Debug Output" + + ${SUDO} "${scriptDir}/${vpn}/pivpnDebug.sh" | tee /tmp/debug.log + + echo "::: " + echo "::: Debug output completed above." + echo "::: Copy saved to /tmp/debug.log" + echo "::: " + exit "${?}" } -function uninstallFunc { - $SUDO ${scriptDir}/uninstall.sh "${vpn}" - exit "$?" +removeOVPNFunc() { + shift + ${SUDO} "${scriptDir}/${vpn}/removeOVPN.sh" "$@" + exit "${?}" } -function update { - shift - $SUDO ${scriptDir}/update.sh "$@" - exit "$?" +uninstallFunc() { + ${SUDO} "${scriptDir}/uninstall.sh" "${vpn}" + exit "${?}" } -function backup { - $SUDO ${scriptDir}/backup.sh "${vpn}" - exit "$?" +update() { + shift + ${SUDO} "${scriptDir}/update.sh" "$@" + exit "${?}" } - -function helpFunc { - echo "::: Control all PiVPN specific functions!" - echo ":::" - echo "::: Usage: pivpn [option]" - echo ":::" - echo "::: Commands:" - echo "::: -a, add [nopass] Create a client ovpn profile, optional nopass" - echo "::: -c, clients List any connected clients to the server" - echo "::: -d, debug Start a debugging session if having trouble" - echo "::: -l, list List all valid and revoked certificates" - echo "::: -r, revoke Revoke a client ovpn profile" - echo "::: -h, help Show this help dialog" - echo "::: -u, uninstall Uninstall PiVPN from your system!" - echo "::: -up, update Updates PiVPN Scripts" - echo "::: -bk, backup Backup Openvpn and ovpns dir" - exit 0 +backup() { + ${SUDO} "${scriptDir}/backup.sh" "${vpn}" + exit "${?}" } -if [[ $# = 0 ]]; then - helpFunc +helpFunc() { + echo "::: Control all PiVPN specific functions!" + echo ":::" + echo "::: Usage: pivpn [option]" + echo ":::" + echo "::: Commands:" + echo "::: -a, add [nopass] Create a client ovpn profile, optional nopass" + echo "::: -c, clients List any connected clients to the server" + echo "::: -d, debug Start a debugging session if having trouble" + echo "::: -l, list List all valid and revoked certificates" + echo "::: -r, revoke Revoke a client ovpn profile" + echo "::: -h, help Show this help dialog" + echo "::: -u, uninstall Uninstall PiVPN from your system!" + echo "::: -up, update Updates PiVPN Scripts" + echo "::: -bk, backup Backup Openvpn and ovpns dir" + exit 0 +} + +if [[ "$#" == 0 ]]; then + helpFunc fi # Handle redirecting to specific functions based on arguments -case "$1" in -"-a" | "add" ) makeOVPNFunc "$@";; -"-c" | "clients" ) listClientsFunc "$@";; -"-d" | "debug" ) debugFunc;; -"-l" | "list" ) listOVPNFunc;; -"-r" | "revoke" ) removeOVPNFunc "$@";; -"-h" | "help" ) helpFunc;; -"-u" | "uninstall" ) uninstallFunc;; -"-up"| "update" ) update "$@" ;; -"-bk"| "backup" ) backup;; -* ) helpFunc;; +case "${1}" in + "-a" | "add") + makeOVPNFunc "$@" + ;; + "-c" | "clients") + listClientsFunc "$@" + ;; + "-d" | "debug") + debugFunc + ;; + "-l" | "list") + listOVPNFunc + ;; + "-r" | "revoke") + removeOVPNFunc "$@" + ;; + "-h" | "help") + helpFunc + ;; + "-u" | "uninstall") + uninstallFunc + ;; + "-up" | "update") + update "$@" + ;; + "-bk" | "backup") + backup + ;; + *) + helpFunc + ;; esac diff --git a/scripts/openvpn/pivpnDebug.sh b/scripts/openvpn/pivpnDebug.sh index 35a39f1..fc78e8d 100755 --- a/scripts/openvpn/pivpnDebug.sh +++ b/scripts/openvpn/pivpnDebug.sh @@ -1,11 +1,15 @@ -#!/usr/bin/env bash +#!/bin/bash # This scripts runs as root setupVars="/etc/pivpn/openvpn/setupVars.conf" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 @@ -15,41 +19,63 @@ echo -e "::::\t\t\e[4mPiVPN debug\e[0m\t\t ::::" printf "=============================================\n" echo -e "::::\t\t\e[4mLatest commit\e[0m\t\t ::::" echo -n "Branch: " + git --git-dir /usr/local/src/pivpn/.git rev-parse --abbrev-ref HEAD -git --git-dir /usr/local/src/pivpn/.git log -n 1 --format='Commit: %H%nAuthor: %an%nDate: %ad%nSummary: %s' +git \ + --git-dir /usr/local/src/pivpn/.git log \ + -n 1 \ + --format='Commit: %H%nAuthor: %an%nDate: %ad%nSummary: %s' + printf "=============================================\n" echo -e "::::\t \e[4mInstallation settings\e[0m \t ::::" + # shellcheck disable=SC2154 -sed "s/$pivpnHOST/REDACTED/" < ${setupVars} +sed "s/${pivpnHOST}/REDACTED/" < "${setupVars}" + printf "=============================================\n" echo -e ":::: \e[4mServer configuration shown below\e[0m ::::" + cat /etc/openvpn/server.conf + printf "=============================================\n" echo -e ":::: \e[4mClient template file shown below\e[0m ::::" -sed "s/$pivpnHOST/REDACTED/" < /etc/openvpn/easy-rsa/pki/Default.txt + +sed "s/${pivpnHOST}/REDACTED/" < /etc/openvpn/easy-rsa/pki/Default.txt + printf "=============================================\n" -echo -e ":::: \t\e[4mRecursive list of files in\e[0m\t ::::\n::: \e[4m/etc/openvpn/easy-rsa/pki shows below\e[0m :::" +echo -e ":::: \t\e[4mRecursive list of files in\e[0m\t ::::\n" +echo -e "::: \e[4m/etc/openvpn/easy-rsa/pki shows below\e[0m :::" + ls -LR /etc/openvpn/easy-rsa/pki/ -Ireqs -Icerts_by_serial + printf "=============================================\n" echo -e "::::\t\t\e[4mSelf check\e[0m\t\t ::::" + /opt/pivpn/self_check.sh "${VPN}" + printf "=============================================\n" echo -e ":::: Having trouble connecting? Take a look at the FAQ:" echo -e ":::: \e[1mhttps://docs.pivpn.io/faq\e[0m" printf "=============================================\n" -if [ "${PLAT}" != 'Alpine' ]; then - echo -e ":::: \e[4mSnippet of the server log\e[0m ::::" - OVPNLOG="$(tail -n 20 /var/log/openvpn.log)" +if [[ "${PLAT}" != 'Alpine' ]]; then + echo -e ":::: \e[4mSnippet of the server log\e[0m ::::" + OVPNLOG="$(tail -n 20 /var/log/openvpn.log)" - # Regular expession taken from https://superuser.com/a/202835, it will match invalid IPs - # like 123.456.789.012 but it's fine since the log only contains valid ones. - declare -a IPS_TO_HIDE=("$(grepcidr -v 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 <<< "$OVPNLOG" | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | uniq)") - for IP in "${IPS_TO_HIDE[@]}"; do - OVPNLOG="${OVPNLOG//"$IP"/REDACTED}" - done + # Regular expession taken from https://superuser.com/a/202835, + # it will match invalid IPs like 123.456.789.012 but it's fine + # since the log only contains valid ones. + declare -a IPS_TO_HIDE=("$(echo "${OVPNLOG}" | + grepcidr -v 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 | + grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | + uniq)") - echo "$OVPNLOG" - printf "=============================================\n" + for IP in "${IPS_TO_HIDE[@]}"; do + OVPNLOG="${OVPNLOG//"$IP"/REDACTED}" + done + + echo "${OVPNLOG}" + printf "=============================================\n" fi + echo -e "::::\t\t\e[4mDebug complete\e[0m\t\t ::::" diff --git a/scripts/openvpn/removeOVPN.sh b/scripts/openvpn/removeOVPN.sh index 3c50398..35210b2 100755 --- a/scripts/openvpn/removeOVPN.sh +++ b/scripts/openvpn/removeOVPN.sh @@ -1,172 +1,196 @@ -#!/usr/bin/env bash +#!/bin/bash # PiVPN: revoke client script setupVars="/etc/pivpn/openvpn/setupVars.conf" INDEX="/etc/openvpn/easy-rsa/pki/index.txt" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + helpFunc() { - echo "::: Revoke a client ovpn profile" - echo ":::" - echo "::: Usage: pivpn <-r|revoke> [-y|--yes] [-h|--help] [] ... [] ..." - echo ":::" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: Client(s) to to revoke" - echo "::: -y,--yes Remove Client(s) without confirmation" - echo "::: -h,--help Show this help dialog" + echo "::: Revoke a client ovpn profile" + echo ":::" + echo -n "::: Usage: pivpn <-r|revoke> [-y|--yes] [-h|--help] " + echo "[ ... [] ...]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: Client(s) to to revoke" + echo "::: -y,--yes Remove Client(s) without confirmation" + echo "::: -h,--help Show this help dialog" } # Parse input arguments -while test $# -gt 0; do - _key="$1" - case "$_key" in - -h|--help) - helpFunc - exit 0 - ;; - -y|--yes) - CONFIRM=true - ;; - *) - CERTS_TO_REVOKE+=("$1") - ;; - esac - shift +while [[ "$#" -gt 0 ]]; do + _key="${1}" + + case "${_key}" in + -h | --help) + helpFunc + exit 0 + ;; + -y | --yes) + CONFIRM=true + ;; + *) + CERTS_TO_REVOKE+=("${1}") + ;; + esac + + shift done -if [ ! -f "${INDEX}" ]; then - printf "The file: %s was not found\n" "$INDEX" - exit 1 +if [[ ! -f "${INDEX}" ]]; then + err "The file: ${INDEX} was not found" + exit 1 fi # Disabling SC2128, just checking if variable is empty or not # shellcheck disable=SC2128 if [[ -z "${CERTS_TO_REVOKE}" ]]; then - printf "\n" - printf " ::\e[4m Certificate List \e[0m:: \n" + printf "\n" + printf " ::\e[4m Certificate List \e[0m:: \n" - i=0 - while read -r line || [ -n "$line" ]; do - STATUS=$(echo "$line" | awk '{print $1}') - if [[ "${STATUS}" = "V" ]]; then - # Disabling SC2001 warning, suggested method doesn't work with regexp - # shellcheck disable=SC2001 - NAME=$(echo "$line" | sed -e 's:.*/CN=::') - if [ "$i" != 0 ]; then - # Prevent printing "server" certificate - CERTS[$i]=$(echo -e "${NAME}") - fi - ((i++)) - fi - done <${INDEX} + i=0 + while read -r line || [[ -n "${line}" ]]; do + STATUS=$(echo "${line}" | awk '{print $1}') - i=1 - len=${#CERTS[@]} - while [ $i -le "${len}" ]; do - printf "%0${#len}s) %s\r\n" ${i} "${CERTS[(($i))]}" - ((i++)) - done - printf "\n" + if [[ "${STATUS}" == "V" ]]; then + # Disabling SC2001 warning, suggested method doesn't work with regexp + # shellcheck disable=SC2001 + NAME=$(echo "${line}" | sed -e 's:.*/CN=::') - echo -n "::: Please enter the Index/Name of the client to be revoked from the list above: " - read -r NAME + if [[ "${i}" != 0 ]]; then + # Prevent printing "server" certificate + CERTS["${i}"]=$(echo -e "${NAME}") + fi - if [[ -z "${NAME}" ]]; then - echo "You can not leave this blank!" - exit 1 + ((i++)) fi + done < "${INDEX}" - re='^[0-9]+$' - if [[ ${NAME} =~ $re ]] ; then - NAME=${CERTS[$((NAME))]} + i=1 + len="${#CERTS[@]}" + while [[ "${i}" -le "${len}" ]]; do + printf "%0${#len}s) %s\r\n" "${i}" "${CERTS[(($i))]}" + ((i++)) + done + + printf "\n" + echo -n "::: Please enter the Index/Name of the client to be revoked " + echo -n "from the list above: " + read -r NAME + + if [[ -z "${NAME}" ]]; then + err "You can not leave this blank!" + exit 1 + fi + + re='^[0-9]+$' + if [[ "${NAME}" =~ $re ]]; then + NAME="${CERTS[$((NAME))]}" + fi + + for ((x = 1; x <= i; ++x)); do + if [[ "${CERTS[$x]}" == "${NAME}" ]]; then + VALID=1 fi + done - for((x=1;x<=i;++x)); do - if [ "${CERTS[$x]}" = "${NAME}" ]; then - VALID=1 - fi - done + if [[ -z "${VALID}" ]]; then + err "You didn't enter a valid cert name!" + exit 1 + fi - if [ -z "${VALID}" ]; then - printf "You didn't enter a valid cert name!\n" - exit 1 - fi - - CERTS_TO_REVOKE=( "${NAME}" ) + CERTS_TO_REVOKE=("${NAME}") else - i=0 - while read -r line || [ -n "$line" ]; do - STATUS=$(echo "$line" | awk '{print $1}') - if [[ "${STATUS}" = "V" ]]; then - NAME=$(echo -e "$line" | sed -e 's:.*/CN=::') - CERTS[$i]=${NAME} - ((i++)) - fi - done <${INDEX} + i=0 + while read -r line || [[ -n "${line}" ]]; do + STATUS=$(echo "${line}" | awk '{print $1}') - for (( ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do - VALID=0 - for((x=1;x<=i;++x)); do - if [ "${CERTS[$x]}" = "${CERTS_TO_REVOKE[ii]}" ]; then - VALID=1 - fi - done + if [[ "${STATUS}" == "V" ]]; then + NAME=$(echo -e "${line}" | sed -e 's:.*/CN=::') + CERTS["${i}"]="${NAME}" + ((i++)) + fi + done < "${INDEX}" - if [ "${VALID}" != 1 ]; then - printf "You passed an invalid cert name: '%s'! \n" "${CERTS_TO_REVOKE[ii]}" - exit 1 - fi + for ((ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do + VALID=0 + + for ((x = 1; x <= i; ++x)); do + if [[ "${CERTS[$x]}" == "${CERTS_TO_REVOKE[ii]}" ]]; then + VALID=1 + fi done + + if [[ "${VALID}" != 1 ]]; then + err "You passed an invalid cert name: '${CERTS_TO_REVOKE[ii]}'!" + exit 1 + fi + done fi cd /etc/openvpn/easy-rsa || exit -for (( ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do - if [ -n "$CONFIRM" ]; then - REPLY="y" - else - read -r -p "Do you really want to revoke '${CERTS_TO_REVOKE[ii]}'? [y/N] " - fi - if [[ $REPLY =~ ^[Yy]$ ]]; then - printf "\n::: Revoking certificate '%s'. \n" "${CERTS_TO_REVOKE[ii]}" - ./easyrsa --batch revoke "${CERTS_TO_REVOKE[ii]}" - ./easyrsa gen-crl - printf "\n::: Certificate revoked, and CRL file updated.\n" - printf "::: Removing certs and client configuration for this profile.\n" - rm -rf "pki/reqs/${CERTS_TO_REVOKE[ii]}.req" - rm -rf "pki/private/${CERTS_TO_REVOKE[ii]}.key" - rm -rf "pki/issued/${CERTS_TO_REVOKE[ii]}.crt" - - # Disabling SC2154 $pivpnNET sourced externally - # shellcheck disable=SC2154 - # Grab the client IP address - NET_REDUCED="${pivpnNET::-2}" - STATIC_IP=$(grep -v "^#" /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" | grep -w ifconfig-push | grep -oE "${NET_REDUCED}\.[0-9]{1,3}") - rm -rf /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" - - # disablung warning SC2154, $install_home sourced externally - # shellcheck disable=SC2154 - rm -rf "${install_home}/ovpns/${CERTS_TO_REVOKE[ii]}.ovpn" - rm -rf "/etc/openvpn/easy-rsa/pki/${CERTS_TO_REVOKE[ii]}.ovpn" - cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem - - # If using Pi-hole, remove the client from the hosts file - if [ -f /etc/pivpn/hosts.openvpn ]; then - sed "\#${STATIC_IP} ${CERTS_TO_REVOKE[ii]}.pivpn#d" -i /etc/pivpn/hosts.openvpn - if killall -SIGHUP pihole-FTL; then - echo "::: Updated hosts file for Pi-hole" - else - echo "::: Failed to reload pihole-FTL configuration" - fi - fi +for ((ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do + if [[ -n "${CONFIRM}" ]]; then + REPLY="y" + else + read -r -p "Do you really want to revoke '${CERTS_TO_REVOKE[ii]}'? [y/N] " + fi + + if [[ "${REPLY}" =~ ^[Yy]$ ]]; then + printf "\n::: Revoking certificate '%s'. \n" "${CERTS_TO_REVOKE[ii]}" + + ./easyrsa --batch revoke "${CERTS_TO_REVOKE[ii]}" + ./easyrsa gen-crl + + printf "\n::: Certificate revoked, and CRL file updated.\n" + printf "::: Removing certs and client configuration for this profile.\n" + + rm -rf "pki/reqs/${CERTS_TO_REVOKE[ii]}.req" + rm -rf "pki/private/${CERTS_TO_REVOKE[ii]}.key" + rm -rf "pki/issued/${CERTS_TO_REVOKE[ii]}.crt" + + # Disabling SC2154 $pivpnNET sourced externally + # shellcheck disable=SC2154 + # Grab the client IP address + NET_REDUCED="${pivpnNET::-2}" + STATIC_IP="$(grep -v "^#" /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" | + grep -w ifconfig-push | + grep -oE "${NET_REDUCED}\.[0-9]{1,3}")" + rm -rf /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" + + # disablung warning SC2154, $install_home sourced externally + # shellcheck disable=SC2154 + rm -rf "${install_home}/ovpns/${CERTS_TO_REVOKE[ii]}.ovpn" + rm -rf "/etc/openvpn/easy-rsa/pki/${CERTS_TO_REVOKE[ii]}.ovpn" + cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem + + # If using Pi-hole, remove the client from the hosts file + if [[ -f /etc/pivpn/hosts.openvpn ]]; then + sed \ + -e "\#${STATIC_IP} ${CERTS_TO_REVOKE[ii]}.pivpn#d" \ + -i \ + /etc/pivpn/hosts.openvpn + + if killall -SIGHUP pihole-FTL; then + echo "::: Updated hosts file for Pi-hole" + else + err "::: Failed to reload pihole-FTL configuration" + fi fi + fi done + printf "::: Completed!\n" diff --git a/scripts/pivpn b/scripts/pivpn index dd97763..875d7aa 100755 --- a/scripts/pivpn +++ b/scripts/pivpn @@ -3,53 +3,69 @@ CHECK_PKG_INSTALLED='dpkg-query -s' if grep -qsEe "^NAME\=['\"]?Alpine[a-zA-Z ]*['\"]?$" /etc/os-release; then - CHECK_PKG_INSTALLED='apk --no-cache info -e' + CHECK_PKG_INSTALLED='apk --no-cache info -e' fi # Must be root to use this tool -if [ $EUID -ne 0 ]; then - if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then - export SUDO="sudo" - else - echo "::: Please install sudo or run this as root." - exit 1 - fi +if [[ "${EUID}" -ne 0 ]]; then + if ${CHECK_PKG_INSTALLED} sudo &> /dev/null; then + export SUDO="sudo" + else + err "::: Please install sudo or run this as root." + exit 1 + fi fi scriptDir="/opt/pivpn" -uninstallServer(){ - $SUDO ${scriptDir}/uninstall.sh - exit "$?" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -backup(){ - $SUDO ${scriptDir}/backup.sh - exit "$?" +uninstallServer() { + ${SUDO} "${scriptDir}/uninstall.sh" + exit "${?}" } -showHelp(){ - echo "::: To pass off to the pivpn command for each protocol" - echo ":::" - echo "::: Usage: pivpn wg [option]" - echo "::: Usage: pivpn ovpn [option]" - echo ":::" - echo "::: -h, help Show this help dialog" - echo "::: -u, uninstall Uninstall pivpn from your system!" - echo "::: -bk, backup Backup VPN configs and user profiles" - exit 0 +backup() { + ${SUDO} "${scriptDir}/backup.sh" + exit "${?}" } -if [ $# = 0 ]; then - showHelp +showHelp() { + echo "::: To pass off to the pivpn command for each protocol" + echo ":::" + echo "::: Usage: pivpn wg [option]" + echo "::: Usage: pivpn ovpn [option]" + echo ":::" + echo "::: -h, help Show this help dialog" + echo "::: -u, uninstall Uninstall pivpn from your system!" + echo "::: -bk, backup Backup VPN configs and user profiles" + exit 0 +} + +if [[ "$#" == 0 ]]; then + showHelp fi # Handle redirecting to specific functions based on arguments -case "$1" in - wg ) "${scriptDir}/wireguard/pivpn.sh" "${@:2}";; - ovpn ) "${scriptDir}/openvpn/pivpn.sh" "${@:2}";; -"-h" | "help" ) showHelp;; -"-u" | "uninstall" ) uninstallServer;; -"-bk" | "backup" ) backup ;; -* ) showHelp;; +case "${1}" in + wg) + "${scriptDir}/wireguard/pivpn.sh" "${@:2}" + ;; + ovpn) + "${scriptDir}/openvpn/pivpn.sh" "${@:2}" + ;; + "-h" | "help") + showHelp + ;; + "-u" | "uninstall") + uninstallServer + ;; + "-bk" | "backup") + backup + ;; + *) + showHelp + ;; esac diff --git a/scripts/self_check.sh b/scripts/self_check.sh index bee00b9..153cdab 100755 --- a/scripts/self_check.sh +++ b/scripts/self_check.sh @@ -1,217 +1,344 @@ #!/bin/bash -PLAT=$(grep -sEe '^NAME\=' /etc/os-release | sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/") +PLAT="$(grep -sEe '^NAME\=' /etc/os-release | + sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/")" # dual protocol, VPN type supplied as $1 -VPN=$1 +VPN="${1}" setupVars="/etc/pivpn/${VPN}/setupVars.conf" ERR=0 -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # SC1090 disabled as setupVars file differs from system to system # shellcheck disable=SC1090 source "${setupVars}" -if [ "$VPN" = "wireguard" ]; then - VPN_SERVICE="wg-quick@wg0" +if [[ "${VPN}" == "wireguard" ]]; then + VPN_PRETTY_NAME="WireGuard" + VPN_SERVICE="wg-quick@wg0" - if [ "${PLAT}" == 'Alpine' ]; then - VPN_SERVICE='wg-quick' - fi - - VPN_PRETTY_NAME="WireGuard" -elif [ "$VPN" = "openvpn" ]; then - VPN_SERVICE="openvpn" - VPN_PRETTY_NAME="OpenVPN" + if [[ "${PLAT}" == 'Alpine' ]]; then + VPN_SERVICE='wg-quick' + fi +elif [[ "${VPN}" == "openvpn" ]]; then + VPN_SERVICE="openvpn" + VPN_PRETTY_NAME="OpenVPN" fi -if [ "$( /dev/null; then + echo ":: [OK] Iptables MASQUERADE rule set" + else + ERR=1 + echo -n ":: [ERR] Iptables MASQUERADE rule is not set, " + echo -n "attempt fix now? [Y/n] " + read -r REPLY - # Disabled SC Warnings for SC2154, values for variables are sourced from setupVars - # shellcheck disable=SC2154 - if iptables -t nat -C POSTROUTING -s "${pivpnNET}/${subnetClass}" -o "${IPv4dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" &> /dev/null; then - echo ":: [OK] Iptables MASQUERADE rule set" - else - ERR=1 - read -r -p ":: [ERR] Iptables MASQUERADE rule is not set, attempt fix now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - iptables -t nat -I POSTROUTING -s "${pivpnNET}/${subnetClass}" -o "${IPv4dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" - iptables-save > /etc/iptables/rules.v4 - echo "Done" - fi - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + iptables \ + -t nat \ + -I POSTROUTING \ + -s "${pivpnNET}/${subnetClass}" \ + -o "${IPv4dev}" \ + -j MASQUERADE \ + -m comment \ + --comment "${VPN}-nat-rule" - if [ "$INPUT_CHAIN_EDITED" -eq 1 ]; then + iptables-save > /etc/iptables/rules.v4 + echo "Done" + fi + fi - # Disabled SC Warnings for SC2154, values for variables are sourced from setupVars - # shellcheck disable=SC2154 - if iptables -C INPUT -i "${IPv4dev}" -p "${pivpnPROTO}" --dport "${pivpnPORT}" -j ACCEPT -m comment --comment "${VPN}-input-rule" &> /dev/null; then - echo ":: [OK] Iptables INPUT rule set" - else - ERR=1 - read -r -p ":: [ERR] Iptables INPUT rule is not set, attempt fix now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - iptables -I INPUT 1 -i "${IPv4dev}" -p "${pivpnPROTO}" --dport "${pivpnPORT}" -j ACCEPT -m comment --comment "${VPN}-input-rule" - iptables-save > /etc/iptables/rules.v4 - echo "Done" - fi - fi - fi + if [[ "${INPUT_CHAIN_EDITED}" -eq 1 ]]; then + # Disabled SC Warnings for SC2154, values + # for variables are sourced from setupVars + # shellcheck disable=SC2154 + if iptables \ + -C INPUT \ + -i "${IPv4dev}" \ + -p "${pivpnPROTO}" \ + --dport "${pivpnPORT}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-input-rule" &> /dev/null; then + echo ":: [OK] Iptables INPUT rule set" + else + ERR=1 + read \ + -r \ + -p ":: [ERR] Iptables INPUT rule is not set, attempt fix now? [Y/n] " \ + REPLY - if [ "$FORWARD_CHAIN_EDITED" -eq 1 ]; then + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + iptables \ + -I INPUT 1 \ + -i "${IPv4dev}" \ + -p "${pivpnPROTO}" \ + --dport "${pivpnPORT}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-input-rule" - # Disabled SC Warnings for SC2154, values for variables are sourced from setupVars - # shellcheck disable=SC2154 - if iptables -C FORWARD -s "${pivpnNET}/${subnetClass}" -i "${pivpnDEV}" -o "${IPv4dev}" -j ACCEPT -m comment --comment "${VPN}-forward-rule" &> /dev/null; then - echo ":: [OK] Iptables FORWARD rule set" - else - ERR=1 - read -r -p ":: [ERR] Iptables FORWARD rule is not set, attempt fix now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - iptables -I FORWARD 1 -d "${pivpnNET}/${subnetClass}" -i "${IPv4dev}" -o "${pivpnDEV}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "${VPN}-forward-rule" - iptables -I FORWARD 2 -s "${pivpnNET}/${subnetClass}" -i "${pivpnDEV}" -o "${IPv4dev}" -j ACCEPT -m comment --comment "${VPN}-forward-rule" - iptables-save > /etc/iptables/rules.v4 - echo "Done" - fi - fi - fi + iptables-save > /etc/iptables/rules.v4 + echo "Done" + fi + fi + fi + if [[ "${FORWARD_CHAIN_EDITED}" -eq 1 ]]; then + # Disabled SC Warnings for SC2154, values + # for variables are sourced from setupVars + # shellcheck disable=SC2154 + if iptables \ + -C FORWARD \ + -s "${pivpnNET}/${subnetClass}" \ + -i "${pivpnDEV}" \ + -o "${IPv4dev}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" &> /dev/null; then + echo ":: [OK] Iptables FORWARD rule set" + else + ERR=1 + echo -n ":: [ERR] Iptables FORWARD rule is not set, " + echo -n "attempt fix now? [Y/n] " + read -r REPLY + + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + iptables \ + -I FORWARD 1 \ + -d "${pivpnNET}/${subnetClass}" \ + -i "${IPv4dev}" \ + -o "${pivpnDEV}" \ + -m conntrack \ + --ctstate RELATED,ESTABLISHED \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + + iptables \ + -I FORWARD 2 \ + -s "${pivpnNET}/${subnetClass}" \ + -i "${pivpnDEV}" \ + -o "${IPv4dev}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + + iptables-save > /etc/iptables/rules.v4 + echo "Done" + fi + fi + fi else + if LANG="en_US.UTF-8" ufw status | grep -qw 'active'; then + echo ":: [OK] Ufw is enabled" + else + ERR=1 + echo -n ":: [ERR] Ufw is not enabled, " + echo -n "try to enable now? [Y/n] " + read -r REPLY - if LANG="en_US.UTF-8" ufw status | grep -qw 'active'; then - echo ":: [OK] Ufw is enabled" - else - ERR=1 - read -r -p ":: [ERR] Ufw is not enabled, try to enable now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - ufw enable - fi - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + ufw enable + fi + fi - if iptables -t nat -C POSTROUTING -s "${pivpnNET}/${subnetClass}" -o "${IPv4dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" &> /dev/null; then - echo ":: [OK] Iptables MASQUERADE rule set" - else - ERR=1 - read -r -p ":: [ERR] Iptables MASQUERADE rule is not set, attempt fix now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - sed "/delete these required/i *nat\n:POSTROUTING ACCEPT [0:0]\n-I POSTROUTING -s ${pivpnNET}/${subnetClass} -o ${IPv4dev} -j MASQUERADE -m comment --comment ${VPN}-nat-rule\nCOMMIT\n" -i /etc/ufw/before.rules - ufw reload - echo "Done" - fi - fi + if iptables \ + -t nat \ + -C POSTROUTING \ + -s "${pivpnNET}/${subnetClass}" \ + -o "${IPv4dev}" \ + -j MASQUERADE \ + -m comment \ + --comment "${VPN}-nat-rule" &> /dev/null; then + echo ":: [OK] Iptables MASQUERADE rule set" + else + ERR=1 + echo -n ":: [ERR] Iptables MASQUERADE rule is not set, " + echo -n "attempt fix now? [Y/n] " + read -r REPLY - if iptables -C ufw-user-input -p "${pivpnPROTO}" --dport "${pivpnPORT}" -j ACCEPT &> /dev/null; then - echo ":: [OK] Ufw input rule set" - else - ERR=1 - read -r -p ":: [ERR] Ufw input rule is not set, attempt fix now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - ufw insert 1 allow "${pivpnPORT}"/"${pivpnPROTO}" - ufw reload - echo "Done" - fi - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + sed_pattern='/delete these required/i' + sed_pattern="${sed_pattern} *nat\n:POSTROUTING ACCEPT [0:0]\n" + sed_pattern="${sed_pattern} -I POSTROUTING" + sed_pattern="${sed_pattern} -s ${pivpnNET}/${subnetClass}" + sed_pattern="${sed_pattern} -o ${IPv4dev}" + sed_pattern="${sed_pattern} -j MASQUERADE" + sed_pattern="${sed_pattern} -m comment" + sed_pattern="${sed_pattern} --comment ${VPN}-nat-rule\n" + sed_pattern="${sed_pattern}COMMIT\n" - if iptables -C ufw-user-forward -i "${pivpnDEV}" -o "${IPv4dev}" -s "${pivpnNET}/${subnetClass}" -j ACCEPT &> /dev/null; then - echo ":: [OK] Ufw forwarding rule set" - else - ERR=1 - read -r -p ":: [ERR] Ufw forwarding rule is not set, attempt fix now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - ufw route insert 1 allow in on "${pivpnDEV}" from "${pivpnNET}/${subnetClass}" out on "${IPv4dev}" to any - ufw reload - echo "Done" - fi - fi + sed "${sed_pattern}" -i /etc/ufw/before.rules + ufw reload + echo "Done" + unset sed_pattern + fi + fi + if iptables \ + -C ufw-user-input \ + -p "${pivpnPROTO}" \ + --dport "${pivpnPORT}" \ + -j ACCEPT &> /dev/null; then + echo ":: [OK] Ufw input rule set" + else + ERR=1 + read \ + -r \ + -p ":: [ERR] Ufw input rule is not set, attempt fix now? [Y/n] " \ + REPLY + + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + ufw insert 1 allow "${pivpnPORT}"/"${pivpnPROTO}" + ufw reload + echo "Done" + fi + fi + + if iptables \ + -C ufw-user-forward \ + -i "${pivpnDEV}" \ + -o "${IPv4dev}" \ + -s "${pivpnNET}/${subnetClass}" \ + -j ACCEPT &> /dev/null; then + echo ":: [OK] Ufw forwarding rule set" + else + ERR=1 + read \ + -r \ + -p ":: [ERR] Ufw forwarding rule is not set, attempt fix now? [Y/n] " \ + REPLY + + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + ufw route insert 1 allow in on "${pivpnDEV}" \ + from "${pivpnNET}/${subnetClass}" out on "${IPv4dev}" to any + ufw reload + echo "Done" + fi + fi fi -if [ "${PLAT}" == 'Alpine' ]; then - if [ "$(rc-service "${VPN_SERVICE}" status | sed -E -e 's/.*status\: (.*)/\1/')" == 'started' ]; then - echo ":: [OK] ${VPN_PRETTY_NAME} is running" - else - ERR=1 - read -r -p ":: [ERR] ${VPN_PRETTY_NAME} is not running, try to start now? [Y/n] " REPLY +if [[ "${PLAT}" == 'Alpine' ]]; then + if [[ "$(rc-service "${VPN_SERVICE}" status | + sed -E -e 's/.*status\: (.*)/\1/')" == 'started' ]]; then + echo ":: [OK] ${VPN_PRETTY_NAME} is running" + else + ERR=1 + echo -n ":: [ERR] ${VPN_PRETTY_NAME} is not running, " + echo -n "try to start now? [Y/n] " + read -r REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - rc-service -s "${VPN_SERVICE}" restart - rc-service -N "${VPN_SERVICE}" start + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + rc-service -s "${VPN_SERVICE}" restart + rc-service -N "${VPN_SERVICE}" start + echo "Done" + fi + fi - echo "Done" - fi - fi + if rc-update show default | + grep -sEe "\s*${VPN_SERVICE} .*" &> /dev/null; then + echo -n ":: [OK] ${VPN_PRETTY_NAME} is enabled " + echo "(it will automatically start on reboot)" + else + ERR=1 + echo -n ":: [ERR] ${VPN_PRETTY_NAME} is not enabled, " + echo -n "try to enable now? [Y/n] " + read -r REPLY - if rc-update show default | grep -sEe "\s*${VPN_SERVICE} .*" &> /dev/null; then - echo ":: [OK] ${VPN_PRETTY_NAME} is enabled (it will automatically start on reboot)" - else - ERR=1 - read -r -p ":: [ERR] ${VPN_PRETTY_NAME} is not enabled, try to enable now? [Y/n] " REPLY - - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - rc-update add "${VPN_SERVICE}" default - - echo "Done" - fi - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + rc-update add "${VPN_SERVICE}" default + echo "Done" + fi + fi else - if systemctl is-active -q "${VPN_SERVICE}"; then - echo ":: [OK] ${VPN_PRETTY_NAME} is running" - else - ERR=1 - read -r -p ":: [ERR] ${VPN_PRETTY_NAME} is not running, try to start now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - systemctl start "${VPN_SERVICE}" - echo "Done" - fi - fi + if systemctl is-active -q "${VPN_SERVICE}"; then + echo ":: [OK] ${VPN_PRETTY_NAME} is running" + else + ERR=1 + echo -n ":: [ERR] ${VPN_PRETTY_NAME} is not running, " + echo -n "try to start now? [Y/n] " + read -r REPLY - if systemctl is-enabled -q "${VPN_SERVICE}"; then - echo ":: [OK] ${VPN_PRETTY_NAME} is enabled (it will automatically start on reboot)" - else - ERR=1 - read -r -p ":: [ERR] ${VPN_PRETTY_NAME} is not enabled, try to enable now? [Y/n] " REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - systemctl enable "${VPN_SERVICE}" - echo "Done" - fi - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + systemctl start "${VPN_SERVICE}" + echo "Done" + fi + fi + + if systemctl is-enabled -q "${VPN_SERVICE}"; then + echo ":: [OK] ${VPN_PRETTY_NAME} is enabled " + echo "(it will automatically start on reboot)" + else + ERR=1 + echo -n ":: [ERR] ${VPN_PRETTY_NAME} is not enabled, " + echo -n "try to enable now? [Y/n] " + read -r REPLY + + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + systemctl enable "${VPN_SERVICE}" + echo "Done" + fi + fi fi # grep -w (whole word) is used so port 11940 won't match when looking for 1194 if netstat -antu | grep -wqE "${pivpnPROTO}.*${pivpnPORT}"; then - echo ":: [OK] ${VPN_PRETTY_NAME} is listening on port ${pivpnPORT}/${pivpnPROTO}" + echo -n ":: [OK] ${VPN_PRETTY_NAME} is listening " + echo "on port ${pivpnPORT}/${pivpnPROTO}" else - ERR=1 - read -r -p ":: [ERR] ${VPN_PRETTY_NAME} is not listening, try to restart now? [Y/n] " REPLY + ERR=1 + echo -n ":: [ERR] ${VPN_PRETTY_NAME} is not listening, " + echo -n "try to restart now? [Y/n] " + read -r REPLY - if [[ ${REPLY} =~ ^[Yy]$ ]] || [[ -z ${REPLY} ]]; then - if [ "${PLAT}" == 'Alpine' ]; then - rc-service -s "${VPN_SERVICE}" restart - rc-service -N "${VPN_SERVICE}" start - else - systemctl restart "${VPN_SERVICE}" - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]] || [[ -z "${REPLY}" ]]; then + if [[ "${PLAT}" == 'Alpine' ]]; then + rc-service -s "${VPN_SERVICE}" restart + rc-service -N "${VPN_SERVICE}" start + else + systemctl restart "${VPN_SERVICE}" + fi - echo "Done" - fi + echo "Done" + fi fi -if [ "$ERR" -eq 1 ]; then - echo -e "[INFO] Run \e[1mpivpn -d\e[0m again to see if we detect issues" +if [[ "${ERR}" -eq 1 ]]; then + echo -e "[INFO] Run \e[1mpivpn -d\e[0m again to see if we detect issues" fi diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 9c4c4d8..1a1ba91 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -1,20 +1,22 @@ -#!/usr/bin/env bash +#!/bin/bash # PiVPN: Uninstall Script -### FIXME: global: config storage, refactor all scripts to adhere to the storage -### FIXME: use variables where appropriate, reduce magic numbers by 99.9%, at least. +### FIXME: +### global: config storage, refactor all scripts to adhere to the storage +### FIXME: +### use variables where appropriate, reduce magic numbers by 99.9%, at least. # Find the rows and columns. Will default to 80x24 if it can not be detected. -screen_size=$(stty size 2>/dev/null || echo 24 80) -rows=$(echo "$screen_size" | awk '{print $1}') -columns=$(echo "$screen_size" | awk '{print $2}') +screen_size="$(stty size 2> /dev/null || echo 24 80)" +rows="$(echo "${screen_size}" | awk '{print $1}')" +columns="$(echo "${screen_size}" | awk '{print $2}')" # Divide by two so the dialogs take up half of the screen, which looks nice. -r=$(( rows / 2 )) -c=$(( columns / 2 )) +r=$((rows / 2)) +c=$((columns / 2)) # Unless the screen is tiny -r=$(( r < 20 ? 20 : r )) -c=$(( c < 70 ? 70 : c )) +r=$((r < 20 ? 20 : r)) +c=$((c < 70 ? 70 : c)) PKG_MANAGER="apt-get" PKG_REMOVE="${PKG_MANAGER} -y remove --purge" @@ -24,295 +26,393 @@ setupConfigDir="/etc/pivpn" pivpnFilesDir="/usr/local/src/pivpn" pivpnScriptDir="/opt/pivpn" -PLAT=$(grep -sEe '^NAME\=' /etc/os-release | sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/") +PLAT="$(grep -sEe '^NAME\=' /etc/os-release | + sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/")" -if [ "${PLAT}" == 'Alpine' ]; then - PKG_MANAGER='apk' - PKG_REMOVE="${PKG_MANAGER} --no-cache --purge del -r" +if [[ "${PLAT}" == 'Alpine' ]]; then + PKG_MANAGER='apk' + PKG_REMOVE="${PKG_MANAGER} --no-cache --purge del -r" fi UPDATE_PKG_CACHE="${PKG_MANAGER} update" -if [ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ] && [ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]; then - vpnStillExists=1 +if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]] && + [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then + vpnStillExists=1 - # Two protocols have been installed, check if the script has passed - # an argument, otherwise ask the user which one he wants to remove - if [ $# -ge 1 ]; then - VPN="$1" - echo "::: Uninstalling VPN: $VPN" - else - chooseVPNCmd=(whiptail --backtitle "Setup PiVPN" --title "Uninstall" --separate-output --radiolist "Both OpenVPN and WireGuard are installed, choose a VPN to uninstall (press space to select):" "${r}" "${c}" 2) - VPNChooseOptions=(WireGuard "" on - OpenVPN "" off) + # Two protocols have been installed, check if the script has passed + # an argument, otherwise ask the user which one he wants to remove + if [[ "$#" -ge 1 ]]; then + VPN="${1}" + echo "::: Uninstalling VPN: ${VPN}" + else + chooseVPNCmd=(whiptail + --backtitle "Setup PiVPN" + --title "Uninstall" + --separate-output + --radiolist "Both OpenVPN and WireGuard are installed, \ +choose a VPN to uninstall (press space to select):" + "${r}" "${c}" 2) + VPNChooseOptions=(WireGuard "" on + OpenVPN "" off) - if VPN=$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 >/dev/tty) ; then - echo "::: Uninstalling VPN: $VPN" - VPN="${VPN,,}" - else - echo "::: Cancel selected, exiting...." - exit 1 - fi - fi + if VPN="$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 \ + > /dev/tty)"; then + echo "::: Uninstalling VPN: ${VPN}" + VPN="${VPN,,}" + else + err "::: Cancel selected, exiting...." + exit 1 + fi + fi - setupVars="${setupConfigDir}/${VPN}/${setupVarsFile}" + setupVars="${setupConfigDir}/${VPN}/${setupVarsFile}" else - vpnStillExists=0 + vpnStillExists=0 - if [ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]; then - setupVars="${setupConfigDir}/wireguard/${setupVarsFile}" - elif [ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]; then - setupVars="${setupConfigDir}/openvpn/${setupVarsFile}" - fi + if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]]; then + setupVars="${setupConfigDir}/wireguard/${setupVarsFile}" + elif [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then + setupVars="${setupConfigDir}/openvpn/${setupVarsFile}" + fi fi -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + ### FIXME: introduce global lib -spinner(){ - local pid=$1 - local delay=0.50 - local spinstr='/-\|' - while ps a | awk '{print $1}' | grep -q "$pid"; do - local temp=${spinstr#?} - printf " [%c] " "$spinstr" - local spinstr=$temp${spinstr%"$temp"} - sleep $delay - printf "\\b\\b\\b\\b\\b\\b" - done - printf " \\b\\b\\b\\b" +spinner() { + local pid="${1}" + local delay=0.50 + local spinstr='/-\|' + + while ps a | awk '{print $1}' | grep -q "${pid}"; do + local temp="${spinstr#?}" + printf " [%c] " "${spinstr}" + local spinstr="${temp}${spinstr%"$temp"}" + sleep "${delay}" + printf "\\b\\b\\b\\b\\b\\b" + done + + printf " \\b\\b\\b\\b" } -removeAll(){ - # Stopping and disabling services - echo "::: Stopping and disabling services..." +removeAll() { + # Stopping and disabling services + echo "::: Stopping and disabling services..." - if [ "${PLAT}" == 'Alpine' ]; then - if [ "${VPN}" = "wireguard" ]; then - rc-service wg-quick stop - rc-update del wg-quick default &> /dev/null - elif [ "${VPN}" = "openvpn" ]; then - rc-service openvpn stop - rc-update del openvpn default &> /dev/null - fi - else - if [ "${VPN}" = "wireguard" ]; then - systemctl stop wg-quick@wg0 - systemctl disable wg-quick@wg0 &> /dev/null - elif [ "${VPN}" = "openvpn" ]; then - systemctl stop openvpn - systemctl disable openvpn &> /dev/null - fi - fi + if [[ "${PLAT}" == 'Alpine' ]]; then + if [[ "${VPN}" == "wireguard" ]]; then + rc-service wg-quick stop + rc-update del wg-quick default &> /dev/null + elif [[ "${VPN}" == "openvpn" ]]; then + rc-service openvpn stop + rc-update del openvpn default &> /dev/null + fi + else + if [[ "${VPN}" == "wireguard" ]]; then + systemctl stop wg-quick@wg0 + systemctl disable wg-quick@wg0 &> /dev/null + elif [[ "${VPN}" == "openvpn" ]]; then + systemctl stop openvpn + systemctl disable openvpn &> /dev/null + fi + fi - # Removing firewall rules. - echo "::: Removing firewall rules..." - - if [ "$USING_UFW" -eq 1 ]; then + # Removing firewall rules. + echo "::: Removing firewall rules..." + if [[ "${USING_UFW}" -eq 1 ]]; then ### Ignoring SC2154, value sourced from setupVars file - # shellcheck disable=SC2154 - ufw delete allow "${pivpnPORT}"/"${pivpnPROTO}" > /dev/null + # shellcheck disable=SC2154 + ufw delete allow "${pivpnPORT}/${pivpnPROTO}" > /dev/null ### Ignoring SC2154, value sourced from setupVars file - # shellcheck disable=SC2154 - ufw route delete allow in on "${pivpnDEV}" from "${pivpnNET}/${subnetClass}" out on "${IPv4dev}" to any > /dev/null - ufw delete allow in on "${pivpnDEV}" to any port 53 from "${pivpnNET}/${subnetClass}" >/dev/null - sed "/-I POSTROUTING -s ${pivpnNET}\\/${subnetClass} -o ${IPv4dev} -j MASQUERADE -m comment --comment ${VPN}-nat-rule/d" -i /etc/ufw/before.rules - iptables -t nat -D POSTROUTING -s "${pivpnNET}/${subnetClass}" -o "${IPv4dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" - ufw reload &> /dev/null + # shellcheck disable=SC2154 + ufw route delete allow in on "${pivpnDEV}" \ + from "${pivpnNET}/${subnetClass}" out on "${IPv4dev}" to any > /dev/null + ufw delete allow in on "${pivpnDEV}" to any port 53 \ + from "${pivpnNET}/${subnetClass}" > /dev/null - elif [ "$USING_UFW" -eq 0 ]; then + sed_pattern='/-I POSTROUTING' + sed_pattern="${sed_pattern} -s ${pivpnNET}\\/${subnetClass}" + sed_pattern="${sed_pattern} -o ${IPv4dev}" + sed_pattern="${sed_pattern} -j MASQUERADE" + sed_pattern="${sed_pattern} -m comment" + sed_pattern="${sed_pattern} --comment ${VPN}-nat-rule/d" + sed "${sed_pattern}" -i /etc/ufw/before.rules + unset sed_pattern - if [ "$INPUT_CHAIN_EDITED" -eq 1 ]; then - iptables -D INPUT -i "${IPv4dev}" -p "${pivpnPROTO}" --dport "${pivpnPORT}" -j ACCEPT -m comment --comment "${VPN}-input-rule" - fi + iptables \ + -t nat \ + -D POSTROUTING \ + -s "${pivpnNET}/${subnetClass}" \ + -o "${IPv4dev}" \ + -j MASQUERADE \ + -m comment \ + --comment "${VPN}-nat-rule" - if [ "$FORWARD_CHAIN_EDITED" -eq 1 ]; then - iptables -D FORWARD -d "${pivpnNET}/${subnetClass}" -i "${IPv4dev}" -o "${pivpnDEV}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "${VPN}-forward-rule" - iptables -D FORWARD -s "${pivpnNET}/${subnetClass}" -i "${pivpnDEV}" -o "${IPv4dev}" -j ACCEPT -m comment --comment "${VPN}-forward-rule" - fi + ufw reload &> /dev/null + elif [[ "${USING_UFW}" -eq 0 ]]; then + if [[ "${INPUT_CHAIN_EDITED}" -eq 1 ]]; then + iptables \ + -D INPUT \ + -i "${IPv4dev}" \ + -p "${pivpnPROTO}" \ + --dport "${pivpnPORT}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-input-rule" + fi - iptables -t nat -D POSTROUTING -s "${pivpnNET}/${subnetClass}" -o "${IPv4dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" - iptables-save > /etc/iptables/rules.v4 + if [[ "${FORWARD_CHAIN_EDITED}" -eq 1 ]]; then + iptables \ + -D FORWARD \ + -d "${pivpnNET}/${subnetClass}" \ + -i "${IPv4dev}" \ + -o "${pivpnDEV}" \ + -m conntrack \ + --ctstate RELATED,ESTABLISHED \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" - fi + iptables \ + -D FORWARD \ + -s "${pivpnNET}/${subnetClass}" \ + -i "${pivpnDEV}" \ + -o "${IPv4dev}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + fi - # Disable IPv4 forwarding - if [ "${vpnStillExists}" -eq 0 ]; then - sed -i '/net.ipv4.ip_forward=1/c\#net.ipv4.ip_forward=1' /etc/sysctl.conf - sysctl -p - fi + iptables \ + -t nat \ + -D POSTROUTING \ + -s "${pivpnNET}/${subnetClass}" \ + -o "${IPv4dev}" \ + -j MASQUERADE \ + -m comment \ + --comment "${VPN}-nat-rule" - # Purge dependencies - echo "::: Purge dependencies..." + iptables-save > /etc/iptables/rules.v4 + fi - for i in "${INSTALLED_PACKAGES[@]}"; do - while true; do - read -rp "::: Do you wish to remove $i from your system? [Y/n]: " yn - case $yn in - [Yy]* ) - if [ "${PLAT}" == 'Alpine' ]; then - if [ "${i}" == 'openvpn' ]; then - deluser openvpn - rm -f /etc/rsyslog.d/30-openvpn.conf /etc/logrotate.d/openvpn - fi - else - if [ "${i}" == "wireguard-tools" ]; then - # The bullseye repo may not exist if wireguard was available at the - # time of installation. - if [ -f /etc/apt/sources.list.d/pivpn-bullseye-repo.list ]; then - echo "::: Removing Debian Bullseye repo..." + # Disable IPv4 forwarding + if [[ "${vpnStillExists}" -eq 0 ]]; then + sed -i '/net.ipv4.ip_forward=1/c\#net.ipv4.ip_forward=1' /etc/sysctl.conf + sysctl -p + fi - rm -f /etc/apt/sources.list.d/pivpn-bullseye-repo.list - rm -f /etc/apt/preferences.d/pivpn-limit-bullseye + # Purge dependencies + echo "::: Purge dependencies..." - echo "::: Updating package cache..." + for i in "${INSTALLED_PACKAGES[@]}"; do + while true; do + read -rp "::: Do you wish to remove ${i} from your system? [Y/n]: " yn - ${UPDATE_PKG_CACHE} &> /dev/null & - spinner $! - fi + case "${yn}" in + [Yy]*) + if [[ "${PLAT}" == 'Alpine' ]]; then + if [[ "${i}" == 'openvpn' ]]; then + deluser openvpn + rm -f /etc/rsyslog.d/30-openvpn.conf /etc/logrotate.d/openvpn + fi + else + if [[ "${i}" == "wireguard-tools" ]]; then + # The bullseye repo may not exist if wireguard was available at + # the time of installation. + tmp_path='/etc/apt/sources.list.d/pivpn-bullseye-repo.list' - if [ -f /etc/systemd/system/wg-quick@.service.d/override.conf ]; then - rm -f /etc/systemd/system/wg-quick@.service.d/override.conf - fi - elif [ "${i}" = "unattended-upgrades" ]; then - rm -rf /var/log/unattended-upgrades /etc/apt/apt.conf.d/*periodic - rm -rf /etc/apt/apt.conf.d/*unattended-upgrades - elif [ "${i}" = "openvpn" ]; then - if [ -f /etc/apt/sources.list.d/pivpn-openvpn-repo.list ]; then - echo "::: Removing OpenVPN software repo..." + if [[ -f "${tmp_path}" ]]; then + echo "::: Removing Debian Bullseye repo..." - rm -f /etc/apt/sources.list.d/pivpn-openvpn-repo.list + rm -f "${tmp_path}" + rm -f /etc/apt/preferences.d/pivpn-limit-bullseye - echo "::: Updating package cache..." + echo "::: Updating package cache..." - ${UPDATE_PKG_CACHE} &> /dev/null & - spinner $! - fi + ${UPDATE_PKG_CACHE} &> /dev/null & + spinner "$!" + fi - deluser openvpn - rm -f /etc/rsyslog.d/30-openvpn.conf /etc/logrotate.d/openvpn - fi - fi + tmp_path='/etc/systemd/system/wg-quick@.service.d/override.conf' - printf ":::\\tRemoving %s..." "$i" + if [[ -f "${tmp_path}" ]]; then + rm -f "${tmp_path}" + fi - "${PKG_REMOVE}" "$i" &> /dev/null & - spinner $! + unset tmp_path + elif [[ "${i}" == "unattended-upgrades" ]]; then + rm -rf /var/log/unattended-upgrades /etc/apt/apt.conf.d/*periodic + rm -rf /etc/apt/apt.conf.d/*unattended-upgrades + elif [[ "${i}" == "openvpn" ]]; then + if [[ -f /etc/apt/sources.list.d/pivpn-openvpn-repo.list ]]; then + echo "::: Removing OpenVPN software repo..." - printf "done!\\n"; - break - ;; - [Nn]* ) - printf ":::\\tSkipping %s\\n" "$i"; - break - ;; - * ) - printf "::: You must answer yes or no!\\n" - ;; - esac - done - done + rm -f /etc/apt/sources.list.d/pivpn-openvpn-repo.list - if [ "${PLAT}" != 'Alpine' ]; then - # Take care of any additional package cleaning - printf "::: Auto removing remaining dependencies..." + echo "::: Updating package cache..." - "${PKG_MANAGER}" -y autoremove &> /dev/null & - spinner $! + ${UPDATE_PKG_CACHE} &> /dev/null & + spinner "$!" + fi - printf "done!\\n"; - printf "::: Auto cleaning remaining dependencies..." + deluser openvpn + rm -f /etc/rsyslog.d/30-openvpn.conf /etc/logrotate.d/openvpn + fi + fi - "${PKG_MANAGER}" -y autoclean &> /dev/null & - spinner $! + printf ":::\\tRemoving %s..." "${i}" - printf "done!\\n"; - fi + ${PKG_REMOVE} "${i}" &> /dev/null & + spinner "$!" - if [ -f "$dnsmasqConfig" ]; then - rm -f "$dnsmasqConfig" - pihole restartdns - fi + printf "done!\\n" + break + ;; + [Nn]*) + printf ":::\\tSkipping %s\\n" "${i}" + break + ;; + *) + err "::: You must answer yes or no!" + ;; + esac + done + done - echo ":::" - echo "::: Removing VPN configuration files..." + if [[ "${PLAT}" != 'Alpine' ]]; then + # Take care of any additional package cleaning + printf "::: Auto removing remaining dependencies..." - if [ "$VPN" = "wireguard" ]; then - rm -f /etc/wireguard/wg0.conf - rm -rf /etc/wireguard/configs - rm -rf /etc/wireguard/keys + "${PKG_MANAGER}" -y autoremove &> /dev/null & + spinner "$!" + + printf "done!\\n" + printf "::: Auto cleaning remaining dependencies..." + + "${PKG_MANAGER}" -y autoclean &> /dev/null & + spinner "$!" + + printf "done!\\n" + fi + + if [[ -f "${dnsmasqConfig}" ]]; then + rm -f "${dnsmasqConfig}" + pihole restartdns + fi + + echo ":::" + echo "::: Removing VPN configuration files..." + + if [[ "${VPN}" == "wireguard" ]]; then + rm -f /etc/wireguard/wg0.conf + rm -rf /etc/wireguard/configs + rm -rf /etc/wireguard/keys ### Ignoring SC2154, value sourced from setupVars file - # shellcheck disable=SC2154 - rm -rf "$install_home/configs" - elif [ "$VPN" = "openvpn" ]; then - rm -rf /var/log/*openvpn* - rm -f /etc/openvpn/server.conf - rm -f /etc/openvpn/crl.pem - rm -rf /etc/openvpn/easy-rsa - rm -rf /etc/openvpn/ccd - rm -rf "$install_home/ovpns" - fi + # shellcheck disable=SC2154 + rm -rf "${install_home}/configs" + elif [[ "${VPN}" == "openvpn" ]]; then + rm -rf /var/log/*openvpn* + rm -f /etc/openvpn/server.conf + rm -f /etc/openvpn/crl.pem + rm -rf /etc/openvpn/easy-rsa + rm -rf /etc/openvpn/ccd + rm -rf "${install_home}/ovpns" + fi - if [ "${vpnStillExists}" -eq 0 ]; then - echo ":::" - echo "::: Removing pivpn system files..." - rm -rf "${setupConfigDir}" - rm -rf "${pivpnFilesDir}" - rm -f /var/log/*pivpn* - rm -f /etc/bash_completion.d/pivpn - unlink "${pivpnScriptDir}" - unlink /usr/local/bin/pivpn - else - if [[ ${VPN} == 'wireguard' ]]; then - othervpn='openvpn' - else - othervpn='wireguard' - fi + if [[ "${vpnStillExists}" -eq 0 ]]; then + echo ":::" + echo "::: Removing pivpn system files..." - echo ":::" - echo "::: Other VPN ${othervpn} still present, so not" - echo "::: removing pivpn system files" - rm -f "${setupConfigDir}/${VPN}/${setupVarsFile}" + rm -rf "${setupConfigDir}" + rm -rf "${pivpnFilesDir}" + rm -f /var/log/*pivpn* + rm -f /etc/bash_completion.d/pivpn - # Restore single pivpn script and bash completion for the remaining VPN - $SUDO unlink /usr/local/bin/pivpn - $SUDO ln -s -T "${pivpnFilesDir}/scripts/${othervpn}/pivpn.sh" /usr/local/bin/pivpn - $SUDO ln -s -T "${pivpnFilesDir}/scripts/${othervpn}/bash-completion" /etc/bash_completion.d/pivpn - # shellcheck disable=SC1091 - . /etc/bash_completion.d/pivpn - fi + unlink "${pivpnScriptDir}" + unlink /usr/local/bin/pivpn + else + if [[ "${VPN}" == 'wireguard' ]]; then + othervpn='openvpn' + else + othervpn='wireguard' + fi - echo ":::" - printf "::: Finished removing PiVPN from your system.\\n" - printf "::: Reinstall by simply running\\n:::\\n:::\\tcurl -L https://install.pivpn.io | bash\\n:::\\n::: at any time!\\n:::\\n" + echo ":::" + echo "::: Other VPN ${othervpn} still present, so not" + echo "::: removing pivpn system files" + rm -f "${setupConfigDir}/${VPN}/${setupVarsFile}" + + # Restore single pivpn script and bash completion for the remaining VPN + ${SUDO} unlink /usr/local/bin/pivpn + + ${SUDO} ln \ + -s \ + -T "${pivpnFilesDir}/scripts/${othervpn}/pivpn.sh" \ + /usr/local/bin/pivpn + + ${SUDO} ln \ + -s \ + -T "${pivpnFilesDir}/scripts/${othervpn}/bash-completion" \ + /etc/bash_completion.d/pivpn + + # shellcheck disable=SC1091 + . /etc/bash_completion.d/pivpn + fi + + echo ":::" + printf "::: Finished removing PiVPN from your system.\\n" + printf "::: Reinstall by simply running\\n:::\\n:::\\t" + printf "curl -L https://install.pivpn.io | " + printf "bash\\n:::\\n::: at any time!\\n:::\\n" } -askreboot(){ - printf "It is \\e[1mstrongly\\e[0m recommended to reboot after un-installation.\\n" - read -p "Would you like to reboot now? [y/n]: " -n 1 -r - echo - if [[ ${REPLY} =~ ^[Yy]$ ]]; then - printf "\\nRebooting system...\\n" - sleep 3 - reboot - fi +askreboot() { + printf "It is \\e[1mstrongly\\e[0m recommended to reboot " + printf "after un-installation.\\n" + + read -p "Would you like to reboot now? [y/n]: " -n 1 -r + + echo + + if [[ "${REPLY}" =~ ^[Yy]$ ]]; then + printf "\\nRebooting system...\\n" + sleep 3 + reboot + fi } ######### SCRIPT ########### -echo "::: Preparing to remove packages, be sure that each may be safely removed depending on your operating system." +echo -n "::: Preparing to remove packages, be sure that each may be safely " +echo "removed depending on your operating system." echo "::: (SAFE TO REMOVE ALL ON RASPBIAN)" -while true; do - read -rp "::: Do you wish to completely remove PiVPN configuration and installed packages from your system? (You will be prompted for each package) [y/n]: " yn - case $yn in - [Yy]* ) removeAll; askreboot; break;; - [Nn]* ) printf "::: Not removing anything, exiting...\\n"; break;; - esac +while true; do + echo -n "::: Do you wish to completely remove PiVPN configuration and " + echo -n "installed packages from your system? " + echo -n "(You will be prompted for each package) [y/n]: " + read -r yn + + case "${yn}" in + [Yy]*) + removeAll + askreboot + break + ;; + [Nn]*) + err "::: Not removing anything, exiting..." + break + ;; + esac done diff --git a/scripts/update.sh b/scripts/update.sh index f296e24..7c922d4 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -1,130 +1,145 @@ #!/bin/bash -###Updates pivpn scripts (Not PiVPN) -###Main Vars +### Updates pivpn scripts (Not PiVPN) +### Main Vars pivpnrepo="https://github.com/pivpn/pivpn.git" pivpnlocalpath="/etc/.pivpn" pivpnscripts="/opt/pivpn/" bashcompletiondir="/etc/bash_completion.d/" # Find the rows and columns. Will default to 80x24 if it can not be detected. -screen_size=$(stty size 2>/dev/null || echo 24 80) -rows=$(echo "$screen_size" | awk '{print $1}') -columns=$(echo "$screen_size" | awk '{print $2}') +screen_size="$(stty size 2> /dev/null || echo 24 80)" +rows="$(echo "${screen_size}" | awk '{print $1}')" +columns="$(echo "${screen_size}" | awk '{print $2}')" # Divide by two so the dialogs take up half of the screen, which looks nice. -r=$(( rows / 2 )) -c=$(( columns / 2 )) +r=$((rows / 2)) +c=$((columns / 2)) # Unless the screen is tiny -r=$(( r < 20 ? 20 : r )) -c=$(( c < 70 ? 70 : c )) +r=$((r < 20 ? 20 : r)) +c=$((c < 70 ? 70 : c)) -echo "::: The updating functionality for PiVPN scripts is temporarily disabled" -echo "::: To keep the VPN (and the system) up to date, use 'apt update' and 'apt upgrade'" +# TODO: Delete this section when the updating functionality will be re-enabled +### +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +err "::: The updating functionality for PiVPN scripts is temporarily disabled" +err "::: To keep the VPN (and the system) up to date, use:" +err " apt update; apt upgrade" exit 0 +### - chooseVPNCmd=(whiptail --backtitle "Setup PiVPN" --title "Installation mode" --separate-output --radiolist "Choose a VPN to update (press space to select):" "${r}" "${c}" 2) - VPNChooseOptions=(WireGuard "" on - OpenVPN "" off) +chooseVPNCmd=(whiptail + --backtitle "Setup PiVPN" + --title "Installation mode" + --separate-output + --radiolist "Choose a VPN to update (press space to select):" + "${r}" "${c}" 2) +VPNChooseOptions=(WireGuard "" on + OpenVPN "" off) - if VPN=$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 >/dev/tty) ; then - echo "::: Using VPN: $VPN" - VPN="${VPN,,}" - else - echo "::: Cancel selected, exiting...." - exit 1 - fi +if VPN="$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 > /dev/tty)"; then + echo "::: Using VPN: ${VPN}" + VPN="${VPN,,}" +else + err "::: Cancel selected, exiting...." + exit 1 +fi setupVars="/etc/pivpn/${VPN}/setupVars.conf" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -scriptusage(){ - echo "::: Updates PiVPN scripts" - echo ":::" - echo "::: Usage: pivpn <-up|update> [-t|--test]" - echo ":::" - echo "::: Commands:" - echo "::: [none] Updates from master branch" - echo "::: -t, test Updates from test branch" - echo "::: -h, help Show this usage dialog" +# TODO: Uncomment this function when the updating functionality +# will be re-enabled +#err() { +# echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +#} + +scriptusage() { + echo "::: Updates PiVPN scripts" + echo ":::" + echo "::: Usage: pivpn <-up|update> [-t|--test]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Updates from master branch" + echo "::: -t, test Updates from test branch" + echo "::: -h, help Show this usage dialog" } -###Functions -##Updates scripts -updatepivpnscripts(){ - ##We don't know what sort of changes users have made. - ##Lets remove first /etc/.pivpn dir then clone it back again - echo "going do update PiVPN Scripts" - if [[ -d "$pivpnlocalpath" ]]; then - if [[ -n "$pivpnlocalpath" ]]; then - rm -rf "${pivpnlocalpath}/../.pivpn" - cloneandupdate - fi - else - cloneandupdate - fi - echo "PiVPN Scripts have been updated" -# Disabling warning for SC1090 +### Functions +## Updates scripts +updatepivpnscripts() { + local branch + branch="${1}" + ## We don't know what sort of changes users have made. + ## Lets remove first /etc/.pivpn dir then clone it back again + echo -n "Going do update PiVPN Scripts" + + if [[ -z "${branch}" ]]; then + echo "from ${branch} branch" + else + echo + fi + + if [[ -d "${pivpnlocalpath}" ]] && + [[ -n "${pivpnlocalpath}" ]]; then + rm -rf "${pivpnlocalpath}/../.pivpn" + fi + + cloneandupdate "${branch}" + echo -n "PiVPN Scripts have been updated" + + if [[ -z "${branch}" ]]; then + echo "from ${branch} branch" + else + echo + fi } -##Updates scripts using test branch -updatefromtest(){ - ##We don't know what sort of changes users have made. - ##Lets remove first /etc/.pivpn dir then clone it back again - echo "PiVPN Scripts updating from test branch" - if [[ -d "$pivpnlocalpath" ]]; then - if [[ -n "$pivpnlocalpath" ]]; then - rm -rf "${pivpnlocalpath}/../.pivpn" - cloneupdttest - fi - else - cloneupdttest - fi - echo "PiVPN Scripts updated have been updated from test branch" - } +## Clone and copy pivpn scripts to /opt/pivpn +cloneandupdate() { + local branch + branch="${1}" + git clone "${pivpnrepo}" "${pivpnlocalpath}" -##Clone and copy pivpn scripts to /opt/pivpn -cloneandupdate(){ - git clone "$pivpnrepo" "$pivpnlocalpath" - cp "${pivpnlocalpath}"/scripts/*.sh "$pivpnscripts" - cp "${pivpnlocalpath}"/scripts/"$VPN"/*.sh "$pivpnscripts" - cp "${pivpnlocalpath}"/scripts/"$VPN"/bash-completion "$bashcompletiondir" -} + if [[ -z "${branch}" ]]; then + git -C "${pivpnlocalpath}" checkout "${branch}" + git -C "${pivpnlocalpath}" pull origin "${branch}" + fi -##same as cloneandupdate() but from test branch -##and falls back to master branch again after updating -cloneupdttest(){ - git clone "$pivpnrepo" "$pivpnlocalpath" - git -C "$pivpnlocalpath" checkout test - git -C "$pivpnlocalpath" pull origin test - cp "${pivpnlocalpath}"/scripts/*.sh "$pivpnscripts" - cp "${pivpnlocalpath}"/scripts/"$VPN"/*.sh "$pivpnscripts" - cp "${pivpnlocalpath}"/scripts/"$VPN"/bash-completion "$bashcompletiondir" - git -C "$pivpnlocalpath" checkout master + cp "${pivpnlocalpath}"/scripts/*.sh "${pivpnscripts}" + cp "${pivpnlocalpath}"/scripts/"${VPN}"/*.sh "${pivpnscripts}" + cp "${pivpnlocalpath}"/scripts/"${VPN}"/bash-completion "${bashcompletiondir}" + + if [[ -z "${branch}" ]]; then + git -C "${pivpnlocalpath}" checkout master + fi } ## SCRIPT -if [[ $# -eq 0 ]]; then - updatepivpnscripts +if [[ "$#" -eq 0 ]]; then + updatepivpnscripts else while true; do - case "$1" in - -t|test) - updatefromtest - exit 0 - ;; - -h|help) - scriptusage - exit 0 - ;; + case "${1}" in + -t | test) + updatepivpnscripts 'test' + exit 0 + ;; + -h | help) + scriptusage + exit 0 + ;; *) updatepivpnscripts exit 0 diff --git a/scripts/wireguard/bash-completion b/scripts/wireguard/bash-completion index eda7a14..b0ce6ea 100644 --- a/scripts/wireguard/bash-completion +++ b/scripts/wireguard/bash-completion @@ -1,18 +1,22 @@ #!/bin/bash -_pivpn() -{ - local cur opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - dashopts="-a -c -d -l -qr -r -h -u -up -bk -off -on" - opts="add clients debug list qrcode remove help uninstall update backup (temp) off (temp) on" - if [ "${#COMP_WORDS[@]}" -eq 2 ]; then - if [[ ${cur} == -* ]] ; then - COMPREPLY=( "$(compgen -W "${dashopts}" -- "${cur}")" ) - else - COMPREPLY=( "$(compgen -W "${opts}" -- "${cur}")" ) - fi + +_pivpn() { + local cur opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + dashopts="-a -c -d -l -qr -r -h -u -up -bk -off -on" + opts="add clients debug list qrcode remove help uninstall update" + opts="${opts} backup (temp) off (temp) on" + + if [[ "${#COMP_WORDS[@]}" -eq 2 ]]; then + if [[ "${cur}" == -* ]]; then + COMPREPLY=("$(compgen -W "${dashopts}" -- "${cur}")") + else + COMPREPLY=("$(compgen -W "${opts}" -- "${cur}")") fi - return 0 + fi + + return 0 } + complete -F _pivpn pivpn diff --git a/scripts/wireguard/clientSTAT.sh b/scripts/wireguard/clientSTAT.sh index 139b09d..248db38 100755 --- a/scripts/wireguard/clientSTAT.sh +++ b/scripts/wireguard/clientSTAT.sh @@ -1,94 +1,108 @@ -#!/usr/bin/env bash +#!/bin/bash # PiVPN: client status script CLIENTS_FILE="/etc/wireguard/configs/clients.txt" -if [ ! -s "$CLIENTS_FILE" ]; then - echo "::: There are no clients to list" - exit 0 +if [[ ! -s "${CLIENTS_FILE}" ]]; then + err "::: There are no clients to list" + exit 0 fi -scriptusage(){ - echo "::: List any connected clients to the server" - echo ":::" - echo "::: Usage: pivpn <-c|clients> [-b|bytes]" - echo ":::" - echo "::: Commands:" - echo "::: [none] List clients with human readable format" - echo "::: -b, bytes List clients with dotted decimal notation" - echo "::: -h, help Show this usage dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -hr(){ - numfmt --to=iec-i --suffix=B "$1" +scriptusage() { + echo "::: List any connected clients to the server" + echo ":::" + echo "::: Usage: pivpn <-c|clients> [-b|bytes]" + echo ":::" + echo "::: Commands:" + echo "::: [none] List clients with human readable format" + echo "::: -b, bytes List clients with dotted decimal notation" + echo "::: -h, help Show this usage dialog" } -listClients(){ - if DUMP="$(wg show wg0 dump)"; then - DUMP="$(tail -n +2 <<< "$DUMP")" - else - exit 1 - fi +hr() { + numfmt --to=iec-i --suffix=B "${1}" +} - printf "\e[1m::: Connected Clients List :::\e[0m\n" +listClients() { + if DUMP="$(wg show wg0 dump)"; then + DUMP="$(tail -n +2 <<< "${DUMP}")" + else + exit 1 + fi - { - printf "\e[4mName\e[0m \t \e[4mRemote IP\e[0m \t \e[4mVirtual IP\e[0m \t \e[4mBytes Received\e[0m \t \e[4mBytes Sent\e[0m \t \e[4mLast Seen\e[0m\n" + printf "\e[1m::: Connected Clients List :::\e[0m\n" + + { + printf "\e[4mName\e[0m \t \e[4mRemote IP\e[0m \t \e[4mVirtual IP\e[0m" + printf "\t \e[4mBytes Received\e[0m \t \e[4mBytes Sent\e[0m " + printf "\t \e[4mLast Seen\e[0m\n" while IFS= read -r LINE; do - if [ -n "${LINE}" ]; then - PUBLIC_KEY="$(awk '{ print $1 }' <<< "$LINE")" - REMOTE_IP="$(awk '{ print $3 }' <<< "$LINE")" - VIRTUAL_IP="$(awk '{ print $4 }' <<< "$LINE")" - BYTES_RECEIVED="$(awk '{ print $6 }' <<< "$LINE")" - BYTES_SENT="$(awk '{ print $7 }' <<< "$LINE")" - LAST_SEEN="$(awk '{ print $5 }' <<< "$LINE")" - CLIENT_NAME="$(grep "$PUBLIC_KEY" "$CLIENTS_FILE" | awk '{ print $1 }')" - if [ "$HR" = 1 ]; then - if [ "$LAST_SEEN" -ne 0 ]; then - printf "%s \t %s \t %s \t %s \t %s \t %s\n" "$CLIENT_NAME" "$REMOTE_IP" "${VIRTUAL_IP/\/32/}" "$(hr "$BYTES_RECEIVED")" "$(hr "$BYTES_SENT")" "$(date -d @"$LAST_SEEN" '+%b %d %Y - %T')" - else - printf "%s \t %s \t %s \t %s \t %s \t %s\n" "$CLIENT_NAME" "$REMOTE_IP" "${VIRTUAL_IP/\/32/}" "$(hr "$BYTES_RECEIVED")" "$(hr "$BYTES_SENT")" "(not yet)" - fi - else - if [ "$LAST_SEEN" -ne 0 ]; then - printf "%s \t %s \t %s \t %'d \t %'d \t %s\n" "$CLIENT_NAME" "$REMOTE_IP" "${VIRTUAL_IP/\/32/}" "$BYTES_RECEIVED" "$BYTES_SENT" "$(date -d @"$LAST_SEEN" '+%b %d %Y - %T')" - else - printf "%s \t %s \t %s \t %'d \t %'d \t %s\n" "$CLIENT_NAME" "$REMOTE_IP" "${VIRTUAL_IP/\/32/}" "$BYTES_RECEIVED" "$BYTES_SENT" "(not yet)" - fi - fi + if [[ -n "${LINE}" ]]; then + PUBLIC_KEY="$(awk '{ print $1 }' <<< "${LINE}")" + REMOTE_IP="$(awk '{ print $3 }' <<< "${LINE}")" + VIRTUAL_IP="$(awk '{ print $4 }' <<< "${LINE}")" + BYTES_RECEIVED="$(awk '{ print $6 }' <<< "${LINE}")" + BYTES_SENT="$(awk '{ print $7 }' <<< "${LINE}")" + LAST_SEEN="$(awk '{ print $5 }' <<< "${LINE}")" + CLIENT_NAME="$(grep "${PUBLIC_KEY}" "${CLIENTS_FILE}" | + awk '{ print $1 }')" + printf "%s \t %s \t %s \t " \ + "${CLIENT_NAME}" \ + "${REMOTE_IP}" \ + "${VIRTUAL_IP/\/32/}" + + if [[ "${HR}" == 1 ]]; then + printf "%s \t %s \t " \ + "$(hr "${BYTES_RECEIVED}")" \ + "$(hr "${BYTES_SENT}")" + else + printf "%s \t %s \t " "${BYTES_RECEIVED}" "${BYTES_SENT}" fi - done <<< "$DUMP" + + if [[ "${LAST_SEEN}" -ne 0 ]]; then + printf "%s" "$(date -d @"${LAST_SEEN}" '+%b %d %Y - %T')" + else + printf "(not yet)" + fi + + printf "\n" + fi + done <<< "${DUMP}" printf "\n" - } | column -t -s $'\t' + } | column -ts $'\t' - cd /etc/wireguard || return - echo "::: Disabled clients :::" - grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' + cd /etc/wireguard || return + echo "::: Disabled clients :::" + grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' } -if [[ $# -eq 0 ]]; then - HR=1 - listClients +if [[ "$#" -eq 0 ]]; then + HR=1 + listClients else - while true; do - case "$1" in - -b|bytes) - HR=0 - listClients - exit 0 - ;; - -h|help) - scriptusage - exit 0 - ;; - *) - HR=0 - listClients - exit 0 - ;; - esac - done + while true; do + case "${1}" in + -b | bytes) + HR=0 + listClients + exit 0 + ;; + -h | help) + scriptusage + exit 0 + ;; + *) + HR=0 + listClients + exit 0 + ;; + esac + done fi diff --git a/scripts/wireguard/disableCONF.sh b/scripts/wireguard/disableCONF.sh index 4fe5147..0dc4f1e 100755 --- a/scripts/wireguard/disableCONF.sh +++ b/scripts/wireguard/disableCONF.sh @@ -2,127 +2,137 @@ setupVars="/etc/pivpn/wireguard/setupVars.conf" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -helpFunc(){ - echo "::: Disable client conf profiles" - echo ":::" - echo "::: Usage: pivpn <-off|off> [-h|--help] [-v] [ ... [] ...] " - echo ":::" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: Client" - echo "::: -y,--yes Disable client(s) without confirmation" - echo "::: -v Show disabled clients only" - echo "::: -h,--help Show this help dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +helpFunc() { + echo "::: Disable client conf profiles" + echo ":::" + echo -n "::: Usage: pivpn <-off|off> [-h|--help] [-v] " + echo "[ ... [] ...]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: Client" + echo "::: -y,--yes Disable client(s) without confirmation" + echo "::: -v Show disabled clients only" + echo "::: -h,--help Show this help dialog" } # Parse input arguments -while test $# -gt 0; do - _key="$1" - case "$_key" in - -h|--help) - helpFunc - exit 0 - ;; - -y|--yes) - CONFIRM=true - ;; - -v) - DISPLAY_DISABLED=true - ;; - *) - CLIENTS_TO_CHANGE+=("$1") - ;; - esac - shift +while [[ "$#" -gt 0 ]]; do + _key="${1}" + + case "${_key}" in + -h | --help) + helpFunc + exit 0 + ;; + -y | --yes) + CONFIRM=true + ;; + -v) + DISPLAY_DISABLED=true + ;; + *) + CLIENTS_TO_CHANGE+=("${1}") + ;; + esac + + shift done cd /etc/wireguard || exit -if [ ! -s configs/clients.txt ]; then - echo "::: There are no clients to change" - exit 1 + +if [[ ! -s configs/clients.txt ]]; then + err "::: There are no clients to change" + exit 1 fi - -if [ "$DISPLAY_DISABLED" ]; then - grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' - exit 1 +if [[ "${DISPLAY_DISABLED}" ]]; then + grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' + exit 1 fi - mapfile -t LIST < <(awk '{print $1}' configs/clients.txt) -if [ "${#CLIENTS_TO_CHANGE[@]}" -eq 0 ]; then - echo -e "::\e[4m Client list \e[0m::" - len=${#LIST[@]} - COUNTER=1 - while [ $COUNTER -le "${len}" ]; do - printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER-1))]}" - ((COUNTER++)) - done - read -r -p "Please enter the Index/Name of the Client to be removed from the list above: " CLIENTS_TO_CHANGE +if [[ "${#CLIENTS_TO_CHANGE[@]}" -eq 0 ]]; then + echo -e "::\e[4m Client list \e[0m::" + len="${#LIST[@]}" + COUNTER=1 - if [ -z "${CLIENTS_TO_CHANGE}" ]; then - echo "::: You can not leave this blank!" - exit 1 - fi + while [[ "${COUNTER}" -le "${len}" ]]; do + printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER - 1))]}" + ((COUNTER++)) + done + + echo -n "Please enter the Index/Name of the Client to be removed " + echo -n "from the list above: " + read -r CLIENTS_TO_CHANGE + + if [[ -z "${CLIENTS_TO_CHANGE}" ]]; then + err "::: You can not leave this blank!" + exit 1 + fi fi CHANGED_COUNT=0 for CLIENT_NAME in "${CLIENTS_TO_CHANGE[@]}"; do + re='^[0-9]+$' + if [[ "${CLIENT_NAME}" =~ $re ]]; then + CLIENT_NAME="${LIST[$((CLIENT_NAME - 1))]}" + fi - re='^[0-9]+$' - if [[ ${CLIENT_NAME} =~ $re ]] ; then - CLIENT_NAME=${LIST[$((CLIENT_NAME -1))]} - fi + if ! grep -q "^${CLIENT_NAME} " configs/clients.txt; then + echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" + elif grep -q "#\[disabled\] ### begin ${CLIENT_NAME}" wg0.conf; then + echo -e "::: \e[1m${CLIENT_NAME}\e[0m is already disabled" + else + if [[ -n "${CONFIRM}" ]]; then + REPLY="y" + else + read -r -p "Confirm you want to disable ${CLIENT_NAME}? [Y/n] " + fi - if ! grep -q "^${CLIENT_NAME} " configs/clients.txt; then - echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" - elif grep -q "#\[disabled\] ### begin ${CLIENT_NAME}" wg0.conf; then - echo -e "::: \e[1m${CLIENT_NAME}\e[0m is already disabled" - else - if [ -n "$CONFIRM" ]; then - REPLY="y" - else - read -r -p "Confirm you want to disable $CLIENT_NAME? [Y/n] " - fi + if [[ "${REPLY}" =~ ^[Yy]$ ]]; then + # Disable the peer section from the server config + echo "${CLIENT_NAME}" - if [[ $REPLY =~ ^[Yy]$ ]]; then - - # Disable the peer section from the server config - echo "${CLIENT_NAME}" - sed -e "/### begin ${CLIENT_NAME}/,/end ${CLIENT_NAME}/ s/^/#\[disabled\] /" -i wg0.conf - echo "::: Updated server config" - - ((CHANGED_COUNT++)) - echo "::: Successfully disabled ${CLIENT_NAME}" - - fi - fi + sed_pattern="/### begin ${CLIENT_NAME}/," + sed_pattern="${sed_pattern}/end ${CLIENT_NAME}/ s/^/#\[disabled\] /" + sed -e "${sed_pattern}" -i wg0.conf + unset sed_pattern + echo "::: Updated server config" + ((CHANGED_COUNT++)) + echo "::: Successfully disabled ${CLIENT_NAME}" + fi + fi done # Restart WireGuard only if some clients were actually deleted -if [ "${CHANGED_COUNT}" -gt 0 ]; then - if [ "${PLAT}" == 'Alpine' ]; then - if rc-service wg-quick restart; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi - else - if systemctl reload wg-quick@wg0; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi - fi +if [[ "${CHANGED_COUNT}" -gt 0 ]]; then + if [[ "${PLAT}" == 'Alpine' ]]; then + if rc-service wg-quick restart; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi + else + if systemctl reload wg-quick@wg0; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi + fi fi diff --git a/scripts/wireguard/enableCONF.sh b/scripts/wireguard/enableCONF.sh index dbfd02a..87e648c 100755 --- a/scripts/wireguard/enableCONF.sh +++ b/scripts/wireguard/enableCONF.sh @@ -2,123 +2,136 @@ setupVars="/etc/pivpn/wireguard/setupVars.conf" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -helpFunc(){ - echo "::: Enables client conf profiles" - echo ":::" - echo "::: Usage: pivpn <-on|on> [-h|--help] [-v] [ ... [] ...] " - echo ":::" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: Client" - echo "::: -y,--yes Enable client(s) without confirmation" - echo "::: -v Show disabled clients only" - echo "::: -h,--help Show this help dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +helpFunc() { + echo "::: Enables client conf profiles" + echo ":::" + echo -n "::: Usage: pivpn <-on|on> [-h|--help] [-v] " + echo "[ ... [] ...]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: Client" + echo "::: -y,--yes Enable client(s) without confirmation" + echo "::: -v Show disabled clients only" + echo "::: -h,--help Show this help dialog" } # Parse input arguments -while test $# -gt 0; do - _key="$1" - case "$_key" in - -h|--help) - helpFunc - exit 0 - ;; - -y|--yes) - CONFIRM=true - ;; - -v) - DISPLAY_DISABLED=true - ;; - *) - CLIENTS_TO_CHANGE+=("$1") - ;; - esac - shift +while [[ "$#" -gt 0 ]]; do + _key="${1}" + + case "${_key}" in + -h | --help) + helpFunc + exit 0 + ;; + -y | --yes) + CONFIRM=true + ;; + -v) + DISPLAY_DISABLED=true + ;; + *) + CLIENTS_TO_CHANGE+=("${1}") + ;; + esac + + shift done cd /etc/wireguard || exit -if [ ! -s configs/clients.txt ]; then - echo "::: There are no clients to change" - exit 1 + +if [[ ! -s configs/clients.txt ]]; then + err "::: There are no clients to change" + exit 1 fi -if [ "$DISPLAY_DISABLED" ]; then - grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' - exit 1 +if [[ "${DISPLAY_DISABLED}" ]]; then + grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' + exit 1 fi mapfile -t LIST < <(awk '{print $1}' configs/clients.txt) -if [ "${#CLIENTS_TO_CHANGE[@]}" -eq 0 ]; then - echo -e "::\e[4m Client list \e[0m::" - len=${#LIST[@]} - COUNTER=1 - while [ $COUNTER -le "${len}" ]; do - printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER-1))]}" - ((COUNTER++)) - done - read -r -p "Please enter the Index/Name of the Client to be enabled from the list above: " CLIENTS_TO_CHANGE +if [[ "${#CLIENTS_TO_CHANGE[@]}" -eq 0 ]]; then + echo -e "::\e[4m Client list \e[0m::" + len="${#LIST[@]}" + COUNTER=1 - if [ -z "${CLIENTS_TO_CHANGE}" ]; then - echo "::: You can not leave this blank!" - exit 1 - fi + while [[ "${COUNTER}" -le "${len}" ]]; do + printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER - 1))]}" + ((COUNTER++)) + done + + echo -n "Please enter the Index/Name of the Client to be enabled " + echo -n "from the list above: " + read -r CLIENTS_TO_CHANGE + + if [[ -z "${CLIENTS_TO_CHANGE}" ]]; then + err "::: You can not leave this blank!" + exit 1 + fi fi CHANGED_COUNT=0 for CLIENT_NAME in "${CLIENTS_TO_CHANGE[@]}"; do + re='^[0-9]+$' - re='^[0-9]+$' - if [[ ${CLIENT_NAME} =~ $re ]] ; then - CLIENT_NAME=${LIST[$((CLIENT_NAME -1))]} - fi + if [[ "${CLIENT_NAME}" =~ $re ]]; then + CLIENT_NAME="${LIST[$((CLIENT_NAME - 1))]}" + fi - if ! grep -q "^${CLIENT_NAME} " configs/clients.txt; then - echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" - else - if [ -n "$CONFIRM" ]; then - REPLY="y" - else - read -r -p "Confirm you want to enable $CLIENT_NAME? [Y/n] " - fi + if ! grep -q "^${CLIENT_NAME} " configs/clients.txt; then + echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" + else + if [[ -n "${CONFIRM}" ]]; then + REPLY="y" + else + read -r -p "Confirm you want to enable ${CLIENT_NAME}? [Y/n] " + fi - if [[ $REPLY =~ ^[Yy]$ ]]; then + if [[ "${REPLY}" =~ ^[Yy]$ ]]; then + # Enable the peer section from the server config + echo "${CLIENT_NAME}" - # Enable the peer section from the server config - echo "${CLIENT_NAME}" - sed -e "/begin ${CLIENT_NAME}/,/end ${CLIENT_NAME}/ s/#\[disabled\] //" -i wg0.conf - echo "::: Updated server config" - - ((CHANGED_COUNT++)) - echo "::: Successfully enabled ${CLIENT_NAME}" - - fi - fi + sed_pattern="/begin ${CLIENT_NAME}/," + sed_pattern="${sed_pattern}/end ${CLIENT_NAME}/ s/#\[disabled\] //" + sed -e "${sed_pattern}" -i wg0.conf + unset sed_pattern + echo "::: Updated server config" + ((CHANGED_COUNT++)) + echo "::: Successfully enabled ${CLIENT_NAME}" + fi + fi done # Restart WireGuard only if some clients were actually deleted -if [ "${CHANGED_COUNT}" -gt 0 ]; then - if [ "${PLAT}" == 'Alpine' ]; then - if rc-service wg-quick restart; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi - else - if systemctl reload wg-quick@wg0; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi - fi +if [[ "${CHANGED_COUNT}" -gt 0 ]]; then + if [[ "${PLAT}" == 'Alpine' ]]; then + if rc-service wg-quick restart; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi + else + if systemctl reload wg-quick@wg0; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi + fi fi diff --git a/scripts/wireguard/listCONF.sh b/scripts/wireguard/listCONF.sh index c89bae3..da37b87 100755 --- a/scripts/wireguard/listCONF.sh +++ b/scripts/wireguard/listCONF.sh @@ -1,33 +1,34 @@ #!/bin/bash +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + cd /etc/wireguard/configs || exit -if [ ! -s clients.txt ]; then - echo "::: There are no clients to list" - exit 1 + +if [[ ! -s clients.txt ]]; then + err "::: There are no clients to list" + exit 1 fi printf "\e[1m::: Clients Summary :::\e[0m\n" # Present the user with a summary of the clients, fetching info from dates. { -echo -e "\e[4mClient\e[0m \t \e[4mPublic key\e[0m \t \e[4mCreation date\e[0m" - -while read -r LINE; do - CLIENT_NAME="$(awk '{print $1}' <<< "$LINE")" - - PUBLIC_KEY="$(awk '{print $2}' <<< "$LINE")" - - CREATION_DATE="$(awk '{print $3}' <<< "$LINE")" + echo -ne "\e[4mClient\e[0m \t \e[4mPublic key\e[0m \t " + echo -e "\e[4mCreation date\e[0m" + while read -r LINE; do + CLIENT_NAME="$(awk '{print $1}' <<< "${LINE}")" + PUBLIC_KEY="$(awk '{print $2}' <<< "${LINE}")" + CREATION_DATE="$(awk '{print $3}' <<< "${LINE}")" # Dates are converted from UNIX time to human readable. - CD_FORMAT="$(date -d @"$CREATION_DATE" +'%d %b %Y, %H:%M, %Z')" - - echo -e "$CLIENT_NAME \t $PUBLIC_KEY \t $CD_FORMAT" -done < clients.txt - + CD_FORMAT="$(date -d @"${CREATION_DATE}" +'%d %b %Y, %H:%M, %Z')" + echo -e "${CLIENT_NAME} \t ${PUBLIC_KEY} \t ${CD_FORMAT}" + done < clients.txt } | column -t -s $'\t' - cd /etc/wireguard || return + echo "::: Disabled clients :::" grep '\[disabled\] ### begin' wg0.conf | sed 's/#//g; s/begin//' diff --git a/scripts/wireguard/makeCONF.sh b/scripts/wireguard/makeCONF.sh index 25f5ee0..c396125 100755 --- a/scripts/wireguard/makeCONF.sh +++ b/scripts/wireguard/makeCONF.sh @@ -1,108 +1,113 @@ #!/bin/bash -######## Some vars that might be empty -# but need to be defined for checks +# Some vars that might be empty but need to be defined for checks pivpnPERSISTENTKEEPALIVE="" pivpnDNS2="" setupVars="/etc/pivpn/wireguard/setupVars.conf" +# shellcheck disable=SC2154 +userGroup="${install_user}:${install_user}" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -helpFunc(){ - echo "::: Create a client conf profile" - echo ":::" - echo "::: Usage: pivpn <-a|add> [-n|--name ] [-h|--help]" - echo ":::" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: -n,--name Name for the Client (default: '$HOSTNAME')" - echo "::: -h,--help Show this help dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +helpFunc() { + echo "::: Create a client conf profile" + echo ":::" + echo "::: Usage: pivpn <-a|add> [-n|--name ] [-h|--help]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: -n,--name Name for the Client (default: '${HOSTNAME}')" + echo "::: -h,--help Show this help dialog" } # Parse input arguments -while test $# -gt 0; do - _key="$1" - case "$_key" in - -n|--name|--name=*) - _val="${_key##--name=}" - if test "$_val" = "$_key"; then - test $# -lt 2 && echo "::: Missing value for the optional argument '$_key'." && exit 1 - _val="$2" - shift - fi - CLIENT_NAME="$_val" - ;; - -h|--help) - helpFunc - exit 0 - ;; - *) - echo "::: Error: Got an unexpected argument '$1'" - helpFunc - exit 1 - ;; - esac - shift +while [[ "$#" -gt 0 ]]; do + _key="${1}" + + case "${_key}" in + -n | --name | --name=*) + _val="${_key##--name=}" + + if [[ "${_val}" == "${_key}" ]]; then + [[ "$#" -lt 2 ]] && + err "::: Missing value for the optional argument '${_key}'." && + exit 1 + + _val="${2}" + shift + fi + + CLIENT_NAME="${_val}" + ;; + -h | --help) + helpFunc + exit 0 + ;; + *) + err "::: Error: Got an unexpected argument '${1}'" + helpFunc + exit 1 + ;; + esac + + shift done # Disabling SC2154, variables sourced externaly # shellcheck disable=SC2154 # The home folder variable was sourced from the settings file. -if [ ! -d "${install_home}/configs" ]; then - mkdir "${install_home}/configs" - chown "${install_user}":"${install_user}" "${install_home}/configs" - chmod 0750 "${install_home}/configs" +if [[ ! -d "${install_home}/configs" ]]; then + mkdir "${install_home}/configs" + chown "${userGroup}" "${install_home}/configs" + chmod 0750 "${install_home}/configs" fi cd /etc/wireguard || exit -if [ -z "${CLIENT_NAME}" ]; then - read -r -p "Enter a Name for the Client: " CLIENT_NAME +if [[ -z "${CLIENT_NAME}" ]]; then + read -r -p "Enter a Name for the Client: " CLIENT_NAME +elif [[ "${CLIENT_NAME}" =~ [^a-zA-Z0-9.@_-] ]]; then + err "Name can only contain alphanumeric characters and these symbols (.-@_)." + exit 1 +elif [[ "${CLIENT_NAME:0:1}" == "-" ]]; then + err "Name cannot start with -" + exit 1 +elif [[ "${CLIENT_NAME}" =~ ^[0-9]+$ ]]; then + err "Names cannot be integers." + exit 1 +elif [[ -z "${CLIENT_NAME}" ]]; then + err "::: You cannot leave the name blank." + exit 1 +elif [[ -f "configs/${CLIENT_NAME}.conf" ]]; then + err "::: A client with this name already exists" + exit 1 fi -if [[ "${CLIENT_NAME}" =~ [^a-zA-Z0-9.@_-] ]]; then - echo "Name can only contain alphanumeric characters and these characters (.-@_)." - exit 1 -fi - -if [[ "${CLIENT_NAME:0:1}" == "-" ]]; then - echo "Name cannot start with -" - exit 1 -fi - -if [[ "${CLIENT_NAME}" =~ ^[0-9]+$ ]]; then - echo "Names cannot be integers." - exit 1 -fi - -if [ -z "${CLIENT_NAME}" ]; then - echo "::: You cannot leave the name blank." - exit 1 -fi - -if [ -f "configs/${CLIENT_NAME}.conf" ]; then - echo "::: A client with this name already exists" - exit 1 -fi - -wg genkey | tee "keys/${CLIENT_NAME}_priv" | wg pubkey > "keys/${CLIENT_NAME}_pub" +wg genkey | + tee "keys/${CLIENT_NAME}_priv" | + wg pubkey > "keys/${CLIENT_NAME}_pub" wg genpsk | tee "keys/${CLIENT_NAME}_psk" &> /dev/null echo "::: Client Keys generated" # Find an unused number for the last octet of the client IP for i in {2..254}; do - if ! grep -q " $i$" configs/clients.txt; then - COUNT="$i" - echo "${CLIENT_NAME} $(> configs/clients.txt - break - fi + if ! grep -q " ${i}$" configs/clients.txt; then + COUNT="${i}" + echo "${CLIENT_NAME} $(< keys/"${CLIENT_NAME}"_pub) $(date +%s) ${COUNT}" | + tee -a configs/clients.txt > /dev/null + break + fi done # Disabling SC2154, variables sourced externaly @@ -110,88 +115,95 @@ done NET_REDUCED="${pivpnNET::-2}" # shellcheck disable=SC2154 -if [ "$pivpnenableipv6" == "1" ]; then -echo "[Interface] -PrivateKey = $(cat "keys/${CLIENT_NAME}_priv") -Address = ${NET_REDUCED}.${COUNT}/${subnetClass},${pivpnNETv6}${COUNT}/${subnetClassv6}" > "configs/${CLIENT_NAME}.conf" -else -echo "[Interface] -PrivateKey = $(cat "keys/${CLIENT_NAME}_priv") -Address = ${NET_REDUCED}.${COUNT}/${subnetClass}" > "configs/${CLIENT_NAME}.conf" -fi +{ + echo '[Interface]' + echo "PrivateKey = $(cat "keys/${CLIENT_NAME}_priv")" + echo -n "Address = ${NET_REDUCED}.${COUNT}/${subnetClass}" -# shellcheck disable=SC2154 -echo -n "DNS = ${pivpnDNS1}" >> "configs/${CLIENT_NAME}.conf" -if [ -n "${pivpnDNS2}" ]; then - echo ", ${pivpnDNS2}" >> "configs/${CLIENT_NAME}.conf" -else - echo >> "configs/${CLIENT_NAME}.conf" -fi -echo >> "configs/${CLIENT_NAME}.conf" + if [[ "${pivpnenableipv6}" == 1 ]]; then + echo ",${pivpnNETv6}${COUNT}/${subnetClassv6}" + else + echo + fi -# shellcheck disable=SC2154 -echo "[Peer] -PublicKey = $(cat keys/server_pub) -PresharedKey = $(cat "keys/${CLIENT_NAME}_psk") -Endpoint = ${pivpnHOST}:${pivpnPORT} -AllowedIPs = ${ALLOWED_IPS}" >> "configs/${CLIENT_NAME}.conf" + echo -n "DNS = ${pivpnDNS1}" + + if [[ -n "${pivpnDNS2}" ]]; then + echo ", ${pivpnDNS2}" + else + echo + fi + + echo + echo '[Peer]' + echo "PublicKey = $(cat keys/server_pub)" + echo "PresharedKey = $(cat "keys/${CLIENT_NAME}_psk")" + echo "Endpoint = ${pivpnHOST}:${pivpnPORT}" + echo "AllowedIPs = ${ALLOWED_IPS}" + + if [[ -n "${pivpnPERSISTENTKEEPALIVE}" ]]; then + echo "PersistentKeepalive = ${pivpnPERSISTENTKEEPALIVE}" + fi +} > "configs/${CLIENT_NAME}.conf" -if [ -n "${pivpnPERSISTENTKEEPALIVE}" ]; then - echo "PersistentKeepalive = ${pivpnPERSISTENTKEEPALIVE}" >> "configs/${CLIENT_NAME}.conf" -fi echo "::: Client config generated" -if [ "$pivpnenableipv6" == "1" ]; then -echo "### begin ${CLIENT_NAME} ### -[Peer] -PublicKey = $(cat "keys/${CLIENT_NAME}_pub") -PresharedKey = $(cat "keys/${CLIENT_NAME}_psk") -AllowedIPs = ${NET_REDUCED}.${COUNT}/32,${pivpnNETv6}${COUNT}/128 -### end ${CLIENT_NAME} ###" >> wg0.conf -else -echo "### begin ${CLIENT_NAME} ### -[Peer] -PublicKey = $(cat "keys/${CLIENT_NAME}_pub") -PresharedKey = $(cat "keys/${CLIENT_NAME}_psk") -AllowedIPs = ${NET_REDUCED}.${COUNT}/32 -### end ${CLIENT_NAME} ###" >> wg0.conf -fi +{ + echo "### begin ${CLIENT_NAME} ###" + echo '[Peer]' + echo "PublicKey = $(cat "keys/${CLIENT_NAME}_pub")" + echo "PresharedKey = $(cat "keys/${CLIENT_NAME}_psk")" + echo -n "AllowedIPs = ${NET_REDUCED}.${COUNT}/32" + + if [[ "${pivpnenableipv6}" == 1 ]]; then + echo ",${pivpnNETv6}${COUNT}/128" + else + echo + fi + + echo "### end ${CLIENT_NAME} ###" +} >> wg0.conf echo "::: Updated server config" -if [ -f /etc/pivpn/hosts.wireguard ]; then - echo "${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn" >> /etc/pivpn/hosts.wireguard - if [ "$pivpnenableipv6" == "1" ]; then - echo "${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn" >> /etc/pivpn/hosts.wireguard - fi - if killall -SIGHUP pihole-FTL; then - echo "::: Updated hosts file for Pi-hole" - else - echo "::: Failed to reload pihole-FTL configuration" - fi +if [[ -f /etc/pivpn/hosts.wireguard ]]; then + echo "${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn" | + tee -a /etc/pivpn/hosts.wireguard > /dev/null + + if [[ "${pivpnenableipv6}" == 1 ]]; then + echo "${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn" | + tee -a /etc/pivpn/hosts.wireguard > /dev/null + fi + + if killall -SIGHUP pihole-FTL; then + echo "::: Updated hosts file for Pi-hole" + else + err "::: Failed to reload pihole-FTL configuration" + fi fi -if [ "${PLAT}" == 'Alpine' ]; then - if rc-service wg-quick restart; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi +if [[ "${PLAT}" == 'Alpine' ]]; then + if rc-service wg-quick restart; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi else - if systemctl reload wg-quick@wg0; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi + if systemctl reload wg-quick@wg0; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi fi cp "configs/${CLIENT_NAME}.conf" "${install_home}/configs/${CLIENT_NAME}.conf" -chown "${install_user}":"${install_user}" "${install_home}/configs/${CLIENT_NAME}.conf" +chown "${userGroup}" "${install_home}/configs/${CLIENT_NAME}.conf" chmod 640 "${install_home}/configs/${CLIENT_NAME}.conf" echo "======================================================================" echo -e "::: Done! \e[1m${CLIENT_NAME}.conf successfully created!\e[0m" -echo "::: ${CLIENT_NAME}.conf was copied to ${install_home}/configs for easy transfer." +echo -n "::: ${CLIENT_NAME}.conf was copied to ${install_home}/configs for easy" +echo "transfer." echo "::: Please use this profile only on one device and create additional" echo -e "::: profiles for other devices. You can also use \e[1mpivpn -qr\e[0m" echo "::: to generate a QR Code you can scan with the mobile app." diff --git a/scripts/wireguard/pivpn.sh b/scripts/wireguard/pivpn.sh index 27a5138..894caeb 100755 --- a/scripts/wireguard/pivpn.sh +++ b/scripts/wireguard/pivpn.sh @@ -3,127 +3,160 @@ CHECK_PKG_INSTALLED='dpkg-query -s' if grep -qsEe "^NAME\=['\"]?Alpine[a-zA-Z ]*['\"]?$" /etc/os-release; then - CHECK_PKG_INSTALLED='apk --no-cache info -e' + CHECK_PKG_INSTALLED='apk --no-cache info -e' fi # Must be root to use this tool -if [ $EUID -ne 0 ]; then - if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then - export SUDO="sudo" - else - echo "::: Please install sudo or run this as root." - exit 1 +if [[ "${EUID}" -ne 0 ]]; then + if ${CHECK_PKG_INSTALLED} sudo &> /dev/null; then + export SUDO="sudo" + else + err "::: Please install sudo or run this as root." + exit 1 fi fi scriptdir="/opt/pivpn" vpn="wireguard" -makeConf(){ - shift - $SUDO ${scriptdir}/${vpn}/makeCONF.sh "$@" - exit "$?" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -listConnected(){ - shift - $SUDO ${scriptdir}/${vpn}/clientSTAT.sh "$@" - exit "$?" +makeConf() { + shift + ${SUDO} "${scriptdir}/${vpn}/makeCONF.sh" "$@" + exit "${?}" } -debug(){ - echo "::: Generating Debug Output" - $SUDO ${scriptdir}/${vpn}/pivpnDEBUG.sh | tee /tmp/debug.log - echo "::: " - echo "::: Debug output completed above." - echo "::: Copy saved to /tmp/debug.log" - echo "::: " - exit "$?" +listConnected() { + shift + ${SUDO} "${scriptdir}/${vpn}/clientSTAT.sh" "$@" + exit "${?}" } -listClients(){ - $SUDO ${scriptdir}/${vpn}/listCONF.sh - exit "$?" +debug() { + echo "::: Generating Debug Output" + + ${SUDO} "${scriptdir}/${vpn}/pivpnDEBUG.sh" | tee /tmp/debug.log + + echo "::: " + echo "::: Debug output completed above." + echo "::: Copy saved to /tmp/debug.log" + echo "::: " + exit "${?}" } -showQrcode(){ - shift - $SUDO ${scriptdir}/${vpn}/qrcodeCONF.sh "$@" - exit "$?" +listClients() { + ${SUDO} "${scriptdir}/${vpn}/listCONF.sh" + exit "${?}" } -removeClient(){ - shift - $SUDO ${scriptdir}/${vpn}/removeCONF.sh "$@" - exit "$?" +showQrcode() { + shift + ${SUDO} "${scriptdir}/${vpn}/qrcodeCONF.sh" "$@" + exit "${?}" } -disableClient(){ - shift - $SUDO ${scriptdir}/${vpn}/disableCONF.sh "$@" - exit "$?" +removeClient() { + shift + ${SUDO} "${scriptdir}/${vpn}/removeCONF.sh" "$@" + exit "${?}" } -enableClient(){ - shift - $SUDO ${scriptdir}/${vpn}/enableCONF.sh "$@" - exit "$?" +disableClient() { + shift + ${SUDO} "${scriptdir}/${vpn}/disableCONF.sh" "$@" + exit "${?}" } -uninstallServer(){ - $SUDO ${scriptdir}/uninstall.sh "${vpn}" - exit "$?" +enableClient() { + shift + ${SUDO} "${scriptdir}/${vpn}/enableCONF.sh" "$@" + exit "${?}" } -updateScripts(){ - shift - $SUDO ${scriptdir}/update.sh "$@" - exit "$?" +uninstallServer() { + ${SUDO} "${scriptdir}/uninstall.sh" "${vpn}" + exit "${?}" } -backup(){ - $SUDO ${scriptdir}/backup.sh "${vpn}" - exit "$?" +updateScripts() { + shift + ${SUDO} "${scriptdir}/update.sh" "$@" + exit "${?}" } -showHelp(){ - echo "::: Control all PiVPN specific functions!" - echo ":::" - echo "::: Usage: pivpn [option]" - echo ":::" - echo "::: Commands:" - echo "::: -a, add Create a client conf profile" - echo "::: -c, clients List any connected clients to the server" - echo "::: -d, debug Start a debugging session if having trouble" - echo "::: -l, list List all clients" - echo "::: -qr, qrcode Show the qrcode of a client for use with the mobile app" - echo "::: -r, remove Remove a client" - echo "::: -off, off Disable a client" - echo "::: -on, on Enable a client" - echo "::: -h, help Show this help dialog" - echo "::: -u, uninstall Uninstall pivpn from your system!" - echo "::: -up, update Updates PiVPN Scripts" - echo "::: -bk, backup Backup VPN configs and user profiles" - exit 0 +backup() { + ${SUDO} "${scriptdir}/backup.sh" "${vpn}" + exit "${?}" } -if [ $# = 0 ]; then - showHelp +showHelp() { + echo "::: Control all PiVPN specific functions!" + echo ":::" + echo "::: Usage: pivpn [option]" + echo ":::" + echo "::: Commands:" + echo "::: -a, add Create a client conf profile" + echo "::: -c, clients List any connected clients to the server" + echo "::: -d, debug Start a debugging session if having trouble" + echo "::: -l, list List all clients" + echo -n "::: -qr, qrcode Show the qrcode of a client for use " + echo "with the mobile app" + echo "::: -r, remove Remove a client" + echo "::: -off, off Disable a client" + echo "::: -on, on Enable a client" + echo "::: -h, help Show this help dialog" + echo "::: -u, uninstall Uninstall pivpn from your system!" + echo "::: -up, update Updates PiVPN Scripts" + echo "::: -bk, backup Backup VPN configs and user profiles" + exit 0 +} + +if [[ "$#" == 0 ]]; then + showHelp fi # Handle redirecting to specific functions based on arguments -case "$1" in -"-a" | "add" ) makeConf "$@";; -"-c" | "clients" ) listConnected "$@";; -"-d" | "debug" ) debug;; -"-l" | "list" ) listClients;; -"-qr" | "qrcode" ) showQrcode "$@";; -"-r" | "remove" ) removeClient "$@";; -"-off" | "off" ) disableClient "$@";; -"-on" | "on" ) enableClient "$@";; -"-h" | "help" ) showHelp;; -"-u" | "uninstall" ) uninstallServer;; -"-up" | "update" ) updateScripts "$@" ;; -"-bk" | "backup" ) backup ;; -* ) showHelp;; +case "${1}" in + "-a" | "add") + makeConf "$@" + ;; + "-c" | "clients") + listConnected "$@" + ;; + "-d" | "debug") + debug + ;; + "-l" | "list") + listClients + ;; + "-qr" | "qrcode") + showQrcode "$@" + ;; + "-r" | "remove") + removeClient "$@" + ;; + "-off" | "off") + disableClient "$@" + ;; + "-on" | "on") + enableClient "$@" + ;; + "-h" | "help") + showHelp + ;; + "-u" | "uninstall") + uninstallServer + ;; + "-up" | "update") + updateScripts "$@" + ;; + "-bk" | "backup") + backup + ;; + *) + showHelp + ;; esac diff --git a/scripts/wireguard/pivpnDEBUG.sh b/scripts/wireguard/pivpnDEBUG.sh index 3a68198..2ecc011 100755 --- a/scripts/wireguard/pivpnDEBUG.sh +++ b/scripts/wireguard/pivpnDEBUG.sh @@ -1,11 +1,15 @@ -#!/usr/bin/env bash +#!/bin/bash # This scripts runs as root setupVars="/etc/pivpn/wireguard/setupVars.conf" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 @@ -15,51 +19,76 @@ echo -e "::::\t\t\e[4mPiVPN debug\e[0m\t\t ::::" printf "=============================================\n" echo -e "::::\t\t\e[4mLatest commit\e[0m\t\t ::::" echo -n "Branch: " + git --git-dir /usr/local/src/pivpn/.git rev-parse --abbrev-ref HEAD -git --git-dir /usr/local/src/pivpn/.git log -n 1 --format='Commit: %H%nAuthor: %an%nDate: %ad%nSummary: %s' +git \ + --git-dir /usr/local/src/pivpn/.git log \ + -n 1 \ + --format='Commit: %H%nAuthor: %an%nDate: %ad%nSummary: %s' + printf "=============================================\n" echo -e "::::\t \e[4mInstallation settings\e[0m \t ::::" + # Disabling SC2154 warning, variable is sourced externaly and may vary # shellcheck disable=SC2154 -sed "s/$pivpnHOST/REDACTED/" < ${setupVars} +sed "s/${pivpnHOST}/REDACTED/" < "${setupVars}" + printf "=============================================\n" echo -e ":::: \e[4mServer configuration shown below\e[0m ::::" + cd /etc/wireguard/keys || exit cp ../wg0.conf ../wg0.tmp + # Replace every key in the server configuration with just its file name for k in *; do - sed "s#$(<"$k")#$k#" -i ../wg0.tmp + sed "s#$(< "${k}")#${k}#" -i ../wg0.tmp done + cat ../wg0.tmp rm ../wg0.tmp + printf "=============================================\n" echo -e ":::: \e[4mClient configuration shown below\e[0m ::::" + EXAMPLE="$(head -1 /etc/wireguard/configs/clients.txt | awk '{print $1}')" -if [ -n "$EXAMPLE" ]; then - cp ../configs/"$EXAMPLE".conf ../configs/"$EXAMPLE".tmp - for k in *; do - sed "s#$(<"$k")#$k#" -i ../configs/"$EXAMPLE".tmp - done - sed "s/$pivpnHOST/REDACTED/" < ../configs/"$EXAMPLE".tmp - rm ../configs/"$EXAMPLE".tmp + +if [[ -n "${EXAMPLE}" ]]; then + cp ../configs/"${EXAMPLE}".conf ../configs/"${EXAMPLE}".tmp + + for k in *; do + sed "s#$(< "${k}")#${k}#" -i ../configs/"${EXAMPLE}".tmp + done + + sed "s/${pivpnHOST}/REDACTED/" < ../configs/"${EXAMPLE}".tmp + rm ../configs/"${EXAMPLE}".tmp else - echo "::: There are no clients yet" + echo "::: There are no clients yet" fi printf "=============================================\n" -echo -e ":::: \t\e[4mRecursive list of files in\e[0m\t ::::\n::::\t\e[4m/etc/wireguard shown below\e[0m\t ::::" +echo -e ":::: \t\e[4mRecursive list of files in\e[0m\t ::::" +echo -e "::::\t\e[4m/etc/wireguard shown below\e[0m\t ::::" + ls -LR /etc/wireguard + printf "=============================================\n" echo -e "::::\t\t\e[4mSelf check\e[0m\t\t ::::" + /opt/pivpn/self_check.sh "${VPN}" + printf "=============================================\n" echo -e ":::: Having trouble connecting? Take a look at the FAQ:" echo -e ":::: \e[1mhttps://docs.pivpn.io/faq\e[0m" printf "=============================================\n" -echo -e ":::: \e[1mWARNING\e[0m: This script should have automatically masked sensitive ::::" -echo -e ":::: information, however, still make sure that \e[4mPrivateKey\e[0m, \e[4mPublicKey\e[0m ::::" -echo -e ":::: and \e[4mPresharedKey\e[0m are masked before reporting an issue. An example key ::::" -echo ":::: that you should NOT see in this log looks like this: ::::" -echo ":::: YIAoJVsdIeyvXfGGDDadHh6AxsMRymZTnnzZoAb9cxRe ::::" +echo -ne ":::: \e[1mWARNING\e[0m: This script should have " +echo -e "automatically masked sensitive ::::" +echo -ne ":::: information, however, still make sure that " +echo -e "\e[4mPrivateKey\e[0m, \e[4mPublicKey\e[0m ::::" +echo -ne ":::: and \e[4mPresharedKey\e[0m are masked before " +echo -e "reporting an issue. An example key ::::" +echo -n ":::: that you should NOT see in this log looks like this:" +echo " ::::" +echo -n ":::: YIAoJVsdIeyvXfGGDDadHh6AxsMRymZTnnzZoAb9cxRe" +echo " ::::" printf "=============================================\n" echo -e "::::\t\t\e[4mDebug complete\e[0m\t\t ::::" diff --git a/scripts/wireguard/qrcodeCONF.sh b/scripts/wireguard/qrcodeCONF.sh index 8f707f5..1f7bbd8 100755 --- a/scripts/wireguard/qrcodeCONF.sh +++ b/scripts/wireguard/qrcodeCONF.sh @@ -1,76 +1,91 @@ #!/bin/bash -helpFunc(){ - echo "::: Show the qrcode of a client for use with the mobile app" - echo ":::" - echo "::: Usage: pivpn <-qr|qrcode> [-h|--help] [Options] [] ... [] ..." - echo ":::" - echo "::: Options:" - echo "::: -a256|ansi256 Shows QR Code in ansi256 characters" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: Client(s) to show" - echo "::: -h,--help Show this help dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +helpFunc() { + echo "::: Show the qrcode of a client for use with the mobile app" + echo ":::" + echo -n "::: Usage: pivpn <-qr|qrcode> [-h|--help] [Options] " + echo "[ ... [] ...]" + echo ":::" + echo "::: Options:" + echo "::: -a256|ansi256 Shows QR Code in ansi256 characters" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: Client(s) to show" + echo "::: -h,--help Show this help dialog" } # Parse input arguments encoding="ansiutf8" -while test $# -gt 0; do - _key="$1" - case "$_key" in - -h|--help) - helpFunc - exit 0 - ;; - -a256|--ansi256) - encoding="ansi256" - ;; - *) - CLIENTS_TO_SHOW+=("$1") - ;; - esac - shift + +while [[ "$#" -gt 0 ]]; do + _key="${1}" + + case "${_key}" in + -h | --help) + helpFunc + exit 0 + ;; + -a256 | --ansi256) + encoding="ansi256" + ;; + *) + CLIENTS_TO_SHOW+=("${1}") + ;; + esac + + shift done cd /etc/wireguard/configs || exit -if [ ! -s clients.txt ]; then - echo "::: There are no clients to show" - exit 1 + +if [[ ! -s clients.txt ]]; then + err "::: There are no clients to show" + exit 1 fi mapfile -t LIST < <(awk '{print $1}' clients.txt) -if [ "${#CLIENTS_TO_SHOW[@]}" -eq 0 ]; then - echo -e "::\e[4m Client list \e[0m::" - len=${#LIST[@]} - COUNTER=1 - while [ $COUNTER -le "${len}" ]; do - printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER-1))]}" - ((COUNTER++)) - done +if [[ "${#CLIENTS_TO_SHOW[@]}" -eq 0 ]]; then + echo -e "::\e[4m Client list \e[0m::" + len="${#LIST[@]}" + COUNTER=1 - read -r -p "Please enter the Index/Name of the Client to show: " CLIENTS_TO_SHOW + while [[ "${COUNTER}" -le "${len}" ]]; do + printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER - 1))]}" + ((COUNTER++)) + done - if [ -z "${CLIENTS_TO_SHOW}" ]; then - echo "::: You can not leave this blank!" - exit 1 - fi + echo -n "Please enter the Index/Name of the Client to show: " + read -r CLIENTS_TO_SHOW + + if [[ -z "${CLIENTS_TO_SHOW}" ]]; then + err "::: You can not leave this blank!" + exit 1 + fi fi for CLIENT_NAME in "${CLIENTS_TO_SHOW[@]}"; do - re='^[0-9]+$' - if [[ ${CLIENT_NAME:0:1} == "-" ]]; then - echo "${CLIENT_NAME} is not a valid client name or option" - exit 1 - elif [[ ${CLIENT_NAME} =~ $re ]] ; then - CLIENT_NAME=${LIST[$((CLIENT_NAME -1))]} - fi - if grep -qw "${CLIENT_NAME}" clients.txt; then - echo -e "::: Showing client \e[1m${CLIENT_NAME}\e[0m below" - echo "=====================================================================" - qrencode -t "${encoding}" < "${CLIENT_NAME}.conf" - echo "=====================================================================" - else - echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" - fi + re='^[0-9]+$' + + if [[ "${CLIENT_NAME:0:1}" == "-" ]]; then + err "${CLIENT_NAME} is not a valid client name or option" + exit 1 + elif [[ "${CLIENT_NAME}" =~ $re ]]; then + CLIENT_NAME="${LIST[$((CLIENT_NAME - 1))]}" + fi + + if grep -qw "${CLIENT_NAME}" clients.txt; then + echo -e "::: Showing client \e[1m${CLIENT_NAME}\e[0m below" + echo "=====================================================================" + + qrencode -t "${encoding}" < "${CLIENT_NAME}.conf" + + echo "=====================================================================" + else + echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" + fi done diff --git a/scripts/wireguard/removeCONF.sh b/scripts/wireguard/removeCONF.sh index ad1af7c..9f15b21 100755 --- a/scripts/wireguard/removeCONF.sh +++ b/scripts/wireguard/removeCONF.sh @@ -2,159 +2,187 @@ setupVars="/etc/pivpn/wireguard/setupVars.conf" -if [ ! -f "${setupVars}" ]; then - echo "::: Missing setup vars file!" - exit 1 +if [[ ! -f "${setupVars}" ]]; then + err "::: Missing setup vars file!" + exit 1 fi # shellcheck disable=SC1090 source "${setupVars}" -helpFunc(){ - echo "::: Remove a client conf profile" - echo ":::" - echo "::: Usage: pivpn <-r|remove> [-y|--yes] [-h|--help] [] ... [] ..." - echo ":::" - echo "::: Commands:" - echo "::: [none] Interactive mode" - echo "::: Client(s) to remove" - echo "::: -y,--yes Remove Client(s) without confirmation" - echo "::: -h,--help Show this help dialog" +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 +} + +helpFunc() { + echo "::: Remove a client conf profile" + echo ":::" + echo -n "::: Usage: pivpn <-r|remove> [-y|--yes] [-h|--help] " + echo "[ ... [] ...]" + echo ":::" + echo "::: Commands:" + echo "::: [none] Interactive mode" + echo "::: Client(s) to remove" + echo "::: -y,--yes Remove Client(s) without confirmation" + echo "::: -h,--help Show this help dialog" } # Parse input arguments -while test $# -gt 0 -do - _key="$1" - case "$_key" in - -h|--help) - helpFunc - exit 0 - ;; - -y|--yes) - CONFIRM=true - ;; - *) - CLIENTS_TO_REMOVE+=("$1") - ;; - esac - shift +while [[ "$#" -gt 0 ]]; do + _key="${1}" + + case "${_key}" in + -h | --help) + helpFunc + exit 0 + ;; + -y | --yes) + CONFIRM=true + ;; + *) + CLIENTS_TO_REMOVE+=("${1}") + ;; + esac + + shift done cd /etc/wireguard || exit -if [ ! -s configs/clients.txt ]; then - echo "::: There are no clients to remove" - exit 1 + +if [[ ! -s configs/clients.txt ]]; then + err "::: There are no clients to remove" + exit 1 fi mapfile -t LIST < <(awk '{print $1}' configs/clients.txt) -if [ "${#CLIENTS_TO_REMOVE[@]}" -eq 0 ]; then - echo -e "::\e[4m Client list \e[0m::" - len=${#LIST[@]} - COUNTER=1 - while [ $COUNTER -le "${len}" ]; do - printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER-1))]}" - ((COUNTER++)) - done - read -r -p "Please enter the Index/Name of the Client to be removed from the list above: " CLIENTS_TO_REMOVE +if [[ "${#CLIENTS_TO_REMOVE[@]}" -eq 0 ]]; then + echo -e "::\e[4m Client list \e[0m::" + len="${#LIST[@]}" + COUNTER=1 - if [ -z "${CLIENTS_TO_REMOVE}" ]; then - echo "::: You can not leave this blank!" - exit 1 - fi + while [[ "${COUNTER}" -le "${len}" ]]; do + printf "%0${#len}s) %s\r\n" "${COUNTER}" "${LIST[(($COUNTER - 1))]}" + ((COUNTER++)) + done + + echo -n "Please enter the Index/Name of the Client to be removed " + echo -n "from the list above: " + read -r CLIENTS_TO_REMOVE + + if [[ -z "${CLIENTS_TO_REMOVE}" ]]; then + err "::: You can not leave this blank!" + exit 1 + fi fi DELETED_COUNT=0 for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do + re='^[0-9]+$' - re='^[0-9]+$' - if [[ ${CLIENT_NAME} =~ $re ]] ; then - CLIENT_NAME=${LIST[$((CLIENT_NAME -1))]} - fi + if [[ "${CLIENT_NAME}" =~ $re ]]; then + CLIENT_NAME="${LIST[$((CLIENT_NAME - 1))]}" + fi - if ! grep -q "^${CLIENT_NAME} " configs/clients.txt; then - echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" + if ! grep -q "^${CLIENT_NAME} " configs/clients.txt; then + echo -e "::: \e[1m${CLIENT_NAME}\e[0m does not exist" + else + REQUESTED="$(sha256sum "configs/${CLIENT_NAME}.conf" | cut -c 1-64)" + + if [[ -n "${CONFIRM}" ]]; then + REPLY="y" else - REQUESTED="$(sha256sum "configs/${CLIENT_NAME}.conf" | cut -c 1-64)" - if [ -n "$CONFIRM" ]; then - REPLY="y" - else - read -r -p "Do you really want to delete $CLIENT_NAME? [y/N] " - fi - - if [[ $REPLY =~ ^[Yy]$ ]]; then - - # Grab the least significant octed of the client IP address - COUNT=$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $4}') - # The creation date of the client - CREATION_DATE="$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $3}')" - # And its public key - PUBLIC_KEY="$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $2}')" - - # Then remove the client matching the variables above - sed "\#${CLIENT_NAME} ${PUBLIC_KEY} ${CREATION_DATE} ${COUNT}#d" -i configs/clients.txt - - # Remove the peer section from the server config - sed "/### begin ${CLIENT_NAME} ###/,/### end ${CLIENT_NAME} ###/d" -i wg0.conf - echo "::: Updated server config" - - rm "configs/${CLIENT_NAME}.conf" - echo "::: Client config for ${CLIENT_NAME} removed" - - rm "keys/${CLIENT_NAME}_priv" - rm "keys/${CLIENT_NAME}_pub" - rm "keys/${CLIENT_NAME}_psk" - echo "::: Client Keys for ${CLIENT_NAME} removed" - - # Find all .conf files in the home folder of the user matching the checksum of the - # config and delete them. '-maxdepth 3' is used to avoid traversing too many folders. - # Disabling SC2154, variable sourced externaly and may vary - # shellcheck disable=SC2154 - find "${install_home}" -maxdepth 3 -type f -name '*.conf' -print0 | while IFS= read -r -d '' CONFIG; do - if sha256sum -c <<< "${REQUESTED} ${CONFIG}" &> /dev/null; then - rm "${CONFIG}" - fi - done - - ((DELETED_COUNT++)) - echo "::: Successfully deleted ${CLIENT_NAME}" - - # If using Pi-hole, remove the client from the hosts file - # Disabling SC2154, variable sourced externaly and may vary - # shellcheck disable=SC2154 - if [ -f /etc/pivpn/hosts.wireguard ]; then - NET_REDUCED="${pivpnNET::-2}" - sed -e "\#${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn#d" -e "\#${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn#d" -i /etc/pivpn/hosts.wireguard - if killall -SIGHUP pihole-FTL; then - echo "::: Updated hosts file for Pi-hole" - else - echo "::: Failed to reload pihole-FTL configuration" - fi - fi - - else - echo "Aborting operation" - exit 1 - fi + read -r -p "Do you really want to delete ${CLIENT_NAME}? [y/N] " fi + if [[ "${REPLY}" =~ ^[Yy]$ ]]; then + # Grab the least significant octed of the client IP address + COUNT="$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $4}')" + # The creation date of the client + CREATION_DATE="$(grep "^${CLIENT_NAME} " configs/clients.txt | + awk '{print $3}')" + # And its public key + PUBLIC_KEY="$(grep "^${CLIENT_NAME} " configs/clients.txt | + awk '{print $2}')" + + # Then remove the client matching the variables above + sed \ + -e "\#${CLIENT_NAME} ${PUBLIC_KEY} ${CREATION_DATE} ${COUNT}#d" \ + -i \ + configs/clients.txt + + # Remove the peer section from the server config + sed_pattern="/### begin ${CLIENT_NAME} ###/," + sed_pattern="${sed_pattern}/### end ${CLIENT_NAME} ###/d" + sed -e "${sed_pattern}" -i wg0.conf + echo "::: Updated server config" + + rm "configs/${CLIENT_NAME}.conf" + echo "::: Client config for ${CLIENT_NAME} removed" + + rm "keys/${CLIENT_NAME}_priv" + rm "keys/${CLIENT_NAME}_pub" + rm "keys/${CLIENT_NAME}_psk" + echo "::: Client Keys for ${CLIENT_NAME} removed" + + # Find all .conf files in the home folder of the user matching the + # checksum of the config and delete them. '-maxdepth 3' is used to + # avoid traversing too many folders. + # Disabling SC2154, variable sourced externaly and may vary + # shellcheck disable=SC2154 + while IFS= read -r -d '' CONFIG; do + if sha256sum -c <<< "${REQUESTED} ${CONFIG}" &> /dev/null; then + rm "${CONFIG}" + fi + done < <(find "${install_home}" \ + -maxdepth 3 \ + -type f \ + -name '*.conf' \ + -print0) + + ((DELETED_COUNT++)) + echo "::: Successfully deleted ${CLIENT_NAME}" + + # If using Pi-hole, remove the client from the hosts file + # Disabling SC2154, variable sourced externaly and may vary + # shellcheck disable=SC2154 + if [[ -f /etc/pivpn/hosts.wireguard ]]; then + NET_REDUCED="${pivpnNET::-2}" + sed \ + -e "\#${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn#d" \ + -e "\#${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn#d" \ + -i \ + /etc/pivpn/hosts.wireguard + + if killall -SIGHUP pihole-FTL; then + echo "::: Updated hosts file for Pi-hole" + else + err "::: Failed to reload pihole-FTL configuration" + fi + fi + + unset sed_pattern + else + err "Aborting operation" + exit 1 + fi + fi done # Restart WireGuard only if some clients were actually deleted -if [ "${DELETED_COUNT}" -gt 0 ]; then - if [ "${PLAT}" == 'Alpine' ]; then - if rc-service wg-quick restart; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi +if [[ "${DELETED_COUNT}" -gt 0 ]]; then + if [[ "${PLAT}" == 'Alpine' ]]; then + if rc-service wg-quick restart; then + echo "::: WireGuard reloaded" else - if systemctl reload wg-quick@wg0; then - echo "::: WireGuard reloaded" - else - echo "::: Failed to reload WireGuard" - fi + err "::: Failed to reload WireGuard" fi + else + if systemctl reload wg-quick@wg0; then + echo "::: WireGuard reloaded" + else + err "::: Failed to reload WireGuard" + fi + fi fi From 0f491339e2f2f54839c282d6a54403acd5baf282 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Fri, 5 Aug 2022 00:52:33 +0200 Subject: [PATCH 02/14] docs(CONTRIBUTING): add new rules to docs Add the new rules to the CONTRIBUTING section --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e7b7c4..92bbc6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,11 @@ Please report unacceptable behavior to any project maintainer. * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. +* Use the following [commit rules](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format). + +* Use the following [code style rules](https://google.github.io/styleguide/shellguide.html). +We suggest you to use `shfmt` with the options `-i 2 -ci -sr -w` + ### **PiVPN Website** * Yes the site is open source as well! You can find and contribute to [pivpn.io](https://github.com/pivpn/pivpn.io) directly. From b17d3098602363eaf15d4635db8e0edd17574a35 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Fri, 5 Aug 2022 01:02:18 +0200 Subject: [PATCH 03/14] refactor(installer): format the code Format the code following the new rules --- auto_install/install.sh | 5612 +++++++++++++++++++++++---------------- 1 file changed, 3282 insertions(+), 2330 deletions(-) diff --git a/auto_install/install.sh b/auto_install/install.sh index aa78a10..e0f6ed1 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -10,7 +10,6 @@ # curl -sSfL https://install.pivpn.io | bash # Make sure you have `curl` installed - ######## VARIABLES ######### pivpnGitUrl="https://github.com/pivpn/pivpn.git" # Uncomment to checkout a custom branch for local pivpn files @@ -29,26 +28,34 @@ ovpnUserGroup="openvpn:openvpn" ######## PKG Vars ######## PKG_MANAGER="apt-get" -### FIXME: quoting UPDATE_PKG_CACHE and PKG_INSTALL hangs the script, shellcheck SC2086 +### FIXME: quoting UPDATE_PKG_CACHE and PKG_INSTALL hangs the script, +### shellcheck SC2086 UPDATE_PKG_CACHE="${PKG_MANAGER} update -y" PKG_INSTALL="${PKG_MANAGER} --yes --no-install-recommends install" -PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true" +PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | " +PKG_COUNT="${PKG_COUNT} grep -c ^Inst || true" CHECK_PKG_INSTALLED='dpkg-query -s' -# Dependencies that are required by the script, regardless of the VPN protocol chosen -BASE_DEPS=(git tar curl grep dnsutils grepcidr whiptail net-tools bsdmainutils bash-completion) +# Dependencies that are required by the script, +# regardless of the VPN protocol chosen +BASE_DEPS=(git tar curl grep dnsutils grepcidr whiptail net-tools) +BASE_DEPS+=(bsdmainutils bash-completion) -BASE_DEPS_ALPINE=(git grep bind-tools newt net-tools bash-completion coreutils openssl) -BASE_DEPS_ALPINE+=(util-linux openrc iptables ip6tables coreutils sed perl) +BASE_DEPS_ALPINE=(git grep bind-tools newt net-tools bash-completion coreutils) +BASE_DEPS_ALPINE+=(openssl util-linux openrc iptables ip6tables coreutils sed) +BASE_DEPS_ALPINE+=(perl) -# Dependencies that where actually installed by the script. For example if the script requires -# grep and dnsutils but dnsutils is already installed, we save grep here. This way when uninstalling -# PiVPN we won't prompt to remove packages that may have been installed by the user for other reasons +# Dependencies that where actually installed by the script. For example if the +# script requires grep and dnsutils but dnsutils is already installed, we save +# grep here. This way when uninstalling PiVPN we won't prompt to remove packages +# that may have been installed by the user for other reasons INSTALLED_PACKAGES=() ######## URLs ######## easyrsaVer="3.1.0" -easyrsaRel="https://github.com/OpenVPN/easy-rsa/releases/download/v${easyrsaVer}/EasyRSA-${easyrsaVer}.tgz" +easyrsaRel="https://github.com/OpenVPN/easy-rsa" +easyrsaRel="${easyrsaRel}/releases/download/v${easyrsaVer}" +easyrsaRel="${easyrsaRel}/EasyRSA-${easyrsaVer}.tgz" ######## Undocumented Flags. Shhh ######## runUnattended=false @@ -62,933 +69,1167 @@ pivpnPERSISTENTKEEPALIVE="" pivpnDNS2="" ######## IPv6 related config -# cli parameter "--noipv6" allows to disable IPv6 which also prevents forced IPv6 route -# cli parameter "--ignoreipv6leak" allows to skip the forced IPv6 route if required (not recommended) +# cli parameter "--noipv6" allows to disable IPv6 which also prevents forced +# IPv6 route +# cli parameter "--ignoreipv6leak" allows to skip the forced IPv6 route if +# required (not recommended) ## Force IPv6 through VPN even if IPv6 is not supported by the server ## This will prevent an IPv6 leak on the client site but might cause ## issues on the client site accessing IPv6 addresses. ## This option is useless if routes are set manually. ## It's also irrelevant when IPv6 is (forced) enabled. -pivpnforceipv6route="1" +pivpnforceipv6route=1 ## Enable or disable IPv6. ## Leaving it empty or set to "1" will trigger an IPv6 uplink check pivpnenableipv6="" -## Enable to skip IPv6 connectivity check and also force client IPv6 traffic through wireguard -## regardless if there is a working IPv6 route on the server. -pivpnforceipv6="0" +## Enable to skip IPv6 connectivity check and also force client IPv6 traffic +## through wireguard regardless if there is a working IPv6 route on the server. +pivpnforceipv6=0 ######## SCRIPT ######## # Find the rows and columns. Will default to 80x24 if it can not be detected. -screen_size=$(stty size 2>/dev/null || echo 24 80) -rows=$(echo "$screen_size" | awk '{print $1}') -columns=$(echo "$screen_size" | awk '{print $2}') +screen_size="$(stty size 2> /dev/null || echo 24 80)" +rows="$(echo "${screen_size}" | awk '{print $1}')" +columns="$(echo "${screen_size}" | awk '{print $2}')" # Divide by two so the dialogs take up half of the screen, which looks nice. -r=$(( rows / 2 )) -c=$(( columns / 2 )) +r=$((rows / 2)) +c=$((columns / 2)) # Unless the screen is tiny -r=$(( r < 20 ? 20 : r )) -c=$(( c < 70 ? 70 : c )) +r=$((r < 20 ? 20 : r)) +c=$((c < 70 ? 70 : c)) # Override localization settings so the output is in English language. export LC_ALL=C -main(){ - # Pre install checks and configs - distroCheck - rootCheck - flagsCheck "$@" - unattendedCheck - checkExistingInstall "$@" - checkHostname - # Verify there is enough disk space for the install - if [[ "${skipSpaceCheck}" == true ]]; then - echo "::: --skip-space-check passed to script, skipping free disk space verification!" - else - verifyFreeDiskSpace - fi - updatePackageCache - notifyPackageUpdatesAvailable - preconfigurePackages +main() { + # Pre install checks and configs + distroCheck + rootCheck + flagsCheck "$@" + unattendedCheck + checkExistingInstall "$@" + checkHostname - if [ "${PLAT}" == 'Alpine' ]; then - installDependentPackages BASE_DEPS_ALPINE[@] - else - installDependentPackages BASE_DEPS[@] - fi + # Verify there is enough disk space for the install + if [[ "${skipSpaceCheck}" == 'true' ]]; then + echo -n "::: --skip-space-check passed to script, " + echo "skipping free disk space verification!" + else + verifyFreeDiskSpace + fi - welcomeDialogs + updatePackageCache + notifyPackageUpdatesAvailable + preconfigurePackages - if [ "$pivpnforceipv6" == "1" ]; then - echo "::: Forced IPv6 config, skipping IPv6 uplink check!" - pivpnenableipv6="1" - else - if [ -z "$pivpnenableipv6" ] || [ "$pivpnenableipv6" == "1" ]; then - checkipv6uplink - fi - if [ "$pivpnenableipv6" == "0" ] && [ "$pivpnforceipv6route" == "1" ]; then - askforcedipv6route - fi - fi + if [[ "${PLAT}" == 'Alpine' ]]; then + installDependentPackages BASE_DEPS_ALPINE[@] + else + installDependentPackages BASE_DEPS[@] + fi - chooseInterface - if checkStaticIpSupported; then - getStaticIPv4Settings - if [ -z "$dhcpReserv" ] || [ "$dhcpReserv" -ne 1 ]; then - setStaticIPv4 - fi - else - staticIpNotSupported - fi + welcomeDialogs - chooseUser - cloneOrUpdateRepos - # Install - if installPiVPN; then - echo "::: Install Complete..." - else - exit 1 - fi - restartServices - # Ask if unattended-upgrades will be enabled - askUnattendedUpgrades - if [ "$UNATTUPG" -eq 1 ]; then - confUnattendedUpgrades - fi - writeConfigFiles - installScripts - displayFinalMessage - echo ":::" + if [[ "${pivpnforceipv6}" -eq 1 ]]; then + echo "::: Forced IPv6 config, skipping IPv6 uplink check!" + pivpnenableipv6=1 + else + if [[ -z "${pivpnenableipv6}" ]] || + [[ "${pivpnenableipv6}" -eq 1 ]]; then + checkipv6uplink + fi + + if [[ "${pivpnenableipv6}" -eq 0 ]] && + [[ "${pivpnforceipv6route}" -eq 1 ]]; then + askforcedipv6route + fi + fi + + chooseInterface + + if checkStaticIpSupported; then + getStaticIPv4Settings + + if [[ -z "${dhcpReserv}" ]] || + [[ "${dhcpReserv}" -ne 1 ]]; then + setStaticIPv4 + fi + else + staticIpNotSupported + fi + + chooseUser + cloneOrUpdateRepos + + # Install + if installPiVPN; then + echo "::: Install Complete..." + else + exit 1 + fi + + restartServices + # Ask if unattended-upgrades will be enabled + askUnattendedUpgrades + + if [[ "${UNATTUPG}" -eq 1 ]]; then + confUnattendedUpgrades + fi + + writeConfigFiles + installScripts + displayFinalMessage + echo ":::" } ####### FUNCTIONS ########## -rootCheck(){ - ######## FIRST CHECK ######## - # Must be root to install - echo ":::" - if [[ $EUID -eq 0 ]]; then - echo "::: You are root." - else - echo "::: sudo will be used for the install." - # Check if it is actually installed - # If it isn't, exit because the install cannot complete - if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then - export SUDO="sudo" - export SUDOE="sudo -E" - else - echo "::: Please install sudo or run this as root." - exit 1 - fi - fi +err() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } -flagsCheck(){ - # Check arguments for the undocumented flags - for ((i=1; i <= "$#"; i++)); do - j="$((i+1))" - case "${!i}" in - "--skip-space-check" ) skipSpaceCheck=true;; - "--unattended" ) runUnattended=true; unattendedConfig="${!j}";; - "--reconfigure" ) reconfigure=true;; - "--show-unsupported-nics" ) showUnsupportedNICs=true;; - "--giturl" ) pivpnGitUrl="${!j}";; - "--gitbranch" ) pivpnGitBranch="${!j}";; - "--noipv6" ) pivpnforceipv6="0"; pivpnenableipv6="0"; pivpnforceipv6route="0";; - "--ignoreipv6leak" ) pivpnforceipv6route="0";; - esac - done +rootCheck() { + ######## FIRST CHECK ######## + # Must be root to install + echo ":::" + if [[ "${EUID}" -eq 0 ]]; then + echo "::: You are root." + else + echo "::: sudo will be used for the install." + + # Check if it is actually installed + # If it isn't, exit because the install cannot complete + if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then + export SUDO="sudo" + export SUDOE="sudo -E" + else + err "::: Please install sudo or run this as root." + exit 1 + fi + fi } -unattendedCheck(){ - if [[ "${runUnattended}" == true ]]; then - echo "::: --unattended passed to install script, no whiptail dialogs will be displayed" - if [ -z "$unattendedConfig" ]; then - echo "::: No configuration file passed" - exit 1 - else - if [ -r "$unattendedConfig" ]; then - # shellcheck disable=SC1090 - source "$unattendedConfig" - else - echo "::: Can't open $unattendedConfig" - exit 1 - fi - fi - fi +flagsCheck() { + # Check arguments for the undocumented flags + for ((i = 1; i <= "$#"; i++)); do + j="$((i + 1))" + + case "${!i}" in + "--skip-space-check") + skipSpaceCheck=true + ;; + "--unattended") + runUnattended=true + unattendedConfig="${!j}" + ;; + "--reconfigure") + reconfigure=true + ;; + "--show-unsupported-nics") + showUnsupportedNICs=true + ;; + "--giturl") + pivpnGitUrl="${!j}" + ;; + "--gitbranch") + pivpnGitBranch="${!j}" + ;; + "--noipv6") + pivpnforceipv6=0 + pivpnenableipv6=0 + pivpnforceipv6route=0 + ;; + "--ignoreipv6leak") + pivpnforceipv6route=0 + ;; + esac + done } -checkExistingInstall(){ +unattendedCheck() { + if [[ "${runUnattended}" == 'true' ]]; then + echo -n "::: --unattended passed to install script, " + echo "no whiptail dialogs will be displayed" + + if [[ -z "${unattendedConfig}" ]]; then + err "::: No configuration file passed" + exit 1 + else + if [[ -r "${unattendedConfig}" ]]; then + # shellcheck disable=SC1090 + . "${unattendedConfig}" + else + err "::: Can't open ${unattendedConfig}" + exit 1 + fi + fi + fi +} + +checkExistingInstall() { # see which setup already exists - if [ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]; then - setupVars="${setupConfigDir}/wireguard/${setupVarsFile}" - elif [ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]; then - setupVars="${setupConfigDir}/openvpn/${setupVarsFile}" - fi + if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]]; then + setupVars="${setupConfigDir}/wireguard/${setupVarsFile}" + elif [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then + setupVars="${setupConfigDir}/openvpn/${setupVarsFile}" + fi - if [ -r "$setupVars" ]; then - if [[ "${reconfigure}" == true ]]; then - echo "::: --reconfigure passed to install script, will reinstall PiVPN overwriting existing settings" - UpdateCmd="Reconfigure" - elif [[ "${runUnattended}" == true ]]; then - ### What should the script do when passing --unattended to an existing installation? - UpdateCmd="Reconfigure" - else - askAboutExistingInstall ${setupVars} - fi - fi + if [[ -r "${setupVars}" ]]; then + if [[ "${reconfigure}" == 'true' ]]; then + echo -n "::: --reconfigure passed to install script, " + echo "will reinstall PiVPN overwriting existing settings" + UpdateCmd="Reconfigure" + elif [[ "${runUnattended}" == 'true' ]]; then + ### What should the script do when passing --unattended to + ### an existing installation? + UpdateCmd="Reconfigure" + else + askAboutExistingInstall "${setupVars}" + fi + fi - if [ -z "$UpdateCmd" ] || [ "$UpdateCmd" = "Reconfigure" ]; then - : - elif [ "$UpdateCmd" = "Update" ]; then - $SUDO ${pivpnScriptDir}/update.sh "$@" - exit "$?" - elif [ "$UpdateCmd" = "Repair" ]; then - # shellcheck disable=SC1090 - source "$setupVars" - runUnattended=true - fi + if [[ -z "${UpdateCmd}" ]] || + [[ "${UpdateCmd}" == "Reconfigure" ]]; then + : + elif [[ "${UpdateCmd}" == "Update" ]]; then + ${SUDO} "${pivpnScriptDir}/update.sh" "$@" + exit "$?" + elif [[ "${UpdateCmd}" == "Repair" ]]; then + # shellcheck disable=SC1090 + . "${setupVars}" + runUnattended=true + fi } -askAboutExistingInstall(){ - opt1a="Update" - opt1b="Get the latest PiVPN scripts" +askAboutExistingInstall() { + opt1a="Update" + opt1b="Get the latest PiVPN scripts" - opt2a="Repair" - opt2b="Reinstall PiVPN using existing settings" + opt2a="Repair" + opt2b="Reinstall PiVPN using existing settings" - opt3a="Reconfigure" - opt3b="Reinstall PiVPN with new settings" + opt3a="Reconfigure" + opt3b="Reinstall PiVPN with new settings" - UpdateCmd=$(whiptail --title "Existing Install Detected!" --menu "\nWe have detected an existing install.\n$1\n\nPlease choose from the following options (Reconfigure can be used to add a second VPN type):" ${r} ${c} 3 \ - "${opt1a}" "${opt1b}" \ - "${opt2a}" "${opt2b}" \ - "${opt3a}" "${opt3b}" 3>&2 2>&1 1>&3) || \ - { echo "::: Cancel selected. Exiting"; exit 1; } + UpdateCmd="$(whiptail \ + --title "Existing Install Detected!" \ + --menu " +We have detected an existing install. +${1} - echo "::: ${UpdateCmd} option selected." +Please choose from the following options \ +(Reconfigure can be used to add a second VPN type):" "${r}" "${c}" 3 \ + "${opt1a}" "${opt1b}" \ + "${opt2a}" "${opt2b}" \ + "${opt3a}" "${opt3b}" \ + 3>&2 2>&1 1>&3)" || + { + err "::: Cancel selected. Exiting" + exit 1 + } + + echo "::: ${UpdateCmd} option selected." } +distroCheck() { + # Check for supported distribution + # Compatibility, functions to check for supported OS + # distroCheck, maybeOSSupport, noOSSupport + # if lsb_release command is on their system + if command -v lsb_release > /dev/null; then + PLAT="$(lsb_release -si)" + OSCN="$(lsb_release -sc)" + else # else get info from os-release + . /etc/os-release + PLAT="$(awk '{print $1}' <<< "${NAME}")" + VER="${VERSION_ID}" + declare -A VER_MAP=(["9"]="stretch" + ["10"]="buster" + ["11"]="bullseye" + ["16.04"]="xenial" + ["18.04"]="bionic" + ["20.04"]="focal") + OSCN="${VER_MAP["${VER}"]}" -distroCheck(){ - # Check for supported distribution - # Compatibility, functions to check for supported OS - # distroCheck, maybeOSSupport, noOSSupport - # if lsb_release command is on their system - if command -v lsb_release > /dev/null; then + # Alpine support + if [[ -z "${OSCN}" ]]; then + OSCN="${VER}" + fi + fi - PLAT=$(lsb_release -si) - OSCN=$(lsb_release -sc) + case "${PLAT}" in + Debian | Raspbian | Ubuntu) + case "${OSCN}" in + stretch | buster | bullseye | xenial | bionic | focal) + : + ;; + *) + maybeOSSupport + ;; + esac + ;; + Alpine) + PKG_MANAGER='apk' + UPDATE_PKG_CACHE="${PKG_MANAGER} update" + PKG_INSTALL="${PKG_MANAGER} --no-cache add" + PKG_COUNT="${PKG_MANAGER} list -u | wc -l || true" + CHECK_PKG_INSTALLED="${PKG_MANAGER} --no-cache info -e" + ;; + *) + noOSSupport + ;; + esac - else # else get info from os-release - - # shellcheck disable=SC1091 - source /etc/os-release - PLAT=$(awk '{print $1}' <<< "$NAME") - VER="$VERSION_ID" - declare -A VER_MAP=(["9"]="stretch" ["10"]="buster" ["11"]="bullseye" ["16.04"]="xenial" ["18.04"]="bionic" ["20.04"]="focal") - OSCN=${VER_MAP["${VER}"]} - - # Alpine support - if [ -z "${OSCN}" ]; then - OSCN="${VER}" - fi - fi - - case ${PLAT} in - Debian|Raspbian|Ubuntu) - case ${OSCN} in - stretch|buster|bullseye|xenial|bionic|focal) - : - ;; - *) - maybeOSSupport - ;; - esac - ;; - Alpine) - PKG_MANAGER='apk' - UPDATE_PKG_CACHE="${PKG_MANAGER} update" - PKG_INSTALL="${PKG_MANAGER} --no-cache add" - PKG_COUNT="${PKG_MANAGER} list -u | wc -l || true" - CHECK_PKG_INSTALLED="${PKG_MANAGER} --no-cache info -e" - ;; - *) - noOSSupport - ;; - esac - - echo "PLAT=${PLAT}" > ${tempsetupVarsFile} - echo "OSCN=${OSCN}" >> ${tempsetupVarsFile} + { + echo "PLAT=${PLAT}" + echo "OSCN=${OSCN}" + } > "${tempsetupVarsFile}" } -noOSSupport(){ - if [ "${runUnattended}" = 'true' ]; then - echo "::: Invalid OS detected" - echo "::: We have not been able to detect a supported OS." - echo "::: Currently this installer supports Raspbian, Debian and Ubuntu." - exit 1 - fi +noOSSupport() { + if [[ "${runUnattended}" == 'true' ]]; then + err "::: Invalid OS detected" + err "::: We have not been able to detect a supported OS." + err "::: Currently this installer supports Raspbian, Debian and Ubuntu." + exit 1 + fi - whiptail --msgbox --backtitle "INVALID OS DETECTED" --title "Invalid OS" "We have not been able to detect a supported OS. + whiptail \ + --backtitle "INVALID OS DETECTED" \ + --title "Invalid OS" \ + --msgbox "We have not been able to detect a supported OS. Currently this installer supports Raspbian, Debian and Ubuntu. -For more details, check our documentation at https://github.com/pivpn/pivpn/wiki " ${r} ${c} - exit 1 +For more details, check our documentation at \ +https://github.com/pivpn/pivpn/wiki" "${r}" "${c}" + exit 1 } -maybeOSSupport(){ - if [ "${runUnattended}" = 'true' ]; then - echo "::: OS Not Supported" - echo "::: You are on an OS that we have not tested but MAY work, continuing anyway..." - return - fi +maybeOSSupport() { + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: OS Not Supported" + echo -n "::: You are on an OS that we have not tested but MAY work, " + echo "continuing anyway..." + return + fi - if (whiptail --backtitle "Untested OS" --title "Untested OS" --yesno "You are on an OS that we have not tested but MAY work. + if whiptail \ + --backtitle "Untested OS" \ + --title "Untested OS" \ + --yesno "You are on an OS that we have not tested but MAY work. Currently this installer supports Raspbian, Debian and Ubuntu. -For more details about supported OS please check our documentation at https://github.com/pivpn/pivpn/wiki -Would you like to continue anyway?" ${r} ${c}) then - echo "::: Did not detect perfectly supported OS but," - echo "::: Continuing installation at user's own risk..." - else - echo "::: Exiting due to untested OS" - exit 1 - fi +For more details about supported OS please check our documentation \ +at https://github.com/pivpn/pivpn/wiki +Would you like to continue anyway?" "${r}" "${c}"; then + echo "::: Did not detect perfectly supported OS but," + echo -n "::: Continuing installation at user's own " + echo "risk..." + else + err "::: Exiting due to untested OS" + exit 1 + fi } +checkHostname() { + # Checks for hostname Length + host_name="$(hostname -s)" -checkHostname(){ - # Checks for hostname Length - host_name=$(hostname -s) - if [[ ! ${#host_name} -le 28 ]]; then - if [ "${runUnattended}" = 'true' ]; then - echo "::: Your hostname is too long." - echo "::: Use 'hostnamectl set-hostname YOURHOSTNAME' to set a new hostname" - echo "::: It must be less then 28 characters long and it must not use special characters" - exit 1 - fi - until [[ ${#host_name} -le 28 && $host_name =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; do - host_name=$(whiptail --inputbox "Your hostname is too long.\\nEnter new hostname with less then 28 characters\\nNo special characters allowed." \ - --title "Hostname too long" ${r} ${c} 3>&1 1>&2 2>&3) - $SUDO hostnamectl set-hostname "${host_name}" - if [[ ${#host_name} -le 28 && $host_name =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; then - echo "::: Hostname valid and length OK, proceeding..." - fi - done - else - echo "::: Hostname length OK" - fi + if [[ "${#host_name}" -gt 28 ]]; then + if [[ "${runUnattended}" == 'true' ]]; then + err "::: Your hostname is too long." + err "::: Use 'hostnamectl set-hostname YOURHOSTNAME' to set a new hostname" + err "::: It must be less then 28 characters long and it must not use special characters" + exit 1 + fi + + until [[ "${#host_name}" -le 28 ]] && + [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; do + host_name="$(whiptail \ + --title "Hostname too long" \ + --inputbox "Your hostname is too long. +Enter new hostname with less then 28 characters +No special characters allowed." "${r}" "${c}" \ + 3>&1 1>&2 2>&3)" + ${SUDO} hostnamectl set-hostname "${host_name}" + + if [[ "${#host_name}" -le 28 ]] && + [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; then + echo "::: Hostname valid and length OK, proceeding..." + fi + done + else + echo "::: Hostname length OK" + fi } -spinner(){ - local pid=$1 - local delay=0.50 - local spinstr='/-\|' - while ps a | awk '{print $1}' | grep -q "$pid"; do - local temp=${spinstr#?} - printf " [%c] " "${spinstr}" - local spinstr=${temp}${spinstr%"$temp"} - sleep ${delay} - printf "\\b\\b\\b\\b\\b\\b" - done - printf " \\b\\b\\b\\b" +spinner() { + local pid="${1}" + local delay=0.50 + local spinstr='/-\|' + + while ps a | awk '{print $1}' | grep -q "${pid}"; do + local temp="${spinstr#?}" + printf " [%c] " "${spinstr}" + local spinstr="${temp}${spinstr%"$temp"}" + sleep "${delay}" + printf "\\b\\b\\b\\b\\b\\b" + done + + printf " \\b\\b\\b\\b" } -verifyFreeDiskSpace(){ - # If user installs unattended-upgrades we'd need about 60MB so will check for 75MB free - echo "::: Verifying free disk space..." - local required_free_kilobytes=76800 - local existing_free_kilobytes - existing_free_kilobytes=$(df -Pk | grep -m1 '\/$' | awk '{print $4}') +verifyFreeDiskSpace() { + # If user installs unattended-upgrades we'd need about 60MB so + # will check for 75MB free + echo "::: Verifying free disk space..." + local required_free_kilobytes=76800 + local existing_free_kilobytes + existing_free_kilobytes="$(df -Pk | + grep -m1 '\/$' | + awk '{print $4}')" - # - Unknown free disk space , not a integer - if ! [[ "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then - echo "::: Unknown free disk space!" - echo "::: We were unable to determine available free disk space on this system." - if [ "${runUnattended}" = 'true' ]; then - exit 1 - fi - echo "::: You may continue with the installation, however, it is not recommended." - read -r -p "::: If you are sure you want to continue, type YES and press enter :: " response - case $response in - [Y][E][S]) - ;; - *) - echo "::: Confirmation not received, exiting..." - exit 1 - ;; - esac - # - Insufficient free disk space - elif [[ ${existing_free_kilobytes} -lt ${required_free_kilobytes} ]]; then - echo "::: Insufficient Disk Space!" - echo "::: Your system appears to be low on disk space. PiVPN recommends a minimum of $required_free_kilobytes KiloBytes." - echo "::: You only have ${existing_free_kilobytes} KiloBytes free." - echo "::: If this is a new install on a Raspberry Pi you may need to expand your disk." - echo "::: Try running 'sudo raspi-config', and choose the 'expand file system option'" - echo "::: After rebooting, run this installation again. (curl -sSfL https://install.pivpn.io | bash)" + # - Unknown free disk space , not a integer + if [[ ! "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then + echo "::: Unknown free disk space!" + echo -n "::: We were unable to determine available free disk space " + echo "on this system." - echo "Insufficient free space, exiting..." - exit 1 - fi + if [[ "${runUnattended}" == 'true' ]]; then + exit 1 + fi + + echo -n "::: You may continue with the installation, however, " + echo "it is not recommended." + echo -n "::: If you are sure you want to continue, " + echo -n "type YES and press enter :: " + read -r response + + case "${response}" in + [Yy][Ee][Ss]) + : + ;; + *) + err "::: Confirmation not received, exiting..." + exit 1 + ;; + esac + # - Insufficient free disk space + elif [[ "${existing_free_kilobytes}" -lt "${required_free_kilobytes}" ]]; then + err "::: Insufficient Disk Space!" + err "::: Your system appears to be low on disk space. PiVPN recommends a minimum of ${required_free_kilobytes} KiloBytes." + err "::: You only have ${existing_free_kilobytes} KiloBytes free." + err "::: If this is a new install on a Raspberry Pi you may need to expand your disk." + err "::: Try running 'sudo raspi-config', and choose the 'expand file system option'" + err "::: After rebooting, run this installation again. (curl -sSfL https://install.pivpn.io | bash)" + err "Insufficient free space, exiting..." + exit 1 + fi } -updatePackageCache(){ - #update package lists - echo ":::" - echo -ne "::: Package Cache update is needed, running ${UPDATE_PKG_CACHE} ...\\n" - # shellcheck disable=SC2086 - $SUDO ${UPDATE_PKG_CACHE} &> /dev/null & - spinner $! - echo " done!" +updatePackageCache() { + # update package lists + echo ":::" + echo -e "::: Package Cache update is needed, running ${UPDATE_PKG_CACHE} ..." + # shellcheck disable=SC2086 + ${SUDO} ${UPDATE_PKG_CACHE} &> /dev/null & + spinner "$!" + echo " done!" } -notifyPackageUpdatesAvailable(){ - # Let user know if they have outdated packages on their system and - # advise them to run a package update at soonest possible. - echo ":::" - echo -n "::: Checking ${PKG_MANAGER} for upgraded packages...." - updatesToInstall=$(eval "${PKG_COUNT}") - echo " done!" - echo ":::" - if [[ ${updatesToInstall} -eq "0" ]]; then - echo "::: Your system is up to date! Continuing with PiVPN installation..." - else - echo "::: There are ${updatesToInstall} updates available for your system!" - echo "::: We recommend you update your OS after installing PiVPN! " - echo ":::" - fi +notifyPackageUpdatesAvailable() { + # Let user know if they have outdated packages on their system and + # advise them to run a package update at soonest possible. + echo ":::" + echo -n "::: Checking ${PKG_MANAGER} for upgraded packages...." + updatesToInstall="$(eval "${PKG_COUNT}")" + echo " done!" + echo ":::" + + if [[ "${updatesToInstall}" -eq 0 ]]; then + echo "::: Your system is up to date! Continuing with PiVPN installation..." + else + echo "::: There are ${updatesToInstall} updates available for your system!" + echo "::: We recommend you update your OS after installing PiVPN! " + echo ":::" + fi } -preconfigurePackages(){ - # Install packages used by this installation script - # If apt is older than 1.5 we need to install an additional package to add - # support for https repositories that will be used later on - if [ "${PKG_MANAGER}" == 'apt-get' ] && [ -f /etc/apt/sources.list ]; then - INSTALLED_APT="$(apt-cache policy apt | grep -m1 'Installed: ' | grep -v '(none)' | awk '{print $2}')" - if dpkg --compare-versions "$INSTALLED_APT" lt 1.5; then - BASE_DEPS+=("apt-transport-https") - fi - fi +preconfigurePackages() { + # Install packages used by this installation script + # If apt is older than 1.5 we need to install an additional package to add + # support for https repositories that will be used later on + if [[ "${PKG_MANAGER}" == 'apt-get' ]] && + [[ -f /etc/apt/sources.list ]]; then + INSTALLED_APT="$(apt-cache policy apt | + grep -m1 'Installed: ' | + grep -v '(none)' | + awk '{print $2}')" - # We set static IP only on Raspberry Pi OS - if checkStaticIpSupported; then - BASE_DEPS+=(dhcpcd5) - fi + if dpkg --compare-versions "${INSTALLED_APT}" lt 1.5; then + BASE_DEPS+=("apt-transport-https") + fi + fi - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - DPKG_ARCH="$(dpkg --print-architecture)" - elif [ "${PKG_MANAGER}" == 'apk' ]; then - DPKG_ARCH="$(apk --print-arch)" - fi + # We set static IP only on Raspberry Pi OS + if checkStaticIpSupported; then + BASE_DEPS+=(dhcpcd5) + fi - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - AVAILABLE_OPENVPN="$(apt-cache policy openvpn | grep -m1 'Candidate: ' | grep -v '(none)' | awk '{print $2}')" - elif [ "${PKG_MANAGER}" == 'apk' ]; then - AVAILABLE_OPENVPN="$(apk search -e openvpn | sed -E -e 's/openvpn\-(.*)/\1/')" - fi + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + DPKG_ARCH="$(dpkg --print-architecture)" + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + DPKG_ARCH="$(apk --print-arch)" + fi - OPENVPN_SUPPORT=0 - NEED_OPENVPN_REPO=0 + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + AVAILABLE_OPENVPN="$(apt-cache policy openvpn | + grep -m1 'Candidate: ' | + grep -v '(none)' | + awk '{print $2}')" + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + AVAILABLE_OPENVPN="$(apk search -e openvpn | + sed -E -e 's/openvpn\-(.*)/\1/')" + fi - # We require OpenVPN 2.4 or later for ECC support. If not available in the - # repositories but we are running x86 Debian or Ubuntu, add the official repo - # which provides the updated package. - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - if [ -n "$AVAILABLE_OPENVPN" ] && dpkg --compare-versions "$AVAILABLE_OPENVPN" ge 2.4; then - OPENVPN_SUPPORT=1 - else - if [ "$PLAT" = "Debian" ] || [ "$PLAT" = "Ubuntu" ]; then - if [ "$DPKG_ARCH" = "amd64" ] || [ "$DPKG_ARCH" = "i386" ]; then - NEED_OPENVPN_REPO=1 - OPENVPN_SUPPORT=1 - else - OPENVPN_SUPPORT=0 - fi - else - OPENVPN_SUPPORT=0 - fi - fi - elif [ "${PKG_MANAGER}" == 'apk' ]; then - if [ -n "${AVAILABLE_OPENVPN}" ] && [ "$(apk version -t "${AVAILABLE_OPENVPN}" 2.4)" == '>' ]; then - OPENVPN_SUPPORT=1 - else - OPENVPN_SUPPORT=0 - fi - fi + OPENVPN_SUPPORT=0 + NEED_OPENVPN_REPO=0 - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - AVAILABLE_WIREGUARD="$(apt-cache policy wireguard | grep -m1 'Candidate: ' | grep -v '(none)' | awk '{print $2}')" - elif [ "${PKG_MANAGER}" == 'apk' ]; then - AVAILABLE_WIREGUARD="$(apk search -e wireguard-tools | sed -E -e 's/wireguard\-tools\-(.*)/\1/')" - fi + # We require OpenVPN 2.4 or later for ECC support. If not available in the + # repositories but we are running x86 Debian or Ubuntu, add the official repo + # which provides the updated package. + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + if [[ -n "${AVAILABLE_OPENVPN}" ]] && + dpkg --compare-versions "${AVAILABLE_OPENVPN}" ge 2.4; then + OPENVPN_SUPPORT=1 + else + if [[ "${PLAT}" == "Debian" ]] || + [[ "${PLAT}" == "Ubuntu" ]]; then + if [[ "${DPKG_ARCH}" == "amd64" ]] || + [[ "${DPKG_ARCH}" == "i386" ]]; then + NEED_OPENVPN_REPO=1 + OPENVPN_SUPPORT=1 + else + OPENVPN_SUPPORT=0 + fi + else + OPENVPN_SUPPORT=0 + fi + fi + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + if [[ -n "${AVAILABLE_OPENVPN}" ]] && + [[ "$(apk version -t "${AVAILABLE_OPENVPN}" 2.4)" == '>' ]]; then + OPENVPN_SUPPORT=1 + else + OPENVPN_SUPPORT=0 + fi + fi - WIREGUARD_SUPPORT=0 + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + AVAILABLE_WIREGUARD="$(apt-cache policy wireguard | + grep -m1 'Candidate: ' | + grep -v '(none)' | + awk '{print $2}')" + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + AVAILABLE_WIREGUARD="$(apk search -e wireguard-tools | + sed -E -e 's/wireguard\-tools\-(.*)/\1/')" + fi - # If a wireguard kernel object is found and is part of any installed package, then - # it has not been build via DKMS or manually (installing via wireguard-dkms does not - # make the module part of the package since the module itself is built at install time - # and not part of the .deb). - # Source: https://github.com/MichaIng/DietPi/blob/7bf5e1041f3b2972d7827c48215069d1c90eee07/dietpi/dietpi-software#L1807-L1815 - WIREGUARD_BUILTIN=0 + WIREGUARD_SUPPORT=0 - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - if dpkg-query -S '/lib/modules/*/wireguard.ko*' &> /dev/null || modinfo wireguard 2> /dev/null | grep -q '^filename:[[:blank:]]*(builtin)$'; then - WIREGUARD_BUILTIN=1 - fi - fi + # If a wireguard kernel object is found and is part of any installed package, + # then it has not been build via DKMS or manually (installing via + # wireguard-dkms does not make the module part of the package since the + # module itself is built at install time and not part of the .deb). + # Source: https://github.com/MichaIng/DietPi/blob/7bf5e1041f3b2972d7827c48215069d1c90eee07/dietpi/dietpi-software#L1807-L1815 + WIREGUARD_BUILTIN=0 - if - # If the module is builtin and the package available, we only need to install wireguard-tools. - [[ $WIREGUARD_BUILTIN == 1 && -n $AVAILABLE_WIREGUARD ]] || - # If the package is not available, on Debian and Raspbian we can add it via Bullseye repository. - [[ $WIREGUARD_BUILTIN == 1 && ( $PLAT == 'Debian' || $PLAT == 'Raspbian' ) ]] || - # If the module is not builtin, on Raspbian we know the headers package: raspberrypi-kernel-headers - [[ $PLAT == 'Raspbian' ]] || - # On Alpine, the kernel must be linux-lts or linux-virt if we want to load the kernel module - [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] || - # On Alpine Docker Container, the responsibility to have a WireGuard module on the host system is at user side - [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] || - # On Debian (and Ubuntu), we can only reliably assume the headers package for amd64: linux-image-amd64 - [[ $PLAT == 'Debian' && $DPKG_ARCH == 'amd64' ]] || - # On Ubuntu, additionally the WireGuard package needs to be available, since we didn't test mixing Ubuntu repositories. - [[ $PLAT == 'Ubuntu' && $DPKG_ARCH == 'amd64' && -n $AVAILABLE_WIREGUARD ]] || - # Ubuntu focal has wireguard support - [[ $PLAT == 'Ubuntu' && $DPKG_ARCH == 'arm64' && $OSCN == 'focal' && -n $AVAILABLE_WIREGUARD ]]; then - WIREGUARD_SUPPORT=1 - fi + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + if dpkg-query -S '/lib/modules/*/wireguard.ko*' &> /dev/null || + modinfo wireguard 2> /dev/null | + grep -q '^filename:[[:blank:]]*(builtin)$'; then + WIREGUARD_BUILTIN=1 + fi + fi - if [ "$OPENVPN_SUPPORT" -eq 0 ] && [ "$WIREGUARD_SUPPORT" -eq 0 ]; then - echo "::: Neither OpenVPN nor WireGuard are available to install by PiVPN, exiting..." - exit 1 - fi + if + # If the module is builtin and the package available, we only need + # to install wireguard-tools. + [[ "${WIREGUARD_BUILTIN}" -eq 1 && + -n "${AVAILABLE_WIREGUARD}" ]] || + # If the package is not available, on Debian and Raspbian we can + # add it via Bullseye repository. + [[ "${WIREGUARD_BUILTIN}" -eq 1 && + ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] || + # If the module is not builtin, on Raspbian we know the headers + # package: raspberrypi-kernel-headers + [[ "${PLAT}" == 'Raspbian' ]] || + # On Alpine, the kernel must be linux-lts or linux-virt if we want to + # load the kernel module + [[ "${PLAT}" == 'Alpine' && + ! -f /.dockerenv && + "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] || + # On Alpine Docker Container, the responsibility to have a WireGuard + # module on the host system is at user side + [[ "${PLAT}" == 'Alpine' && + -f /.dockerenv ]] || + # On Debian (and Ubuntu), we can only reliably assume the headers package + # for amd64: linux-image-amd64 + [[ "${PLAT}" == 'Debian' && + "${DPKG_ARCH}" == 'amd64' ]] || + # On Ubuntu, additionally the WireGuard package needs to be available, + # since we didn't test mixing Ubuntu repositories. + [[ "${PLAT}" == 'Ubuntu' && + "${DPKG_ARCH}" == 'amd64' && + -n "${AVAILABLE_WIREGUARD}" ]] || + # Ubuntu focal has wireguard support + [[ "${PLAT}" == 'Ubuntu' && + "${DPKG_ARCH}" == 'arm64' && + "${OSCN}" == 'focal' && + -n "${AVAILABLE_WIREGUARD}" ]] + then + WIREGUARD_SUPPORT=1 + fi - # if ufw is enabled, configure that. - # running as root because sometimes the executable is not in the user's $PATH - if $SUDO bash -c 'command -v ufw' > /dev/null; then - if ! ${SUDO} ufw status || $SUDO ufw status | grep -q inactive; then - USING_UFW=0 - else - USING_UFW=1 - fi - else - USING_UFW=0 - fi + if [[ "${OPENVPN_SUPPORT}" -eq 0 ]] && + [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then + err "::: Neither OpenVPN nor WireGuard are available to install by PiVPN, exiting..." + exit 1 + fi - if [ "${PKG_MANAGER}" == 'apt-get' ] && [ "$USING_UFW" -eq 0 ]; then - BASE_DEPS+=(iptables-persistent) - echo iptables-persistent iptables-persistent/autosave_v4 boolean true | $SUDO debconf-set-selections - echo iptables-persistent iptables-persistent/autosave_v6 boolean false | $SUDO debconf-set-selections - fi + # if ufw is enabled, configure that. + # running as root because sometimes the executable is not in the user's $PATH + if ${SUDO} bash -c 'command -v ufw' > /dev/null; then + if ! ${SUDO} ufw status || ${SUDO} ufw status | grep -q inactive; then + USING_UFW=0 + else + USING_UFW=1 + fi + else + USING_UFW=0 + fi - if [[ "${PLAT}" == 'Alpine' ]] && ! command -v grepcidr &> /dev/null; then - local down_dir - ## install dependencies - # shellcheck disable=SC2086 - ${SUDO} ${PKG_INSTALL} build-base make curl tar + if [[ "${PKG_MANAGER}" == 'apt-get' ]] && [[ "${USING_UFW}" -eq 0 ]]; then + BASE_DEPS+=(iptables-persistent) + echo iptables-persistent iptables-persistent/autosave_v4 boolean true | + ${SUDO} debconf-set-selections + echo iptables-persistent iptables-persistent/autosave_v6 boolean false | + ${SUDO} debconf-set-selections + fi - if ! down_dir="$(mktemp -d)"; then - echo "::: Failed to create download directory for grepcidr!" - exit 1 - fi + if [[ "${PLAT}" == 'Alpine' ]] && + ! command -v grepcidr &> /dev/null; then + local down_dir + ## install dependencies + # shellcheck disable=SC2086 + ${SUDO} ${PKG_INSTALL} build-base make curl tar - ## download binaries - curl -fLo "${down_dir}/master.tar.gz" https://github.com/pivpn/grepcidr/archive/master.tar.gz - tar -xzC "${down_dir}" -f "${down_dir}/master.tar.gz" + if ! down_dir="$(mktemp -d)"; then + err "::: Failed to create download directory for grepcidr!" + exit 1 + fi - ( - cd "${down_dir}/grepcidr-master" || exit 1 + ## download binaries + curl \ + -f \ + -L \ + -o "${down_dir}/master.tar.gz" \ + https://github.com/pivpn/grepcidr/archive/master.tar.gz + tar -xzC "${down_dir}" -f "${down_dir}/master.tar.gz" - ## personalize binaries - sed -i -E -e 's/^PREFIX\=.*/PREFIX\=\/usr\nCC\=gcc/' Makefile + ( + cd "${down_dir}/grepcidr-master" || exit - ## install - make - ${SUDO} make install + ## personalize binaries + sed -i -E -e 's/^PREFIX\=.*/PREFIX\=\/usr\nCC\=gcc/' Makefile - if ! command -v grepcidr &> /dev/null; then - echo "::: Failed to compile and install grepcidr!" - exit 1 - fi - ) || exit 1 - fi + ## install + make + ${SUDO} make install - echo "USING_UFW=${USING_UFW}" >> ${tempsetupVarsFile} + if ! command -v grepcidr &> /dev/null; then + err "::: Failed to compile and install grepcidr!" + exit + fi + ) || exit 1 + fi + + echo "USING_UFW=${USING_UFW}" >> "${tempsetupVarsFile}" } -installDependentPackages(){ +installDependentPackages() { + # Install packages passed via argument array + # No spinner - conflicts with set -e + local FAILED=0 + local APTLOGFILE + declare -a TO_INSTALL=() + declare -a argArray1=("${!1}") - declare -a TO_INSTALL=() + for i in "${argArray1[@]}"; do + echo -n "::: Checking for ${i}..." - # Install packages passed via argument array - # No spinner - conflicts with set -e + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null | + grep -q "ok installed"; then + echo " already installed!" + else + echo " not installed!" + # Add this package to the list of packages in the argument array that + # need to be installed + TO_INSTALL+=("${i}") + fi + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + if eval "${SUDO} ${CHECK_PKG_INSTALLED} ${i}" &> /dev/null; then + echo " already installed!" + else + echo " not installed!" + # Add this package to the list of packages in the argument array that + # need to be installed + TO_INSTALL+=("${i}") + fi + fi + done - declare -a argArray1=("${!1}") + APTLOGFILE="$(${SUDO} mktemp)" - for i in "${argArray1[@]}"; do - echo -n "::: Checking for $i..." + # shellcheck disable=SC2086 + ${SUDO} ${PKG_INSTALL} "${TO_INSTALL[@]}" - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep -q "ok installed"; then - echo " already installed!" - else - echo " not installed!" - # Add this package to the list of packages in the argument array that need to be installed - TO_INSTALL+=("${i}") - fi - elif [ "${PKG_MANAGER}" == 'apk' ]; then - if eval "${SUDO} ${CHECK_PKG_INSTALLED} ${i}" &> /dev/null; then - echo " already installed!" - else - echo " not installed!" - # Add this package to the list of packages in the argument array that need to be installed - TO_INSTALL+=("${i}") - fi - fi - done + for i in "${TO_INSTALL[@]}"; do + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null | + grep -q "ok installed"; then + echo "::: Package ${i} successfully installed!" + # Add this package to the total list of packages that were actually + # installed by the script + INSTALLED_PACKAGES+=("${i}") + else + echo "::: Failed to install ${i}!" + ((FAILED++)) + fi + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + if eval "${SUDO} ${CHECK_PKG_INSTALLED} ${i}" &> /dev/null; then + echo "::: Package ${i} successfully installed!" + # Add this package to the total list of packages that were actually + # installed by the script + INSTALLED_PACKAGES+=("${i}") + else + echo "::: Failed to install ${i}!" + ((FAILED++)) + fi + fi + done - local APTLOGFILE - APTLOGFILE="$($SUDO mktemp)" - - if [ "${runUnattended}" = 'true' ]; then - # shellcheck disable=SC2086 - $SUDO ${PKG_INSTALL} "${TO_INSTALL[@]}" - else - # shellcheck disable=SC2086 - $SUDO ${PKG_INSTALL} "${TO_INSTALL[@]}" - fi - - local FAILED=0 - - for i in "${TO_INSTALL[@]}"; do - if [ "${PKG_MANAGER}" == 'apt-get' ]; then - if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep -q "ok installed"; then - echo "::: Package $i successfully installed!" - # Add this package to the total list of packages that were actually installed by the script - INSTALLED_PACKAGES+=("${i}") - else - echo "::: Failed to install $i!" - ((FAILED++)) - fi - elif [ "${PKG_MANAGER}" == 'apk' ]; then - if eval "${SUDO} ${CHECK_PKG_INSTALLED} ${i}" &> /dev/null; then - echo "::: Package $i successfully installed!" - # Add this package to the total list of packages that were actually installed by the script - INSTALLED_PACKAGES+=("${i}") - else - echo "::: Failed to install $i!" - ((FAILED++)) - fi - fi - done - - if [ "$FAILED" -gt 0 ]; then - $SUDO cat "${APTLOGFILE}" - exit 1 - fi + if [[ "${FAILED}" -gt 0 ]]; then + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]:" >&2 + ${SUDO} cat "${APTLOGFILE}" >&2 + exit 1 + fi } -welcomeDialogs(){ - if [ "${runUnattended}" = 'true' ]; then - echo "::: PiVPN Automated Installer" - echo "::: This installer will transform your ${PLAT} host into an OpenVPN or WireGuard server!" - echo "::: Initiating network interface" - return - fi +welcomeDialogs() { + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: PiVPN Automated Installer" + echo -n "::: This installer will transform your ${PLAT} host into an " + echo "OpenVPN or WireGuard server!" + echo "::: Initiating network interface" + return + fi - # Display the welcome dialog - whiptail --msgbox --backtitle "Welcome" --title "PiVPN Automated Installer" "This installer will transform your Raspberry Pi into an OpenVPN or WireGuard server!" ${r} ${c} + # Display the welcome dialog + whiptail \ + --backtitle "Welcome" \ + --title "PiVPN Automated Installer" \ + --msgbox "This installer will transform your Raspberry Pi into an \ +OpenVPN or WireGuard server!" "${r}" "${c}" - # Explain the need for a static address - whiptail --msgbox --backtitle "Initiating network interface" --title "Static IP Needed" "The PiVPN is a SERVER so it needs a STATIC IP ADDRESS to function properly. + # Explain the need for a static address + whiptail \ + --backtitle "Initiating network interface" \ + --title "Static IP Needed" \ + --msgbox "The PiVPN is a SERVER so it needs a STATIC IP ADDRESS to \ +function properly. -In the next section, you can choose to use your current network settings (DHCP) or to manually edit them." ${r} ${c} +In the next section, you can choose to use your current network settings \ +(DHCP) or to manually edit them." "${r}" "${c}" } -chooseInterface(){ -# Find interfaces and let the user choose one +chooseInterface() { + # Find interfaces and let the user choose one -# Turn the available interfaces into an array so it can be used with a whiptail dialog -local interfacesArray=() -# Number of available interfaces -local interfaceCount -# Whiptail variable storage -local chooseInterfaceCmd -# Temporary Whiptail options storage -local chooseInterfaceOptions -# Loop sentinel variable -local firstloop=1 + # Turn the available interfaces into an array so it can be used with + # a whiptail dialog + local interfacesArray=() + # Number of available interfaces + local interfaceCount + # Whiptail variable storage + local chooseInterfaceCmd + # Temporary Whiptail options storage + local chooseInterfaceOptions + # Loop sentinel variable + local firstloop=1 -if [[ "${showUnsupportedNICs}" == true ]]; then - # Show every network interface, could be useful for those who install PiVPN inside virtual machines - # or on Raspberry Pis with USB adapters (the loopback and docker interfaces are still skipped) - availableInterfaces=$(ip -o link | awk '{print $2}' | cut -d':' -f1 | cut -d'@' -f1 | grep -v -w 'lo' | grep -v '^docker') -else - # Find network interfaces whose state is UP, so as to skip virtual, loopback and docker interfaces. - availableInterfaces=$(ip -o link | awk '/state UP/ {print $2}' | cut -d':' -f1 | cut -d'@' -f1 | grep -v -w 'lo' | grep -v '^docker') -fi + availableInterfaces="$(ip -o link)" -if [ -z "$availableInterfaces" ]; then - echo "::: Could not find any active network interface, exiting" - exit 1 -else - while read -r line; do - mode="OFF" - if [[ ${firstloop} -eq 1 ]]; then - firstloop=0 - mode="ON" - fi - interfacesArray+=("${line}" "available" "${mode}") - ((interfaceCount++)) - done <<< "${availableInterfaces}" -fi + if [[ "${showUnsupportedNICs}" == 'true' ]]; then + # Show every network interface, could be useful for those who + # install PiVPN inside virtual machines or on Raspberry Pis + # with USB adapters + availableInterfaces="$(echo "${availableInterfaces}" | + awk '{print $2}')" + else + # Find network interfaces whose state is UP + availableInterfaces="$(echo "${availableInterfaces}" | + awk '/state UP/ {print $2}')" + fi -if [ "${runUnattended}" = 'true' ]; then - if [ -z "$IPv4dev" ]; then - if [ "$interfaceCount" -eq 1 ]; then - IPv4dev="${availableInterfaces}" - echo "::: No interface specified for IPv4, but only ${IPv4dev} is available, using it" - else - echo "::: No interface specified for IPv4 and failed to determine one" - exit 1 - fi - else - if ip -o link | grep -qw "${IPv4dev}"; then - echo "::: Using interface: ${IPv4dev} for IPv4" - else - echo "::: Interface ${IPv4dev} for IPv4 does not exist" - exit 1 - fi - fi - echo "IPv4dev=${IPv4dev}" >> ${tempsetupVarsFile} - if [ "$pivpnenableipv6" == "1" ]; then - if [ -z "$IPv6dev" ]; then - if [ "$interfaceCount" -eq 1 ]; then - IPv6dev="${availableInterfaces}" - echo "::: No interface specified for IPv6, but only ${IPv6dev} is available, using it" - else - echo "::: No interface specified for IPv6 and failed to determine one" - exit 1 - fi - else - if ip -o link | grep -qw "${IPv6dev}"; then - echo "::: Using interface: ${IPv6dev} for IPv6" - else - echo "::: Interface ${IPv6dev} for IPv6 does not exist" - exit 1 - fi - fi - fi - if [ "$pivpnenableipv6" == "1" ] && [ -z "$IPv6dev" ]; then - echo "IPv6dev=${IPv6dev}" >> ${tempsetupVarsFile} - fi - return -else - if [ "$interfaceCount" -eq 1 ]; then - IPv4dev="${availableInterfaces}" - echo "IPv4dev=${IPv4dev}" >> ${tempsetupVarsFile} - if [ "$pivpnenableipv6" == "1" ]; then - IPv6dev="${availableInterfaces}" - echo "IPv6dev=${IPv6dev}" >> ${tempsetupVarsFile} - fi - return - fi -fi + # Skip virtual, loopback and docker interfaces + availableInterfaces="$(echo "${availableInterfaces}" | + cut -d ':' -f 1 | + cut -d '@' -f 1 | + grep -v -w 'lo' | + grep -v '^docker')" -chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An interface for IPv4 (press space to select):" "${r}" "${c}" "${interfaceCount}") -if chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) ; then - for desiredInterface in ${chooseInterfaceOptions}; do - IPv4dev=${desiredInterface} - echo "::: Using interface: $IPv4dev" - echo "IPv4dev=${IPv4dev}" >> ${tempsetupVarsFile} - done -else - echo "::: Cancel selected, exiting...." - exit 1 -fi -if [ "$pivpnenableipv6" == "1" ]; then - chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An interface for IPv6, usually the same as used by IPv4 (press space to select):" "${r}" "${c}" "${interfaceCount}") - if chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) ; then - for desiredInterface in ${chooseInterfaceOptions}; do - IPv6dev=${desiredInterface} - echo "::: Using interface: $IPv6dev" - echo "IPv6dev=${IPv6dev}" >> ${tempsetupVarsFile} - done - else - echo "::: Cancel selected, exiting...." - exit 1 - fi -fi + if [[ -z "${availableInterfaces}" ]]; then + err "::: Could not find any active network interface, exiting" + exit 1 + else + while read -r line; do + mode="OFF" + + if [[ "${firstloop}" -eq 1 ]]; then + firstloop=0 + mode="ON" + fi + + interfacesArray+=("${line}" "available" "${mode}") + ((interfaceCount++)) + done <<< "${availableInterfaces}" + fi + + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${IPv4dev}" ]]; then + if [[ "${interfaceCount}" -eq 1 ]]; then + IPv4dev="${availableInterfaces}" + echo -n "::: No interface specified for IPv4, but only ${IPv4dev} " + echo "is available, using it" + else + err "::: No interface specified for IPv4 and failed to determine one" + exit 1 + fi + else + if ip -o link | grep -qw "${IPv4dev}"; then + echo "::: Using interface: ${IPv4dev} for IPv4" + else + err "::: Interface ${IPv4dev} for IPv4 does not exist" + exit 1 + fi + fi + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + if [[ -z "${IPv6dev}" ]]; then + if [[ "${interfaceCount}" -eq 1 ]]; then + IPv6dev="${availableInterfaces}" + echo -n "::: No interface specified for IPv6, but only ${IPv6dev} " + echo "is available, using it" + else + err "::: No interface specified for IPv6 and failed to determine one" + exit 1 + fi + else + if ip -o link | grep -qw "${IPv6dev}"; then + echo "::: Using interface: ${IPv6dev} for IPv6" + else + err "::: Interface ${IPv6dev} for IPv6 does not exist" + exit 1 + fi + fi + fi + + { + echo "IPv4dev=${IPv4dev}" + + if [[ "${pivpnenableipv6}" -eq 1 ]] && + [[ -z "${IPv6dev}" ]]; then + echo "IPv6dev=${IPv6dev}" + fi + } >> "${tempsetupVarsFile}" + + return + else + if [[ "${interfaceCount}" -eq 1 ]]; then + IPv4dev="${availableInterfaces}" + + { + echo "IPv4dev=${IPv4dev}" + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + IPv6dev="${availableInterfaces}" + echo "IPv6dev=${IPv6dev}" + fi + } >> "${tempsetupVarsFile}" + + return + fi + fi + + chooseInterfaceCmd=(whiptail + --separate-output + --radiolist "Choose An interface for IPv4 \ +(press space to select):" "${r}" "${c}" "${interfaceCount}") + + if chooseInterfaceOptions="$("${chooseInterfaceCmd[@]}" \ + "${interfacesArray[@]}" \ + 2>&1 > /dev/tty)"; then + for desiredInterface in ${chooseInterfaceOptions}; do + IPv4dev="${desiredInterface}" + echo "::: Using interface: ${IPv4dev}" + echo "IPv4dev=${IPv4dev}" >> "${tempsetupVarsFile}" + done + else + err "::: Cancel selected, exiting...." + exit 1 + fi + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + chooseInterfaceCmd=(whiptail + --separate-output + --radiolist "Choose An interface for IPv6, usually the same as used by \ +IPv4 (press space to select):" "${r}" "${c}" "${interfaceCount}") + + if chooseInterfaceOptions="$("${chooseInterfaceCmd[@]}" \ + "${interfacesArray[@]}" \ + 2>&1 > /dev/tty)"; then + for desiredInterface in ${chooseInterfaceOptions}; do + IPv6dev="${desiredInterface}" + echo "::: Using interface: ${IPv6dev}" + echo "IPv6dev=${IPv6dev}" >> "${tempsetupVarsFile}" + done + else + err "::: Cancel selected, exiting...." + exit 1 + fi + fi } -checkStaticIpSupported(){ - # Not really robust and correct, we should actually check for dhcpcd, not the distro, but works on Raspbian and Debian. - if [ "${PLAT}" = "Raspbian" ]; then - return 0 - # If we are on 'Debian' but the raspi.list file is present, then we actually are on 64-bit Raspberry Pi OS. - elif [ "${PLAT}" = "Debian" ] && [ -s /etc/apt/sources.list.d/raspi.list ]; then - return 0 - else - return 1 - fi +checkStaticIpSupported() { + # Not really robust and correct, we should actually check for dhcpcd, + # not the distro, but works on Raspbian and Debian. + if [[ "${PLAT}" == "Raspbian" ]]; then + return 0 + # If we are on 'Debian' but the raspi.list file is present, + # then we actually are on 64-bit Raspberry Pi OS. + elif [[ "${PLAT}" == "Debian" ]] && + [[ -s /etc/apt/sources.list.d/raspi.list ]]; then + return 0 + else + return 1 + fi } staticIpNotSupported() { - if [ "${runUnattended}" = 'true' ]; then - echo "::: Since we think you are not using Raspberry Pi OS, we will not configure a static IP for you." - return - fi + if [[ "${runUnattended}" == 'true' ]]; then + echo -n "::: Since we think you are not using Raspberry Pi OS, " + echo "we will not configure a static IP for you." + return + fi - # If we are in Ubuntu then they need to have previously set their network, so just use what you have. - whiptail --msgbox --backtitle "IP Information" --title "IP Information" "Since we think you are not using Raspberry Pi OS, we will not configure a static IP for you. -If you are in Amazon then you can not configure a static IP anyway. Just ensure before this installer started you had set an elastic IP on your instance." ${r} ${c} + # If we are in Ubuntu then they need to have previously set their network, + # so just use what you have. + whiptail \ + --backtitle "IP Information" \ + --title "IP Information" \ + --msgbox "Since we think you are not using Raspberry Pi OS, we will not \ +configure a static IP for you. +If you are in Amazon then you can not configure a static IP anyway. Just \ +ensure before this installer started you had set an elastic IP on your \ +instance." "${r}" "${c}" } -validIP(){ - local ip=$1 - local stat=1 +validIP() { + local ip="${1}" + local stat=1 - if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - OIFS=$IFS - IFS='.' - read -r -a ip <<< "$ip" - IFS=$OIFS - [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ - && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] - stat=$? - fi - return $stat + if [[ "${ip}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + OIFS="${IFS}" + IFS='.' + read -r -a ip <<< "${ip}" + IFS="${OIFS}" + + [[ "${ip[0]}" -le 255 && + "${ip[1]}" -le 255 && + "${ip[2]}" -le 255 && + "${ip[3]}" -le 255 ]] + + stat="$?" + fi + + return "${stat}" } -validIPAndNetmask(){ - local ip - ip=$1 - local stat=1 - ip="${ip/\//.}" +validIPAndNetmask() { + # shellcheck disable=SC2178 + local ip="${1}" + local stat=1 - if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,2}$ ]]; then - OIFS=$IFS - IFS='.' - read -r -a ip <<< "$ip" - IFS=$OIFS - [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ - && ${ip[2]} -le 255 && ${ip[3]} -le 255 \ - && ${ip[4]} -le 32 ]] - stat=$? - fi - return $stat + # shellcheck disable=SC2178 + ip="${ip/\//.}" + + # shellcheck disable=SC2128 + if [[ "${ip}" =~ ^([0-9]{1,3}\.){4}[0-9]{1,2}$ ]]; then + OIFS="${IFS}" + IFS='.' + # shellcheck disable=SC2128 + read -r -a ip <<< "${ip}" + IFS="${OIFS}" + + [[ "${ip[0]}" -le 255 && + "${ip[1]}" -le 255 && + "${ip[2]}" -le 255 && + "${ip[3]}" -le 255 && + "${ip[4]}" -le 32 ]] + + stat="$?" + fi + + return "${stat}" } -checkipv6uplink(){ - curl --max-time 3 --connect-timeout 3 --silent --fail -6 https://google.com > /dev/null - curlv6testres=$? - if [ "$curlv6testres" != "0" ]; then - echo "::: IPv6 test connections to google.com have failed. Disabling IPv6 support. (The curl test failed with code: $curlv6testres)" - pivpnenableipv6="0" - else - echo "::: IPv6 test connections to google.com successful. Enabling IPv6 support." - pivpnenableipv6="1" - fi - return +checkipv6uplink() { + curl \ + --max-time 3 \ + --connect-timeout 3 \ + --silent \ + --fail \ + -6 \ + https://google.com \ + > /dev/null + curlv6testres="$?" + + if [[ "${curlv6testres}" -ne 0 ]]; then + echo -n "::: IPv6 test connections to google.com have failed. " + echo -n "Disabling IPv6 support. " + echo "(The curl test failed with code: ${curlv6testres})" + pivpnenableipv6=0 + else + echo -n "::: IPv6 test connections to google.com successful. " + echo "Enabling IPv6 support." + pivpnenableipv6=1 + fi } -askforcedipv6route(){ - if [ "${runUnattended}" = 'true' ]; then - echo "::: Enable forced IPv6 route with no IPv6 uplink on server." - echo "pivpnforceipv6route=${pivpnforceipv6route}" >> ${tempsetupVarsFile} - return - fi +askforcedipv6route() { + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: Enable forced IPv6 route with no IPv6 uplink on server." + echo "pivpnforceipv6route=${pivpnforceipv6route}" >> "${tempsetupVarsFile}" + return + fi - if (whiptail --backtitle "Privacy setting" --title "IPv6 leak" --yesno "Although this server doesn't seem to have a working IPv6 connection or IPv6 was disabled on purpose, it is still recommended you force all IPv6 connections through the VPN.\\n\\nThis will prevent the client from bypassing the tunnel and leaking its real IPv6 address to servers, though it might cause the client to have slow response when browsing the web on IPv6 networks.\\n\\nDo you want to force routing IPv6 to block the leakage?" ${r} ${c}); then - pivpnforceipv6route="1" - else - pivpnforceipv6route="0" - fi + if whiptail \ + --backtitle "Privacy setting" \ + --title "IPv6 leak" \ + --yesno "Although this server doesn't seem to have a working IPv6 \ +connection or IPv6 was disabled on purpose, it is still recommended you \ +force all IPv6 connections through the VPN.\\n\\nThis will prevent the \ +client from bypassing the tunnel and leaking its real IPv6 address to servers, \ +though it might cause the client to have slow response when browsing the web \ +on IPv6 networks. + +Do you want to force routing IPv6 to block the leakage?" "${r}" "${c}"; then + pivpnforceipv6route=1 + else + pivpnforceipv6route=0 + fi - echo "pivpnforceipv6route=${pivpnforceipv6route}" >> ${tempsetupVarsFile} + echo "pivpnforceipv6route=${pivpnforceipv6route}" >> "${tempsetupVarsFile}" } getStaticIPv4Settings() { - # Find the gateway IP used to route to outside world - CurrentIPv4gw="$(ip -o route get 192.0.2.1 | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | awk 'NR==2')" + # Find the gateway IP used to route to outside world + CurrentIPv4gw="$(ip -o route get 192.0.2.1 | + grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | + awk 'NR==2')" - # Find the IP address (and netmask) of the desidered interface - CurrentIPv4addr="$(ip -o -f inet address show dev "${IPv4dev}" | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" + # Find the IP address (and netmask) of the desidered interface + CurrentIPv4addr="$(ip -o -f inet address show dev "${IPv4dev}" | + grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" - # Grab their current DNS servers - IPv4dns=$(grep -v "^#" /etc/resolv.conf | grep -w nameserver | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | xargs) + # Grab their current DNS servers + IPv4dns="$(grep -v "^#" /etc/resolv.conf | + grep -w nameserver | + grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | + xargs)" - if [ "${runUnattended}" = 'true' ]; then + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${dhcpReserv}" ]] || + [[ "${dhcpReserv}" -ne 1 ]]; then + local MISSING_STATIC_IPV4_SETTINGS=0 - if [ -z "$dhcpReserv" ] || [ "$dhcpReserv" -ne 1 ]; then - local MISSING_STATIC_IPV4_SETTINGS=0 + if [[ -z "${IPv4addr}" ]]; then + echo "::: Missing static IP address" + ((MISSING_STATIC_IPV4_SETTINGS++)) + fi - if [ -z "$IPv4addr" ]; then - echo "::: Missing static IP address" - ((MISSING_STATIC_IPV4_SETTINGS++)) - fi + if [[ -z "${IPv4gw}" ]]; then + echo "::: Missing static IP gateway" + ((MISSING_STATIC_IPV4_SETTINGS++)) + fi - if [ -z "$IPv4gw" ]; then - echo "::: Missing static IP gateway" - ((MISSING_STATIC_IPV4_SETTINGS++)) - fi + if [[ "${MISSING_STATIC_IPV4_SETTINGS}" -eq 0 ]]; then + # If both settings are not empty, check if they are valid and proceed + if validIPAndNetmask "${IPv4addr}"; then + echo "::: Your static IPv4 address: ${IPv4addr}" + else + err "::: ${IPv4addr} is not a valid IP address" + exit 1 + fi - if [ "$MISSING_STATIC_IPV4_SETTINGS" -eq 0 ]; then + if validIP "${IPv4gw}"; then + echo "::: Your static IPv4 gateway: ${IPv4gw}" + else + err "::: ${IPv4gw} is not a valid IP address" + exit 1 + fi + elif [[ "${MISSING_STATIC_IPV4_SETTINGS}" -eq 1 ]]; then + # If either of the settings is missing, consider the input inconsistent + err "::: Incomplete static IP settings" + exit 1 + elif [[ "${MISSING_STATIC_IPV4_SETTINGS}" -eq 2 ]]; then + # If both of the settings are missing, + # assume the user wants to use current settings + IPv4addr="${CurrentIPv4addr}" + IPv4gw="${CurrentIPv4gw}" + echo "::: No static IP settings, using current settings" + echo "::: Your static IPv4 address: ${IPv4addr}" + echo "::: Your static IPv4 gateway: ${IPv4gw}" + fi + else + echo "::: Skipping setting static IP address" + fi - # If both settings are not empty, check if they are valid and proceed - if validIPAndNetmask "${IPv4addr}"; then - echo "::: Your static IPv4 address: ${IPv4addr}" - else - echo "::: ${IPv4addr} is not a valid IP address" - exit 1 - fi + { + echo "dhcpReserv=${dhcpReserv}" + echo "IPv4addr=${IPv4addr}" + echo "IPv4gw=${IPv4gw}" + } >> "${tempsetupVarsFile}" + return + fi - if validIP "${IPv4gw}"; then - echo "::: Your static IPv4 gateway: ${IPv4gw}" - else - echo "::: ${IPv4gw} is not a valid IP address" - exit 1 - fi + local ipSettingsCorrect + local IPv4AddrValid + local IPv4gwValid + # Some users reserve IP addresses on another DHCP Server or on their routers, + # Lets ask them if they want to make any changes to their interfaces. - elif [ "$MISSING_STATIC_IPV4_SETTINGS" -eq 1 ]; then - - # If either of the settings is missing, consider the input inconsistent - echo "::: Incomplete static IP settings" - exit 1 - - elif [ "$MISSING_STATIC_IPV4_SETTINGS" -eq 2 ]; then - - # If both of the settings are missing, assume the user wants to use current settings - IPv4addr="${CurrentIPv4addr}" - IPv4gw="${CurrentIPv4gw}" - echo "::: No static IP settings, using current settings" - echo "::: Your static IPv4 address: ${IPv4addr}" - echo "::: Your static IPv4 gateway: ${IPv4gw}" - - fi - else - echo "::: Skipping setting static IP address" - fi - - { - echo "dhcpReserv=${dhcpReserv}" - echo "IPv4addr=${IPv4addr}" - echo "IPv4gw=${IPv4gw}" - } >> ${tempsetupVarsFile} - return - fi - - local ipSettingsCorrect - local IPv4AddrValid - local IPv4gwValid - # Some users reserve IP addresses on another DHCP Server or on their routers, - # Lets ask them if they want to make any changes to their interfaces. - - if (whiptail --backtitle "Calibrating network interface" --title "DHCP Reservation" --yesno --defaultno \ - "Are you Using DHCP Reservation on your Router/DHCP Server? + if whiptail \ + --backtitle "Calibrating network interface" \ + --title "DHCP Reservation" \ + --defaultno \ + --yesno "Are you Using DHCP Reservation on your Router/DHCP Server? These are your current Network Settings: IP address: ${CurrentIPv4addr} @@ -996,1828 +1237,2539 @@ These are your current Network Settings: Yes: Keep using DHCP reservation No: Setup static IP address -Don't know what DHCP Reservation is? Answer No." ${r} ${c}); then - dhcpReserv=1 - # shellcheck disable=SC2129 - echo "dhcpReserv=${dhcpReserv}" >> ${tempsetupVarsFile} - # We don't really need to save them as we won't set a static IP but they might be useful for debugging - echo "IPv4addr=${CurrentIPv4addr}" >> ${tempsetupVarsFile} - echo "IPv4gw=${CurrentIPv4gw}" >> ${tempsetupVarsFile} - else - # Ask if the user wants to use DHCP settings as their static IP - if (whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address? +Don't know what DHCP Reservation is? Answer No." "${r}" "${c}"; then + dhcpReserv=1 + + { + echo "dhcpReserv=${dhcpReserv}" + # We don't really need to save them as we won't set a static IP + # but they might be useful for debugging + echo "IPv4addr=${CurrentIPv4addr}" + echo "IPv4gw=${CurrentIPv4gw}" + } >> "${tempsetupVarsFile}" + else + # Ask if the user wants to use DHCP settings as their static IP + if whiptail \ + --backtitle "Calibrating network interface" \ + --title "Static IP Address" \ + --yesno "Do you want to use your current network settings as a static \ +address? IP address: ${CurrentIPv4addr} - Gateway: ${CurrentIPv4gw}" ${r} ${c}); then - IPv4addr=${CurrentIPv4addr} - IPv4gw=${CurrentIPv4gw} - echo "IPv4addr=${IPv4addr}" >> ${tempsetupVarsFile} - echo "IPv4gw=${IPv4gw}" >> ${tempsetupVarsFile} + Gateway: ${CurrentIPv4gw}" "${r}" "${c}"; then + IPv4addr="${CurrentIPv4addr}" + IPv4gw="${CurrentIPv4gw}" - # If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict. - whiptail --msgbox --backtitle "IP information" --title "FYI: IP Conflict" "It is possible your router could still try to assign this IP to a device, which would cause a conflict. But in most cases the router is smart enough to not do that. -If you are worried, either manually set the address, or modify the DHCP reservation pool so it does not include the IP you want. -It is also possible to use a DHCP reservation, but if you are going to do that, you might as well set a static address." ${r} ${c} - # Nothing else to do since the variables are already set above - else - # Otherwise, we need to ask the user to input their desired settings. - # Start by getting the IPv4 address (pre-filling it with info gathered from DHCP) - # Start a loop to let the user enter their information with the chance to go back and edit it if necessary - until [[ ${ipSettingsCorrect} = True ]]; do + { + echo "IPv4addr=${IPv4addr}" + echo "IPv4gw=${IPv4gw}" + } >> "${tempsetupVarsFile}" - until [[ ${IPv4AddrValid} = True ]]; do - # Ask for the IPv4 address - if IPv4addr=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 address" --inputbox "Enter your desired IPv4 address" ${r} ${c} "${CurrentIPv4addr}" 3>&1 1>&2 2>&3) ; then - if validIPAndNetmask "${IPv4addr}"; then - echo "::: Your static IPv4 address: ${IPv4addr}" - IPv4AddrValid=True - else - whiptail --msgbox --backtitle "Calibrating network interface" --title "IPv4 address" "You've entered an invalid IP address: ${IPv4addr}\\n\\nPlease enter an IP address in the CIDR notation, example: 192.168.23.211/24\\n\\nIf you are not sure, please just keep the default." ${r} ${c} - echo "::: Invalid IPv4 address: ${IPv4addr}" - IPv4AddrValid=False - fi - else - # Cancelling IPv4 settings window - echo "::: Cancel selected. Exiting..." - exit 1 - fi - done + # If they choose yes, let the user know that the IP address will not + # be available via DHCP and may cause a conflict. + whiptail \ + --backtitle "IP information" \ + --title "FYI: IP Conflict" \ + --msgbox "It is possible your router could still try to assign this \ +IP to a device, which would cause a conflict. But in most cases the router is \ +smart enough to not do that. +If you are worried, either manually set the address, or modify the DHCP \ +reservation pool so it does not include the IP you want. +It is also possible to use a DHCP reservation, but if you are going to do \ +that, you mightas well set a static address." "${r}" "${c}" + # Nothing else to do since the variables are already set above + else + # Otherwise, we need to ask the user to input their desired settings. + # Start by getting the IPv4 address + # (pre-filling it with info gathered from DHCP) + # Start a loop to let the user enter their information with the chance + # to go back and edit it if necessary + until [[ "${ipSettingsCorrect}" == 'true' ]]; do + until [[ "${IPv4AddrValid}" == 'true' ]]; do + # Ask for the IPv4 address + if IPv4addr="$(whiptail \ + --backtitle "Calibrating network interface" \ + --title "IPv4 address" \ + --inputbox "Enter your desired \ +IPv4 address" "${r}" "${c}" "${CurrentIPv4addr}" \ + 3>&1 1>&2 2>&3)"; then + if validIPAndNetmask "${IPv4addr}"; then + echo "::: Your static IPv4 address: ${IPv4addr}" + IPv4AddrValid=true + else + whiptail \ + --backtitle "Calibrating network interface" \ + --title "IPv4 address" \ + --msgbox "You've entered an invalid IP address: ${IPv4addr} + +Please enter an IP address in the CIDR notation, example: 192.168.23.211/24 - until [[ ${IPv4gwValid} = True ]]; do - # Ask for the gateway - if IPv4gw=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 gateway (router)" --inputbox "Enter your desired IPv4 default gateway" ${r} ${c} "${CurrentIPv4gw}" 3>&1 1>&2 2>&3) ; then - if validIP "${IPv4gw}"; then - echo "::: Your static IPv4 gateway: ${IPv4gw}" - IPv4gwValid=True - else - whiptail --msgbox --backtitle "Calibrating network interface" --title "IPv4 gateway (router)" "You've entered an invalid gateway IP: ${IPv4gw}\\n\\nPlease enter the IP address of your gateway (router), example: 192.168.23.1\\n\\nIf you are not sure, please just keep the default." ${r} ${c} - echo "::: Invalid IPv4 gateway: ${IPv4gw}" - IPv4gwValid=False - fi - else - # Cancelling gateway settings window - echo "::: Cancel selected. Exiting..." - exit 1 - fi - done +If you are not sure, please just keep the default." "${r}" "${c}" + echo "::: Invalid IPv4 address: ${IPv4addr}" + IPv4AddrValid=false + fi + else + # Cancelling IPv4 settings window + err "::: Cancel selected. Exiting..." + exit 1 + fi + done - # Give the user a chance to review their settings before moving on - if (whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct? + until [[ "${IPv4gwValid}" == 'true' ]]; do + # Ask for the gateway + if IPv4gw="$(whiptail \ + --backtitle "Calibrating network interface" \ + --title "IPv4 gateway (router)" \ + --inputbox "Enter your desired IPv4 \ +default gateway" "${r}" "${c}" "${CurrentIPv4gw}" \ + 3>&1 1>&2 2>&3)"; then + if validIP "${IPv4gw}"; then + echo "::: Your static IPv4 gateway: ${IPv4gw}" + IPv4gwValid=true + else + whiptail \ + --backtitle "Calibrating network interface" \ + --title "IPv4 gateway (router)" \ + --msgbox "You've entered an invalid gateway IP: ${IPv4gw} + +Please enter the IP address of your gateway (router), example: 192.168.23.1 + +If you are not sure, please just keep the default." "${r}" "${c}" + echo "::: Invalid IPv4 gateway: ${IPv4gw}" + IPv4gwValid=false + fi + else + # Cancelling gateway settings window + err "::: Cancel selected. Exiting..." + exit 1 + fi + done + + # Give the user a chance to review their settings before moving on + if whiptail \ + --backtitle "Calibrating network interface" \ + --title "Static IP Address" \ + --yesno "Are these settings correct? IP address: ${IPv4addr} - Gateway: ${IPv4gw}" ${r} ${c}); then - # If the settings are correct, then we need to set the pivpnIP - echo "IPv4addr=${IPv4addr}" >> ${tempsetupVarsFile} - echo "IPv4gw=${IPv4gw}" >> ${tempsetupVarsFile} - # After that's done, the loop ends and we move on - ipSettingsCorrect=True - else - # If the settings are wrong, the loop continues - ipSettingsCorrect=False - IPv4AddrValid=False - IPv4gwValid=False - fi - done - # End the if statement for DHCP vs. static - fi - # End of If Statement for DCHCP Reservation - fi + Gateway: ${IPv4gw}" "${r}" "${c}"; then + # If the settings are correct, then we need to set the pivpnIP + echo "IPv4addr=${IPv4addr}" >> "${tempsetupVarsFile}" + echo "IPv4gw=${IPv4gw}" >> "${tempsetupVarsFile}" + # After that's done, the loop ends and we move on + ipSettingsCorrect=true + else + # If the settings are wrong, the loop continues + ipSettingsCorrect=false + IPv4AddrValid=false + IPv4gwValid=false + fi + done + # End the if statement for DHCP vs. static + fi + # End of If Statement for DCHCP Reservation + fi } -setDHCPCD(){ - # Append these lines to dhcpcd.conf to enable a static IP - echo "interface ${IPv4dev} - static ip_address=${IPv4addr} - static routers=${IPv4gw} - static domain_name_servers=${IPv4dns}" | $SUDO tee -a ${dhcpcdFile} >/dev/null +setDHCPCD() { + # Append these lines to dhcpcd.conf to enable a static IP + { + echo "interface ${IPv4dev}" + echo "static ip_address=${IPv4addr}" + echo "static routers=${IPv4gw}" + echo "static domain_name_servers=${IPv4dns}" + } | ${SUDO} tee -a "${dhcpcdFile}" > /dev/null } -setStaticIPv4(){ - # Tries to set the IPv4 address - if [[ -f /etc/dhcpcd.conf ]]; then - if grep -q "${IPv4addr}" ${dhcpcdFile}; then - echo "::: Static IP already configured." - else - setDHCPCD - $SUDO ip addr replace dev "${IPv4dev}" "${IPv4addr}" - echo ":::" - echo "::: Setting IP to ${IPv4addr}. You may need to restart after the install is complete." - echo ":::" - fi - else - echo "::: Critical: Unable to locate configuration file to set static IPv4 address!" - exit 1 - fi +setStaticIPv4() { + # Tries to set the IPv4 address + if [[ -f /etc/dhcpcd.conf ]]; then + if grep -q "${IPv4addr}" "${dhcpcdFile}"; then + echo "::: Static IP already configured." + else + setDHCPCD + ${SUDO} ip addr replace dev "${IPv4dev}" "${IPv4addr}" + echo ":::" + echo -n "::: Setting IP to ${IPv4addr}. " + echo "You may need to restart after the install is complete." + echo ":::" + fi + else + err "::: Critical: Unable to locate configuration file to set static IPv4 address!" + exit 1 + fi } -chooseUser(){ - # Choose the user for the ovpns - if [ "${runUnattended}" = 'true' ]; then - if [ -z "$install_user" ]; then - if [ "$(awk -F':' 'BEGIN {count=0} $3>=1000 && $3<=60000 { count++ } END{ print count }' /etc/passwd)" -eq 1 ]; then - install_user="$(awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd)" - echo "::: No user specified, but only ${install_user} is available, using it" - else - echo "::: No user specified" - exit 1 - fi - else - if awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd | grep -qw "${install_user}"; then - echo "::: ${install_user} will hold your ovpn configurations." - else - echo "::: User ${install_user} does not exist, creating..." +chooseUser() { + # Choose the user for the ovpns + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${install_user}" ]]; then + if [[ "$(awk \ + -F':' \ + 'BEGIN {count=0} $3>=1000 && $3<=60000 { count++ } END{ print count }' \ + /etc/passwd)" -eq 1 ]]; then + install_user="$(awk \ + -F':' \ + '$3>=1000 && $3<=60000 {print $1}' \ + /etc/passwd)" + echo -n "::: No user specified, but only ${install_user} is available, " + echo "using it" + else + err "::: No user specified" + exit 1 + fi + else + if awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd | + grep -qw "${install_user}"; then + echo "::: ${install_user} will hold your ovpn configurations." + else + echo "::: User ${install_user} does not exist, creating..." - if [ "${PLAT}" == 'Alpine' ]; then - ${SUDO} adduser -s /bin/bash "${install_user}" - ${SUDO} addgroup "${install_user}" wheel - else - ${SUDO} useradd -ms /bin/bash "${install_user}" - fi + if [[ "${PLAT}" == 'Alpine' ]]; then + ${SUDO} adduser -s /bin/bash "${install_user}" + ${SUDO} addgroup "${install_user}" wheel + else + ${SUDO} useradd -ms /bin/bash "${install_user}" + fi - echo "::: User created without a password, please do sudo passwd $install_user to create one" - fi - fi - install_home=$(grep -m1 "^${install_user}:" /etc/passwd | cut -d: -f6) - install_home=${install_home%/} - echo "install_user=${install_user}" >> ${tempsetupVarsFile} - echo "install_home=${install_home}" >> ${tempsetupVarsFile} - return - fi + echo -n "::: User created without a password, " + echo "please do sudo passwd ${install_user} to create one" + fi + fi - # Explain the local user - whiptail --msgbox --backtitle "Parsing User List" --title "Local Users" "Choose a local user that will hold your ovpn configurations." ${r} ${c} - # First, let's check if there is a user available. - numUsers=$(awk -F':' 'BEGIN {count=0} $3>=1000 && $3<=60000 { count++ } END{ print count }' /etc/passwd) - if [ "$numUsers" -eq 0 ]; then - # We don't have a user, let's ask to add one. - if userToAdd=$(whiptail --title "Choose A User" --inputbox "No non-root user account was found. Please type a new username." ${r} ${c} 3>&1 1>&2 2>&3); then - # See https://askubuntu.com/a/667842/459815 - PASSWORD=$(whiptail --title "password dialog" --passwordbox "Please enter the new user password" ${r} ${c} 3>&1 1>&2 2>&3) - CRYPT=$(perl -e 'printf("%s\n", crypt($ARGV[0], "password"))' "${PASSWORD}") + install_home="$(grep -m1 "^${install_user}:" /etc/passwd | + cut -d ':' -f 6)" + install_home="${install_home%/}" - if [ "${PLAT}" == 'Alpine' ]; then - if ${SUDO} adduser -Ds /bin/bash "${userToAdd}"; then - ${SUDO} addgroup "${userToAdd}" wheel + { + echo "install_user=${install_user}" + echo "install_home=${install_home}" + } >> "${tempsetupVarsFile}" + return + fi - ${SUDO} chpasswd <<< "${userToAdd}:${PASSWORD}" - ${SUDO} passwd -u "${userToAdd}" + # Explain the local user + whiptail \ + --msgbox \ + --backtitle "Parsing User List" \ + --title "Local Users" \ + "Choose a local user that will hold your ovpn configurations." \ + "${r}" \ + "${c}" + # First, let's check if there is a user available. + numUsers="$(awk \ + -F':' \ + 'BEGIN {count=0} $3>=1000 && $3<=60000 { count++ } END{ print count }' \ + /etc/passwd)" - echo "Succeeded" - ((numUsers+=1)) - else - exit 1 - fi - else - if ${SUDO} useradd -mp "${CRYPT}" -s /bin/bash "${userToAdd}"; then - echo "Succeeded" - ((numUsers+=1)) - else - exit 1 - fi - fi - else - exit 1 - fi - fi - availableUsers=$(awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd) - local userArray=() - local firstloop=1 + if [[ "${numUsers}" -eq 0 ]]; then + # We don't have a user, let's ask to add one. + if userToAdd="$(whiptail \ + --title "Choose A User" \ + --inputbox \ + "No non-root user account was found. Please type a new username." \ + "${r}" \ + "${c}" \ + 3>&1 1>&2 2>&3)"; then + # See https://askubuntu.com/a/667842/459815 + PASSWORD="$(whiptail \ + --title "password dialog" \ + --passwordbox \ + "Please enter the new user password" \ + "${r}" \ + "${c}" \ + 3>&1 1>&2 2>&3)" + CRYPT="$(perl \ + -e 'printf("%s\n", crypt($ARGV[0], "password"))' "${PASSWORD}")" - while read -r line; do - mode="OFF" - if [[ $firstloop -eq 1 ]]; then - firstloop=0 - mode="ON" - fi - userArray+=("${line}" "" "${mode}") - done <<< "${availableUsers}" - chooseUserCmd=(whiptail --title "Choose A User" --separate-output --radiolist - "Choose (press space to select):" "${r}" "${c}" "${numUsers}") - if chooseUserOptions=$("${chooseUserCmd[@]}" "${userArray[@]}" 2>&1 >/dev/tty) ; then - for desiredUser in ${chooseUserOptions}; do - install_user=${desiredUser} - echo "::: Using User: $install_user" - install_home=$(grep -m1 "^${install_user}:" /etc/passwd | cut -d: -f6) - install_home=${install_home%/} # remove possible trailing slash - echo "install_user=${install_user}" >> ${tempsetupVarsFile} - echo "install_home=${install_home}" >> ${tempsetupVarsFile} - done - else - echo "::: Cancel selected, exiting...." - exit 1 - fi + if [[ "${PLAT}" == 'Alpine' ]]; then + if ${SUDO} adduser -Ds /bin/bash "${userToAdd}"; then + ${SUDO} addgroup "${userToAdd}" wheel + + ${SUDO} chpasswd <<< "${userToAdd}:${PASSWORD}" + ${SUDO} passwd -u "${userToAdd}" + + echo "Succeeded" + ((numUsers += 1)) + else + exit 1 + fi + else + if ${SUDO} useradd -mp "${CRYPT}" -s /bin/bash "${userToAdd}"; then + echo "Succeeded" + ((numUsers += 1)) + else + exit 1 + fi + fi + else + exit 1 + fi + fi + + availableUsers="$(awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd)" + local userArray=() + local firstloop=1 + + while read -r line; do + mode="OFF" + + if [[ "${firstloop}" -eq 1 ]]; then + firstloop=0 + mode="ON" + fi + + userArray+=("${line}" "" "${mode}") + done <<< "${availableUsers}" + + chooseUserCmd=(whiptail + --title "Choose A User" + --separate-output + --radiolist + "Choose (press space to select):" + "${r}" + "${c}" + "${numUsers}") + + if chooseUserOptions=$("${chooseUserCmd[@]}" \ + "${userArray[@]}" \ + 2>&1 > /dev/tty); then + for desiredUser in ${chooseUserOptions}; do + install_user=${desiredUser} + echo "::: Using User: ${install_user}" + install_home=$(grep -m1 "^${install_user}:" /etc/passwd | + cut -d ':' -f 6) + install_home=${install_home%/} # remove possible trailing slash + + { + echo "install_user=${install_user}" + echo "install_home=${install_home}" + } >> "${tempsetupVarsFile}" + done + else + err "::: Cancel selected, exiting...." + exit 1 + fi } -isRepo(){ - # If the directory does not have a .git folder it is not a repo - echo -n "::: Checking $1 is a repo..." - cd "${1}" &> /dev/null || { echo " not found!"; return 1; } - $SUDO git status &> /dev/null && echo " OK!"; return 0 || echo " not found!"; return 1 +isRepo() { + # If the directory does not have a .git folder it is not a repo + echo -n "::: Checking ${1} is a repo..." + cd "${1}" &> /dev/null || { + echo " not found!" + return 1 + } + ${SUDO} git status &> /dev/null && echo " OK!" + return 0 || echo " not found!" + return 1 } -updateRepo(){ - if [ "${UpdateCmd}" = "Repair" ]; then - echo "::: Repairing an existing installation, not downloading/updating local repos" - else - # Pull the latest commits - echo -n "::: Updating repo in ${1} from ${2} ..." - ### FIXME: Never call rm -rf with a plain variable. Never again as SU! - #$SUDO rm -rf "${1}" - if test -n "$1"; then - $SUDO rm -rf "$(dirname "$1")/pivpn" - fi - # Go back to /usr/local/src otherwise git will complain when the current working - # directory has just been deleted (/usr/local/src/pivpn). - cd /usr/local/src && \ - $SUDO git clone -q --depth 1 --no-single-branch "${2}" "${1}" > /dev/null & - spinner $! - cd "${1}" || exit 1 - echo " done!" - if [ -n "${pivpnGitBranch}" ]; then - echo "::: Checkout branch '${pivpnGitBranch}' from ${2} in ${1}..." - ${SUDOE} git checkout -q ${pivpnGitBranch} - echo "::: Custom branch checkout done!" - elif [ -z "${TESTING+x}" ]; then - : - else - echo "::: Checkout branch 'test' from ${2} in ${1}..." - ${SUDOE} git checkout -q test - echo "::: 'test' branch checkout done!" - fi - fi +updateRepo() { + if [[ "${UpdateCmd}" == "Repair" ]]; then + echo -n "::: Repairing an existing installation, " + echo "not downloading/updating local repos" + else + # Pull the latest commits + echo -n "::: Updating repo in ${1} from ${2} ..." + + ### FIXME: Never call rm -rf with a plain variable. Never again as SU! + #${SUDO} rm -rf "${1}" + if [[ -n "${1}" ]]; then + ${SUDO} rm -rf "$(dirname "${1}")/pivpn" + fi + + # Go back to /usr/local/src otherwise git will complain when the current + # working directory has just been deleted (/usr/local/src/pivpn). + cd /usr/local/src && + ${SUDO} git clone \ + -q \ + --depth 1 \ + --no-single-branch \ + "${2}" \ + "${1}" \ + > /dev/null \ + & + spinner $! + cd "${1}" || exit 1 + echo " done!" + + if [[ -n "${pivpnGitBranch}" ]]; then + echo "::: Checkout branch '${pivpnGitBranch}' from ${2} in ${1}..." + ${SUDOE} git checkout -q "${pivpnGitBranch}" + echo "::: Custom branch checkout done!" + elif [[ -z "${TESTING+x}" ]]; then + : + else + echo "::: Checkout branch 'test' from ${2} in ${1}..." + ${SUDOE} git checkout -q test + echo "::: 'test' branch checkout done!" + fi + fi } -makeRepo(){ - # Remove the non-repos interface and clone the interface - echo -n "::: Cloning ${2} into ${1} ..." - ### FIXME: Never call rm -rf with a plain variable. Never again as SU! - #$SUDO rm -rf "${1}" - if test -n "$1"; then - $SUDO rm -rf "$(dirname "$1")/pivpn" - fi - # Go back to /usr/local/src otherwhise git will complain when the current working - # directory has just been deleted (/usr/local/src/pivpn). - cd /usr/local/src && \ - $SUDO git clone -q --depth 1 --no-single-branch "${2}" "${1}" > /dev/null & - spinner $! - cd "${1}" || exit 1 - echo " done!" - if [ -n "${pivpnGitBranch}" ]; then - echo "::: Checkout branch '${pivpnGitBranch}' from ${2} in ${1}..." - ${SUDOE} git checkout -q ${pivpnGitBranch} - echo "::: Custom branch checkout done!" - elif [ -z "${TESTING+x}" ]; then - : - else - echo "::: Checkout branch 'test' from ${2} in ${1}..." - ${SUDOE} git checkout -q test - echo "::: 'test' branch checkout done!" - fi +makeRepo() { + # Remove the non-repos interface and clone the interface + echo -n "::: Cloning ${2} into ${1} ..." + + ### FIXME: Never call rm -rf with a plain variable. Never again as SU! + #${SUDO} rm -rf "${1}" + if [[ -n "${1}" ]]; then + ${SUDO} rm -rf "$(dirname "${1}")/pivpn" + fi + + # Go back to /usr/local/src otherwhise git will complain when the current + # working directory has just been deleted (/usr/local/src/pivpn). + cd /usr/local/src && + ${SUDO} git clone \ + -q \ + --depth 1 \ + --no-single-branch \ + "${2}" \ + "${1}" \ + > /dev/null \ + & + spinner $! + cd "${1}" || exit 1 + echo " done!" + + if [[ -n "${pivpnGitBranch}" ]]; then + echo "::: Checkout branch '${pivpnGitBranch}' from ${2} in ${1}..." + ${SUDOE} git checkout -q "${pivpnGitBranch}" + echo "::: Custom branch checkout done!" + elif [[ -z "${TESTING+x}" ]]; then + : + else + echo "::: Checkout branch 'test' from ${2} in ${1}..." + ${SUDOE} git checkout -q test + echo "::: 'test' branch checkout done!" + fi } -getGitFiles(){ - # Setup git repos for base files - echo ":::" - echo "::: Checking for existing base files..." - if isRepo "${1}"; then - updateRepo "${1}" "${2}" - else - makeRepo "${1}" "${2}" - fi +getGitFiles() { + # Setup git repos for base files + echo ":::" + echo "::: Checking for existing base files..." + + if isRepo "${1}"; then + updateRepo "${1}" "${2}" + else + makeRepo "${1}" "${2}" + fi } -cloneOrUpdateRepos(){ - # Clone/Update the repos - # /usr/local should always exist, not sure about the src subfolder though - $SUDO mkdir -p /usr/local/src +cloneOrUpdateRepos() { + # Clone/Update the repos + # /usr/local should always exist, not sure about the src subfolder though + ${SUDO} mkdir -p /usr/local/src - # Get Git files - getGitFiles ${pivpnFilesDir} ${pivpnGitUrl} || \ - { echo "!!! Unable to clone ${pivpnGitUrl} into ${pivpnFilesDir}, unable to continue."; \ - exit 1; \ -} + # Get Git files + getGitFiles "${pivpnFilesDir}" "${pivpnGitUrl}" || + { + err "!!! Unable to clone ${pivpnGitUrl} into ${pivpnFilesDir}, unable to continue." + exit 1 + } } -installPiVPN(){ - $SUDO mkdir -p /etc/pivpn/ - askWhichVPN - setVPNDefaultVars +installPiVPN() { + ${SUDO} mkdir -p /etc/pivpn/ + askWhichVPN + setVPNDefaultVars - if [ "${VPN}" == 'openvpn' ]; then - setOpenVPNDefaultVars - askAboutCustomizing - installOpenVPN - askCustomProto - elif [ "${VPN}" == 'wireguard' ]; then - setWireguardDefaultVars - installWireGuard - fi + if [[ "${VPN}" == 'openvpn' ]]; then + setOpenVPNDefaultVars + askAboutCustomizing + installOpenVPN + askCustomProto + elif [[ "${VPN}" == 'wireguard' ]]; then + setWireguardDefaultVars + installWireGuard + fi - askCustomPort - askClientDNS + askCustomPort + askClientDNS - if [ "${VPN}" == 'openvpn' ]; then - askCustomDomain - fi + if [[ "${VPN}" == 'openvpn' ]]; then + askCustomDomain + fi - askPublicIPOrDNS + askPublicIPOrDNS - if [ "${VPN}" == 'openvpn' ]; then - askEncryption - confOpenVPN - confOVPN - elif [ "${VPN}" == 'wireguard' ]; then - confWireGuard - fi + if [[ "${VPN}" == 'openvpn' ]]; then + askEncryption + confOpenVPN + confOVPN + elif [[ "${VPN}" == 'wireguard' ]]; then + confWireGuard + fi - confNetwork + confNetwork - if [ "${VPN}" == 'openvpn' ]; then - confLogging - elif [ "${VPN}" == 'wireguard' ]; then - writeWireguardTempVarsFile - fi + if [[ "${VPN}" == 'openvpn' ]]; then + confLogging + elif [[ "${VPN}" == 'wireguard' ]]; then + writeWireguardTempVarsFile + fi - writeVPNTempVarsFile + writeVPNTempVarsFile } -setVPNDefaultVars(){ - # Allow custom subnetClass via unattend setupVARs file. Use default if not provided. - if [ -z "$subnetClass" ]; then - subnetClass="24" - fi - if [ -z "$subnetClassv6" ]; then - subnetClassv6="64" - fi +setVPNDefaultVars() { + # Allow custom subnetClass via unattend setupVARs file. + # Use default if not provided. + if [[ -z "${subnetClass}" ]]; then + subnetClass="24" + fi + + if [[ -z "${subnetClassv6}" ]]; then + subnetClassv6="64" + fi } generateRandomSubnet() { - # Source: https://community.openvpn.net/openvpn/wiki/AvoidRoutingConflicts - declare -a SUBNET_EXCLUDE_LIST + local MATCHES + # Source: https://community.openvpn.net/openvpn/wiki/AvoidRoutingConflicts + declare -a SUBNET_EXCLUDE_LIST - SUBNET_EXCLUDE_LIST=(10.0.0.0/24) - SUBNET_EXCLUDE_LIST+=(10.0.1.0/24) - SUBNET_EXCLUDE_LIST+=(10.1.1.0/24) - SUBNET_EXCLUDE_LIST+=(10.1.10.0/24) - SUBNET_EXCLUDE_LIST+=(10.2.0.0/24) - SUBNET_EXCLUDE_LIST+=(10.8.0.0/24) - SUBNET_EXCLUDE_LIST+=(10.10.1.0/24) - SUBNET_EXCLUDE_LIST+=(10.90.90.0/24) - SUBNET_EXCLUDE_LIST+=(10.100.1.0/24) - SUBNET_EXCLUDE_LIST+=(10.255.255.0/24) + SUBNET_EXCLUDE_LIST=(10.0.0.0/24) + SUBNET_EXCLUDE_LIST+=(10.0.1.0/24) + SUBNET_EXCLUDE_LIST+=(10.1.1.0/24) + SUBNET_EXCLUDE_LIST+=(10.1.10.0/24) + SUBNET_EXCLUDE_LIST+=(10.2.0.0/24) + SUBNET_EXCLUDE_LIST+=(10.8.0.0/24) + SUBNET_EXCLUDE_LIST+=(10.10.1.0/24) + SUBNET_EXCLUDE_LIST+=(10.90.90.0/24) + SUBNET_EXCLUDE_LIST+=(10.100.1.0/24) + SUBNET_EXCLUDE_LIST+=(10.255.255.0/24) - readarray -t CURRENTLY_USED_SUBNETS <<< "$(ip route show | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" - SUBNET_EXCLUDE_LIST=("${SUBNET_EXCLUDE_LIST[@]}" "${CURRENTLY_USED_SUBNETS[@]}") + readarray -t CURRENTLY_USED_SUBNETS <<< "$(ip route show | + grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" + SUBNET_EXCLUDE_LIST=("${SUBNET_EXCLUDE_LIST[@]}" + "${CURRENTLY_USED_SUBNETS[@]}") - local MATCHES - while true; do - MATCHES=0 - pivpnNET="10.$((RANDOM%256)).$((RANDOM%256)).0" + while true; do + MATCHES=0 + pivpnNET="10.$((RANDOM % 256)).$((RANDOM % 256)).0" - for SUB in "${SUBNET_EXCLUDE_LIST[@]}"; do - if grepcidr "${SUB}" <<< "${pivpnNET}/$subnetClass" 2>&1 > /dev/null; then - ((MATCHES++)) - fi - done + for SUB in "${SUBNET_EXCLUDE_LIST[@]}"; do + if grepcidr "${SUB}" <<< "${pivpnNET}/${subnetClass}" \ + 2>&1 > /dev/null; then + ((MATCHES++)) + fi + done - if [ "${MATCHES}" -eq 0 ]; then - break - fi - done + if [[ "${MATCHES}" -eq 0 ]]; then + break + fi + done - echo "${pivpnNET}" + echo "${pivpnNET}" } -setOpenVPNDefaultVars(){ - pivpnDEV="tun0" - # Allow custom NET via unattend setupVARs file. Use default if not provided. - if [ -z "$pivpnNET" ]; then - pivpnNET="$(generateRandomSubnet)" - fi - vpnGw="$(cut -d '.' -f 1-3 <<< "${pivpnNET}").1" +setOpenVPNDefaultVars() { + pivpnDEV="tun0" + + # Allow custom NET via unattend setupVARs file. + # Use default if not provided. + if [[ -z "${pivpnNET}" ]]; then + pivpnNET="$(generateRandomSubnet)" + fi + + vpnGw="$(cut -d '.' -f 1-3 <<< "${pivpnNET}").1" } -setWireguardDefaultVars(){ - # Since WireGuard only uses UDP, askCustomProto() is never called so we - # set the protocol here. - pivpnPROTO="udp" - pivpnDEV="wg0" - # Allow custom NET via unattend setupVARs file. Use default if not provided. - if [ -z "$pivpnNET" ]; then - pivpnNET="$(generateRandomSubnet)" - fi - if [ "$pivpnenableipv6" == "1" ] && [ -z "$pivpnNETv6" ]; then - pivpnNETv6="fd11:5ee:bad:c0de::" - fi - vpnGw="$(cut -d '.' -f 1-3 <<< "${pivpnNET}").1" - if [ "$pivpnenableipv6" == "1" ]; then - vpnGwv6="${pivpnNETv6}1" - fi - # Allow custom allowed IPs via unattend setupVARs file. Use default if not provided. - if [ -z "$ALLOWED_IPS" ]; then - # Forward all traffic through PiVPN (i.e. full-tunnel), may be modified by - # the user after the installation. - if [ "$pivpnenableipv6" == "1" ] || [ "$pivpnforceipv6route" == "1" ]; then - ALLOWED_IPS="0.0.0.0/0, ::0/0" - else - ALLOWED_IPS="0.0.0.0/0" - fi - fi - # The default MTU should be fine for most users but we allow to set a - # custom MTU via unattend setupVARs file. Use default if not provided. - if [ -z "$pivpnMTU" ]; then - # Using default Wireguard MTU - pivpnMTU="1420" - fi +setWireguardDefaultVars() { + # Since WireGuard only uses UDP, askCustomProto() is never + # called so we set the protocol here. + pivpnPROTO="udp" + pivpnDEV="wg0" - CUSTOMIZE=0 + # Allow custom NET via unattend setupVARs file. + # Use default if not provided. + if [[ -z "${pivpnNET}" ]]; then + pivpnNET="$(generateRandomSubnet)" + fi + + if [[ "${pivpnenableipv6}" -eq 1 ]] && + [[ -z "${pivpnNETv6}" ]]; then + pivpnNETv6="fd11:5ee:bad:c0de::" + fi + + vpnGw="$(cut -d '.' -f 1-3 <<< "${pivpnNET}").1" + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + vpnGwv6="${pivpnNETv6}1" + fi + + # Allow custom allowed IPs via unattend setupVARs file. + # Use default if not provided. + if [[ -z "${ALLOWED_IPS}" ]]; then + ALLOWED_IPS="0.0.0.0/0" + + # Forward all traffic through PiVPN (i.e. full-tunnel), may be modified by + # the user after the installation. + if [[ "${pivpnenableipv6}" -eq 1 ]] || + [[ "${pivpnforceipv6route}" -eq 1 ]]; then + ALLOWED_IPS="${ALLOWED_IPS}, ::0/0" + fi + fi + + # The default MTU should be fine for most users but we allow to set a + # custom MTU via unattend setupVARs file. Use default if not provided. + if [[ -z "${pivpnMTU}" ]]; then + # Using default Wireguard MTU + pivpnMTU="1420" + fi + + CUSTOMIZE=0 } -writeVPNTempVarsFile(){ - { - echo "pivpnDEV=${pivpnDEV}" - echo "pivpnNET=${pivpnNET}" - echo "subnetClass=${subnetClass}" - if [ "$pivpnenableipv6" == "1" ]; then - echo "pivpnenableipv6=1" - echo "pivpnNETv6=\"${pivpnNETv6}\"" - echo "subnetClassv6=${subnetClassv6}" - else - echo "pivpnenableipv6=0" - fi - echo "ALLOWED_IPS=\"${ALLOWED_IPS}\"" - } >> ${tempsetupVarsFile} +writeVPNTempVarsFile() { + { + echo "pivpnDEV=${pivpnDEV}" + echo "pivpnNET=${pivpnNET}" + echo "subnetClass=${subnetClass}" + echo "pivpnenableipv6=${pivpnenableipv6}" + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + echo "pivpnNETv6=\"${pivpnNETv6}\"" + echo "subnetClassv6=${subnetClassv6}" + fi + + echo "ALLOWED_IPS=\"${ALLOWED_IPS}\"" + } >> "${tempsetupVarsFile}" } -writeWireguardTempVarsFile(){ - echo "pivpnPROTO=${pivpnPROTO}" >> ${tempsetupVarsFile} - echo "pivpnMTU=${pivpnMTU}" >> ${tempsetupVarsFile} +writeWireguardTempVarsFile() { + { + echo "pivpnPROTO=${pivpnPROTO}" + echo "pivpnMTU=${pivpnMTU}" - # Write PERSISTENTKEEPALIVE if provided via unattended file - # May also be added manually to /etc/pivpn/wireguard/setupVars.conf - # post installation to be used for client profile generation - if [ -n "${pivpnPERSISTENTKEEPALIVE}" ]; then - echo "pivpnPERSISTENTKEEPALIVE=${pivpnPERSISTENTKEEPALIVE}" >> ${tempsetupVarsFile} - fi + # Write PERSISTENTKEEPALIVE if provided via unattended file + # May also be added manually to /etc/pivpn/wireguard/setupVars.conf + # post installation to be used for client profile generation + if [[ -n "${pivpnPERSISTENTKEEPALIVE}" ]]; then + echo "pivpnPERSISTENTKEEPALIVE=${pivpnPERSISTENTKEEPALIVE}" + fi + } >> "${tempsetupVarsFile}" } -askWhichVPN(){ - if [ "${runUnattended}" = 'true' ]; then - if [ "$WIREGUARD_SUPPORT" -eq 1 ]; then - if [ -z "$VPN" ]; then - echo ":: No VPN protocol specified, using WireGuard" - VPN="wireguard" - else - VPN="${VPN,,}" - if [ "$VPN" = "wireguard" ]; then - echo "::: WireGuard will be installed" - elif [ "$VPN" = "openvpn" ]; then - echo "::: OpenVPN will be installed" - else - echo ":: $VPN is not a supported VPN protocol, please specify 'wireguard' or 'openvpn'" - exit 1 - fi - fi - else - if [ -z "$VPN" ]; then - echo ":: No VPN protocol specified, using OpenVPN" - VPN="openvpn" - else - VPN="${VPN,,}" - if [ "$VPN" = "openvpn" ]; then - echo "::: OpenVPN will be installed" - else - echo ":: $VPN is not a supported VPN protocol on $DPKG_ARCH $PLAT, only 'openvpn' is" - exit 1 - fi - fi - fi - else - if [ "$WIREGUARD_SUPPORT" -eq 1 ] && [ "$OPENVPN_SUPPORT" -eq 1 ]; then - chooseVPNCmd=(whiptail --backtitle "Setup PiVPN" --title "Installation mode" --separate-output --radiolist "WireGuard is a new kind of VPN that provides near-instantaneous connection speed, high performance, and modern cryptography.\\n\\nIt's the recommended choice especially if you use mobile devices where WireGuard is easier on battery than OpenVPN.\\n\\nOpenVPN is still available if you need the traditional, flexible, trusted VPN protocol or if you need features like TCP and custom search domain.\\n\\nChoose a VPN (press space to select):" "${r}" "${c}" 2) - VPNChooseOptions=(WireGuard "" on - OpenVPN "" off) +askWhichVPN() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ "${WIREGUARD_SUPPORT}" -eq 1 ]]; then + if [[ -z "${VPN}" ]]; then + echo ":: No VPN protocol specified, using WireGuard" + VPN="wireguard" + else + VPN="${VPN,,}" - if VPN=$("${chooseVPNCmd[@]}" "${VPNChooseOptions[@]}" 2>&1 >/dev/tty) ; then - echo "::: Using VPN: $VPN" - VPN="${VPN,,}" - else - echo "::: Cancel selected, exiting...." - exit 1 - fi - elif [ "$OPENVPN_SUPPORT" -eq 1 ] && [ "$WIREGUARD_SUPPORT" -eq 0 ]; then - echo "::: Using VPN: OpenVPN" - VPN="openvpn" - elif [ "$OPENVPN_SUPPORT" -eq 0 ] && [ "$WIREGUARD_SUPPORT" -eq 1 ]; then - echo "::: Using VPN: WireGuard" - VPN="wireguard" - fi - fi + if [[ "${VPN}" == "wireguard" ]]; then + echo "::: WireGuard will be installed" + elif [[ "${VPN}" == "openvpn" ]]; then + echo "::: OpenVPN will be installed" + else + err ":: ${VPN} is not a supported VPN protocol, please specify 'wireguard' or 'openvpn'" + exit 1 + fi + fi + else + if [[ -z "${VPN}" ]]; then + echo ":: No VPN protocol specified, using OpenVPN" + VPN="openvpn" + else + VPN="${VPN,,}" - echo "VPN=${VPN}" >> ${tempsetupVarsFile} + if [[ "${VPN}" == "openvpn" ]]; then + echo "::: OpenVPN will be installed" + else + err ":: ${VPN} is not a supported VPN protocol on ${DPKG_ARCH} ${PLAT}, only 'openvpn' is" + exit 1 + fi + fi + fi + else + if [[ "${WIREGUARD_SUPPORT}" -eq 1 ]] && + [[ "${OPENVPN_SUPPORT}" -eq 1 ]]; then + chooseVPNCmd=(whiptail + --backtitle "Setup PiVPN" + --title "Installation mode" + --separate-output + --radiolist "WireGuard is a new kind of VPN that provides \ +near-instantaneous connection speed, high performance, and modern cryptography. + +It's the recommended choice especially if you use mobile devices where \ +WireGuard is easier on battery than OpenVPN. + +OpenVPN is still available if you need the traditional, flexible, trusted \ +VPN protocol or if you need features like TCP and custom search domain. + +Choose a VPN (press space to select):" "${r}" "${c}" 2) + VPNChooseOptions=(WireGuard "" on + OpenVPN "" off) + + if VPN="$("${chooseVPNCmd[@]}" \ + "${VPNChooseOptions[@]}" \ + 2>&1 > /dev/tty)"; then + echo "::: Using VPN: ${VPN}" + VPN="${VPN,,}" + else + err "::: Cancel selected, exiting...." + exit 1 + fi + elif [[ "${OPENVPN_SUPPORT}" -eq 1 ]] && + [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then + echo "::: Using VPN: OpenVPN" + VPN="openvpn" + elif [[ "${OPENVPN_SUPPORT}" -eq 0 ]] && + [[ "${WIREGUARD_SUPPORT}" -eq 1 ]]; then + echo "::: Using VPN: WireGuard" + VPN="wireguard" + fi + fi + + echo "VPN=${VPN}" >> "${tempsetupVarsFile}" } -askAboutCustomizing(){ - if [ "${runUnattended}" = 'false' ]; then - if (whiptail --backtitle "Setup PiVPN" --title "Installation mode" --yesno --defaultno "PiVPN uses the following settings that we believe are good defaults for most users. However, we still want to keep flexibility, so if you need to customize them, choose Yes.\n\n* UDP or TCP protocol: UDP\n* Custom search domain for the DNS field: None\n* Modern features or best compatibility: Modern features (256 bit certificate + additional TLS encryption)" ${r} ${c}); then - CUSTOMIZE=1 - else - CUSTOMIZE=0 - fi - fi +askAboutCustomizing() { + if [[ "${runUnattended}" == 'false' ]]; then + if whiptail \ + --backtitle "Setup PiVPN" \ + --title "Installation mode" \ + --defaultno \ + --yesno "PiVPN uses the following settings that we believe are good \ +defaults for most users. However, we still want to keep flexibility, so if \ +you need to customize them, choose Yes. + +* UDP or TCP protocol: UDP +* Custom search domain for the DNS field: None +* Modern features or best compatibility: Modern features \ +(256 bit certificate + additional TLS encryption)" "${r}" "${c}"; then + CUSTOMIZE=1 + else + CUSTOMIZE=0 + fi + fi } -installOpenVPN(){ - local PIVPN_DEPS +installOpenVPN() { + local PIVPN_DEPS gpg_path + gpg_path="${pivpnFilesDir}/files/etc/apt/repo-public.gpg" + echo "::: Installing OpenVPN from Debian package... " - echo "::: Installing OpenVPN from Debian package... " + if [[ "${NEED_OPENVPN_REPO}" -eq 1 ]]; then + # gnupg is used by apt-key to import the openvpn GPG key into the + # APT keyring + PIVPN_DEPS=(gnupg) + installDependentPackages PIVPN_DEPS[@] - if [ "$NEED_OPENVPN_REPO" -eq 1 ]; then - # gnupg is used by apt-key to import the openvpn GPG key into the - # APT keyring - PIVPN_DEPS=(gnupg) - installDependentPackages PIVPN_DEPS[@] + # OpenVPN repo's public GPG key + # (fingerprint 0x30EBF4E73CCE63EEE124DD278E6DA8B4E158C569) + echo "::: Adding repository key..." - # OpenVPN repo's public GPG key (fingerprint 0x30EBF4E73CCE63EEE124DD278E6DA8B4E158C569) - echo "::: Adding repository key..." - if ! $SUDO apt-key add "${pivpnFilesDir}"/files/etc/apt/repo-public.gpg; then - echo "::: Can't import OpenVPN GPG key" - exit 1 - fi + if ! ${SUDO} apt-key add "${gpg_path}"; then + err "::: Can't import OpenVPN GPG key" + exit 1 + fi - echo "::: Adding OpenVPN repository... " - echo "deb https://build.openvpn.net/debian/openvpn/stable $OSCN main" | $SUDO tee /etc/apt/sources.list.d/pivpn-openvpn-repo.list > /dev/null + echo "::: Adding OpenVPN repository... " + echo "deb https://build.openvpn.net/debian/openvpn/stable ${OSCN} main" | + ${SUDO} tee /etc/apt/sources.list.d/pivpn-openvpn-repo.list > /dev/null - echo "::: Updating package cache..." - # shellcheck disable=SC2086 - updatePackageCache - fi + echo "::: Updating package cache..." + updatePackageCache + fi - # Expect is used to feed easy-rsa with passwords - PIVPN_DEPS=(openvpn expect) + # Expect is used to feed easy-rsa with passwords + PIVPN_DEPS=(openvpn expect) - installDependentPackages PIVPN_DEPS[@] + installDependentPackages PIVPN_DEPS[@] } -installWireGuard(){ - local PIVPN_DEPS +installWireGuard() { + local PIVPN_DEPS - if [ "$PLAT" = "Raspbian" ]; then + echo -n "::: Installing WireGuard" + PIVPN_DEPS=(wireguard-tools) - echo "::: Installing WireGuard from Debian package... " + if [[ "${PLAT}" == "Raspbian" ]]; then + echo " from Raspbian package..." - if [ -z "$AVAILABLE_WIREGUARD" ]; then - echo "::: Adding Raspbian Bullseye repository... " - echo "deb http://raspbian.raspberrypi.org/raspbian/ bullseye main" | $SUDO tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null + # qrencode is used to generate qrcodes from config file, + # for use with mobile clients + PIVPN_DEPS+=(qrencode) + elif [[ "${PLAT}" == "Debian" ]]; then + echo " from Debian package..." - # Do not upgrade packages from the bullseye repository except for wireguard - printf 'Package: *\nPin: release n=bullseye\nPin-Priority: -1\n\nPackage: wireguard wireguard-dkms wireguard-tools\nPin: release n=bullseye\nPin-Priority: 100\n' | $SUDO tee /etc/apt/preferences.d/pivpn-limit-bullseye > /dev/null + PIVPN_DEPS+=(qrencode) - echo "::: Updating package cache..." - # shellcheck disable=SC2086 - updatePackageCache - fi + if [[ "${WIREGUARD_BUILTIN}" -eq 0 ]]; then + # Explicitly install the module if not built-in + PIVPN_DEPS+=(linux-headers-amd64 wireguard-dkms) + fi + elif [[ "${PLAT}" == "Ubuntu" ]]; then + echo "..." - # qrencode is used to generate qrcodes from config file, for use with mobile clients - PIVPN_DEPS=(wireguard-tools qrencode) + PIVPN_DEPS+=(qrencode) - installDependentPackages PIVPN_DEPS[@] + if [[ "${WIREGUARD_BUILTIN}" -eq 0 ]]; then + PIVPN_DEPS+=(linux-headers-generic wireguard-dkms) + fi + elif [[ "${PLAT}" == 'Alpine' ]]; then + echo "..." - elif [ "$PLAT" = "Debian" ]; then + PIVPN_DEPS+=(libqrencode) + fi - echo "::: Installing WireGuard from Debian package... " + if [[ "${PLAT}" == "Raspbian" || "${PLAT}" == "Debian" ]] && + [[ -z "${AVAILABLE_WIREGUARD}" ]]; then + if [[ "${PLAT}" == "Debian" ]]; then + echo "::: Adding Debian Bullseye repository... " + echo "deb https://deb.debian.org/debian/ bullseye main" | + ${SUDO} tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null + else + echo "::: Adding Raspbian Bullseye repository... " + echo "deb http://raspbian.raspberrypi.org/raspbian/ bullseye main" | + ${SUDO} tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null + fi - if [ -z "$AVAILABLE_WIREGUARD" ]; then - echo "::: Adding Debian Bullseye repository... " - echo "deb https://deb.debian.org/debian/ bullseye main" | $SUDO tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null + { + printf 'Package: *\n' + printf 'Pin: release n=bullseye\n' + printf 'Pin-Priority: -1\n\n' + printf 'Package: wireguard wireguard-dkms wireguard-tools\n' + printf 'Pin: release n=bullseye\n' + printf 'Pin-Priority: 100\n' + } | ${SUDO} tee /etc/apt/preferences.d/pivpn-limit-bullseye > /dev/null - printf 'Package: *\nPin: release n=bullseye\nPin-Priority: -1\n\nPackage: wireguard wireguard-dkms wireguard-tools\nPin: release n=bullseye\nPin-Priority: 100\n' | $SUDO tee /etc/apt/preferences.d/pivpn-limit-bullseye > /dev/null + echo "::: Updating package cache..." + updatePackageCache + fi - echo "::: Updating package cache..." - # shellcheck disable=SC2086 - updatePackageCache - fi - - PIVPN_DEPS=(wireguard-tools qrencode) - - if [ "$WIREGUARD_BUILTIN" -eq 0 ]; then - # Explicitly install the module if not built-in - PIVPN_DEPS+=(linux-headers-amd64 wireguard-dkms) - fi - - installDependentPackages PIVPN_DEPS[@] - - elif [ "$PLAT" = "Ubuntu" ]; then - - echo "::: Installing WireGuard... " - - PIVPN_DEPS=(wireguard-tools qrencode) - - if [ "$WIREGUARD_BUILTIN" -eq 0 ]; then - PIVPN_DEPS+=(linux-headers-generic wireguard-dkms) - fi - - installDependentPackages PIVPN_DEPS[@] - - elif [ "${PLAT}" == 'Alpine' ]; then - echo "::: Installing WireGuard... " - - PIVPN_DEPS=(wireguard-tools libqrencode) - - installDependentPackages PIVPN_DEPS[@] - fi + installDependentPackages PIVPN_DEPS[@] } -askCustomProto(){ - if [ "${runUnattended}" = 'true' ]; then - if [ -z "$pivpnPROTO" ]; then - echo "::: No TCP/IP protocol specified, using the default protocol udp" - pivpnPROTO="udp" - else - pivpnPROTO="${pivpnPROTO,,}" - if [ "$pivpnPROTO" = "udp" ] || [ "$pivpnPROTO" = "tcp" ]; then - echo "::: Using the $pivpnPROTO protocol" - else - echo ":: $pivpnPROTO is not a supported TCP/IP protocol, please specify 'udp' or 'tcp'" - exit 1 - fi - fi - echo "pivpnPROTO=${pivpnPROTO}" >> ${tempsetupVarsFile} - return - fi +askCustomProto() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${pivpnPROTO}" ]]; then + echo "::: No TCP/IP protocol specified, using the default protocol udp" + pivpnPROTO="udp" + else + pivpnPROTO="${pivpnPROTO,,}" - if [ "$CUSTOMIZE" -eq 0 ]; then - if [ "$VPN" = "openvpn" ]; then - pivpnPROTO="udp" - echo "pivpnPROTO=${pivpnPROTO}" >> ${tempsetupVarsFile} - return - fi - fi + if [[ "${pivpnPROTO}" == "udp" ]] || + [[ "${pivpnPROTO}" == "tcp" ]]; then + echo "::: Using the ${pivpnPROTO} protocol" + else + err ":: ${pivpnPROTO} is not a supported TCP/IP protocol, please specify 'udp' or 'tcp'" + exit 1 + fi + fi - # Set the available protocols into an array so it can be used with a whiptail dialog - if pivpnPROTO=$(whiptail --title "Protocol" --radiolist \ - "Choose a protocol (press space to select). Please only choose TCP if you know why you need TCP." ${r} ${c} 2 \ - "UDP" "" ON \ - "TCP" "" OFF 3>&1 1>&2 2>&3); then - # Convert option into lowercase (UDP->udp) - pivpnPROTO="${pivpnPROTO,,}" - echo "::: Using protocol: $pivpnPROTO" - echo "pivpnPROTO=${pivpnPROTO}" >> ${tempsetupVarsFile} - else - echo "::: Cancel selected, exiting...." - exit 1 - fi + echo "pivpnPROTO=${pivpnPROTO}" >> "${tempsetupVarsFile}" + return + fi + + if [[ "${CUSTOMIZE}" -eq 0 ]]; then + if [[ "${VPN}" == "openvpn" ]]; then + pivpnPROTO="udp" + echo "pivpnPROTO=${pivpnPROTO}" >> "${tempsetupVarsFile}" + return + fi + fi + + # Set the available protocols into an array so it can be used + # with a whiptail dialog + if pivpnPROTO="$(whiptail \ + --title "Protocol" \ + --radiolist "Choose a protocol (press space to select). \ +Please only choose TCP if you know why you need TCP." "${r}" "${c}" 2 \ + "UDP" "" ON \ + "TCP" "" OFF \ + 3>&1 1>&2 2>&3)"; then + # Convert option into lowercase (UDP->udp) + pivpnPROTO="${pivpnPROTO,,}" + echo "::: Using protocol: ${pivpnPROTO}" + echo "pivpnPROTO=${pivpnPROTO}" >> "${tempsetupVarsFile}" + else + err "::: Cancel selected, exiting...." + exit 1 + fi } -askCustomPort(){ - if [ "${runUnattended}" = 'true' ]; then - if [ -z "$pivpnPORT" ]; then - if [ "$VPN" = "wireguard" ]; then - echo "::: No port specified, using the default port 51820" - pivpnPORT=51820 - elif [ "$VPN" = "openvpn" ]; then - if [ "$pivpnPROTO" = "udp" ]; then - echo "::: No port specified, using the default port 1194" - pivpnPORT=1194 - elif [ "$pivpnPROTO" = "tcp" ]; then - echo "::: No port specified, using the default port 443" - pivpnPORT=443 - fi - fi - else - if [[ "$pivpnPORT" =~ ^[0-9]+$ ]] && [ "$pivpnPORT" -ge 1 ] && [ "$pivpnPORT" -le 65535 ]; then - echo "::: Using port $pivpnPORT" - else - echo "::: $pivpnPORT is not a valid port, use a port within the range [1,65535] (inclusive)" - exit 1 - fi - fi - echo "pivpnPORT=${pivpnPORT}" >> ${tempsetupVarsFile} - return - fi +askCustomPort() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${pivpnPORT}" ]]; then + if [[ "${VPN}" == "wireguard" ]]; then + echo "::: No port specified, using the default port 51820" + pivpnPORT=51820 + elif [[ "${VPN}" == "openvpn" ]]; then + if [[ "${pivpnPROTO}" == "udp" ]]; then + echo "::: No port specified, using the default port 1194" + pivpnPORT=1194 + elif [[ "${pivpnPROTO}" == "tcp" ]]; then + echo "::: No port specified, using the default port 443" + pivpnPORT=443 + fi + fi + else + if [[ "${pivpnPORT}" =~ ^[0-9]+$ ]] && + [[ "${pivpnPORT}" -ge 1 ]] && + [[ "${pivpnPORT}" -le 65535 ]]; then + echo "::: Using port ${pivpnPORT}" + else + err "::: ${pivpnPORT} is not a valid port, use a port within the range [1,65535] (inclusive)" + exit 1 + fi + fi - until [[ $PORTNumCorrect = True ]]; do - portInvalid="Invalid" + echo "pivpnPORT=${pivpnPORT}" >> "${tempsetupVarsFile}" + return + fi - if [ "$VPN" = "wireguard" ]; then - DEFAULT_PORT=51820 - elif [ "$VPN" = "openvpn" ]; then - if [ "$pivpnPROTO" = "udp" ]; then - DEFAULT_PORT=1194 - else - DEFAULT_PORT=443 - fi - fi + until [[ "${PORTNumCorrect}" == 'true' ]]; do + portInvalid="Invalid" - if pivpnPORT=$(whiptail --title "Default $VPN Port" --inputbox "You can modify the default $VPN port. \\nEnter a new value or hit 'Enter' to retain the default" ${r} ${c} $DEFAULT_PORT 3>&1 1>&2 2>&3); then - if [[ "$pivpnPORT" =~ ^[0-9]+$ ]] && [ "$pivpnPORT" -ge 1 ] && [ "$pivpnPORT" -le 65535 ]; then - : - else - pivpnPORT=$portInvalid - fi - else - echo "::: Cancel selected, exiting...." - exit 1 - fi + if [[ "${VPN}" == "wireguard" ]]; then + DEFAULT_PORT=51820 + elif [[ "${VPN}" == "openvpn" ]]; then + if [[ "${pivpnPROTO}" == "udp" ]]; then + DEFAULT_PORT=1194 + else + DEFAULT_PORT=443 + fi + fi - if [[ $pivpnPORT == "$portInvalid" ]]; then - whiptail --msgbox --backtitle "Invalid Port" --title "Invalid Port" "You entered an invalid Port number.\\n Please enter a number from 1 - 65535.\\n If you are not sure, please just keep the default." ${r} ${c} - PORTNumCorrect=False - else - if (whiptail --backtitle "Specify Custom Port" --title "Confirm Custom Port Number" --yesno "Are these settings correct?\\n PORT: $pivpnPORT" ${r} ${c}) then - PORTNumCorrect=True - else - # If the settings are wrong, the loop continues - PORTNumCorrect=False - fi - fi - done - # write out the port - echo "pivpnPORT=${pivpnPORT}" >> ${tempsetupVarsFile} + if pivpnPORT="$(whiptail \ + --title "Default ${VPN} Port" \ + --inputbox "You can modify the default ${VPN} port. +Enter a new value or hit 'Enter' to retain \ +the default" "${r}" "${c}" "${DEFAULT_PORT}" \ + 3>&1 1>&2 2>&3)"; then + if [[ "${pivpnPORT}" =~ ^[0-9]+$ ]] && + [[ "${pivpnPORT}" -ge 1 ]] && + [[ "${pivpnPORT}" -le 65535 ]]; then + : + else + pivpnPORT="${portInvalid}" + fi + else + err "::: Cancel selected, exiting...." + exit 1 + fi + + if [[ "${pivpnPORT}" == "${portInvalid}" ]]; then + whiptail \ + --backtitle "Invalid Port" \ + --title "Invalid Port" \ + --msgbox "You entered an invalid Port number. + Please enter a number from 1 - 65535. + If you are not sure, please just keep the default." "${r}" "${c}" + PORTNumCorrect=false + else + if whiptail \ + --backtitle "Specify Custom Port" \ + --title "Confirm Custom Port Number" \ + --yesno "Are these settings correct? + PORT: ${pivpnPORT}" "${r}" "${c}"; then + PORTNumCorrect=true + else + # If the settings are wrong, the loop continues + PORTNumCorrect=false + fi + fi + done + + # write out the port + echo "pivpnPORT=${pivpnPORT}" >> "${tempsetupVarsFile}" } -askClientDNS(){ - if [ "${runUnattended}" = 'true' ]; then +askClientDNS() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${pivpnDNS1}" ]] && + [[ -n "${pivpnDNS2}" ]]; then + pivpnDNS1="${pivpnDNS2}" + unset pivpnDNS2 + elif [[ -z "${pivpnDNS1}" ]] && + [[ -z "${pivpnDNS2}" ]]; then + pivpnDNS1="9.9.9.9" + pivpnDNS2="149.112.112.112" + echo -n "::: No DNS provider specified, " + echo "using Quad9 DNS (${pivpnDNS1} ${pivpnDNS2})" + fi - if [ -z "$pivpnDNS1" ] && [ -n "$pivpnDNS2" ]; then - pivpnDNS1="$pivpnDNS2" - unset pivpnDNS2 - elif [ -z "$pivpnDNS1" ] && [ -z "$pivpnDNS2" ]; then - pivpnDNS1="9.9.9.9" - pivpnDNS2="149.112.112.112" - echo "::: No DNS provider specified, using Quad9 DNS ($pivpnDNS1 $pivpnDNS2)" - fi + local INVALID_DNS_SETTINGS=0 - local INVALID_DNS_SETTINGS=0 + if ! validIP "${pivpnDNS1}"; then + INVALID_DNS_SETTINGS=1 + echo "::: Invalid DNS ${pivpnDNS1}" + fi - if ! validIP "$pivpnDNS1"; then - INVALID_DNS_SETTINGS=1 - echo "::: Invalid DNS $pivpnDNS1" - fi + if [[ -n "${pivpnDNS2}" ]] && + ! validIP "${pivpnDNS2}"; then + INVALID_DNS_SETTINGS=1 + echo "::: Invalid DNS ${pivpnDNS2}" + fi - if [ -n "$pivpnDNS2" ] && ! validIP "$pivpnDNS2"; then - INVALID_DNS_SETTINGS=1 - echo "::: Invalid DNS $pivpnDNS2" - fi + if [[ "${INVALID_DNS_SETTINGS}" -eq 0 ]]; then + echo "::: Using DNS ${pivpnDNS1} ${pivpnDNS2}" + else + exit 1 + fi - if [ "$INVALID_DNS_SETTINGS" -eq 0 ]; then - echo "::: Using DNS $pivpnDNS1 $pivpnDNS2" - else - exit 1 - fi + { + echo "pivpnDNS1=${pivpnDNS1}" + echo "pivpnDNS2=${pivpnDNS2}" + } >> "${tempsetupVarsFile}" + return + fi - echo "pivpnDNS1=${pivpnDNS1}" >> ${tempsetupVarsFile} - echo "pivpnDNS2=${pivpnDNS2}" >> ${tempsetupVarsFile} - return - fi + # Detect and offer to use Pi-hole + if command -v pihole > /dev/null; then + if whiptail \ + --backtitle "Setup PiVPN" \ + --title "Pi-hole" \ + --yesno "We have detected a Pi-hole installation, \ +do you want to use it as the DNS server for the VPN, so you \ +get ad blocking on the go?" "${r}" "${c}"; then + if [[ ! -r "${piholeSetupVars}" ]]; then + err "::: Unable to read ${piholeSetupVars}" + exit 1 + fi - # Detect and offer to use Pi-hole - if command -v pihole > /dev/null; then - if (whiptail --backtitle "Setup PiVPN" --title "Pi-hole" --yesno "We have detected a Pi-hole installation, do you want to use it as the DNS server for the VPN, so you get ad blocking on the go?" ${r} ${c}); then - if [ ! -r "$piholeSetupVars" ]; then - echo "::: Unable to read $piholeSetupVars" - exit 1 - fi + # Add a custom hosts file for VPN clients so they appear + # as 'name.pivpn' in the Pi-hole dashboard as well as resolve + # by their names. + echo "addn-hosts=/etc/pivpn/hosts.${VPN}" | + ${SUDO} tee "${dnsmasqConfig}" > /dev/null - # Add a custom hosts file for VPN clients so they appear as 'name.pivpn' in the - # Pi-hole dashboard as well as resolve by their names. - echo "addn-hosts=/etc/pivpn/hosts.$VPN" | $SUDO tee "$dnsmasqConfig" > /dev/null + # Then create an empty hosts file or clear if it exists. + ${SUDO} bash -c "> /etc/pivpn/hosts.${VPN}" - # Then create an empty hosts file or clear if it exists. - $SUDO bash -c "> /etc/pivpn/hosts.$VPN" + # Setting Pi-hole to "Listen on all interfaces" allows + # dnsmasq to listen on the VPN interface while permitting + # queries only from hosts whose address is on the LAN and + # VPN subnets. + ${SUDO} pihole -a -i local - # Setting Pi-hole to "Listen on all interfaces" allows dnsmasq to listen on the - # VPN interface while permitting queries only from hosts whose address is on - # the LAN and VPN subnets. - $SUDO pihole -a -i local + # Use the Raspberry Pi VPN IP as DNS server. + pivpnDNS1="${vpnGw}" - # Use the Raspberry Pi VPN IP as DNS server. - pivpnDNS1="$vpnGw" + { + echo "pivpnDNS1=${pivpnDNS1}" + echo "pivpnDNS2=${pivpnDNS2}" + } >> "${tempsetupVarsFile}" - echo "pivpnDNS1=${pivpnDNS1}" >> ${tempsetupVarsFile} - echo "pivpnDNS2=${pivpnDNS2}" >> ${tempsetupVarsFile} + # Allow incoming DNS requests through UFW. + if [[ "${USING_UFW}" -eq 1 ]]; then + ${SUDO} ufw insert 1 allow in \ + on "${pivpnDEV}" to any port 53 \ + from "${pivpnNET}/${subnetClass}" > /dev/null + fi - # Allow incoming DNS requests through UFW. - if [ "$USING_UFW" -eq 1 ]; then - $SUDO ufw insert 1 allow in on "${pivpnDEV}" to any port 53 from "${pivpnNET}/${subnetClass}" >/dev/null - fi + return + fi + fi - return - fi - fi + DNSChoseCmd=(whiptail + --backtitle "Setup PiVPN" + --title "DNS Provider" + --separate-output + --radiolist "Select the DNS Provider for your VPN Clients \ +(press space to select). +To use your own, select Custom. - DNSChoseCmd=(whiptail --backtitle "Setup PiVPN" --title "DNS Provider" --separate-output --radiolist "Select the DNS Provider for your VPN Clients (press space to select).\nTo use your own, select Custom.\n\nIn case you have a local resolver running, i.e. unbound, select \"PiVPN-is-local-DNS\" and make sure your resolver is listening on \"$vpnGw\", allowing requests from \"${pivpnNET}/${subnetClass}\"." "${r}" "${c}" 6) - DNSChooseOptions=(Quad9 "" on - OpenDNS "" off - Level3 "" off - DNS.WATCH "" off - Norton "" off - FamilyShield "" off - CloudFlare "" off - Google "" off - PiVPN-is-local-DNS "" off - Custom "" off) +In case you have a local resolver running, i.e. unbound, select \ +\"PiVPN-is-local-DNS\" and make sure your resolver is listening on \ +\"${vpnGw}\", allowing requests from \ +\"${pivpnNET}/${subnetClass}\"." "${r}" "${c}" 6) + DNSChooseOptions=(Quad9 "" on + OpenDNS "" off + Level3 "" off + DNS.WATCH "" off + Norton "" off + FamilyShield "" off + CloudFlare "" off + Google "" off + PiVPN-is-local-DNS "" off + Custom "" off) - if DNSchoices=$("${DNSChoseCmd[@]}" "${DNSChooseOptions[@]}" 2>&1 >/dev/tty); then + if DNSchoices="$("${DNSChoseCmd[@]}" \ + "${DNSChooseOptions[@]}" \ + 2>&1 > /dev/tty)"; then + if [[ "${DNSchoices}" != "Custom" ]]; then + echo "::: Using ${DNSchoices} servers." + declare -A DNS_MAP=(["Quad9"]="9.9.9.9 149.112.112.112" + ["OpenDNS"]="208.67.222.222 208.67.220.220" + ["Level3"]="209.244.0.3 209.244.0.4" + ["DNS.WATCH"]="84.200.69.80 84.200.70.40" + ["Norton"]="199.85.126.10 199.85.127.10" + ["FamilyShield"]="208.67.222.123 208.67.220.123" + ["CloudFlare"]="1.1.1.1 1.0.0.1" + ["Google"]="8.8.8.8 8.8.4.4" + ["PiVPN-is-local-DNS"]="${vpnGw}") + pivpnDNS1=$(awk '{print $1}' <<< "${DNS_MAP["${DNSchoices}"]}") + pivpnDNS2=$(awk '{print $2}' <<< "${DNS_MAP["${DNSchoices}"]}") + else + until [[ "${DNSSettingsCorrect}" == 'true' ]]; do + strInvalid="Invalid" - if [[ ${DNSchoices} != "Custom" ]]; then + if pivpnDNS="$(whiptail \ + --backtitle "Specify Upstream DNS Provider(s)" \ + --inputbox "Enter your desired upstream DNS provider(s), \ +separated by a comma. + +For example '1.1.1.1, 9.9.9.9'" "${r}" "${c}" "" \ + 3>&1 1>&2 2>&3)"; then + pivpnDNS1="$(echo "${pivpnDNS}" | + sed 's/[, \t]\+/,/g' | + awk -F, '{print$1}')" + pivpnDNS2="$(echo "${pivpnDNS}" | + sed 's/[, \t]\+/,/g' | + awk -F, '{print$2}')" - echo "::: Using ${DNSchoices} servers." - declare -A DNS_MAP=(["Quad9"]="9.9.9.9 149.112.112.112" - ["OpenDNS"]="208.67.222.222 208.67.220.220" - ["Level3"]="209.244.0.3 209.244.0.4" - ["DNS.WATCH"]="84.200.69.80 84.200.70.40" - ["Norton"]="199.85.126.10 199.85.127.10" - ["FamilyShield"]="208.67.222.123 208.67.220.123" - ["CloudFlare"]="1.1.1.1 1.0.0.1" - ["Google"]="8.8.8.8 8.8.4.4" - ["PiVPN-is-local-DNS"]="$vpnGw") + if ! validIP "${pivpnDNS1}" || + [[ ! "${pivpnDNS1}" ]]; then + pivpnDNS1="${strInvalid}" + fi - pivpnDNS1=$(awk '{print $1}' <<< "${DNS_MAP["${DNSchoices}"]}") - pivpnDNS2=$(awk '{print $2}' <<< "${DNS_MAP["${DNSchoices}"]}") + if ! validIP "${pivpnDNS2}" && + [[ "${pivpnDNS2}" ]]; then + pivpnDNS2="${strInvalid}" + fi + else + err "::: Cancel selected, exiting...." + exit 1 + fi - else + if [[ "${pivpnDNS1}" == "${strInvalid}" ]] || + [[ "${pivpnDNS2}" == "${strInvalid}" ]]; then + whiptail \ + --backtitle "Invalid IP" \ + --title "Invalid IP" \ + --msgbox "One or both entered IP addresses were invalid. \ +Please try again. + DNS Server 1: ${pivpnDNS1} + DNS Server 2: ${pivpnDNS2}" "${r}" "${c}" - until [[ $DNSSettingsCorrect = True ]]; do - strInvalid="Invalid" + if [[ "${pivpnDNS1}" == "${strInvalid}" ]]; then + pivpnDNS1="" + fi - if pivpnDNS=$(whiptail --backtitle "Specify Upstream DNS Provider(s)" --inputbox "Enter your desired upstream DNS provider(s), separated by a comma.\\n\\nFor example '1.1.1.1, 9.9.9.9'" ${r} ${c} "" 3>&1 1>&2 2>&3); then - pivpnDNS1=$(echo "$pivpnDNS" | sed 's/[, \t]\+/,/g' | awk -F, '{print$1}') - pivpnDNS2=$(echo "$pivpnDNS" | sed 's/[, \t]\+/,/g' | awk -F, '{print$2}') - if ! validIP "$pivpnDNS1" || [ ! "$pivpnDNS1" ]; then - pivpnDNS1=$strInvalid - fi - if ! validIP "$pivpnDNS2" && [ "$pivpnDNS2" ]; then - pivpnDNS2=$strInvalid - fi - else - echo "::: Cancel selected, exiting...." - exit 1 - fi + if [[ "${pivpnDNS2}" == "${strInvalid}" ]]; then + pivpnDNS2="" + fi - if [[ $pivpnDNS1 == "$strInvalid" ]] || [[ $pivpnDNS2 == "$strInvalid" ]]; then - whiptail --msgbox --backtitle "Invalid IP" --title "Invalid IP" "One or both entered IP addresses were invalid. Please try again.\\n\\n DNS Server 1: $pivpnDNS1\\n DNS Server 2: $pivpnDNS2" ${r} ${c} - if [[ $pivpnDNS1 == "$strInvalid" ]]; then - pivpnDNS1="" - fi - if [[ $pivpnDNS2 == "$strInvalid" ]]; then - pivpnDNS2="" - fi - DNSSettingsCorrect=False - else - if (whiptail --backtitle "Specify Upstream DNS Provider(s)" --title "Upstream DNS Provider(s)" --yesno "Are these settings correct?\\n DNS Server 1: $pivpnDNS1\\n DNS Server 2: $pivpnDNS2" ${r} ${c}) then - DNSSettingsCorrect=True - else - # If the settings are wrong, the loop continues - DNSSettingsCorrect=False - fi - fi - done - fi + DNSSettingsCorrect=false + else + if whiptail \ + --backtitle "Specify Upstream DNS Provider(s)" \ + --title "Upstream DNS Provider(s)" \ + --yesno "Are these settings correct? + DNS Server 1: ${pivpnDNS1} + DNS Server 2: ${pivpnDNS2}" "${r}" "${c}"; then + DNSSettingsCorrect=true + else + # If the settings are wrong, the loop continues + DNSSettingsCorrect=false + fi + fi + done + fi - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi + else + err "::: Cancel selected. Exiting..." + exit 1 + fi - echo "pivpnDNS1=${pivpnDNS1}" >> ${tempsetupVarsFile} - echo "pivpnDNS2=${pivpnDNS2}" >> ${tempsetupVarsFile} + { + echo "pivpnDNS1=${pivpnDNS1}" + echo "pivpnDNS2=${pivpnDNS2}" + } >> "${tempsetupVarsFile}" } -#Call this function to use a regex to check user input for a valid custom domain -validDomain(){ - local domain="$1" - grep -qP '(?=^.{4,253}$)(^(?:[a-zA-Z0-9](?:(?:[a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$)' <<< "$domain" +# Call this function to use a regex to check user +# input for a valid custom domain +validDomain() { + local domain="${1}" + local perl_regexp='(?=^.{4,253}$)' + perl_regexp="${perl_regexp}(^(?:[a-zA-Z0-9](?:(?:[a-zA-Z0-9\-]){0,61}" + perl_regexp="${perl_regexp}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$)" + grep -qP "${perl_regexp}" <<< "${domain}" } -#This procedure allows a user to specify a custom search domain if they have one. -askCustomDomain(){ - if [ "${runUnattended}" = 'true' ]; then - if [ -n "$pivpnSEARCHDOMAIN" ]; then - if validDomain "$pivpnSEARCHDOMAIN"; then - echo "::: Using custom domain $pivpnSEARCHDOMAIN" - else - echo "::: Custom domain $pivpnSEARCHDOMAIN is not valid" - exit 1 - fi - else - echo "::: Skipping custom domain" - fi - echo "pivpnSEARCHDOMAIN=${pivpnSEARCHDOMAIN}" >> ${tempsetupVarsFile} - return - fi +# This procedure allows a user to specify a custom +# search domain if they have one. +askCustomDomain() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -n "${pivpnSEARCHDOMAIN}" ]]; then + if validDomain "${pivpnSEARCHDOMAIN}"; then + echo "::: Using custom domain ${pivpnSEARCHDOMAIN}" + else + err "::: Custom domain ${pivpnSEARCHDOMAIN} is not valid" + exit 1 + fi + else + echo "::: Skipping custom domain" + fi - if [ "$CUSTOMIZE" -eq 0 ]; then - if [ "$VPN" = "openvpn" ]; then - echo "pivpnSEARCHDOMAIN=${pivpnSEARCHDOMAIN}" >> ${tempsetupVarsFile} - return - fi - fi + echo "pivpnSEARCHDOMAIN=${pivpnSEARCHDOMAIN}" >> "${tempsetupVarsFile}" + return + fi - DomainSettingsCorrect=False + if [[ "${CUSTOMIZE}" -eq 0 ]]; then + if [[ "${VPN}" == "openvpn" ]]; then + echo "pivpnSEARCHDOMAIN=${pivpnSEARCHDOMAIN}" >> "${tempsetupVarsFile}" + return + fi + fi - if (whiptail --backtitle "Custom Search Domain" --title "Custom Search Domain" --yesno --defaultno "Would you like to add a custom search domain? \\n (This is only for advanced users who have their own domain)\\n" ${r} ${c}); then + DomainSettingsCorrect=false - until [[ $DomainSettingsCorrect = True ]]; do - if pivpnSEARCHDOMAIN=$(whiptail --inputbox "Enter Custom Domain\\nFormat: mydomain.com" ${r} ${c} --title "Custom Domain" 3>&1 1>&2 2>&3); then - if validDomain "$pivpnSEARCHDOMAIN"; then - if (whiptail --backtitle "Custom Search Domain" --title "Custom Search Domain" --yesno "Are these settings correct?\\n Custom Search Domain: $pivpnSEARCHDOMAIN" ${r} ${c}); then - DomainSettingsCorrect=True - else - # If the settings are wrong, the loop continues - DomainSettingsCorrect=False - fi - else - whiptail --msgbox --backtitle "Invalid Domain" --title "Invalid Domain" "Domain is invalid. Please try again.\\n\\n DOMAIN: $pivpnSEARCHDOMAIN\\n" ${r} ${c} - DomainSettingsCorrect=False - fi - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi - done - fi + if whiptail \ + --backtitle "Custom Search Domain" \ + --title "Custom Search Domain" \ + --defaultno \ + --yesno "Would you like to add a custom search domain? +(This is only for advanced users who have their own domain) +" "${r}" "${c}"; then + until [[ "${DomainSettingsCorrect}" == 'true' ]]; do + if pivpnSEARCHDOMAIN="$(whiptail \ + --inputbox "Enter Custom Domain +Format: mydomain.com" "${r}" "${c}" \ + --title "Custom Domain" \ + 3>&1 1>&2 2>&3)"; then + if validDomain "${pivpnSEARCHDOMAIN}"; then + if whiptail \ + --backtitle "Custom Search Domain" \ + --title "Custom Search Domain" \ + --yesno "Are these settings correct? + Custom Search Domain: ${pivpnSEARCHDOMAIN}" "${r}" "${c}"; then + DomainSettingsCorrect=true + else + # If the settings are wrong, the loop continues + DomainSettingsCorrect=false + fi + else + whiptail \ + --backtitle "Invalid Domain" \ + --title "Invalid Domain" \ + --msgbox "Domain is invalid. Please try again. + DOMAIN: ${pivpnSEARCHDOMAIN} +" "${r}" "${c}" + DomainSettingsCorrect=false + fi + else + err "::: Cancel selected. Exiting..." + exit 1 + fi + done + fi - echo "pivpnSEARCHDOMAIN=${pivpnSEARCHDOMAIN}" >> ${tempsetupVarsFile} + echo "pivpnSEARCHDOMAIN=${pivpnSEARCHDOMAIN}" >> "${tempsetupVarsFile}" } -askPublicIPOrDNS(){ - if ! IPv4pub=$(dig +short myip.opendns.com @208.67.222.222) || ! validIP "$IPv4pub"; then - echo "dig failed, now trying to curl checkip.amazonaws.com" - if ! IPv4pub=$(curl -sSf https://checkip.amazonaws.com) || ! validIP "$IPv4pub"; then - echo "checkip.amazonaws.com failed, please check your internet connection/DNS" - exit 1 - fi - fi +askPublicIPOrDNS() { + if ! IPv4pub="$(dig +short myip.opendns.com @208.67.222.222)" || + ! validIP "${IPv4pub}"; then + err "dig failed, now trying to curl checkip.amazonaws.com" - if [ "${runUnattended}" = 'true' ]; then - if [ -z "$pivpnHOST" ]; then - echo "::: No IP or domain name specified, using public IP $IPv4pub" - pivpnHOST="$IPv4pub" - else - if validIP "$pivpnHOST"; then - echo "::: Using public IP $pivpnHOST" - elif validDomain "$pivpnHOST"; then - echo "::: Using domain name $pivpnHOST" - else - echo "::: $pivpnHOST is not a valid IP or domain name" - exit 1 - fi - fi - echo "pivpnHOST=${pivpnHOST}" >> ${tempsetupVarsFile} - return - fi + if ! IPv4pub="$(curl -sSf https://checkip.amazonaws.com)" || + ! validIP "${IPv4pub}"; then + err "checkip.amazonaws.com failed, please check your internet connection/DNS" + exit 1 + fi + fi - local publicDNSCorrect - local publicDNSValid + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${pivpnHOST}" ]]; then + echo "::: No IP or domain name specified, using public IP ${IPv4pub}" + pivpnHOST="${IPv4pub}" + else + if validIP "${pivpnHOST}"; then + echo "::: Using public IP ${pivpnHOST}" + elif validDomain "${pivpnHOST}"; then + echo "::: Using domain name ${pivpnHOST}" + else + err "::: ${pivpnHOST} is not a valid IP or domain name" + exit 1 + fi + fi - if METH=$(whiptail --title "Public IP or DNS" --radiolist "Will clients use a Public IP or DNS Name to connect to your server (press space to select)?" ${r} ${c} 2 \ - "$IPv4pub" "Use this public IP" "ON" \ - "DNS Entry" "Use a public DNS" "OFF" 3>&1 1>&2 2>&3); then + echo "pivpnHOST=${pivpnHOST}" >> "${tempsetupVarsFile}" + return + fi - if [ "$METH" = "$IPv4pub" ]; then - pivpnHOST="${IPv4pub}" - else - until [[ ${publicDNSCorrect} = True ]]; do + local publicDNSCorrect + local publicDNSValid - until [[ ${publicDNSValid} = True ]]; do - if PUBLICDNS=$(whiptail --title "PiVPN Setup" --inputbox "What is the public DNS name of this Server?" ${r} ${c} 3>&1 1>&2 2>&3); then - if validDomain "$PUBLICDNS"; then - publicDNSValid=True - pivpnHOST="${PUBLICDNS}" - else - whiptail --msgbox --backtitle "PiVPN Setup" --title "Invalid DNS name" "This DNS name is invalid. Please try again.\\n\\n DNS name: $PUBLICDNS\\n" ${r} ${c} - publicDNSValid=False - fi - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi - done + if METH="$(whiptail \ + --title "Public IP or DNS" \ + --radiolist \ + "Will clients use a Public IP or DNS Name to connect to your server \ +(press space to select)?" "${r}" "${c}" 2 \ + "${IPv4pub}" "Use this public IP" "ON" \ + "DNS Entry" "Use a public DNS" "OFF" \ + 3>&1 1>&2 2>&3)"; then + if [[ "${METH}" == "${IPv4pub}" ]]; then + pivpnHOST="${IPv4pub}" + else + until [[ "${publicDNSCorrect}" == 'true' ]]; do + until [[ "${publicDNSValid}" == 'true' ]]; do + if PUBLICDNS="$(whiptail \ + --title "PiVPN Setup" \ + --inputbox "What is the public DNS \ +name of this Server?" "${r}" "${c}" \ + 3>&1 1>&2 2>&3)"; then + if validDomain "${PUBLICDNS}"; then + publicDNSValid=true + pivpnHOST="${PUBLICDNS}" + else + whiptail \ + --backtitle "PiVPN Setup" \ + --title "Invalid DNS name" \ + --msgbox "This DNS name is invalid. Please try again. + DNS name: ${PUBLICDNS} +" "${r}" "${c}" + publicDNSValid=false + fi + else + err "::: Cancel selected. Exiting..." + exit 1 + fi + done - if (whiptail --backtitle "PiVPN Setup" --title "Confirm DNS Name" --yesno "Is this correct?\\n\\n Public DNS Name: $PUBLICDNS" ${r} ${c}) then - publicDNSCorrect=True - else - publicDNSCorrect=False - publicDNSValid=False - fi - done - fi - else - echo "::: Cancel selected. Exiting..." - exit 1 - fi + if whiptail \ + --backtitle "PiVPN Setup" \ + --title "Confirm DNS Name" \ + --yesno "Is this correct? +Public DNS Name: ${PUBLICDNS}" "${r}" "${c}"; then + publicDNSCorrect=true + else + publicDNSCorrect=false + publicDNSValid=false + fi + done + fi + else + err "::: Cancel selected. Exiting..." + exit 1 + fi - echo "pivpnHOST=${pivpnHOST}" >> ${tempsetupVarsFile} + echo "pivpnHOST=${pivpnHOST}" >> "${tempsetupVarsFile}" } -askEncryption(){ - if [ "${runUnattended}" = 'true' ]; then +askEncryption() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${TWO_POINT_FOUR}" ]] || + [[ "${TWO_POINT_FOUR}" -eq 1 ]]; then + TWO_POINT_FOUR=1 + echo "::: Using OpenVPN 2.4 features" - if [ -z "$TWO_POINT_FOUR" ] || [ "$TWO_POINT_FOUR" -eq 1 ]; then - TWO_POINT_FOUR=1 - echo "::: Using OpenVPN 2.4 features" + if [[ -z "${pivpnENCRYPT}" ]]; then + pivpnENCRYPT=256 + fi - if [ -z "$pivpnENCRYPT" ]; then - pivpnENCRYPT=256 - echo "::: Using a 256 bit certificate" - else - if [ "$pivpnENCRYPT" -eq 256 ] || [ "$pivpnENCRYPT" -eq 384 ] || [ "$pivpnENCRYPT" -eq 521 ]; then - echo "::: Using a ${pivpnENCRYPT}-bit certificate" - else - echo "::: ${pivpnENCRYPT} is not a valid certificate size, use 256, 384, or 521" - exit 1 - fi - fi - else - TWO_POINT_FOUR=0 - echo "::: Using traditional OpenVPN configuration" + if [[ "${pivpnENCRYPT}" -eq 256 ]] || + [[ "${pivpnENCRYPT}" -eq 384 ]] || + [[ "${pivpnENCRYPT}" -eq 521 ]]; then + echo "::: Using a ${pivpnENCRYPT}-bit certificate" + else + err "::: ${pivpnENCRYPT} is not a valid certificate size, use 256, 384, or 521" + exit 1 + fi + else + TWO_POINT_FOUR=0 + echo "::: Using traditional OpenVPN configuration" - if [ -z "$pivpnENCRYPT" ]; then - pivpnENCRYPT=2048 - echo "::: Using a 2048 bit certificate" - else - if [ "$pivpnENCRYPT" -eq 2048 ] || [ "$pivpnENCRYPT" -eq 3072 ] || [ "$pivpnENCRYPT" -eq 4096 ]; then - echo "::: Using a ${pivpnENCRYPT}-bit certificate" - else - echo "::: ${pivpnENCRYPT} is not a valid certificate size, use 2048, 3072, or 4096" - exit 1 - fi - fi + if [[ -z "${pivpnENCRYPT}" ]]; then + pivpnENCRYPT=2048 + fi - if [ -z "$USE_PREDEFINED_DH_PARAM" ]; then - USE_PREDEFINED_DH_PARAM=1 - echo "::: Pre-defined DH parameters will be used" - else - if [ "$USE_PREDEFINED_DH_PARAM" -eq 1 ]; then - echo "::: Pre-defined DH parameters will be used" - else - echo "::: DH parameters will be generated locally" - fi - fi - fi + if [[ "${pivpnENCRYPT}" -eq 2048 ]] || + [[ "${pivpnENCRYPT}" -eq 3072 ]] || + [[ "${pivpnENCRYPT}" -eq 4096 ]]; then + echo "::: Using a ${pivpnENCRYPT}-bit certificate" + else + err "::: ${pivpnENCRYPT} is not a valid certificate size, use 2048, 3072, or 4096" + exit 1 + fi - { - echo "TWO_POINT_FOUR=${TWO_POINT_FOUR}" - echo "pivpnENCRYPT=${pivpnENCRYPT}" - echo "USE_PREDEFINED_DH_PARAM=${USE_PREDEFINED_DH_PARAM}" - } >> ${tempsetupVarsFile} - return - fi + if [[ -z "${USE_PREDEFINED_DH_PARAM}" ]]; then + USE_PREDEFINED_DH_PARAM=1 + fi - if [ "$CUSTOMIZE" -eq 0 ]; then - if [ "$VPN" = "openvpn" ]; then - TWO_POINT_FOUR=1 - pivpnENCRYPT=256 - { - echo "TWO_POINT_FOUR=${TWO_POINT_FOUR}" - echo "pivpnENCRYPT=${pivpnENCRYPT}" - echo "USE_PREDEFINED_DH_PARAM=${USE_PREDEFINED_DH_PARAM}" - } >> ${tempsetupVarsFile} - return - fi - fi + if [[ "${USE_PREDEFINED_DH_PARAM}" -eq 1 ]]; then + echo "::: Pre-defined DH parameters will be used" + else + echo "::: DH parameters will be generated locally" + fi + fi - if (whiptail --backtitle "Setup OpenVPN" --title "Installation mode" --yesno "OpenVPN 2.4 can take advantage of Elliptic Curves to provide higher connection speed and improved security over RSA, while keeping smaller certificates.\\n\\nMoreover, the 'tls-crypt' directive encrypts the certificates being used while authenticating, increasing privacy.\\n\\nIf your clients do run OpenVPN 2.4 or later you can enable these features, otherwise choose 'No' for best compatibility." "${r}" "${c}"); then - TWO_POINT_FOUR=1 - pivpnENCRYPT=$(whiptail --backtitle "Setup OpenVPN" --title "ECDSA certificate size" --radiolist \ - "Choose the desired size of your certificate (press space to select):\\nThis is a certificate that will be generated on your system. The larger the certificate, the more time this will take. For most applications, it is recommended to use 256 bits. You can increase the number of bits if you care about, however, consider that 256 bits are already as secure as 3072 bit RSA." ${r} ${c} 3 \ - "256" "Use a 256-bit certificate (recommended level)" ON \ - "384" "Use a 384-bit certificate" OFF \ - "521" "Use a 521-bit certificate (paranoid level)" OFF 3>&1 1>&2 2>&3) - else - TWO_POINT_FOUR=0 - pivpnENCRYPT=$(whiptail --backtitle "Setup OpenVPN" --title "RSA certificate size" --radiolist \ - "Choose the desired size of your certificate (press space to select):\\nThis is a certificate that will be generated on your system. The larger the certificate, the more time this will take. For most applications, it is recommended to use 2048 bits. If you are paranoid about ... things... then grab a cup of joe and pick 4096 bits." ${r} ${c} 3 \ - "2048" "Use a 2048-bit certificate (recommended level)" ON \ - "3072" "Use a 3072-bit certificate " OFF \ - "4096" "Use a 4096-bit certificate (paranoid level)" OFF 3>&1 1>&2 2>&3) - fi + { + echo "TWO_POINT_FOUR=${TWO_POINT_FOUR}" + echo "pivpnENCRYPT=${pivpnENCRYPT}" + echo "USE_PREDEFINED_DH_PARAM=${USE_PREDEFINED_DH_PARAM}" + } >> "${tempsetupVarsFile}" + return + fi - exitstatus=$? - if [ $exitstatus != 0 ]; then - echo "::: Cancel selected. Exiting..." - exit 1 - fi + if [[ "${CUSTOMIZE}" -eq 0 ]]; then + if [[ "${VPN}" == "openvpn" ]]; then + TWO_POINT_FOUR=1 + pivpnENCRYPT=256 - if ([ "$pivpnENCRYPT" -ge 2048 ] && whiptail --backtitle "Setup OpenVPN" --title "Generate Diffie-Hellman Parameters" --yesno "Generating DH parameters can take many hours on a Raspberry Pi. You can instead use Pre-defined DH parameters recommended by the Internet Engineering Task Force.\\n\\nMore information about those can be found here: https://wiki.mozilla.org/Security/Archive/Server_Side_TLS_4.0#Pre-defined_DHE_groups\\n\\nIf you want unique parameters, choose 'No' and new Diffie-Hellman parameters will be generated on your device." ${r} ${c}); then - USE_PREDEFINED_DH_PARAM=1 - else - USE_PREDEFINED_DH_PARAM=0 - fi + { + echo "TWO_POINT_FOUR=${TWO_POINT_FOUR}" + echo "pivpnENCRYPT=${pivpnENCRYPT}" + echo "USE_PREDEFINED_DH_PARAM=${USE_PREDEFINED_DH_PARAM}" + } >> "${tempsetupVarsFile}" + return + fi + fi - { - echo "TWO_POINT_FOUR=${TWO_POINT_FOUR}" - echo "pivpnENCRYPT=${pivpnENCRYPT}" - echo "USE_PREDEFINED_DH_PARAM=${USE_PREDEFINED_DH_PARAM}" - } >> ${tempsetupVarsFile} + if whiptail \ + --backtitle "Setup OpenVPN" \ + --title "Installation mode" \ + --yesno "OpenVPN 2.4 can take advantage of Elliptic Curves \ +to provide higher connection speed and improved security over \ +RSA, while keeping smaller certificates. + +Moreover, the 'tls-crypt' directive encrypts the certificates \ +being used while authenticating, increasing privacy. + +If your clients do run OpenVPN 2.4 or later you can enable \ +these features, otherwise choose 'No' for best \ +compatibility." \ + "${r}" \ + "${c}"; then + TWO_POINT_FOUR=1 + pivpnENCRYPT="$(whiptail \ + --backtitle "Setup OpenVPN" \ + --title "ECDSA certificate size" \ + --radiolist "Choose the desired size of your certificate \ +(press space to select): +This is a certificate that will be generated on your system. \ +The larger the certificate, the more time this will take. \ +For most applications, it is recommended to use 256 bits. \ +You can increase the number of bits if you care about, however, consider \ +that 256 bits are already as secure as 3072 bit RSA." "${r}" "${c}" 3 \ + "256" "Use a 256-bit certificate (recommended level)" ON \ + "384" "Use a 384-bit certificate" OFF \ + "521" "Use a 521-bit certificate (paranoid level)" OFF \ + 3>&1 1>&2 2>&3)" + else + TWO_POINT_FOUR=0 + pivpnENCRYPT="$(whiptail \ + --backtitle "Setup OpenVPN" \ + --title "RSA certificate size" \ + --radiolist "Choose the desired size of your certificate \ +(press space to select): +This is a certificate that will be generated on your system. \ +The larger the certificate, the more time this will take. \ +For most applications, it is recommended to use 2048 bits. \ +If you are paranoid about ... things... \ +then grab a cup of joe and pick 4096 bits." "${r}" "${c}" 3 \ + "2048" "Use a 2048-bit certificate (recommended level)" ON \ + "3072" "Use a 3072-bit certificate " OFF \ + "4096" "Use a 4096-bit certificate (paranoid level)" OFF \ + 3>&1 1>&2 2>&3)" + fi + + exitstatus="$?" + + if [[ "${exitstatus}" != 0 ]]; then + err "::: Cancel selected. Exiting..." + exit 1 + fi + + if [[ "${pivpnENCRYPT}" -ge 2048 ]] && + whiptail \ + --backtitle "Setup OpenVPN" \ + --title "Generate Diffie-Hellman Parameters" \ + --yesno "Generating DH parameters can take many hours on a Raspberry Pi. \ +You can instead use Pre-defined DH parameters recommended by the \ +Internet Engineering Task Force. +More information about those can be found here: \ +https://wiki.mozilla.org/Security/Archive/Server_Side_TLS_4.0#\ +Pre-defined_DHE_groups +If you want unique parameters, choose 'No' and new Diffie-Hellman \ +parameters will be generated on your device." "${r}" "${c}"; then + USE_PREDEFINED_DH_PARAM=1 + else + USE_PREDEFINED_DH_PARAM=0 + fi + + { + echo "TWO_POINT_FOUR=${TWO_POINT_FOUR}" + echo "pivpnENCRYPT=${pivpnENCRYPT}" + echo "USE_PREDEFINED_DH_PARAM=${USE_PREDEFINED_DH_PARAM}" + } >> "${tempsetupVarsFile}" } -cidrToMask(){ - # Source: https://stackoverflow.com/a/20767392 - set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0 - shift "$1" - echo "${1-0}"."${2-0}"."${3-0}"."${4-0}" +cidrToMask() { + # Source: https://stackoverflow.com/a/20767392 + set -- $((5 - ($1 / 8))) \ + 255 255 255 255 \ + $(((255 << (8 - ($1 % 8))) & 255)) \ + 0 0 0 + shift "${1}" + echo "${1-0}.${2-0}.${3-0}.${4-0}" } -confOpenVPN(){ - # Grab the existing Hostname - host_name=$(hostname -s) - # Generate a random UUID for this server so that we can use verify-x509-name later that is unique for this server installation. - NEW_UUID=$( /dev/null - umask "$CURRENT_UMASK" + # Grab the existing Hostname + host_name="$(hostname -s)" + # Generate a random UUID for this server so that we can use + # verify-x509-name later that is unique for this server + # installation. + NEW_UUID="$(< /proc/sys/kernel/random/uuid)" + # Create a unique server name using the host name and UUID + SERVER_NAME="${host_name}_${NEW_UUID}" - if [ -f /etc/openvpn/server.conf ]; then - $SUDO rm /etc/openvpn/server.conf - fi + # Backup the openvpn folder + OPENVPN_BACKUP="openvpn_$(date +%Y-%m-%d-%H%M%S).tar.gz" + echo "::: Backing up the openvpn folder to /etc/${OPENVPN_BACKUP}" + CURRENT_UMASK="$(umask)" + umask 0077 + ${SUDO} tar -czf "/etc/${OPENVPN_BACKUP}" /etc/openvpn &> /dev/null + umask "${CURRENT_UMASK}" - if [ -d /etc/openvpn/ccd ]; then - $SUDO rm -rf /etc/openvpn/ccd - fi + if [[ -f /etc/openvpn/server.conf ]]; then + ${SUDO} rm /etc/openvpn/server.conf + fi - # Create folder to store client specific directives used to push static IPs - $SUDO mkdir /etc/openvpn/ccd + if [[ -d /etc/openvpn/ccd ]]; then + ${SUDO} rm -rf /etc/openvpn/ccd + fi - # If easy-rsa exists, remove it - if [[ -d /etc/openvpn/easy-rsa/ ]]; then - $SUDO rm -rf /etc/openvpn/easy-rsa/ - fi + # Create folder to store client specific directives used to push static IPs + ${SUDO} mkdir /etc/openvpn/ccd - # Get easy-rsa - curl -sSfL "${easyrsaRel}" | $SUDO tar xz --one-top-level=/etc/openvpn/easy-rsa --strip-components 1 - if ! test -s /etc/openvpn/easy-rsa/easyrsa; then - echo "$0: ERR: Failed to download EasyRSA." - exit 1 - fi + # If easy-rsa exists, remove it + if [[ -d /etc/openvpn/easy-rsa/ ]]; then + ${SUDO} rm -rf /etc/openvpn/easy-rsa/ + fi - # fix ownership - $SUDO chown -R root:root /etc/openvpn/easy-rsa - $SUDO mkdir /etc/openvpn/easy-rsa/pki - $SUDO chmod 700 /etc/openvpn/easy-rsa/pki + # Get easy-rsa + curl -sSfL "${easyrsaRel}" | + ${SUDO} tar -xz --one-top-level=/etc/openvpn/easy-rsa --strip-components 1 - cd /etc/openvpn/easy-rsa || exit 1 + if [[ ! -s /etc/openvpn/easy-rsa/easyrsa ]]; then + err "${0}: ERR: Failed to download EasyRSA." + exit 1 + fi - if [ "$TWO_POINT_FOUR" -eq 1 ]; then - pivpnCERT="ec" - pivpnTLSPROT="tls-crypt" - else - pivpnCERT="rsa" - pivpnTLSPROT="tls-auth" - fi + # fix ownership + ${SUDO} chown -R root:root /etc/openvpn/easy-rsa + ${SUDO} mkdir /etc/openvpn/easy-rsa/pki + ${SUDO} chmod 700 /etc/openvpn/easy-rsa/pki - # Remove any previous keys - ${SUDOE} ./easyrsa --batch init-pki + cd /etc/openvpn/easy-rsa || exit 1 - # Copy template vars file - ${SUDOE} cp vars.example pki/vars + if [[ "${TWO_POINT_FOUR}" -eq 1 ]]; then + pivpnCERT="ec" + pivpnTLSPROT="tls-crypt" + else + pivpnCERT="rsa" + pivpnTLSPROT="tls-auth" + fi - # Set elliptic curve certificate or traditional rsa certificates - ${SUDOE} sed -i 's/#set_var EASYRSA_ALGO.*/set_var EASYRSA_ALGO '"${pivpnCERT}"'/' pki/vars + # Remove any previous keys + ${SUDOE} ./easyrsa --batch init-pki - # Set expiration for the CRL to 10 years - ${SUDOE} sed -i 's/#set_var EASYRSA_CRL_DAYS.*/set_var EASYRSA_CRL_DAYS 3650/' pki/vars + # Copy template vars file + ${SUDOE} cp vars.example pki/vars - if [ "$pivpnENCRYPT" -ge 2048 ]; then - # Set custom key size if different from the default - ${SUDOE} sed -i 's/#set_var EASYRSA_KEY_SIZE.*/set_var EASYRSA_KEY_SIZE '"${pivpnENCRYPT}"'/' pki/vars - else - # If less than 2048, then it must be 521 or lower, which means elliptic curve certificate was selected. - # We set the curve in this case. - declare -A ECDSA_MAP=(["256"]="prime256v1" ["384"]="secp384r1" ["521"]="secp521r1") - ${SUDOE} sed -i 's/#set_var EASYRSA_CURVE.*/set_var EASYRSA_CURVE '"${ECDSA_MAP["${pivpnENCRYPT}"]}"'/' pki/vars - fi + # Set elliptic curve certificate or traditional rsa certificates + ${SUDOE} sed \ + -i \ + "s/#set_var EASYRSA_ALGO.*/set_var EASYRSA_ALGO ${pivpnCERT}/" \ + pki/vars - # Build the certificate authority - printf "::: Building CA...\\n" - ${SUDOE} ./easyrsa --batch build-ca nopass - printf "\\n::: CA Complete.\\n" + # Set expiration for the CRL to 10 years + ${SUDOE} sed \ + -i \ + 's/#set_var EASYRSA_CRL_DAYS.*/set_var EASYRSA_CRL_DAYS 3650/' \ + pki/vars - if [ "$pivpnCERT" = "rsa" ] && [ "$USE_PREDEFINED_DH_PARAM" -ne 1 ]; then - if [ "${runUnattended}" = 'true' ]; then - echo "::: The server key, Diffie-Hellman parameters, and HMAC key will now be generated." - else - whiptail --msgbox --backtitle "Setup OpenVPN" --title "Server Information" "The server key, Diffie-Hellman parameters, and HMAC key will now be generated." ${r} ${c} - fi - elif [ "$pivpnCERT" = "ec" ] || { [ "$pivpnCERT" = "rsa" ] && [ "$USE_PREDEFINED_DH_PARAM" -eq 1 ]; }; then - if [ "${runUnattended}" = 'true' ]; then - echo "::: The server key and HMAC key will now be generated." - else - whiptail --msgbox --backtitle "Setup OpenVPN" --title "Server Information" "The server key and HMAC key will now be generated." ${r} ${c} - fi - fi + if [[ "${pivpnENCRYPT}" -ge 2048 ]]; then + # Set custom key size if different from the default + sed_pattern="s/#set_var EASYRSA_KEY_SIZE.*/" + sed_pattern="${sed_pattern} set_var EASYRSA_KEY_SIZE ${pivpnENCRYPT}/" + ${SUDOE} sed \ + -i \ + "${sed_pattern}" \ + pki/vars + else + # If less than 2048, then it must be 521 or lower, + # which means elliptic curve certificate was selected. + # We set the curve in this case. + declare -A ECDSA_MAP=(["256"]="prime256v1" + ["384"]="secp384r1" + ["521"]="secp521r1") - # Build the server - EASYRSA_CERT_EXPIRE=3650 ${SUDOE} ./easyrsa build-server-full "${SERVER_NAME}" nopass + sed_pattern="s/#set_var EASYRSA_CURVE.*/" + sed_pattern="${sed_pattern} set_var EASYRSA_CURVE" + sed_pattern="${sed_pattern} ${ECDSA_MAP["${pivpnENCRYPT}"]}/" + ${SUDOE} sed \ + -i \ + "${sed_pattern}" \ + pki/vars + fi - if [ "$pivpnCERT" = "rsa" ]; then - if [ "${USE_PREDEFINED_DH_PARAM}" -eq 1 ]; then - # Use Diffie-Hellman parameters from RFC 7919 (FFDHE) - ${SUDOE} install -m 644 "${pivpnFilesDir}"/files/etc/openvpn/easy-rsa/pki/ffdhe"${pivpnENCRYPT}".pem pki/dh"${pivpnENCRYPT}".pem - else - # Generate Diffie-Hellman key exchange - ${SUDOE} ./easyrsa gen-dh - ${SUDOE} mv pki/dh.pem pki/dh"${pivpnENCRYPT}".pem - fi - fi + # Build the certificate authority + printf "::: Building CA...\\n" + ${SUDOE} ./easyrsa --batch build-ca nopass + printf "\\n::: CA Complete.\\n" - # Generate static HMAC key to defend against DDoS - ${SUDOE} openvpn --genkey --secret pki/ta.key + if [[ "${pivpnCERT}" == "rsa" ]] && + [[ "${USE_PREDEFINED_DH_PARAM}" -ne 1 ]]; then + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: The server key, Diffie-Hellman parameters, \ +and HMAC key will now be generated." + else + whiptail \ + --msgbox \ + --backtitle "Setup OpenVPN" \ + --title "Server Information" \ + "The server key, Diffie-Hellman parameters, \ +and HMAC key will now be generated." \ + "${r}" \ + "${c}" + fi + elif [[ "${pivpnCERT}" == "ec" ]] || + [[ "${pivpnCERT}" == "rsa" && "${USE_PREDEFINED_DH_PARAM}" -eq 1 ]]; then + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: The server key and HMAC key will now be generated." + else + whiptail \ + --msgbox \ + --backtitle "Setup OpenVPN" \ + --title "Server Information" \ + "The server key and HMAC key will now be generated." \ + "${r}" \ + "${c}" + fi + fi - # Generate an empty Certificate Revocation List - ${SUDOE} ./easyrsa gen-crl - ${SUDOE} cp pki/crl.pem /etc/openvpn/crl.pem + # Build the server + EASYRSA_CERT_EXPIRE=3650 ${SUDOE} ./easyrsa \ + build-server-full \ + "${SERVER_NAME}" \ + nopass - if [ "${PLAT}" == 'Alpine' ]; then - if ! getent passwd "${ovpnUserGroup%:*}"; then - ${SUDOE} adduser -SDh /var/lib/openvpn/ -s /sbin/nologin "${ovpnUserGroup%:*}" - fi - else - if ! getent passwd "${ovpnUserGroup%:*}"; then - ${SUDOE} useradd --system --home /var/lib/openvpn/ --shell /usr/sbin/nologin "${ovpnUserGroup%:*}" - fi - fi + if [[ "${pivpnCERT}" == "rsa" ]]; then + if [[ "${USE_PREDEFINED_DH_PARAM}" -eq 1 ]]; then + file_pattern="${pivpnFilesDir}/files/etc/openvpn" + file_pattern="${file_pattern}/easy-rsa/pki/ffdhe${pivpnENCRYPT}.pem" + # Use Diffie-Hellman parameters from RFC 7919 (FFDHE) + ${SUDOE} install \ + -m 644 \ + "${file_pattern}" \ + "pki/dh${pivpnENCRYPT}.pem" + else + # Generate Diffie-Hellman key exchange + ${SUDOE} ./easyrsa gen-dh + ${SUDOE} mv pki/dh.pem "pki/dh${pivpnENCRYPT}".pem + fi + fi - ${SUDOE} chown "${ovpnUserGroup}" /etc/openvpn/crl.pem + # Generate static HMAC key to defend against DDoS + ${SUDOE} openvpn --genkey --secret pki/ta.key - # Write config file for server using the template.txt file - $SUDO install -m 644 "$pivpnFilesDir"/files/etc/openvpn/server_config.txt /etc/openvpn/server.conf + # Generate an empty Certificate Revocation List + ${SUDOE} ./easyrsa gen-crl + ${SUDOE} cp pki/crl.pem /etc/openvpn/crl.pem - # Apply client DNS settings - ${SUDOE} sed -i '0,/\(dhcp-option DNS \)/ s/\(dhcp-option DNS \).*/\1'"${pivpnDNS1}"'\"/' /etc/openvpn/server.conf + if ! getent passwd "${ovpnUserGroup%:*}"; then + if [[ "${PLAT}" == 'Alpine' ]]; then + ${SUDOE} adduser \ + -S \ + -D \ + -h /var/lib/openvpn/ \ + -s /sbin/nologin \ + "${ovpnUserGroup%:*}" + else + ${SUDOE} useradd \ + --system \ + --home /var/lib/openvpn/ \ + --shell /usr/sbin/nologin \ + "${ovpnUserGroup%:*}" + fi + fi - if [ -z "${pivpnDNS2}" ]; then - ${SUDOE} sed -i '/\(dhcp-option DNS \)/{n;N;d}' /etc/openvpn/server.conf - else - ${SUDOE} sed -i '0,/\(dhcp-option DNS \)/! s/\(dhcp-option DNS \).*/\1'"${pivpnDNS2}"'\"/' /etc/openvpn/server.conf - fi + ${SUDOE} chown "${ovpnUserGroup}" /etc/openvpn/crl.pem - # Set the user encryption key size - $SUDO sed -i "s#\\(dh /etc/openvpn/easy-rsa/pki/dh\\).*#\\1${pivpnENCRYPT}.pem#" /etc/openvpn/server.conf + # Write config file for server using the template.txt file + ${SUDO} install \ + -m 644 \ + "${pivpnFilesDir}/files/etc/openvpn/server_config.txt" \ + /etc/openvpn/server.conf - if [ "$pivpnTLSPROT" = "tls-crypt" ]; then - #If they enabled 2.4 use tls-crypt instead of tls-auth to encrypt control channel - $SUDO sed -i "s/tls-auth \/etc\/openvpn\/easy-rsa\/pki\/ta.key 0/tls-crypt \/etc\/openvpn\/easy-rsa\/pki\/ta.key/" /etc/openvpn/server.conf - fi + # Apply client DNS settings + ${SUDOE} sed \ + -i \ + "0,/\(dhcp-option DNS \)/ s/\(dhcp-option DNS \).*/\1${pivpnDNS1}\"/" \ + /etc/openvpn/server.conf - if [ "$pivpnCERT" = "ec" ]; then - #If they enabled 2.4 disable dh parameters and specify the matching curve from the ECDSA certificate - $SUDO sed -i "s/\(dh \/etc\/openvpn\/easy-rsa\/pki\/dh\).*/dh none\necdh-curve ${ECDSA_MAP["${pivpnENCRYPT}"]}/" /etc/openvpn/server.conf - elif [ "$pivpnCERT" = "rsa" ]; then - # Otherwise set the user encryption key size - $SUDO sed -i "s#\\(dh /etc/openvpn/easy-rsa/pki/dh\\).*#\\1${pivpnENCRYPT}.pem#" /etc/openvpn/server.conf - fi + if [[ -z "${pivpnDNS2}" ]]; then + ${SUDOE} sed -i '/\(dhcp-option DNS \)/{n;N;d}' /etc/openvpn/server.conf + else + ${SUDOE} sed \ + -i \ + "0,/\(dhcp-option DNS \)/! s/\(dhcp-option DNS \).*/\1${pivpnDNS2}\"/" \ + /etc/openvpn/server.conf + fi - # if they modified VPN network put value in server.conf - if [ "$pivpnNET" != "10.8.0.0" ]; then - $SUDO sed -i "s/10.8.0.0/${pivpnNET}/g" /etc/openvpn/server.conf - fi + # Set the user encryption key size + ${SUDO} sed \ + -i \ + "s#\\(dh /etc/openvpn/easy-rsa/pki/dh\\).*#\\1${pivpnENCRYPT}.pem#" \ + /etc/openvpn/server.conf - # if they modified VPN subnet class put value in server.conf - if [ "$(cidrToMask "$subnetClass")" != "255.255.255.0" ]; then - $SUDO sed -i "s/255.255.255.0/$(cidrToMask "$subnetClass")/g" /etc/openvpn/server.conf - fi + if [[ "${pivpnTLSPROT}" == "tls-crypt" ]]; then + # If they enabled 2.4 use tls-crypt instead of + # tls-auth to encrypt control channel + sed_pattern="s/tls-auth" + sed_pattern="${sed_pattern} \/etc\/openvpn\/easy-rsa\/pki\/ta.key 0/" + sed_pattern="${sed_pattern} tls-crypt" + sed_pattern="${sed_pattern} \/etc\/openvpn\/easy-rsa\/pki\/ta.key/" + ${SUDO} sed -i "${sed_pattern}" /etc/openvpn/server.conf + fi - # if they modified port put value in server.conf - if [ "$pivpnPORT" != 1194 ]; then - $SUDO sed -i "s/1194/${pivpnPORT}/g" /etc/openvpn/server.conf - fi + if [[ "${pivpnCERT}" == "ec" ]]; then + # If they enabled 2.4 disable dh parameters and specify the + # matching curve from the ECDSA certificate + sed_pattern="s/\(dh \/etc\/openvpn\/easy-rsa\/pki\/dh\).*/dh" + sed_pattern="${sed_pattern} none\necdh-curve" + sed_pattern="${sed_pattern} ${ECDSA_MAP["${pivpnENCRYPT}"]}/" + ${SUDO} sed \ + -i \ + "${sed_pattern}" \ + /etc/openvpn/server.conf + elif [[ "${pivpnCERT}" == "rsa" ]]; then + # Otherwise set the user encryption key size + ${SUDO} sed \ + -i \ + "s#\\(dh /etc/openvpn/easy-rsa/pki/dh\\).*#\\1${pivpnENCRYPT}.pem#" \ + /etc/openvpn/server.conf + fi - # if they modified protocol put value in server.conf - if [ "$pivpnPROTO" != "udp" ]; then - $SUDO sed -i "s/proto udp/proto tcp/g" /etc/openvpn/server.conf - fi + # if they modified VPN network put value in server.conf + if [[ "${pivpnNET}" != "10.8.0.0" ]]; then + ${SUDO} sed -i "s/10.8.0.0/${pivpnNET}/g" /etc/openvpn/server.conf + fi - if [ -n "$pivpnSEARCHDOMAIN" ]; then - $SUDO sed -i "0,/\\(.*dhcp-option.*\\)/s//push \"dhcp-option DOMAIN ${pivpnSEARCHDOMAIN}\" \\n&/" /etc/openvpn/server.conf - fi + # if they modified VPN subnet class put value in server.conf + if [[ "$(cidrToMask "${subnetClass}")" != "255.255.255.0" ]]; then + ${SUDO} sed \ + -i \ + "s/255.255.255.0/$(cidrToMask "${subnetClass}")/g" \ + /etc/openvpn/server.conf + fi - # write out server certs to conf file - $SUDO sed -i "s#\\(key /etc/openvpn/easy-rsa/pki/private/\\).*#\\1${SERVER_NAME}.key#" /etc/openvpn/server.conf - $SUDO sed -i "s#\\(cert /etc/openvpn/easy-rsa/pki/issued/\\).*#\\1${SERVER_NAME}.crt#" /etc/openvpn/server.conf + # if they modified port put value in server.conf + if [[ "${pivpnPORT}" -ne 1194 ]]; then + ${SUDO} sed -i "s/1194/${pivpnPORT}/g" /etc/openvpn/server.conf + fi - # On Alpine Linux, the default config file for OpenVPN is "/etc/openvpn/openvpn.conf" - # To avoid crash thorugh OpenRC, we symlink this file - if [[ "${PLAT}" == 'Alpine' ]]; then - ${SUDO} ln -sfT /etc/openvpn/server.conf /etc/openvpn/openvpn.conf > /dev/null - fi + # if they modified protocol put value in server.conf + if [[ "${pivpnPROTO}" != "udp" ]]; then + ${SUDO} sed -i "s/proto udp/proto tcp/g" /etc/openvpn/server.conf + fi + + if [[ -n "${pivpnSEARCHDOMAIN}" ]]; then + sed_pattern="0,/\\(.*dhcp-option.*\\)/" + sed_pattern="${sed_pattern}s//push \"dhcp-option " + sed_pattern="${sed_pattern}DOMAIN ${pivpnSEARCHDOMAIN}\" \\n&/" + ${SUDO} sed \ + -i \ + "${sed_pattern}" \ + /etc/openvpn/server.conf + fi + + # write out server certs to conf file + ${SUDO} sed \ + -i \ + "s#\\(key /etc/openvpn/easy-rsa/pki/private/\\).*#\\1${SERVER_NAME}.key#" \ + /etc/openvpn/server.conf + ${SUDO} sed \ + -i \ + "s#\\(cert /etc/openvpn/easy-rsa/pki/issued/\\).*#\\1${SERVER_NAME}.crt#" \ + /etc/openvpn/server.conf + + # On Alpine Linux, the default config file for OpenVPN is + # "/etc/openvpn/openvpn.conf". + # To avoid crash thorugh OpenRC, we symlink this file. + if [[ "${PLAT}" == 'Alpine' ]]; then + ${SUDO} ln -s \ + -f \ + -T \ + /etc/openvpn/server.conf \ + /etc/openvpn/openvpn.conf \ + > /dev/null + fi } -confOVPN(){ - $SUDO install -m 644 "$pivpnFilesDir"/files/etc/openvpn/easy-rsa/pki/Default.txt /etc/openvpn/easy-rsa/pki/Default.txt +confOVPN() { + ${SUDO} install \ + -m 644 \ + "${pivpnFilesDir}/files/etc/openvpn/easy-rsa/pki/Default.txt" \ + /etc/openvpn/easy-rsa/pki/Default.txt - $SUDO sed -i 's/IPv4pub/'"$pivpnHOST"'/' /etc/openvpn/easy-rsa/pki/Default.txt + ${SUDO} sed \ + -i \ + "s/IPv4pub/${pivpnHOST}/" \ + /etc/openvpn/easy-rsa/pki/Default.txt - # if they modified port put value in Default.txt for clients to use - if [ "$pivpnPORT" != 1194 ]; then - $SUDO sed -i -e "s/1194/${pivpnPORT}/g" /etc/openvpn/easy-rsa/pki/Default.txt - fi + # if they modified port put value in Default.txt for clients to use + if [[ "${pivpnPORT}" -ne 1194 ]]; then + ${SUDO} sed \ + -i \ + "s/1194/${pivpnPORT}/g" \ + /etc/openvpn/easy-rsa/pki/Default.txt + fi - # if they modified protocol put value in Default.txt for clients to use - if [ "$pivpnPROTO" != "udp" ]; then - $SUDO sed -i -e "s/proto udp/proto tcp/g" /etc/openvpn/easy-rsa/pki/Default.txt - fi + # if they modified protocol put value in Default.txt for clients to use + if [[ "${pivpnPROTO}" != "udp" ]]; then + ${SUDO} sed \ + -i \ + "s/proto udp/proto tcp/g" \ + /etc/openvpn/easy-rsa/pki/Default.txt + fi - # verify server name to strengthen security - $SUDO sed -i "s/SRVRNAME/${SERVER_NAME}/" /etc/openvpn/easy-rsa/pki/Default.txt + # verify server name to strengthen security + ${SUDO} sed \ + -i \ + "s/SRVRNAME/${SERVER_NAME}/" \ + /etc/openvpn/easy-rsa/pki/Default.txt - if [ "$pivpnTLSPROT" = "tls-crypt" ]; then - #If they enabled 2.4 remove key-direction options since it's not required - $SUDO sed -i "/key-direction 1/d" /etc/openvpn/easy-rsa/pki/Default.txt - fi + if [[ "${pivpnTLSPROT}" == "tls-crypt" ]]; then + # If they enabled 2.4 remove key-direction options since it's not required + ${SUDO} sed \ + -i \ + "/key-direction 1/d" \ + /etc/openvpn/easy-rsa/pki/Default.txt + fi } -confWireGuard(){ - # Reload job type is not yet available in wireguard-tools shipped with Ubuntu 20.04 - if [ "${PLAT}" == 'Alpine' ]; then - echo '::: Adding wg-quick unit' +confWireGuard() { + # Reload job type is not yet available in wireguard-tools shipped with + # Ubuntu 20.04 + if [[ "${PLAT}" == 'Alpine' ]]; then + echo '::: Adding wg-quick unit' + ${SUDO} install \ + -m 0755 \ + "${pivpnFilesDir}/files/etc/init.d/wg-quick" \ + /etc/init.d/wg-quick + else + if ! grep -q 'ExecReload' /lib/systemd/system/wg-quick@.service; then + local wireguard_service_path + wireguard_service_path="${pivpnFilesDir}/files/etc/systemd/system" + wireguard_service_path="${wireguard_service_path}/wg-quick@.service.d" + wireguard_service_path="${wireguard_service_path}/override.conf" + echo "::: Adding additional reload job type for wg-quick unit" + ${SUDO} install \ + -D \ + -m 644 \ + "${wireguard_service_path}" \ + /etc/systemd/system/wg-quick@.service.d/override.conf + ${SUDO} systemctl daemon-reload + fi + fi - ${SUDO} install -m 0755 "${pivpnFilesDir}/files/etc/init.d/wg-quick" /etc/init.d/wg-quick - else - if ! grep -q 'ExecReload' /lib/systemd/system/wg-quick@.service; then - echo "::: Adding additional reload job type for wg-quick unit" - $SUDO install -D -m 644 "${pivpnFilesDir}"/files/etc/systemd/system/wg-quick@.service.d/override.conf /etc/systemd/system/wg-quick@.service.d/override.conf - $SUDO systemctl daemon-reload - fi - fi + if [[ -d /etc/wireguard ]]; then + # Backup the wireguard folder + WIREGUARD_BACKUP="wireguard_$(date +%Y-%m-%d-%H%M%S).tar.gz" + echo "::: Backing up the wireguard folder to /etc/${WIREGUARD_BACKUP}" + CURRENT_UMASK="$(umask)" + umask 0077 + ${SUDO} tar -czf "/etc/${WIREGUARD_BACKUP}" /etc/wireguard &> /dev/null + umask "${CURRENT_UMASK}" - if [ -d /etc/wireguard ]; then - # Backup the wireguard folder - WIREGUARD_BACKUP="wireguard_$(date +%Y-%m-%d-%H%M%S).tar.gz" - echo "::: Backing up the wireguard folder to /etc/${WIREGUARD_BACKUP}" - CURRENT_UMASK=$(umask) - umask 0077 - $SUDO tar czf "/etc/${WIREGUARD_BACKUP}" /etc/wireguard &> /dev/null - umask "$CURRENT_UMASK" + if [[ -f /etc/wireguard/wg0.conf ]]; then + ${SUDO} rm /etc/wireguard/wg0.conf + fi + else + # If compiled from source, the wireguard folder is not being created + ${SUDO} mkdir /etc/wireguard + fi - if [ -f /etc/wireguard/wg0.conf ]; then - $SUDO rm /etc/wireguard/wg0.conf - fi - else - # If compiled from source, the wireguard folder is not being created - $SUDO mkdir /etc/wireguard - fi + # Ensure that only root is able to enter the wireguard folder + ${SUDO} chown root:root /etc/wireguard + ${SUDO} chmod 700 /etc/wireguard - # Ensure that only root is able to enter the wireguard folder - $SUDO chown root:root /etc/wireguard - $SUDO chmod 700 /etc/wireguard + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: The Server Keys will now be generated." + else + whiptail \ + --title "Server Information" \ + --msgbox "The Server Keys will now be generated." \ + "${r}" \ + "${c}" + fi - if [ "${runUnattended}" = 'true' ]; then - echo "::: The Server Keys will now be generated." - else - whiptail --title "Server Information" --msgbox "The Server Keys will now be generated." "${r}" "${c}" - fi + # Remove configs and keys folders to make space for a new server when + # using 'Repair' or 'Reconfigure' over an existing installation + ${SUDO} rm -rf /etc/wireguard/configs + ${SUDO} rm -rf /etc/wireguard/keys - # Remove configs and keys folders to make space for a new server when using 'Repair' or 'Reconfigure' - # over an existing installation - $SUDO rm -rf /etc/wireguard/configs - $SUDO rm -rf /etc/wireguard/keys + ${SUDO} mkdir -p /etc/wireguard/configs + ${SUDO} touch /etc/wireguard/configs/clients.txt + ${SUDO} mkdir -p /etc/wireguard/keys - $SUDO mkdir -p /etc/wireguard/configs - $SUDO touch /etc/wireguard/configs/clients.txt - $SUDO mkdir -p /etc/wireguard/keys + # Generate private key and derive public key from it + wg genkey | + ${SUDO} tee /etc/wireguard/keys/server_priv &> /dev/null + ${SUDO} cat /etc/wireguard/keys/server_priv | + wg pubkey | + ${SUDO} tee /etc/wireguard/keys/server_pub &> /dev/null - # Generate private key and derive public key from it - wg genkey | $SUDO tee /etc/wireguard/keys/server_priv &> /dev/null - $SUDO cat /etc/wireguard/keys/server_priv | wg pubkey | $SUDO tee /etc/wireguard/keys/server_pub &> /dev/null + echo "::: Server Keys have been generated." - echo "::: Server Keys have been generated." + { + echo '[Interface]' + echo "PrivateKey = $(${SUDO} cat /etc/wireguard/keys/server_priv)" + echo -n "Address = ${vpnGw}/${subnetClass}" - { - echo '[Interface]' - echo "PrivateKey = $(${SUDO} cat /etc/wireguard/keys/server_priv)" - echo -n "Address = ${vpnGw}/${subnetClass}" + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + echo ",${vpnGwv6}/${subnetClassv6}" + else + echo + fi - if [ "$pivpnenableipv6" == "1" ]; then - echo ",${vpnGwv6}/${subnetClassv6}" - else - echo - fi + echo "MTU = ${pivpnMTU}" + echo "ListenPort = ${pivpnPORT}" + } | ${SUDO} tee /etc/wireguard/wg0.conf &> /dev/null - echo "MTU = ${pivpnMTU}" - echo "ListenPort = ${pivpnPORT}" - } | ${SUDO} tee /etc/wireguard/wg0.conf &> /dev/null - - echo "::: Server config generated." + echo "::: Server config generated." } -confNetwork(){ - # Enable forwarding of internet traffic - echo 'net.ipv4.ip_forward=1' | $SUDO tee /etc/sysctl.d/99-pivpn.conf > /dev/null +confNetwork() { + # Enable forwarding of internet traffic + echo 'net.ipv4.ip_forward=1' | + ${SUDO} tee /etc/sysctl.d/99-pivpn.conf > /dev/null - if [ "$pivpnenableipv6" == "1" ]; then - echo "net.ipv6.conf.all.forwarding=1 -net.ipv6.conf.${IPv6dev}.accept_ra=2" | $SUDO tee -a /etc/sysctl.d/99-pivpn.conf > /dev/null - fi + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + { + echo "net.ipv6.conf.all.forwarding=1" + echo "net.ipv6.conf.${IPv6dev}.accept_ra=2" + } | ${SUDO} tee -a /etc/sysctl.d/99-pivpn.conf > /dev/null + fi - ${SUDO} sysctl -p /etc/sysctl.d/99-pivpn.conf > /dev/null + ${SUDO} sysctl -p /etc/sysctl.d/99-pivpn.conf > /dev/null - if [ "$USING_UFW" -eq 1 ]; then + if [[ "${USING_UFW}" -eq 1 ]]; then + echo "::: Detected UFW is enabled." + echo "::: Adding UFW rules..." - echo "::: Detected UFW is enabled." - echo "::: Adding UFW rules..." - ### Basic safeguard: if file is empty, there's been something weird going on. - ### Note: no safeguard against imcomplete content as a result of previous failures. - if test -s /etc/ufw/before.rules; then - $SUDO cp -f /etc/ufw/before.rules /etc/ufw/before.rules.pre-pivpn - else - echo "$0: ERR: Sorry, won't touch empty file \"/etc/ufw/before.rules\"."; - exit 1; - fi - if test -s /etc/ufw/before6.rules; then - $SUDO cp -f /etc/ufw/before6.rules /etc/ufw/before6.rules.pre-pivpn - else - echo "$0: ERR: Sorry, won't touch empty file \"/etc/ufw/before6.rules\"."; - exit 1; - fi - ### If there is already a "*nat" section just add our POSTROUTING MASQUERADE - if $SUDO grep -q "*nat" /etc/ufw/before.rules; then - ### Onyl add the IPv4 NAT rule if it isn't already there - if ! $SUDO grep -q "${VPN}-nat-rule" /etc/ufw/before.rules; then - $SUDO sed "/^*nat/{n;s/\(:POSTROUTING ACCEPT .*\)/\1\n-I POSTROUTING -s ${pivpnNET}\/${subnetClass} -o ${IPv4dev} -j MASQUERADE -m comment --comment ${VPN}-nat-rule/}" -i /etc/ufw/before.rules - fi - else - $SUDO sed "/delete these required/i *nat\n:POSTROUTING ACCEPT [0:0]\n-I POSTROUTING -s ${pivpnNET}\/${subnetClass} -o ${IPv4dev} -j MASQUERADE -m comment --comment ${VPN}-nat-rule\nCOMMIT\n" -i /etc/ufw/before.rules - fi - if [ "$pivpnenableipv6" == "1" ]; then - if $SUDO grep -q "*nat" /etc/ufw/before6.rules; then - ### Onyl add the IPv6 NAT rule if it isn't already there - if ! $SUDO grep -q "${VPN}-nat-rule" /etc/ufw/before6.rules; then - $SUDO sed "/^*nat/{n;s/\(:POSTROUTING ACCEPT .*\)/\1\n-I POSTROUTING -s ${pivpnNETv6}\/${subnetClassv6} -o ${IPv6dev} -j MASQUERADE -m comment --comment ${VPN}-nat-rule/}" -i /etc/ufw/before6.rules - fi - else - $SUDO sed "/delete these required/i *nat\n:POSTROUTING ACCEPT [0:0]\n-I POSTROUTING -s ${pivpnNETv6}\/${subnetClassv6} -o ${IPv6dev} -j MASQUERADE -m comment --comment ${VPN}-nat-rule\nCOMMIT\n" -i /etc/ufw/before6.rules - fi - fi - # Insert rules at the beginning of the chain (in case there are other rules that may drop the traffic) - $SUDO ufw insert 1 allow "${pivpnPORT}"/"${pivpnPROTO}" comment allow-${VPN} >/dev/null - $SUDO ufw route insert 1 allow in on "${pivpnDEV}" from "${pivpnNET}/${subnetClass}" out on "${IPv4dev}" to any >/dev/null - if [ "$pivpnenableipv6" == "1" ]; then - $SUDO ufw route insert 1 allow in on "${pivpnDEV}" from "${pivpnNETv6}/${subnetClassv6}" out on "${IPv6dev}" to any >/dev/null - fi + ### Basic safeguard: if file is empty, there's been something weird going + ### on. + ### Note: no safeguard against imcomplete content as a result of previous + ### failures. + if [[ -s /etc/ufw/before.rules ]]; then + ${SUDO} cp -f /etc/ufw/before.rules /etc/ufw/before.rules.pre-pivpn + else + err "${0}: ERR: Sorry, won't touch empty file \"/etc/ufw/before.rules\"." + exit 1 + fi - $SUDO ufw reload >/dev/null - echo "::: UFW configuration completed." + if [[ -s /etc/ufw/before6.rules ]]; then + ${SUDO} cp -f /etc/ufw/before6.rules /etc/ufw/before6.rules.pre-pivpn + else + err "${0}: ERR: Sorry, won't touch empty file \"/etc/ufw/before6.rules\"." + exit 1 + fi - elif [ "$USING_UFW" -eq 0 ]; then + ### If there is already a "*nat" section just add our POSTROUTING MASQUERADE + if ${SUDO} grep -q "*nat" /etc/ufw/before.rules; then + local sed_pattern - # Now some checks to detect which rules we need to add. On a newly installed system all policies - # should be ACCEPT, so the only required rule would be the MASQUERADE one. + ### Onyl add the IPv4 NAT rule if it isn't already there + if ! ${SUDO} grep -q "${VPN}-nat-rule" /etc/ufw/before.rules; then + sed_pattern="/^*nat/{n;" + sed_pattern="${sed_pattern}s/\(:POSTROUTING ACCEPT .*\)/" + sed_pattern="${sed_pattern}\1\n-I POSTROUTING" + sed_pattern="${sed_pattern} -s ${pivpnNET}\/${subnetClass}" + sed_pattern="${sed_pattern} -o ${IPv4dev}" + sed_pattern="${sed_pattern} -j MASQUERADE" + sed_pattern="${sed_pattern} -m comment" + sed_pattern="${sed_pattern} --comment ${VPN}-nat-rule/" + sed_pattern="${sed_pattern}}" + ${SUDO} sed "${sed_pattern}" -i /etc/ufw/before.rules + fi + else + sed_pattern="/delete these required/i" + sed_pattern="${sed_pattern} *nat\n:POSTROUTING ACCEPT [0:0]\n" + sed_pattern="${sed_pattern}-I POSTROUTING" + sed_pattern="${sed_pattern} -s ${pivpnNET}\/${subnetClass}" + sed_pattern="${sed_pattern} -o ${IPv4dev}" + sed_pattern="${sed_pattern} -j MASQUERADE" + sed_pattern="${sed_pattern} -m comment" + sed_pattern="${sed_pattern} --comment ${VPN}-nat-rule\n" + sed_pattern="${sed_pattern} COMMIT\n" + ${SUDO} sed "${sed_pattern}" -i /etc/ufw/before.rules + fi - if ! $SUDO iptables -t nat -S | grep -q "${VPN}-nat-rule"; then - $SUDO iptables -t nat -I POSTROUTING -s "${pivpnNET}/${subnetClass}" -o "${IPv4dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" - fi - if [ "$pivpnenableipv6" == "1" ]; then - if ! $SUDO ip6tables -t nat -S | grep -q "${VPN}-nat-rule"; then - $SUDO ip6tables -t nat -I POSTROUTING -s "${pivpnNETv6}/${subnetClassv6}" -o "${IPv6dev}" -j MASQUERADE -m comment --comment "${VPN}-nat-rule" - fi - fi - # Count how many rules are in the INPUT and FORWARD chain. When parsing input from - # iptables -S, '^-P' skips the policies and 'ufw-' skips ufw chains (in case ufw was found - # installed but not enabled). + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + local sed_pattern - # Grep returns non 0 exit code where there are no matches, however that would make the script exit, - # for this reasons we use '|| true' to force exit code 0 - INPUT_RULES_COUNT="$($SUDO iptables -S INPUT | grep -vcE '(^-P|ufw-)')" - FORWARD_RULES_COUNT="$($SUDO iptables -S FORWARD | grep -vcE '(^-P|ufw-)')" - INPUT_POLICY="$($SUDO iptables -S INPUT | grep '^-P' | awk '{print $3}')" - FORWARD_POLICY="$($SUDO iptables -S FORWARD | grep '^-P' | awk '{print $3}')" + if ${SUDO} grep -q "*nat" /etc/ufw/before6.rules; then + ### Onyl add the IPv6 NAT rule if it isn't already there + if ! ${SUDO} grep -q "${VPN}-nat-rule" /etc/ufw/before6.rules; then + sed_pattern="/^*nat/{n;" + sed_pattern="${sed_pattern}s/\(:POSTROUTING ACCEPT .*\)/" + sed_pattern="${sed_pattern}\1\n-I POSTROUTING" + sed_pattern="${sed_pattern} -s ${pivpnNETv6}\/${subnetClassv6}" + sed_pattern="${sed_pattern} -o ${IPv6dev}" + sed_pattern="${sed_pattern} -j MASQUERADE" + sed_pattern="${sed_pattern} -m comment" + sed_pattern="${sed_pattern} --comment ${VPN}-nat-rule/" + sed_pattern="${sed_pattern}}" + ${SUDO} sed "${sed_pattern}" -i /etc/ufw/before6.rules + fi + else + sed_pattern="/delete these required/i" + sed_pattern="${sed_pattern} *nat\n:POSTROUTING ACCEPT [0:0]\n" + sed_pattern="${sed_pattern}-I POSTROUTING" + sed_pattern="${sed_pattern} -s ${pivpnNETv6}\/${subnetClassv6}" + sed_pattern="${sed_pattern} -o ${IPv6dev}" + sed_pattern="${sed_pattern} -j MASQUERADE" + sed_pattern="${sed_pattern} -m comment" + sed_pattern="${sed_pattern} --comment ${VPN}-nat-rule\n" + sed_pattern="${sed_pattern} COMMIT\n" + ${SUDO} sed "${sed_pattern}" -i /etc/ufw/before6.rules + fi + fi - if [ "$pivpnenableipv6" == "1" ]; then - INPUT_RULES_COUNTv6="$($SUDO ip6tables -S INPUT | grep -vcE '(^-P|ufw-)')" - FORWARD_RULES_COUNTv6="$($SUDO ip6tables -S FORWARD | grep -vcE '(^-P|ufw-)')" - INPUT_POLICYv6="$($SUDO ip6tables -S INPUT | grep '^-P' | awk '{print $3}')" - FORWARD_POLICYv6="$($SUDO ip6tables -S FORWARD | grep '^-P' | awk '{print $3}')" - fi + # Insert rules at the beginning of the chain + # (in case there are other rules that may drop the traffic) + ${SUDO} ufw insert 1 \ + allow "${pivpnPORT}/${pivpnPROTO}" \ + comment "allow-${VPN}" > /dev/null + ${SUDO} ufw route insert 1 \ + allow in on "${pivpnDEV}" \ + from "${pivpnNET}/${subnetClass}" \ + out on "${IPv4dev}" to any > /dev/null - # If rules count is not zero, we assume we need to explicitly allow traffic. Same conclusion if - # there are no rules and the policy is not ACCEPT. Note that rules are being added to the top of the - # chain (using -I). + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + ${SUDO} ufw route insert 1 \ + allow in on "${pivpnDEV}" \ + from "${pivpnNETv6}/${subnetClassv6}" \ + out on "${IPv6dev}" to any > /dev/null + fi - if [ "$INPUT_RULES_COUNT" -ne 0 ] || [ "$INPUT_POLICY" != "ACCEPT" ]; then - if $SUDO iptables -S | grep -q "${VPN}-input-rule"; then - INPUT_CHAIN_EDITED=0 - else - $SUDO iptables -I INPUT 1 -i "${IPv4dev}" -p "${pivpnPROTO}" --dport "${pivpnPORT}" -j ACCEPT -m comment --comment "${VPN}-input-rule" - fi - INPUT_CHAIN_EDITED=1 - else - INPUT_CHAIN_EDITED=0 - fi + ${SUDO} ufw reload > /dev/null + echo "::: UFW configuration completed." + return + fi - if [ "$pivpnenableipv6" == "1" ]; then - if [ "$INPUT_RULES_COUNTv6" -ne 0 ] || [ "$INPUT_POLICYv6" != "ACCEPT" ]; then - if $SUDO ip6tables -S | grep -q "${VPN}-input-rule"; then - INPUT_CHAIN_EDITEDv6=0 - else - $SUDO ip6tables -I INPUT 1 -i "${IPv6dev}" -p "${pivpnPROTO}" --dport "${pivpnPORT}" -j ACCEPT -m comment --comment "${VPN}-input-rule" - fi - INPUT_CHAIN_EDITEDv6=1 - else - INPUT_CHAIN_EDITEDv6=0 - fi - fi + # Now some checks to detect which rules we need to add. + # On a newly installed system all policies should be ACCEPT, + # so the only required rule would be the MASQUERADE one. - if [ "$FORWARD_RULES_COUNT" -ne 0 ] || [ "$FORWARD_POLICY" != "ACCEPT" ]; then - if $SUDO iptables -S | grep -q "${VPN}-forward-rule"; then - FORWARD_CHAIN_EDITED=0 - else - $SUDO iptables -I FORWARD 1 -d "${pivpnNET}/${subnetClass}" -i "${IPv4dev}" -o "${pivpnDEV}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "${VPN}-forward-rule" - $SUDO iptables -I FORWARD 2 -s "${pivpnNET}/${subnetClass}" -i "${pivpnDEV}" -o "${IPv4dev}" -j ACCEPT -m comment --comment "${VPN}-forward-rule" - fi - FORWARD_CHAIN_EDITED=1 - else - FORWARD_CHAIN_EDITED=0 - fi + if ! ${SUDO} iptables -t nat -S | + grep -q "${VPN}-nat-rule"; then + ${SUDO} iptables \ + -t nat \ + -I POSTROUTING \ + -s "${pivpnNET}/${subnetClass}" \ + -o "${IPv4dev}" \ + -j MASQUERADE \ + -m comment \ + --comment "${VPN}-nat-rule" + fi - if [ "$pivpnenableipv6" == "1" ]; then - if [ "$FORWARD_RULES_COUNTv6" -ne 0 ] || [ "$FORWARD_POLICYv6" != "ACCEPT" ]; then - if $SUDO ip6tables -S | grep -q "${VPN}-forward-rule"; then - FORWARD_CHAIN_EDITEDv6=0 - else - $SUDO ip6tables -I FORWARD 1 -d "${pivpnNETv6}/${subnetClassv6}" -i "${IPv6dev}" -o "${pivpnDEV}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "${VPN}-forward-rule" - $SUDO ip6tables -I FORWARD 2 -s "${pivpnNETv6}/${subnetClassv6}" -i "${pivpnDEV}" -o "${IPv6dev}" -j ACCEPT -m comment --comment "${VPN}-forward-rule" - fi - FORWARD_CHAIN_EDITEDv6=1 - else - FORWARD_CHAIN_EDITEDv6=0 - fi - fi + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + if ! ${SUDO} ip6tables -t nat -S | + grep -q "${VPN}-nat-rule"; then + ${SUDO} ip6tables \ + -t nat \ + -I POSTROUTING \ + -s "${pivpnNETv6}/${subnetClassv6}" \ + -o "${IPv6dev}" \ + -j MASQUERADE \ + -m comment \ + --comment "${VPN}-nat-rule" + fi + fi - case ${PLAT} in - Debian|Raspbian|Ubuntu) - $SUDO iptables-save | $SUDO tee /etc/iptables/rules.v4 > /dev/null - ;; - esac + # Count how many rules are in the INPUT and FORWARD chain. + # When parsing input from iptables -S, '^-P' skips the policies + # and 'ufw-' skips ufw chains (in case ufw was found + # installed but not enabled). - case ${PLAT} in - Debian|Raspbian|Ubuntu) - $SUDO ip6tables-save | $SUDO tee /etc/iptables/rules.v6 > /dev/null - ;; - esac + # Grep returns non 0 exit code where there are no matches, + # however that would make the script exit, + # for this reasons we use '|| true' to force exit code 0 + INPUT_RULES_COUNT="$(${SUDO} iptables -S INPUT | + grep -vcE '(^-P|ufw-)')" + FORWARD_RULES_COUNT="$(${SUDO} iptables -S FORWARD | + grep -vcE '(^-P|ufw-)')" + INPUT_POLICY="$(${SUDO} iptables -S INPUT | + grep '^-P' | + awk '{print $3}')" + FORWARD_POLICY="$(${SUDO} iptables -S FORWARD | + grep '^-P' | + awk '{print $3}')" - { - echo "INPUT_CHAIN_EDITED=${INPUT_CHAIN_EDITED}" - echo "FORWARD_CHAIN_EDITED=${FORWARD_CHAIN_EDITED}" - echo "INPUT_CHAIN_EDITEDv6=${INPUT_CHAIN_EDITEDv6}" - echo "FORWARD_CHAIN_EDITEDv6=${FORWARD_CHAIN_EDITEDv6}" - } >> ${tempsetupVarsFile} - fi + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + INPUT_RULES_COUNTv6="$(${SUDO} ip6tables -S INPUT | + grep -vcE '(^-P|ufw-)')" + FORWARD_RULES_COUNTv6="$(${SUDO} ip6tables -S FORWARD | + grep -vcE '(^-P|ufw-)')" + INPUT_POLICYv6="$(${SUDO} ip6tables -S INPUT | + grep '^-P' | + awk '{print $3}')" + FORWARD_POLICYv6="$(${SUDO} ip6tables -S FORWARD | + grep '^-P' | + awk '{print $3}')" + fi + + # If rules count is not zero, we assume we need to explicitly allow traffic. + # Same conclusion if there are no rules and the policy is not ACCEPT. + # Note that rules are being added to the top of the chain (using -I). + + if [[ "${INPUT_RULES_COUNT}" -ne 0 ]] || + [[ "${INPUT_POLICY}" != "ACCEPT" ]]; then + if ! ${SUDO} iptables -S | + grep -q "${VPN}-input-rule"; then + ${SUDO} iptables \ + -I INPUT 1 \ + -i "${IPv4dev}" \ + -p "${pivpnPROTO}" \ + --dport "${pivpnPORT}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-input-rule" + fi + + INPUT_CHAIN_EDITED=1 + else + INPUT_CHAIN_EDITED=0 + fi + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + if [[ "${INPUT_RULES_COUNTv6}" -ne 0 ]] || + [[ "${INPUT_POLICYv6}" != "ACCEPT" ]]; then + if ! ${SUDO} ip6tables -S | + grep -q "${VPN}-input-rule"; then + ${SUDO} ip6tables \ + -I INPUT 1 \ + -i "${IPv6dev}" \ + -p "${pivpnPROTO}" \ + --dport "${pivpnPORT}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-input-rule" + fi + + INPUT_CHAIN_EDITEDv6=1 + else + INPUT_CHAIN_EDITEDv6=0 + fi + fi + + if [[ "${FORWARD_RULES_COUNT}" -ne 0 ]] || + [[ "${FORWARD_POLICY}" != "ACCEPT" ]]; then + if ! ${SUDO} iptables -S | + grep -q "${VPN}-forward-rule"; then + ${SUDO} iptables \ + -I FORWARD 1 \ + -d "${pivpnNET}/${subnetClass}" \ + -i "${IPv4dev}" \ + -o "${pivpnDEV}" \ + -m conntrack \ + --ctstate RELATED,ESTABLISHED \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + ${SUDO} iptables \ + -I FORWARD 2 \ + -s "${pivpnNET}/${subnetClass}" \ + -i "${pivpnDEV}" \ + -o "${IPv4dev}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + fi + + FORWARD_CHAIN_EDITED=1 + else + FORWARD_CHAIN_EDITED=0 + fi + + if [[ "${pivpnenableipv6}" -eq 1 ]]; then + if [[ "${FORWARD_RULES_COUNTv6}" -ne 0 ]] || + [[ "${FORWARD_POLICYv6}" != "ACCEPT" ]]; then + if ! ${SUDO} ip6tables -S | + grep -q "${VPN}-forward-rule"; then + ${SUDO} ip6tables \ + -I FORWARD 1 \ + -d "${pivpnNETv6}/${subnetClassv6}" \ + -i "${IPv6dev}" \ + -o "${pivpnDEV}" \ + -m conntrack \ + --ctstate RELATED,ESTABLISHED \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + ${SUDO} ip6tables \ + -I FORWARD 2 \ + -s "${pivpnNETv6}/${subnetClassv6}" \ + -i "${pivpnDEV}" \ + -o "${IPv6dev}" \ + -j ACCEPT \ + -m comment \ + --comment "${VPN}-forward-rule" + fi + + FORWARD_CHAIN_EDITEDv6=1 + else + FORWARD_CHAIN_EDITEDv6=0 + fi + fi + + case "${PLAT}" in + Debian | Raspbian | Ubuntu) + ${SUDO} iptables-save | + ${SUDO} tee /etc/iptables/rules.v4 > /dev/null + ${SUDO} ip6tables-save | + ${SUDO} tee /etc/iptables/rules.v6 > /dev/null + ;; + esac + + { + echo "INPUT_CHAIN_EDITED=${INPUT_CHAIN_EDITED}" + echo "FORWARD_CHAIN_EDITED=${FORWARD_CHAIN_EDITED}" + echo "INPUT_CHAIN_EDITEDv6=${INPUT_CHAIN_EDITEDv6}" + echo "FORWARD_CHAIN_EDITEDv6=${FORWARD_CHAIN_EDITEDv6}" + } >> "${tempsetupVarsFile}" } confLogging() { - # Pre-create rsyslog/logrotate config directories if missing, to assure logs are handled as expected when those are installed at a later time - $SUDO mkdir -p /etc/{rsyslog,logrotate}.d + # Pre-create rsyslog/logrotate config directories if missing, + # to assure logs are handled as expected when those are + # installed at a later time + ${SUDO} mkdir -p /etc/{rsyslog,logrotate}.d - if [ "${PLAT}" == 'Alpine' ]; then - program_name='openvpn' - else - program_name='ovpn-server' - fi + if [[ "${PLAT}" == 'Alpine' ]]; then + program_name='openvpn' + else + program_name='ovpn-server' + fi - echo "if \$programname == '${program_name}' then /var/log/openvpn.log -if \$programname == '${program_name}' then stop" | $SUDO tee /etc/rsyslog.d/30-openvpn.conf > /dev/null + { + echo "if \$programname == '${program_name}' then /var/log/openvpn.log" + echo "if \$programname == '${program_name}' then stop" + } | ${SUDO} tee /etc/rsyslog.d/30-openvpn.conf > /dev/null - echo "/var/log/openvpn.log -{ - rotate 4 - weekly - missingok - notifempty - compress - delaycompress - sharedscripts - postrotate - invoke-rc.d rsyslog rotate >/dev/null 2>&1 || true - endscript -}" | $SUDO tee /etc/logrotate.d/openvpn > /dev/null + { + echo "/var/log/openvpn.log" + echo "{" + echo $'\t'"rotate 4" + echo $'\t'"weekly" + echo $'\t'"missingok" + echo $'\t'"notifempty" + echo $'\t'"compress" + echo $'\t'"delaycompress" + echo $'\t'"sharedscripts" + echo $'\t'"postrotate" + echo $'\t'$'\t'"invoke-rc.d rsyslog rotate >/dev/null 2>&1 || true" + echo $'\t'"endscript" + echo "}" + } | ${SUDO} tee /etc/logrotate.d/openvpn > /dev/null - # Restart the logging service - case "${PLAT}" in - Debian | Raspbian | Ubuntu) - $SUDO systemctl -q is-active rsyslog.service && $SUDO systemctl restart rsyslog.service - ;; - Alpine) - ${SUDO} rc-service -is rsyslog restart - ${SUDO} rc-service -iN rsyslog start - ;; - esac + # Restart the logging service + case "${PLAT}" in + Debian | Raspbian | Ubuntu) + ${SUDO} systemctl -q is-active rsyslog.service && + ${SUDO} systemctl restart rsyslog.service + ;; + Alpine) + ${SUDO} rc-service -is rsyslog restart + ${SUDO} rc-service -iN rsyslog start + ;; + esac } +restartServices() { + # Start services + echo "::: Restarting services..." -restartServices(){ - # Start services - echo "::: Restarting services..." - case ${PLAT} in - Debian|Raspbian|Ubuntu) - if [ "$VPN" = "openvpn" ]; then - $SUDO systemctl enable openvpn.service &> /dev/null - $SUDO systemctl restart openvpn.service - elif [ "$VPN" = "wireguard" ]; then - $SUDO systemctl enable wg-quick@wg0.service &> /dev/null - $SUDO systemctl restart wg-quick@wg0.service - fi - ;; - Alpine) - if [ "${VPN}" == 'openvpn' ]; then - ${SUDO} rc-update add openvpn default &> /dev/null - ${SUDO} rc-service -s openvpn restart - ${SUDO} rc-service -N openvpn start - elif [ "${VPN}" == 'wireguard' ]; then - ${SUDO} rc-update add wg-quick default &> /dev/null - ${SUDO} rc-service -s wg-quick restart - ${SUDO} rc-service -N wg-quick start - fi - ;; - esac + case "${PLAT}" in + Debian | Raspbian | Ubuntu) + if [[ "${VPN}" == "openvpn" ]]; then + ${SUDO} systemctl enable openvpn.service &> /dev/null + ${SUDO} systemctl restart openvpn.service + elif [[ "${VPN}" == "wireguard" ]]; then + ${SUDO} systemctl enable wg-quick@wg0.service &> /dev/null + ${SUDO} systemctl restart wg-quick@wg0.service + fi + + ;; + Alpine) + if [[ "${VPN}" == 'openvpn' ]]; then + ${SUDO} rc-update add openvpn default &> /dev/null + ${SUDO} rc-service -s openvpn restart + ${SUDO} rc-service -N openvpn start + elif [[ "${VPN}" == 'wireguard' ]]; then + ${SUDO} rc-update add wg-quick default &> /dev/null + ${SUDO} rc-service -s wg-quick restart + ${SUDO} rc-service -N wg-quick start + fi + + ;; + esac } -askUnattendedUpgrades(){ - if [ "${runUnattended}" = 'true' ]; then - if [ -z "$UNATTUPG" ]; then - UNATTUPG=1 - echo "::: No preference regarding unattended upgrades, assuming yes" - else - if [ "$UNATTUPG" -eq 1 ]; then - echo "::: Enabling unattended upgrades" - else - echo "::: Skipping unattended upgrades" - fi - fi - echo "UNATTUPG=${UNATTUPG}" >> ${tempsetupVarsFile} - return - fi +askUnattendedUpgrades() { + if [[ "${runUnattended}" == 'true' ]]; then + if [[ -z "${UNATTUPG}" ]]; then + UNATTUPG=1 + echo "::: No preference regarding unattended upgrades, assuming yes" + else + if [[ "${UNATTUPG}" -eq 1 ]]; then + echo "::: Enabling unattended upgrades" + else + echo "::: Skipping unattended upgrades" + fi + fi - whiptail --msgbox --backtitle "Security Updates" --title "Unattended Upgrades" "Since this server will have at least one port open to the internet, it is recommended you enable unattended-upgrades.\\nThis feature will check daily for security package updates only and apply them when necessary.\\nIt will NOT automatically reboot the server so to fully apply some updates you should periodically reboot." ${r} ${c} + echo "UNATTUPG=${UNATTUPG}" >> "${tempsetupVarsFile}" + return + fi - if (whiptail --backtitle "Security Updates" --title "Unattended Upgrades" --yesno "Do you want to enable unattended upgrades of security patches to this server?" ${r} ${c}); then - UNATTUPG=1 - else - UNATTUPG=0 - fi + whiptail \ + --msgbox \ + --backtitle "Security Updates" \ + --title "Unattended Upgrades" \ + "Since this server will have at least one port open to the internet, \ +it is recommended you enable unattended-upgrades. +This feature will check daily for security package updates only and apply \ +them when necessary. +It will NOT automatically reboot the server so to fully apply some updates \ +you should periodically reboot." \ + "${r}" \ + "${c}" - echo "UNATTUPG=${UNATTUPG}" >> ${tempsetupVarsFile} + if whiptail \ + --backtitle "Security Updates" \ + --title "Unattended Upgrades" \ + --yesno \ + "Do you want to enable unattended upgrades \ +of security patches to this server?" \ + "${r}" \ + "${c}"; then + UNATTUPG=1 + else + UNATTUPG=0 + fi + + echo "UNATTUPG=${UNATTUPG}" >> "${tempsetupVarsFile}" } -confUnattendedUpgrades(){ - local PIVPN_DEPS +confUnattendedUpgrades() { + local PIVPN_DEPS periodic_file - if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - PIVPN_DEPS=(unattended-upgrades) - installDependentPackages PIVPN_DEPS[@] - aptConfDir="/etc/apt/apt.conf.d" + if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then + PIVPN_DEPS=(unattended-upgrades) + installDependentPackages PIVPN_DEPS[@] + aptConfDir="/etc/apt/apt.conf.d" - if [ "$PLAT" = "Ubuntu" ]; then - # Ubuntu 50unattended-upgrades should already just have security enabled - # so we just need to configure the 10periodic file - echo "APT::Periodic::Update-Package-Lists \"1\"; - APT::Periodic::Download-Upgradeable-Packages \"1\"; - APT::Periodic::AutocleanInterval \"5\"; - APT::Periodic::Unattended-Upgrade \"1\";" | $SUDO tee "${aptConfDir}/10periodic" > /dev/null - else + # Raspbian's unattended-upgrades package downloads Debian's config, + # so we copy over the proper config + # https://github.com/mvo5/unattended-upgrades/blob/master/data/50unattended-upgrades.Raspbian + # Add the remaining settings for all other distributions + if [[ "${PLAT}" == "Raspbian" ]]; then + ${SUDO} install \ + -m 644 \ + "${pivpnFilesDir}/files${aptConfDir}/50unattended-upgrades.Raspbian" \ + "${aptConfDir}/50unattended-upgrades" + fi - # Raspbian's unattended-upgrades package downloads Debian's config, so we copy over the proper config - # Source: https://github.com/mvo5/unattended-upgrades/blob/master/data/50unattended-upgrades.Raspbian - if [ "$PLAT" = "Raspbian" ]; then - $SUDO install -m 644 "${pivpnFilesDir}/files${aptConfDir}/50unattended-upgrades.Raspbian" "${aptConfDir}/50unattended-upgrades" - fi + if [[ "${PLAT}" == "Ubuntu" ]]; then + periodic_file="${aptConfDir}/10periodic" + else + periodic_file="${aptConfDir}/02periodic" + fi - # Add the remaining settings for all other distributions - echo "APT::Periodic::Enable \"1\"; - APT::Periodic::Update-Package-Lists \"1\"; - APT::Periodic::Download-Upgradeable-Packages \"1\"; - APT::Periodic::Unattended-Upgrade \"1\"; - APT::Periodic::AutocleanInterval \"7\"; - APT::Periodic::Verbose \"0\";" | $SUDO tee "${aptConfDir}/02periodic" > /dev/null - fi + # Ubuntu 50unattended-upgrades should already just have security enabled + # so we just need to configure the 10periodic file + { + echo "APT::Periodic::Update-Package-Lists \"1\";" + echo "APT::Periodic::Download-Upgradeable-Packages \"1\";" + echo "APT::Periodic::Unattended-Upgrade \"1\";" - # Enable automatic updates via the bullseye repository when installing from debian package - if [ "$VPN" = "wireguard" ]; then - if [ -f /etc/apt/sources.list.d/pivpn-bullseye-repo.list ]; then - if ! grep -q "\"o=$PLAT,n=bullseye\";" "${aptConfDir}/50unattended-upgrades"; then - $SUDO sed -i "/Unattended-Upgrade::Origins-Pattern {/a\"o=$PLAT,n=bullseye\";" "${aptConfDir}/50unattended-upgrades" - fi - fi - fi - elif [[ "${PKG_MANAGER}" == 'apk' ]]; then - local down_dir - ## install dependencies - # shellcheck disable=SC2086 - ${SUDO} ${PKG_INSTALL} unzip asciidoctor + if [[ "${PLAT}" == "Ubuntu" ]]; then + echo "APT::Periodic::AutocleanInterval \"5\";" + else + echo "APT::Periodic::Enable \"1\";" + echo "APT::Periodic::AutocleanInterval \"7\";" + echo "APT::Periodic::Verbose \"0\";" + fi + } | ${SUDO} tee "${periodic_file}" > /dev/null - if ! down_dir="$(mktemp -d)"; then - echo "::: Failed to create download directory for apk-autoupdate!" - exit 1 - fi + # Enable automatic updates via the bullseye repository + # when installing from debian package + if [[ "${VPN}" == "wireguard" ]]; then + if [[ -f /etc/apt/sources.list.d/pivpn-bullseye-repo.list ]]; then + if ! grep \ + -q \ + "\"o=${PLAT},n=bullseye\";" \ + "${aptConfDir}/50unattended-upgrades"; then + local sed_pattern + sed_pattern=" {/a\"o=${PLAT},n=bullseye\";" + sed_pattern="${sed_pattern} {/a\"o=${PLAT},n=bullseye\";" + ${SUDO} sed -i "${sed_pattern}" "${aptConfDir}/50unattended-upgrades" + fi + fi + fi + elif [[ "${PKG_MANAGER}" == 'apk' ]]; then + local down_dir + ## install dependencies + # shellcheck disable=SC2086 + ${SUDO} ${PKG_INSTALL} unzip asciidoctor - ## download binaries - curl -fLo "${down_dir}/master.zip" https://github.com/jirutka/apk-autoupdate/archive/refs/heads/master.zip - unzip -qd "${down_dir}" "${down_dir}/master.zip" + if ! down_dir="$(mktemp -d)"; then + err "::: Failed to create download directory for apk-autoupdate!" + exit 1 + fi - ( - cd "${down_dir}/apk-autoupdate-master" || exit 1 + ## download binaries + curl \ + -f \ + -L \ + -o "${down_dir}/master.zip" \ + https://github.com/jirutka/apk-autoupdate/archive/refs/heads/master.zip + unzip -qd "${down_dir}" "${down_dir}/master.zip" - ## personalize binaries - sed -i -E -e 's/^(prefix\s*:=).*/\1 \/usr/' Makefile + ( + cd "${down_dir}/apk-autoupdate-master" || exi - ## install - ${SUDO} make install + ## personalize binaries + sed -i -E -e 's/^(prefix\s*:=).*/\1 \/usr/' Makefile - if ! command -v apk-autoupdate &> /dev/null; then - echo "::: Failed to compile and install apk-autoupdate!" - exit 1 - fi - ) || exit 1 + ## install + ${SUDO} make install - ${SUDO} install -m 0755 "${pivpnFilesDir}/files/etc/apk/personal_autoupdate.conf" /etc/apk/personal_autoupdate.conf - ${SUDO} apk-autoupdate /etc/apk/personal_autoupdate.conf - fi + if ! command -v apk-autoupdate &> /dev/null; then + err "::: Failed to compile and install apk-autoupdate!" + exit + fi + ) || exit 1 + + ${SUDO} install \ + -m 0755 \ + "${pivpnFilesDir}/files/etc/apk/personal_autoupdate.conf" \ + /etc/apk/personal_autoupdate.conf + ${SUDO} apk-autoupdate /etc/apk/personal_autoupdate.conf + fi } -writeConfigFiles(){ - # Save installation setting to the final location - echo "INSTALLED_PACKAGES=(${INSTALLED_PACKAGES[*]})" >> ${tempsetupVarsFile} - echo "::: Setupfiles copied to ${setupConfigDir}/${VPN}/${setupVarsFile}" - $SUDO mkdir -p "${setupConfigDir}/${VPN}/" - $SUDO cp ${tempsetupVarsFile} "${setupConfigDir}/${VPN}/${setupVarsFile}" +writeConfigFiles() { + # Save installation setting to the final location + echo "INSTALLED_PACKAGES=(${INSTALLED_PACKAGES[*]})" >> "${tempsetupVarsFile}" + echo "::: Setupfiles copied to ${setupConfigDir}/${VPN}/${setupVarsFile}" + ${SUDO} mkdir -p "${setupConfigDir}/${VPN}/" + ${SUDO} cp "${tempsetupVarsFile}" "${setupConfigDir}/${VPN}/${setupVarsFile}" } -installScripts(){ - # Ensure /opt exists (issue #607) - $SUDO mkdir -p /opt +installScripts() { + # Ensure /opt exists (issue #607) + ${SUDO} mkdir -p /opt - if [[ ${VPN} == 'wireguard' ]]; then - othervpn='openvpn' - else - othervpn='wireguard' - fi + if [[ "${VPN}" == 'wireguard' ]]; then + othervpn='openvpn' + else + othervpn='wireguard' + fi - # Symlink scripts from /usr/local/src/pivpn to their various locations - echo -n -e "::: Installing scripts to ${pivpnScriptDir}...\n" + # Symlink scripts from /usr/local/src/pivpn to their various locations + echo -e "::: Installing scripts to ${pivpnScriptDir}..." - # if the other protocol file exists it has been installed - if [ -r "${setupConfigDir}/${othervpn}/${setupVarsFile}" ]; then - # Both are installed, no bash completion, unlink if already there - $SUDO unlink /etc/bash_completion.d/pivpn + # if the other protocol file exists it has been installed + if [[ -r "${setupConfigDir}/${othervpn}/${setupVarsFile}" ]]; then + # Both are installed, no bash completion, unlink if already there + ${SUDO} unlink /etc/bash_completion.d/pivpn - # Unlink the protocol specific pivpn script and symlink the common - # script to the location instead - $SUDO unlink /usr/local/bin/pivpn - $SUDO ln -sf -T "${pivpnFilesDir}/scripts/pivpn" /usr/local/bin/pivpn - else - # Check if bash_completion scripts dir exists and creates it if not - ${SUDO} mkdir -p /etc/bash_completion.d + # Unlink the protocol specific pivpn script and symlink the common + # script to the location instead + ${SUDO} unlink /usr/local/bin/pivpn + ${SUDO} ln -sfT "${pivpnFilesDir}/scripts/pivpn" /usr/local/bin/pivpn + else + # Check if bash_completion scripts dir exists and creates it if not + ${SUDO} mkdir -p /etc/bash_completion.d - # Only one protocol is installed, symlink bash completion, the pivpn script - # and the script directory - $SUDO ln -sf -T "${pivpnFilesDir}/scripts/${VPN}/bash-completion" /etc/bash_completion.d/pivpn - $SUDO ln -sf -T "${pivpnFilesDir}/scripts/${VPN}/pivpn.sh" /usr/local/bin/pivpn - $SUDO ln -sf "${pivpnFilesDir}/scripts/" "${pivpnScriptDir}" - # shellcheck disable=SC1091 - . /etc/bash_completion.d/pivpn - fi + # Only one protocol is installed, symlink bash completion, the pivpn script + # and the script directory + ${SUDO} ln -s \ + -f \ + -T \ + "${pivpnFilesDir}/scripts/${VPN}/bash-completion" \ + /etc/bash_completion.d/pivpn + ${SUDO} ln \ + -s \ + -f \ + -T \ + "${pivpnFilesDir}/scripts/${VPN}/pivpn.sh" \ + /usr/local/bin/pivpn + ${SUDO} ln -sf "${pivpnFilesDir}/scripts/" "${pivpnScriptDir}" + # shellcheck disable=SC1091 + . /etc/bash_completion.d/pivpn + fi - echo " done." + echo " done." } -displayFinalMessage(){ - # Ensure that cached writes reach persistent storage - echo "::: Flushing writes to disk..." - sync - echo "::: done." +displayFinalMessage() { + # Ensure that cached writes reach persistent storage + echo "::: Flushing writes to disk..." - if [ "${runUnattended}" = 'true' ]; then - echo "::: Installation Complete!" - echo "::: Now run 'pivpn add' to create the client profiles." - echo "::: Run 'pivpn help' to see what else you can do!" - echo - echo "::: If you run into any issue, please read all our documentation carefully." - echo "::: All incomplete posts or bug reports will be ignored or deleted." - echo - echo "::: Thank you for using PiVPN." - echo "::: It is strongly recommended you reboot after installation." - return - fi + sync - # Final completion message to user - whiptail --msgbox --backtitle "Make it so." --title "Installation Complete!" "Now run 'pivpn add' to create the client profiles. -Run 'pivpn help' to see what else you can do!\\n\\nIf you run into any issue, please read all our documentation carefully. -All incomplete posts or bug reports will be ignored or deleted.\\n\\nThank you for using PiVPN." ${r} ${c} - if (whiptail --title "Reboot" --yesno --defaultno "It is strongly recommended you reboot after installation. Would you like to reboot now?" ${r} ${c}); then - whiptail --title "Rebooting" --msgbox "The system will now reboot." ${r} ${c} - printf "\\nRebooting system...\\n" - $SUDO sleep 3 + echo "::: done." - ${SUDO} reboot - fi + if [[ "${runUnattended}" == 'true' ]]; then + echo "::: Installation Complete!" + echo "::: Now run 'pivpn add' to create the client profiles." + echo "::: Run 'pivpn help' to see what else you can do!" + echo + echo -n "::: If you run into any issue, please read all our documentation " + echo "carefully." + echo "::: All incomplete posts or bug reports will be ignored or deleted." + echo + echo "::: Thank you for using PiVPN." + echo "::: It is strongly recommended you reboot after installation." + return + fi + + # Final completion message to user + whiptail \ + --backtitle "Make it so." \ + --title "Installation Complete!" \ + --msgbox "Now run 'pivpn add' to create the client profiles. +Run 'pivpn help' to see what else you can do! + +If you run into any issue, please read all our documentation carefully. +All incomplete posts or bug reports will be ignored or deleted. + +Thank you for using PiVPN." "${r}" "${c}" + + if whiptail \ + --title "Reboot" \ + --defaultno \ + --yesno "It is strongly recommended you reboot after installation. \ +Would you like to reboot now?" "${r}" "${c}"; then + whiptail \ + --title "Rebooting" \ + --msgbox "The system will now reboot." "${r}" "${c}" + printf "\\nRebooting system...\\n" + ${SUDO} sleep 3 + + ${SUDO} reboot + fi } main "$@" From 3a2466df3128a1a6ae4c6b78fd4b22d18567e51e Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Fri, 5 Aug 2022 08:43:45 +0200 Subject: [PATCH 04/14] ci(travis): add style stage and move dependency Add a stage that do a style check and move shellcheck dependency to the stage where is used --- .travis.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4586e26..88b9ad9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,15 +3,13 @@ language: shell addons: hosts: - pivpn.test - apt: - packages: - - shellcheck services: - docker stages: - lint + - style - test branches: @@ -24,11 +22,27 @@ jobs: - stage: lint name: "Shellcheck lint" dist: focal + addons: + apt: + packages: + - shellcheck virt: vm arch: amd64 script: - - shellcheck auto_install/install.sh - - find scripts/ -type f | xargs shellcheck + - shellcheck -x auto_install/install.sh + - find scripts/ -type f | xargs shellcheck -x + + - stage: style + name: "Shell Style Guide" + dist: xenial + addons: + snaps: + - shfmt + virt: vm + arch: amd64 + script: + - shfmt -d -i 2 -ci -sr auto_install/install.sh + - find scripts/ -type f | xargs shfmt -d -i 2 -ci -sr - stage: test name: "OpenVPN Xenial & Commands" From 82a7cbfc27aab7badf3675ed9210d64e50c8c0a2 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Fri, 5 Aug 2022 09:06:25 +0200 Subject: [PATCH 05/14] refactor(installer): fix the code style Fix the code style --- auto_install/install.sh | 42 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/auto_install/install.sh b/auto_install/install.sh index e0f6ed1..3e96b0d 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -659,38 +659,27 @@ preconfigurePackages() { if # If the module is builtin and the package available, we only need # to install wireguard-tools. - [[ "${WIREGUARD_BUILTIN}" -eq 1 && - -n "${AVAILABLE_WIREGUARD}" ]] || + [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] || # If the package is not available, on Debian and Raspbian we can # add it via Bullseye repository. - [[ "${WIREGUARD_BUILTIN}" -eq 1 && - ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] || + [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] || # If the module is not builtin, on Raspbian we know the headers # package: raspberrypi-kernel-headers [[ "${PLAT}" == 'Raspbian' ]] || # On Alpine, the kernel must be linux-lts or linux-virt if we want to # load the kernel module - [[ "${PLAT}" == 'Alpine' && - ! -f /.dockerenv && - "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] || + [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] || # On Alpine Docker Container, the responsibility to have a WireGuard # module on the host system is at user side - [[ "${PLAT}" == 'Alpine' && - -f /.dockerenv ]] || + [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] || # On Debian (and Ubuntu), we can only reliably assume the headers package # for amd64: linux-image-amd64 - [[ "${PLAT}" == 'Debian' && - "${DPKG_ARCH}" == 'amd64' ]] || + [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] || # On Ubuntu, additionally the WireGuard package needs to be available, # since we didn't test mixing Ubuntu repositories. - [[ "${PLAT}" == 'Ubuntu' && - "${DPKG_ARCH}" == 'amd64' && - -n "${AVAILABLE_WIREGUARD}" ]] || + [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] || # Ubuntu focal has wireguard support - [[ "${PLAT}" == 'Ubuntu' && - "${DPKG_ARCH}" == 'arm64' && - "${OSCN}" == 'focal' && - -n "${AVAILABLE_WIREGUARD}" ]] + [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'arm64' && "${OSCN}" == 'focal' && -n "${AVAILABLE_WIREGUARD}" ]] then WIREGUARD_SUPPORT=1 fi @@ -1060,10 +1049,7 @@ validIP() { read -r -a ip <<< "${ip}" IFS="${OIFS}" - [[ "${ip[0]}" -le 255 && - "${ip[1]}" -le 255 && - "${ip[2]}" -le 255 && - "${ip[3]}" -le 255 ]] + [[ "${ip[0]}" -le 255 && "${ip[1]}" -le 255 && "${ip[2]}" -le 255 && "${ip[3]}" -le 255 ]] stat="$?" fi @@ -1087,11 +1073,7 @@ validIPAndNetmask() { read -r -a ip <<< "${ip}" IFS="${OIFS}" - [[ "${ip[0]}" -le 255 && - "${ip[1]}" -le 255 && - "${ip[2]}" -le 255 && - "${ip[3]}" -le 255 && - "${ip[4]}" -le 32 ]] + [[ "${ip[0]}" -le 255 && "${ip[1]}" -le 255 && "${ip[2]}" -le 255 && "${ip[3]}" -le 255 && "${ip[4]}" -le 32 ]] stat="$?" fi @@ -1587,8 +1569,7 @@ updateRepo() { --no-single-branch \ "${2}" \ "${1}" \ - > /dev/null \ - & + > /dev/null & spinner $! cd "${1}" || exit 1 echo " done!" @@ -1626,8 +1607,7 @@ makeRepo() { --no-single-branch \ "${2}" \ "${1}" \ - > /dev/null \ - & + > /dev/null & spinner $! cd "${1}" || exit 1 echo " done!" From 699f72712dd5efc070ff23c43d703188af1dbb2a Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Fri, 5 Aug 2022 22:39:33 +0200 Subject: [PATCH 06/14] fix(travis ci): fix an error Fix an error when the find command is used --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 88b9ad9..bd12be8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ jobs: arch: amd64 script: - shellcheck -x auto_install/install.sh - - find scripts/ -type f | xargs shellcheck -x + - find scripts/ -type f -exec shellcheck -x {} \; - stage: style name: "Shell Style Guide" @@ -42,7 +42,7 @@ jobs: arch: amd64 script: - shfmt -d -i 2 -ci -sr auto_install/install.sh - - find scripts/ -type f | xargs shfmt -d -i 2 -ci -sr + - find scripts/ -type f -exec shfmt -d -i 2 -ci -sr {} \; - stage: test name: "OpenVPN Xenial & Commands" From e09f3a04bdde3c5a2b34aaf096a8d28e29782a8a Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Fri, 5 Aug 2022 23:11:22 +0200 Subject: [PATCH 07/14] fix(installer): fix some code style errors Fix some code style error about the pipelines --- auto_install/install.sh | 465 ++++++++++++++++---------------- ciscripts/startup.sh | 16 +- scripts/backup.sh | 4 +- scripts/openvpn/bash-completion | 4 +- scripts/openvpn/listOVPN.sh | 8 +- scripts/openvpn/makeOVPN.sh | 54 ++-- scripts/openvpn/pivpnDebug.sh | 8 +- scripts/openvpn/removeOVPN.sh | 6 +- scripts/self_check.sh | 12 +- scripts/uninstall.sh | 8 +- scripts/update.sh | 4 +- scripts/wireguard/clientSTAT.sh | 4 +- scripts/wireguard/makeCONF.sh | 24 +- scripts/wireguard/removeCONF.sh | 8 +- 14 files changed, 316 insertions(+), 309 deletions(-) diff --git a/auto_install/install.sh b/auto_install/install.sh index 3e96b0d..e2a66da 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -139,13 +139,13 @@ main() { echo "::: Forced IPv6 config, skipping IPv6 uplink check!" pivpnenableipv6=1 else - if [[ -z "${pivpnenableipv6}" ]] || - [[ "${pivpnenableipv6}" -eq 1 ]]; then + if [[ -z "${pivpnenableipv6}" ]] \ + || [[ "${pivpnenableipv6}" -eq 1 ]]; then checkipv6uplink fi - if [[ "${pivpnenableipv6}" -eq 0 ]] && - [[ "${pivpnforceipv6route}" -eq 1 ]]; then + if [[ "${pivpnenableipv6}" -eq 0 ]] \ + && [[ "${pivpnforceipv6route}" -eq 1 ]]; then askforcedipv6route fi fi @@ -155,8 +155,8 @@ main() { if checkStaticIpSupported; then getStaticIPv4Settings - if [[ -z "${dhcpReserv}" ]] || - [[ "${dhcpReserv}" -ne 1 ]]; then + if [[ -z "${dhcpReserv}" ]] \ + || [[ "${dhcpReserv}" -ne 1 ]]; then setStaticIPv4 fi else @@ -294,8 +294,8 @@ checkExistingInstall() { fi fi - if [[ -z "${UpdateCmd}" ]] || - [[ "${UpdateCmd}" == "Reconfigure" ]]; then + if [[ -z "${UpdateCmd}" ]] \ + || [[ "${UpdateCmd}" == "Reconfigure" ]]; then : elif [[ "${UpdateCmd}" == "Update" ]]; then ${SUDO} "${pivpnScriptDir}/update.sh" "$@" @@ -328,8 +328,8 @@ Please choose from the following options \ "${opt1a}" "${opt1b}" \ "${opt2a}" "${opt2b}" \ "${opt3a}" "${opt3b}" \ - 3>&2 2>&1 1>&3)" || - { + 3>&2 2>&1 1>&3)" \ + || { err "::: Cancel selected. Exiting" exit 1 } @@ -447,8 +447,8 @@ checkHostname() { exit 1 fi - until [[ "${#host_name}" -le 28 ]] && - [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; do + until [[ "${#host_name}" -le 28 ]] \ + && [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; do host_name="$(whiptail \ --title "Hostname too long" \ --inputbox "Your hostname is too long. @@ -457,8 +457,8 @@ No special characters allowed." "${r}" "${c}" \ 3>&1 1>&2 2>&3)" ${SUDO} hostnamectl set-hostname "${host_name}" - if [[ "${#host_name}" -le 28 ]] && - [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; then + if [[ "${#host_name}" -le 28 ]] \ + && [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; then echo "::: Hostname valid and length OK, proceeding..." fi done @@ -489,9 +489,9 @@ verifyFreeDiskSpace() { echo "::: Verifying free disk space..." local required_free_kilobytes=76800 local existing_free_kilobytes - existing_free_kilobytes="$(df -Pk | - grep -m1 '\/$' | - awk '{print $4}')" + existing_free_kilobytes="$(df -Pk \ + | grep -m1 '\/$' \ + | awk '{print $4}')" # - Unknown free disk space , not a integer if [[ ! "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then @@ -563,12 +563,12 @@ preconfigurePackages() { # Install packages used by this installation script # If apt is older than 1.5 we need to install an additional package to add # support for https repositories that will be used later on - if [[ "${PKG_MANAGER}" == 'apt-get' ]] && - [[ -f /etc/apt/sources.list ]]; then - INSTALLED_APT="$(apt-cache policy apt | - grep -m1 'Installed: ' | - grep -v '(none)' | - awk '{print $2}')" + if [[ "${PKG_MANAGER}" == 'apt-get' ]] \ + && [[ -f /etc/apt/sources.list ]]; then + INSTALLED_APT="$(apt-cache policy apt \ + | grep -m1 'Installed: ' \ + | grep -v '(none)' \ + | awk '{print $2}')" if dpkg --compare-versions "${INSTALLED_APT}" lt 1.5; then BASE_DEPS+=("apt-transport-https") @@ -587,13 +587,13 @@ preconfigurePackages() { fi if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - AVAILABLE_OPENVPN="$(apt-cache policy openvpn | - grep -m1 'Candidate: ' | - grep -v '(none)' | - awk '{print $2}')" + AVAILABLE_OPENVPN="$(apt-cache policy openvpn \ + | grep -m1 'Candidate: ' \ + | grep -v '(none)' \ + | awk '{print $2}')" elif [[ "${PKG_MANAGER}" == 'apk' ]]; then - AVAILABLE_OPENVPN="$(apk search -e openvpn | - sed -E -e 's/openvpn\-(.*)/\1/')" + AVAILABLE_OPENVPN="$(apk search -e openvpn \ + | sed -E -e 's/openvpn\-(.*)/\1/')" fi OPENVPN_SUPPORT=0 @@ -603,14 +603,14 @@ preconfigurePackages() { # repositories but we are running x86 Debian or Ubuntu, add the official repo # which provides the updated package. if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - if [[ -n "${AVAILABLE_OPENVPN}" ]] && - dpkg --compare-versions "${AVAILABLE_OPENVPN}" ge 2.4; then + if [[ -n "${AVAILABLE_OPENVPN}" ]] \ + && dpkg --compare-versions "${AVAILABLE_OPENVPN}" ge 2.4; then OPENVPN_SUPPORT=1 else - if [[ "${PLAT}" == "Debian" ]] || - [[ "${PLAT}" == "Ubuntu" ]]; then - if [[ "${DPKG_ARCH}" == "amd64" ]] || - [[ "${DPKG_ARCH}" == "i386" ]]; then + if [[ "${PLAT}" == "Debian" ]] \ + || [[ "${PLAT}" == "Ubuntu" ]]; then + if [[ "${DPKG_ARCH}" == "amd64" ]] \ + || [[ "${DPKG_ARCH}" == "i386" ]]; then NEED_OPENVPN_REPO=1 OPENVPN_SUPPORT=1 else @@ -621,8 +621,8 @@ preconfigurePackages() { fi fi elif [[ "${PKG_MANAGER}" == 'apk' ]]; then - if [[ -n "${AVAILABLE_OPENVPN}" ]] && - [[ "$(apk version -t "${AVAILABLE_OPENVPN}" 2.4)" == '>' ]]; then + if [[ -n "${AVAILABLE_OPENVPN}" ]] \ + && [[ "$(apk version -t "${AVAILABLE_OPENVPN}" 2.4)" == '>' ]]; then OPENVPN_SUPPORT=1 else OPENVPN_SUPPORT=0 @@ -630,13 +630,13 @@ preconfigurePackages() { fi if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - AVAILABLE_WIREGUARD="$(apt-cache policy wireguard | - grep -m1 'Candidate: ' | - grep -v '(none)' | - awk '{print $2}')" + AVAILABLE_WIREGUARD="$(apt-cache policy wireguard \ + | grep -m1 'Candidate: ' \ + | grep -v '(none)' \ + | awk '{print $2}')" elif [[ "${PKG_MANAGER}" == 'apk' ]]; then - AVAILABLE_WIREGUARD="$(apk search -e wireguard-tools | - sed -E -e 's/wireguard\-tools\-(.*)/\1/')" + AVAILABLE_WIREGUARD="$(apk search -e wireguard-tools \ + | sed -E -e 's/wireguard\-tools\-(.*)/\1/')" fi WIREGUARD_SUPPORT=0 @@ -649,9 +649,9 @@ preconfigurePackages() { WIREGUARD_BUILTIN=0 if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - if dpkg-query -S '/lib/modules/*/wireguard.ko*' &> /dev/null || - modinfo wireguard 2> /dev/null | - grep -q '^filename:[[:blank:]]*(builtin)$'; then + if dpkg-query -S '/lib/modules/*/wireguard.ko*' &> /dev/null \ + || modinfo wireguard 2> /dev/null \ + | grep -q '^filename:[[:blank:]]*(builtin)$'; then WIREGUARD_BUILTIN=1 fi fi @@ -659,33 +659,40 @@ preconfigurePackages() { if # If the module is builtin and the package available, we only need # to install wireguard-tools. - [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] || + [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] \ + || # If the package is not available, on Debian and Raspbian we can # add it via Bullseye repository. - [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] || + [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] \ + || # If the module is not builtin, on Raspbian we know the headers # package: raspberrypi-kernel-headers - [[ "${PLAT}" == 'Raspbian' ]] || + [[ "${PLAT}" == 'Raspbian' ]] \ + || # On Alpine, the kernel must be linux-lts or linux-virt if we want to # load the kernel module - [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] || + [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] \ + || # On Alpine Docker Container, the responsibility to have a WireGuard # module on the host system is at user side - [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] || + [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] \ + || # On Debian (and Ubuntu), we can only reliably assume the headers package # for amd64: linux-image-amd64 - [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] || + [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] \ + || # On Ubuntu, additionally the WireGuard package needs to be available, # since we didn't test mixing Ubuntu repositories. - [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] || + [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] \ + || # Ubuntu focal has wireguard support [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'arm64' && "${OSCN}" == 'focal' && -n "${AVAILABLE_WIREGUARD}" ]] then WIREGUARD_SUPPORT=1 fi - if [[ "${OPENVPN_SUPPORT}" -eq 0 ]] && - [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then + if [[ "${OPENVPN_SUPPORT}" -eq 0 ]] \ + && [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then err "::: Neither OpenVPN nor WireGuard are available to install by PiVPN, exiting..." exit 1 fi @@ -704,14 +711,14 @@ preconfigurePackages() { if [[ "${PKG_MANAGER}" == 'apt-get' ]] && [[ "${USING_UFW}" -eq 0 ]]; then BASE_DEPS+=(iptables-persistent) - echo iptables-persistent iptables-persistent/autosave_v4 boolean true | - ${SUDO} debconf-set-selections - echo iptables-persistent iptables-persistent/autosave_v6 boolean false | - ${SUDO} debconf-set-selections + echo iptables-persistent iptables-persistent/autosave_v4 boolean true \ + | ${SUDO} debconf-set-selections + echo iptables-persistent iptables-persistent/autosave_v6 boolean false \ + | ${SUDO} debconf-set-selections fi - if [[ "${PLAT}" == 'Alpine' ]] && - ! command -v grepcidr &> /dev/null; then + if [[ "${PLAT}" == 'Alpine' ]] \ + && ! command -v grepcidr &> /dev/null; then local down_dir ## install dependencies # shellcheck disable=SC2086 @@ -762,8 +769,8 @@ installDependentPackages() { echo -n "::: Checking for ${i}..." if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null | - grep -q "ok installed"; then + if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null \ + | grep -q "ok installed"; then echo " already installed!" else echo " not installed!" @@ -790,8 +797,8 @@ installDependentPackages() { for i in "${TO_INSTALL[@]}"; do if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then - if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null | - grep -q "ok installed"; then + if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null \ + | grep -q "ok installed"; then echo "::: Package ${i} successfully installed!" # Add this package to the total list of packages that were actually # installed by the script @@ -868,20 +875,20 @@ chooseInterface() { # Show every network interface, could be useful for those who # install PiVPN inside virtual machines or on Raspberry Pis # with USB adapters - availableInterfaces="$(echo "${availableInterfaces}" | - awk '{print $2}')" + availableInterfaces="$(echo "${availableInterfaces}" \ + | awk '{print $2}')" else # Find network interfaces whose state is UP - availableInterfaces="$(echo "${availableInterfaces}" | - awk '/state UP/ {print $2}')" + availableInterfaces="$(echo "${availableInterfaces}" \ + | awk '/state UP/ {print $2}')" fi # Skip virtual, loopback and docker interfaces - availableInterfaces="$(echo "${availableInterfaces}" | - cut -d ':' -f 1 | - cut -d '@' -f 1 | - grep -v -w 'lo' | - grep -v '^docker')" + availableInterfaces="$(echo "${availableInterfaces}" \ + | cut -d ':' -f 1 \ + | cut -d '@' -f 1 \ + | grep -v -w 'lo' \ + | grep -v '^docker')" if [[ -z "${availableInterfaces}" ]]; then err "::: Could not find any active network interface, exiting" @@ -942,8 +949,8 @@ chooseInterface() { { echo "IPv4dev=${IPv4dev}" - if [[ "${pivpnenableipv6}" -eq 1 ]] && - [[ -z "${IPv6dev}" ]]; then + if [[ "${pivpnenableipv6}" -eq 1 ]] \ + && [[ -z "${IPv6dev}" ]]; then echo "IPv6dev=${IPv6dev}" fi } >> "${tempsetupVarsFile}" @@ -1012,8 +1019,8 @@ checkStaticIpSupported() { return 0 # If we are on 'Debian' but the raspi.list file is present, # then we actually are on 64-bit Raspberry Pi OS. - elif [[ "${PLAT}" == "Debian" ]] && - [[ -s /etc/apt/sources.list.d/raspi.list ]]; then + elif [[ "${PLAT}" == "Debian" ]] \ + && [[ -s /etc/apt/sources.list.d/raspi.list ]]; then return 0 else return 1 @@ -1120,7 +1127,7 @@ force all IPv6 connections through the VPN.\\n\\nThis will prevent the \ client from bypassing the tunnel and leaking its real IPv6 address to servers, \ though it might cause the client to have slow response when browsing the web \ on IPv6 networks. - + Do you want to force routing IPv6 to block the leakage?" "${r}" "${c}"; then pivpnforceipv6route=1 else @@ -1132,23 +1139,23 @@ Do you want to force routing IPv6 to block the leakage?" "${r}" "${c}"; then getStaticIPv4Settings() { # Find the gateway IP used to route to outside world - CurrentIPv4gw="$(ip -o route get 192.0.2.1 | - grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | - awk 'NR==2')" + CurrentIPv4gw="$(ip -o route get 192.0.2.1 \ + | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' \ + | awk 'NR==2')" # Find the IP address (and netmask) of the desidered interface - CurrentIPv4addr="$(ip -o -f inet address show dev "${IPv4dev}" | - grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" + CurrentIPv4addr="$(ip -o -f inet address show dev "${IPv4dev}" \ + | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" # Grab their current DNS servers - IPv4dns="$(grep -v "^#" /etc/resolv.conf | - grep -w nameserver | - grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | - xargs)" + IPv4dns="$(grep -v "^#" /etc/resolv.conf \ + | grep -w nameserver \ + | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' \ + | xargs)" if [[ "${runUnattended}" == 'true' ]]; then - if [[ -z "${dhcpReserv}" ]] || - [[ "${dhcpReserv}" -ne 1 ]]; then + if [[ -z "${dhcpReserv}" ]] \ + || [[ "${dhcpReserv}" -ne 1 ]]; then local MISSING_STATIC_IPV4_SETTINGS=0 if [[ -z "${IPv4addr}" ]]; then @@ -1283,7 +1290,7 @@ IPv4 address" "${r}" "${c}" "${CurrentIPv4addr}" \ --backtitle "Calibrating network interface" \ --title "IPv4 address" \ --msgbox "You've entered an invalid IP address: ${IPv4addr} - + Please enter an IP address in the CIDR notation, example: 192.168.23.211/24 If you are not sure, please just keep the default." "${r}" "${c}" @@ -1313,7 +1320,7 @@ default gateway" "${r}" "${c}" "${CurrentIPv4gw}" \ --backtitle "Calibrating network interface" \ --title "IPv4 gateway (router)" \ --msgbox "You've entered an invalid gateway IP: ${IPv4gw} - + Please enter the IP address of your gateway (router), example: 192.168.23.1 If you are not sure, please just keep the default." "${r}" "${c}" @@ -1401,8 +1408,8 @@ chooseUser() { exit 1 fi else - if awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd | - grep -qw "${install_user}"; then + if awk -F':' '$3>=1000 && $3<=60000 {print $1}' /etc/passwd \ + | grep -qw "${install_user}"; then echo "::: ${install_user} will hold your ovpn configurations." else echo "::: User ${install_user} does not exist, creating..." @@ -1419,8 +1426,8 @@ chooseUser() { fi fi - install_home="$(grep -m1 "^${install_user}:" /etc/passwd | - cut -d ':' -f 6)" + install_home="$(grep -m1 "^${install_user}:" /etc/passwd \ + | cut -d ':' -f 6)" install_home="${install_home%/}" { @@ -1519,8 +1526,8 @@ chooseUser() { for desiredUser in ${chooseUserOptions}; do install_user=${desiredUser} echo "::: Using User: ${install_user}" - install_home=$(grep -m1 "^${install_user}:" /etc/passwd | - cut -d ':' -f 6) + install_home=$(grep -m1 "^${install_user}:" /etc/passwd \ + | cut -d ':' -f 6) install_home=${install_home%/} # remove possible trailing slash { @@ -1562,8 +1569,8 @@ updateRepo() { # Go back to /usr/local/src otherwise git will complain when the current # working directory has just been deleted (/usr/local/src/pivpn). - cd /usr/local/src && - ${SUDO} git clone \ + cd /usr/local/src \ + && ${SUDO} git clone \ -q \ --depth 1 \ --no-single-branch \ @@ -1600,8 +1607,8 @@ makeRepo() { # Go back to /usr/local/src otherwhise git will complain when the current # working directory has just been deleted (/usr/local/src/pivpn). - cd /usr/local/src && - ${SUDO} git clone \ + cd /usr/local/src \ + && ${SUDO} git clone \ -q \ --depth 1 \ --no-single-branch \ @@ -1643,8 +1650,8 @@ cloneOrUpdateRepos() { ${SUDO} mkdir -p /usr/local/src # Get Git files - getGitFiles "${pivpnFilesDir}" "${pivpnGitUrl}" || - { + getGitFiles "${pivpnFilesDir}" "${pivpnGitUrl}" \ + || { err "!!! Unable to clone ${pivpnGitUrl} into ${pivpnFilesDir}, unable to continue." exit 1 } @@ -1721,8 +1728,8 @@ generateRandomSubnet() { SUBNET_EXCLUDE_LIST+=(10.100.1.0/24) SUBNET_EXCLUDE_LIST+=(10.255.255.0/24) - readarray -t CURRENTLY_USED_SUBNETS <<< "$(ip route show | - grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" + readarray -t CURRENTLY_USED_SUBNETS <<< "$(ip route show \ + | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}')" SUBNET_EXCLUDE_LIST=("${SUBNET_EXCLUDE_LIST[@]}" "${CURRENTLY_USED_SUBNETS[@]}") @@ -1769,8 +1776,8 @@ setWireguardDefaultVars() { pivpnNET="$(generateRandomSubnet)" fi - if [[ "${pivpnenableipv6}" -eq 1 ]] && - [[ -z "${pivpnNETv6}" ]]; then + if [[ "${pivpnenableipv6}" -eq 1 ]] \ + && [[ -z "${pivpnNETv6}" ]]; then pivpnNETv6="fd11:5ee:bad:c0de::" fi @@ -1787,8 +1794,8 @@ setWireguardDefaultVars() { # Forward all traffic through PiVPN (i.e. full-tunnel), may be modified by # the user after the installation. - if [[ "${pivpnenableipv6}" -eq 1 ]] || - [[ "${pivpnforceipv6route}" -eq 1 ]]; then + if [[ "${pivpnenableipv6}" -eq 1 ]] \ + || [[ "${pivpnforceipv6route}" -eq 1 ]]; then ALLOWED_IPS="${ALLOWED_IPS}, ::0/0" fi fi @@ -1867,15 +1874,15 @@ askWhichVPN() { fi fi else - if [[ "${WIREGUARD_SUPPORT}" -eq 1 ]] && - [[ "${OPENVPN_SUPPORT}" -eq 1 ]]; then + if [[ "${WIREGUARD_SUPPORT}" -eq 1 ]] \ + && [[ "${OPENVPN_SUPPORT}" -eq 1 ]]; then chooseVPNCmd=(whiptail --backtitle "Setup PiVPN" --title "Installation mode" --separate-output --radiolist "WireGuard is a new kind of VPN that provides \ near-instantaneous connection speed, high performance, and modern cryptography. - + It's the recommended choice especially if you use mobile devices where \ WireGuard is easier on battery than OpenVPN. @@ -1895,12 +1902,12 @@ Choose a VPN (press space to select):" "${r}" "${c}" 2) err "::: Cancel selected, exiting...." exit 1 fi - elif [[ "${OPENVPN_SUPPORT}" -eq 1 ]] && - [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then + elif [[ "${OPENVPN_SUPPORT}" -eq 1 ]] \ + && [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then echo "::: Using VPN: OpenVPN" VPN="openvpn" - elif [[ "${OPENVPN_SUPPORT}" -eq 0 ]] && - [[ "${WIREGUARD_SUPPORT}" -eq 1 ]]; then + elif [[ "${OPENVPN_SUPPORT}" -eq 0 ]] \ + && [[ "${WIREGUARD_SUPPORT}" -eq 1 ]]; then echo "::: Using VPN: WireGuard" VPN="wireguard" fi @@ -1918,7 +1925,7 @@ askAboutCustomizing() { --yesno "PiVPN uses the following settings that we believe are good \ defaults for most users. However, we still want to keep flexibility, so if \ you need to customize them, choose Yes. - + * UDP or TCP protocol: UDP * Custom search domain for the DNS field: None * Modern features or best compatibility: Modern features \ @@ -1951,8 +1958,8 @@ installOpenVPN() { fi echo "::: Adding OpenVPN repository... " - echo "deb https://build.openvpn.net/debian/openvpn/stable ${OSCN} main" | - ${SUDO} tee /etc/apt/sources.list.d/pivpn-openvpn-repo.list > /dev/null + echo "deb https://build.openvpn.net/debian/openvpn/stable ${OSCN} main" \ + | ${SUDO} tee /etc/apt/sources.list.d/pivpn-openvpn-repo.list > /dev/null echo "::: Updating package cache..." updatePackageCache @@ -1999,16 +2006,16 @@ installWireGuard() { PIVPN_DEPS+=(libqrencode) fi - if [[ "${PLAT}" == "Raspbian" || "${PLAT}" == "Debian" ]] && - [[ -z "${AVAILABLE_WIREGUARD}" ]]; then + if [[ "${PLAT}" == "Raspbian" || "${PLAT}" == "Debian" ]] \ + && [[ -z "${AVAILABLE_WIREGUARD}" ]]; then if [[ "${PLAT}" == "Debian" ]]; then echo "::: Adding Debian Bullseye repository... " - echo "deb https://deb.debian.org/debian/ bullseye main" | - ${SUDO} tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null + echo "deb https://deb.debian.org/debian/ bullseye main" \ + | ${SUDO} tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null else echo "::: Adding Raspbian Bullseye repository... " - echo "deb http://raspbian.raspberrypi.org/raspbian/ bullseye main" | - ${SUDO} tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null + echo "deb http://raspbian.raspberrypi.org/raspbian/ bullseye main" \ + | ${SUDO} tee /etc/apt/sources.list.d/pivpn-bullseye-repo.list > /dev/null fi { @@ -2035,8 +2042,8 @@ askCustomProto() { else pivpnPROTO="${pivpnPROTO,,}" - if [[ "${pivpnPROTO}" == "udp" ]] || - [[ "${pivpnPROTO}" == "tcp" ]]; then + if [[ "${pivpnPROTO}" == "udp" ]] \ + || [[ "${pivpnPROTO}" == "tcp" ]]; then echo "::: Using the ${pivpnPROTO} protocol" else err ":: ${pivpnPROTO} is not a supported TCP/IP protocol, please specify 'udp' or 'tcp'" @@ -2091,9 +2098,9 @@ askCustomPort() { fi fi else - if [[ "${pivpnPORT}" =~ ^[0-9]+$ ]] && - [[ "${pivpnPORT}" -ge 1 ]] && - [[ "${pivpnPORT}" -le 65535 ]]; then + if [[ "${pivpnPORT}" =~ ^[0-9]+$ ]] \ + && [[ "${pivpnPORT}" -ge 1 ]] \ + && [[ "${pivpnPORT}" -le 65535 ]]; then echo "::: Using port ${pivpnPORT}" else err "::: ${pivpnPORT} is not a valid port, use a port within the range [1,65535] (inclusive)" @@ -2124,9 +2131,9 @@ askCustomPort() { Enter a new value or hit 'Enter' to retain \ the default" "${r}" "${c}" "${DEFAULT_PORT}" \ 3>&1 1>&2 2>&3)"; then - if [[ "${pivpnPORT}" =~ ^[0-9]+$ ]] && - [[ "${pivpnPORT}" -ge 1 ]] && - [[ "${pivpnPORT}" -le 65535 ]]; then + if [[ "${pivpnPORT}" =~ ^[0-9]+$ ]] \ + && [[ "${pivpnPORT}" -ge 1 ]] \ + && [[ "${pivpnPORT}" -le 65535 ]]; then : else pivpnPORT="${portInvalid}" @@ -2164,12 +2171,12 @@ the default" "${r}" "${c}" "${DEFAULT_PORT}" \ askClientDNS() { if [[ "${runUnattended}" == 'true' ]]; then - if [[ -z "${pivpnDNS1}" ]] && - [[ -n "${pivpnDNS2}" ]]; then + if [[ -z "${pivpnDNS1}" ]] \ + && [[ -n "${pivpnDNS2}" ]]; then pivpnDNS1="${pivpnDNS2}" unset pivpnDNS2 - elif [[ -z "${pivpnDNS1}" ]] && - [[ -z "${pivpnDNS2}" ]]; then + elif [[ -z "${pivpnDNS1}" ]] \ + && [[ -z "${pivpnDNS2}" ]]; then pivpnDNS1="9.9.9.9" pivpnDNS2="149.112.112.112" echo -n "::: No DNS provider specified, " @@ -2183,8 +2190,8 @@ askClientDNS() { echo "::: Invalid DNS ${pivpnDNS1}" fi - if [[ -n "${pivpnDNS2}" ]] && - ! validIP "${pivpnDNS2}"; then + if [[ -n "${pivpnDNS2}" ]] \ + && ! validIP "${pivpnDNS2}"; then INVALID_DNS_SETTINGS=1 echo "::: Invalid DNS ${pivpnDNS2}" fi @@ -2218,8 +2225,8 @@ get ad blocking on the go?" "${r}" "${c}"; then # Add a custom hosts file for VPN clients so they appear # as 'name.pivpn' in the Pi-hole dashboard as well as resolve # by their names. - echo "addn-hosts=/etc/pivpn/hosts.${VPN}" | - ${SUDO} tee "${dnsmasqConfig}" > /dev/null + echo "addn-hosts=/etc/pivpn/hosts.${VPN}" \ + | ${SUDO} tee "${dnsmasqConfig}" > /dev/null # Then create an empty hosts file or clear if it exists. ${SUDO} bash -c "> /etc/pivpn/hosts.${VPN}" @@ -2296,23 +2303,23 @@ In case you have a local resolver running, i.e. unbound, select \ --backtitle "Specify Upstream DNS Provider(s)" \ --inputbox "Enter your desired upstream DNS provider(s), \ separated by a comma. - + For example '1.1.1.1, 9.9.9.9'" "${r}" "${c}" "" \ 3>&1 1>&2 2>&3)"; then - pivpnDNS1="$(echo "${pivpnDNS}" | - sed 's/[, \t]\+/,/g' | - awk -F, '{print$1}')" - pivpnDNS2="$(echo "${pivpnDNS}" | - sed 's/[, \t]\+/,/g' | - awk -F, '{print$2}')" + pivpnDNS1="$(echo "${pivpnDNS}" \ + | sed 's/[, \t]\+/,/g' \ + | awk -F, '{print$1}')" + pivpnDNS2="$(echo "${pivpnDNS}" \ + | sed 's/[, \t]\+/,/g' \ + | awk -F, '{print$2}')" - if ! validIP "${pivpnDNS1}" || - [[ ! "${pivpnDNS1}" ]]; then + if ! validIP "${pivpnDNS1}" \ + || [[ ! "${pivpnDNS1}" ]]; then pivpnDNS1="${strInvalid}" fi - if ! validIP "${pivpnDNS2}" && - [[ "${pivpnDNS2}" ]]; then + if ! validIP "${pivpnDNS2}" \ + && [[ "${pivpnDNS2}" ]]; then pivpnDNS2="${strInvalid}" fi else @@ -2320,8 +2327,8 @@ For example '1.1.1.1, 9.9.9.9'" "${r}" "${c}" "" \ exit 1 fi - if [[ "${pivpnDNS1}" == "${strInvalid}" ]] || - [[ "${pivpnDNS2}" == "${strInvalid}" ]]; then + if [[ "${pivpnDNS1}" == "${strInvalid}" ]] \ + || [[ "${pivpnDNS2}" == "${strInvalid}" ]]; then whiptail \ --backtitle "Invalid IP" \ --title "Invalid IP" \ @@ -2448,12 +2455,12 @@ Format: mydomain.com" "${r}" "${c}" \ } askPublicIPOrDNS() { - if ! IPv4pub="$(dig +short myip.opendns.com @208.67.222.222)" || - ! validIP "${IPv4pub}"; then + if ! IPv4pub="$(dig +short myip.opendns.com @208.67.222.222)" \ + || ! validIP "${IPv4pub}"; then err "dig failed, now trying to curl checkip.amazonaws.com" - if ! IPv4pub="$(curl -sSf https://checkip.amazonaws.com)" || - ! validIP "${IPv4pub}"; then + if ! IPv4pub="$(curl -sSf https://checkip.amazonaws.com)" \ + || ! validIP "${IPv4pub}"; then err "checkip.amazonaws.com failed, please check your internet connection/DNS" exit 1 fi @@ -2539,8 +2546,8 @@ Public DNS Name: ${PUBLICDNS}" "${r}" "${c}"; then askEncryption() { if [[ "${runUnattended}" == 'true' ]]; then - if [[ -z "${TWO_POINT_FOUR}" ]] || - [[ "${TWO_POINT_FOUR}" -eq 1 ]]; then + if [[ -z "${TWO_POINT_FOUR}" ]] \ + || [[ "${TWO_POINT_FOUR}" -eq 1 ]]; then TWO_POINT_FOUR=1 echo "::: Using OpenVPN 2.4 features" @@ -2548,9 +2555,9 @@ askEncryption() { pivpnENCRYPT=256 fi - if [[ "${pivpnENCRYPT}" -eq 256 ]] || - [[ "${pivpnENCRYPT}" -eq 384 ]] || - [[ "${pivpnENCRYPT}" -eq 521 ]]; then + if [[ "${pivpnENCRYPT}" -eq 256 ]] \ + || [[ "${pivpnENCRYPT}" -eq 384 ]] \ + || [[ "${pivpnENCRYPT}" -eq 521 ]]; then echo "::: Using a ${pivpnENCRYPT}-bit certificate" else err "::: ${pivpnENCRYPT} is not a valid certificate size, use 256, 384, or 521" @@ -2564,9 +2571,9 @@ askEncryption() { pivpnENCRYPT=2048 fi - if [[ "${pivpnENCRYPT}" -eq 2048 ]] || - [[ "${pivpnENCRYPT}" -eq 3072 ]] || - [[ "${pivpnENCRYPT}" -eq 4096 ]]; then + if [[ "${pivpnENCRYPT}" -eq 2048 ]] \ + || [[ "${pivpnENCRYPT}" -eq 3072 ]] \ + || [[ "${pivpnENCRYPT}" -eq 4096 ]]; then echo "::: Using a ${pivpnENCRYPT}-bit certificate" else err "::: ${pivpnENCRYPT} is not a valid certificate size, use 2048, 3072, or 4096" @@ -2612,7 +2619,7 @@ askEncryption() { --yesno "OpenVPN 2.4 can take advantage of Elliptic Curves \ to provide higher connection speed and improved security over \ RSA, while keeping smaller certificates. - + Moreover, the 'tls-crypt' directive encrypts the certificates \ being used while authenticating, increasing privacy. @@ -2661,8 +2668,8 @@ then grab a cup of joe and pick 4096 bits." "${r}" "${c}" 3 \ exit 1 fi - if [[ "${pivpnENCRYPT}" -ge 2048 ]] && - whiptail \ + if [[ "${pivpnENCRYPT}" -ge 2048 ]] \ + && whiptail \ --backtitle "Setup OpenVPN" \ --title "Generate Diffie-Hellman Parameters" \ --yesno "Generating DH parameters can take many hours on a Raspberry Pi. \ @@ -2732,8 +2739,8 @@ confOpenVPN() { fi # Get easy-rsa - curl -sSfL "${easyrsaRel}" | - ${SUDO} tar -xz --one-top-level=/etc/openvpn/easy-rsa --strip-components 1 + curl -sSfL "${easyrsaRel}" \ + | ${SUDO} tar -xz --one-top-level=/etc/openvpn/easy-rsa --strip-components 1 if [[ ! -s /etc/openvpn/easy-rsa/easyrsa ]]; then err "${0}: ERR: Failed to download EasyRSA." @@ -2803,8 +2810,8 @@ confOpenVPN() { ${SUDOE} ./easyrsa --batch build-ca nopass printf "\\n::: CA Complete.\\n" - if [[ "${pivpnCERT}" == "rsa" ]] && - [[ "${USE_PREDEFINED_DH_PARAM}" -ne 1 ]]; then + if [[ "${pivpnCERT}" == "rsa" ]] \ + && [[ "${USE_PREDEFINED_DH_PARAM}" -ne 1 ]]; then if [[ "${runUnattended}" == 'true' ]]; then echo "::: The server key, Diffie-Hellman parameters, \ and HMAC key will now be generated." @@ -2818,8 +2825,8 @@ and HMAC key will now be generated." \ "${r}" \ "${c}" fi - elif [[ "${pivpnCERT}" == "ec" ]] || - [[ "${pivpnCERT}" == "rsa" && "${USE_PREDEFINED_DH_PARAM}" -eq 1 ]]; then + elif [[ "${pivpnCERT}" == "ec" ]] \ + || [[ "${pivpnCERT}" == "rsa" && "${USE_PREDEFINED_DH_PARAM}" -eq 1 ]]; then if [[ "${runUnattended}" == 'true' ]]; then echo "::: The server key and HMAC key will now be generated." else @@ -3100,11 +3107,11 @@ confWireGuard() { ${SUDO} mkdir -p /etc/wireguard/keys # Generate private key and derive public key from it - wg genkey | - ${SUDO} tee /etc/wireguard/keys/server_priv &> /dev/null - ${SUDO} cat /etc/wireguard/keys/server_priv | - wg pubkey | - ${SUDO} tee /etc/wireguard/keys/server_pub &> /dev/null + wg genkey \ + | ${SUDO} tee /etc/wireguard/keys/server_priv &> /dev/null + ${SUDO} cat /etc/wireguard/keys/server_priv \ + | wg pubkey \ + | ${SUDO} tee /etc/wireguard/keys/server_pub &> /dev/null echo "::: Server Keys have been generated." @@ -3128,8 +3135,8 @@ confWireGuard() { confNetwork() { # Enable forwarding of internet traffic - echo 'net.ipv4.ip_forward=1' | - ${SUDO} tee /etc/sysctl.d/99-pivpn.conf > /dev/null + echo 'net.ipv4.ip_forward=1' \ + | ${SUDO} tee /etc/sysctl.d/99-pivpn.conf > /dev/null if [[ "${pivpnenableipv6}" -eq 1 ]]; then { @@ -3249,8 +3256,8 @@ confNetwork() { # On a newly installed system all policies should be ACCEPT, # so the only required rule would be the MASQUERADE one. - if ! ${SUDO} iptables -t nat -S | - grep -q "${VPN}-nat-rule"; then + if ! ${SUDO} iptables -t nat -S \ + | grep -q "${VPN}-nat-rule"; then ${SUDO} iptables \ -t nat \ -I POSTROUTING \ @@ -3262,8 +3269,8 @@ confNetwork() { fi if [[ "${pivpnenableipv6}" -eq 1 ]]; then - if ! ${SUDO} ip6tables -t nat -S | - grep -q "${VPN}-nat-rule"; then + if ! ${SUDO} ip6tables -t nat -S \ + | grep -q "${VPN}-nat-rule"; then ${SUDO} ip6tables \ -t nat \ -I POSTROUTING \ @@ -3283,38 +3290,38 @@ confNetwork() { # Grep returns non 0 exit code where there are no matches, # however that would make the script exit, # for this reasons we use '|| true' to force exit code 0 - INPUT_RULES_COUNT="$(${SUDO} iptables -S INPUT | - grep -vcE '(^-P|ufw-)')" - FORWARD_RULES_COUNT="$(${SUDO} iptables -S FORWARD | - grep -vcE '(^-P|ufw-)')" - INPUT_POLICY="$(${SUDO} iptables -S INPUT | - grep '^-P' | - awk '{print $3}')" - FORWARD_POLICY="$(${SUDO} iptables -S FORWARD | - grep '^-P' | - awk '{print $3}')" + INPUT_RULES_COUNT="$(${SUDO} iptables -S INPUT \ + | grep -vcE '(^-P|ufw-)')" + FORWARD_RULES_COUNT="$(${SUDO} iptables -S FORWARD \ + | grep -vcE '(^-P|ufw-)')" + INPUT_POLICY="$(${SUDO} iptables -S INPUT \ + | grep '^-P' \ + | awk '{print $3}')" + FORWARD_POLICY="$(${SUDO} iptables -S FORWARD \ + | grep '^-P' \ + | awk '{print $3}')" if [[ "${pivpnenableipv6}" -eq 1 ]]; then - INPUT_RULES_COUNTv6="$(${SUDO} ip6tables -S INPUT | - grep -vcE '(^-P|ufw-)')" - FORWARD_RULES_COUNTv6="$(${SUDO} ip6tables -S FORWARD | - grep -vcE '(^-P|ufw-)')" - INPUT_POLICYv6="$(${SUDO} ip6tables -S INPUT | - grep '^-P' | - awk '{print $3}')" - FORWARD_POLICYv6="$(${SUDO} ip6tables -S FORWARD | - grep '^-P' | - awk '{print $3}')" + INPUT_RULES_COUNTv6="$(${SUDO} ip6tables -S INPUT \ + | grep -vcE '(^-P|ufw-)')" + FORWARD_RULES_COUNTv6="$(${SUDO} ip6tables -S FORWARD \ + | grep -vcE '(^-P|ufw-)')" + INPUT_POLICYv6="$(${SUDO} ip6tables -S INPUT \ + | grep '^-P' \ + | awk '{print $3}')" + FORWARD_POLICYv6="$(${SUDO} ip6tables -S FORWARD \ + | grep '^-P' \ + | awk '{print $3}')" fi # If rules count is not zero, we assume we need to explicitly allow traffic. # Same conclusion if there are no rules and the policy is not ACCEPT. # Note that rules are being added to the top of the chain (using -I). - if [[ "${INPUT_RULES_COUNT}" -ne 0 ]] || - [[ "${INPUT_POLICY}" != "ACCEPT" ]]; then - if ! ${SUDO} iptables -S | - grep -q "${VPN}-input-rule"; then + if [[ "${INPUT_RULES_COUNT}" -ne 0 ]] \ + || [[ "${INPUT_POLICY}" != "ACCEPT" ]]; then + if ! ${SUDO} iptables -S \ + | grep -q "${VPN}-input-rule"; then ${SUDO} iptables \ -I INPUT 1 \ -i "${IPv4dev}" \ @@ -3331,10 +3338,10 @@ confNetwork() { fi if [[ "${pivpnenableipv6}" -eq 1 ]]; then - if [[ "${INPUT_RULES_COUNTv6}" -ne 0 ]] || - [[ "${INPUT_POLICYv6}" != "ACCEPT" ]]; then - if ! ${SUDO} ip6tables -S | - grep -q "${VPN}-input-rule"; then + if [[ "${INPUT_RULES_COUNTv6}" -ne 0 ]] \ + || [[ "${INPUT_POLICYv6}" != "ACCEPT" ]]; then + if ! ${SUDO} ip6tables -S \ + | grep -q "${VPN}-input-rule"; then ${SUDO} ip6tables \ -I INPUT 1 \ -i "${IPv6dev}" \ @@ -3351,10 +3358,10 @@ confNetwork() { fi fi - if [[ "${FORWARD_RULES_COUNT}" -ne 0 ]] || - [[ "${FORWARD_POLICY}" != "ACCEPT" ]]; then - if ! ${SUDO} iptables -S | - grep -q "${VPN}-forward-rule"; then + if [[ "${FORWARD_RULES_COUNT}" -ne 0 ]] \ + || [[ "${FORWARD_POLICY}" != "ACCEPT" ]]; then + if ! ${SUDO} iptables -S \ + | grep -q "${VPN}-forward-rule"; then ${SUDO} iptables \ -I FORWARD 1 \ -d "${pivpnNET}/${subnetClass}" \ @@ -3381,10 +3388,10 @@ confNetwork() { fi if [[ "${pivpnenableipv6}" -eq 1 ]]; then - if [[ "${FORWARD_RULES_COUNTv6}" -ne 0 ]] || - [[ "${FORWARD_POLICYv6}" != "ACCEPT" ]]; then - if ! ${SUDO} ip6tables -S | - grep -q "${VPN}-forward-rule"; then + if [[ "${FORWARD_RULES_COUNTv6}" -ne 0 ]] \ + || [[ "${FORWARD_POLICYv6}" != "ACCEPT" ]]; then + if ! ${SUDO} ip6tables -S \ + | grep -q "${VPN}-forward-rule"; then ${SUDO} ip6tables \ -I FORWARD 1 \ -d "${pivpnNETv6}/${subnetClassv6}" \ @@ -3413,10 +3420,10 @@ confNetwork() { case "${PLAT}" in Debian | Raspbian | Ubuntu) - ${SUDO} iptables-save | - ${SUDO} tee /etc/iptables/rules.v4 > /dev/null - ${SUDO} ip6tables-save | - ${SUDO} tee /etc/iptables/rules.v6 > /dev/null + ${SUDO} iptables-save \ + | ${SUDO} tee /etc/iptables/rules.v4 > /dev/null + ${SUDO} ip6tables-save \ + | ${SUDO} tee /etc/iptables/rules.v6 > /dev/null ;; esac @@ -3464,8 +3471,8 @@ confLogging() { # Restart the logging service case "${PLAT}" in Debian | Raspbian | Ubuntu) - ${SUDO} systemctl -q is-active rsyslog.service && - ${SUDO} systemctl restart rsyslog.service + ${SUDO} systemctl -q is-active rsyslog.service \ + && ${SUDO} systemctl restart rsyslog.service ;; Alpine) ${SUDO} rc-service -is rsyslog restart diff --git a/ciscripts/startup.sh b/ciscripts/startup.sh index 6f6e56d..aa03c8d 100644 --- a/ciscripts/startup.sh +++ b/ciscripts/startup.sh @@ -1,13 +1,13 @@ #!/bin/bash -interface=$(ip -o link | - awk '{print $2}' | - cut -d ':' -f 1 | - cut -d '@' -f 1 | - grep -v -w 'lo' | - head -1) -ipaddress=$(ip addr show "${interface}" | - grep -o -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}/[0-9]{2}") +interface=$(ip -o link \ + | awk '{print $2}' \ + | cut -d ':' -f 1 \ + | cut -d '@' -f 1 \ + | grep -v -w 'lo' \ + | head -1) +ipaddress=$(ip addr show "${interface}" \ + | grep -o -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}/[0-9]{2}") gateway=$(ip route show | awk '/default/ {print $3}') hostname="pivpn.test" diff --git a/scripts/backup.sh b/scripts/backup.sh index dc932dd..f711e5b 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -20,8 +20,8 @@ setupConfigDir="/etc/pivpn" CHECK_PKG_INSTALLED='dpkg-query -s' -if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]] && - [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then +if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]] \ + && [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then # Two protocols have been installed, check if the script has passed # an argument, otherwise ask the user which one he wants to remove if [[ "$#" -ge 1 ]]; then diff --git a/scripts/openvpn/bash-completion b/scripts/openvpn/bash-completion index 775d221..1aa353c 100644 --- a/scripts/openvpn/bash-completion +++ b/scripts/openvpn/bash-completion @@ -14,8 +14,8 @@ _pivpn() { else COMPREPLY=("$(compgen -W "${opts}" -- "${cur}")") fi - elif [[ ("${prev}" == "add" || "${prev}" == "-a") ]] && - [[ "${#COMP_WORDS[@]}" -eq 3 ]]; then + elif [[ ("${prev}" == "add" || "${prev}" == "-a") ]] \ + && [[ "${#COMP_WORDS[@]}" -eq 3 ]]; then COMPREPLY=("$(compgen -W "nopass" -- "${cur}")") fi diff --git a/scripts/openvpn/listOVPN.sh b/scripts/openvpn/listOVPN.sh index 3257198..ccba5d3 100755 --- a/scripts/openvpn/listOVPN.sh +++ b/scripts/openvpn/listOVPN.sh @@ -35,10 +35,10 @@ printf "\\e[1m::: Certificate Status List :::\\e[0m\\n" while read -r line || [[ -n "${line}" ]]; do STATUS="$(echo "${line}" | awk '{print $1}')" NAME="$(echo "${line}" | awk -FCN= '{print $2}')" - EXPD="$(echo "${line}" | - awk '{if (length($2) == 15) print $2; else print "20"$2}' | - cut -b 1-8 | - date +"%b %d %Y" -f -)" + EXPD="$(echo "${line}" \ + | awk '{if (length($2) == 15) print $2; else print "20"$2}' \ + | cut -b 1-8 \ + | date +"%b %d %Y" -f -)" if [[ "${STATUS}" == "V" ]]; then printf "Valid" diff --git a/scripts/openvpn/makeOVPN.sh b/scripts/openvpn/makeOVPN.sh index 0f9558b..2d3a1a9 100755 --- a/scripts/openvpn/makeOVPN.sh +++ b/scripts/openvpn/makeOVPN.sh @@ -61,9 +61,9 @@ while [[ "$#" -gt 0 ]]; do _val="${_key##--name=}" if [[ "${_val}" == "${_key}" ]]; then - [[ "$#" -lt 2 ]] && - err "Missing value for the optional argument '${_key}'." && - exit 1 + [[ "$#" -lt 2 ]] \ + && err "Missing value for the optional argument '${_key}'." \ + && exit 1 _val="${2}" shift @@ -75,9 +75,9 @@ while [[ "$#" -gt 0 ]]; do _val="${_key##--password=}" if [[ "${_val}" == "${_key}" ]]; then - [[ "$#" -lt 2 ]] && - err "Missing value for the optional argument '${_key}'." && - exit 1 + [[ "$#" -lt 2 ]] \ + && err "Missing value for the optional argument '${_key}'." \ + && exit 1 _val="${2}" shift @@ -89,9 +89,9 @@ while [[ "$#" -gt 0 ]]; do _val="${_key##--days=}" if [[ "${_val}" == "${_key}" ]]; then - [[ "$#" -lt 2 ]] && - err "Missing value for the optional argument '${_key}'." && - exit 1 + [[ "$#" -lt 2 ]] \ + && err "Missing value for the optional argument '${_key}'." \ + && exit 1 _val="${2}" shift @@ -180,9 +180,9 @@ useBitwarden() { read -r NAME # check name - until [[ "${NAME}" =~ ^[a-zA-Z0-9.@_-]+$ ]] && - [[ "${NAME::1}" != "." ]] && - [[ "${NAME::1}" != "-" ]]; do + until [[ "${NAME}" =~ ^[a-zA-Z0-9.@_-]+$ ]] \ + && [[ "${NAME::1}" != "." ]] \ + && [[ "${NAME::1}" != "-" ]]; do echo -n "Name can only contain alphanumeric characters and these " echo -n "characters (.-@_). The name also cannot start with a dot (.)" echo " or a dash (-). Please try again." @@ -210,13 +210,13 @@ useBitwarden() { # create a new item for your PiVPN Password PASSWD="$(bw generate -usln --length "${LENGTH}")" - bw get template item | - jq '.login.type = "1"' | - jq '.name = "PiVPN"' | - jq -r --arg NAME "${NAME}" '.login.username = $NAME' | - jq -r --arg PASSWD "${PASSWD}" '.login.password = $PASSWD' | - bw encode | - bw create item + bw get template item \ + | jq '.login.type = "1"' \ + | jq '.name = "PiVPN"' \ + | jq -r --arg NAME "${NAME}" '.login.username = $NAME' \ + | jq -r --arg PASSWD "${PASSWD}" '.login.password = $PASSWD' \ + | bw encode \ + | bw create item bw logout } @@ -357,9 +357,9 @@ else err "Please choose another name or revoke this certificate first." exit 1 # Check if name is reserved - elif [[ "${NAME}" == "ta" ]] || - [[ "${NAME}" == "server" ]] || - [[ "${NAME}" == "ca" ]]; then + elif [[ "${NAME}" == "ta" ]] \ + || [[ "${NAME}" == "server" ]] \ + || [[ "${NAME}" == "ca" ]]; then err "Sorry, this is in use by the server and cannot be used by clients." exit 1 fi @@ -370,9 +370,9 @@ else read -r -e -p "How many days should the certificate last? " -i 1080 DAYS fi - if [[ ! "${DAYS}" =~ ^[0-9]+$ ]] || - [[ "${DAYS}" -lt 1 ]] || - [[ "${DAYS}" -gt 3650 ]]; then + if [[ ! "${DAYS}" =~ ^[0-9]+$ ]] \ + || [[ "${DAYS}" -lt 1 ]] \ + || [[ "${DAYS}" -gt 3650 ]]; then # The CRL lasts 3650 days so it doesn't make much sense # that certificates would last longer err "Please input a valid number of days, between 1 and 3650 inclusive." @@ -516,8 +516,8 @@ for i in {2..254}; do # cycle to the end without finding and available octet. # disabling SC2514, variable sourced externaly # shellcheck disable=SC2154 - if [[ -z "$(ls -A /etc/openvpn/ccd)" ]] || - ! find /etc/openvpn/ccd \ + if [[ -z "$(ls -A /etc/openvpn/ccd)" ]] \ + || ! find /etc/openvpn/ccd \ -type f \ -exec grep -q "${NET_REDUCED}.${i}" {} +; then COUNT="${i}" diff --git a/scripts/openvpn/pivpnDebug.sh b/scripts/openvpn/pivpnDebug.sh index fc78e8d..0df047c 100755 --- a/scripts/openvpn/pivpnDebug.sh +++ b/scripts/openvpn/pivpnDebug.sh @@ -65,10 +65,10 @@ if [[ "${PLAT}" != 'Alpine' ]]; then # Regular expession taken from https://superuser.com/a/202835, # it will match invalid IPs like 123.456.789.012 but it's fine # since the log only contains valid ones. - declare -a IPS_TO_HIDE=("$(echo "${OVPNLOG}" | - grepcidr -v 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 | - grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | - uniq)") + declare -a IPS_TO_HIDE=("$(echo "${OVPNLOG}" \ + | grepcidr -v 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 \ + | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' \ + | uniq)") for IP in "${IPS_TO_HIDE[@]}"; do OVPNLOG="${OVPNLOG//"$IP"/REDACTED}" diff --git a/scripts/openvpn/removeOVPN.sh b/scripts/openvpn/removeOVPN.sh index 35210b2..bc523c6 100755 --- a/scripts/openvpn/removeOVPN.sh +++ b/scripts/openvpn/removeOVPN.sh @@ -166,9 +166,9 @@ for ((ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do # shellcheck disable=SC2154 # Grab the client IP address NET_REDUCED="${pivpnNET::-2}" - STATIC_IP="$(grep -v "^#" /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" | - grep -w ifconfig-push | - grep -oE "${NET_REDUCED}\.[0-9]{1,3}")" + STATIC_IP="$(grep -v "^#" /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" \ + | grep -w ifconfig-push \ + | grep -oE "${NET_REDUCED}\.[0-9]{1,3}")" rm -rf /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" # disablung warning SC2154, $install_home sourced externally diff --git a/scripts/self_check.sh b/scripts/self_check.sh index 153cdab..218b502 100755 --- a/scripts/self_check.sh +++ b/scripts/self_check.sh @@ -1,7 +1,7 @@ #!/bin/bash -PLAT="$(grep -sEe '^NAME\=' /etc/os-release | - sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/")" +PLAT="$(grep -sEe '^NAME\=' /etc/os-release \ + | sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/")" # dual protocol, VPN type supplied as $1 VPN="${1}" @@ -255,8 +255,8 @@ else fi if [[ "${PLAT}" == 'Alpine' ]]; then - if [[ "$(rc-service "${VPN_SERVICE}" status | - sed -E -e 's/.*status\: (.*)/\1/')" == 'started' ]]; then + if [[ "$(rc-service "${VPN_SERVICE}" status \ + | sed -E -e 's/.*status\: (.*)/\1/')" == 'started' ]]; then echo ":: [OK] ${VPN_PRETTY_NAME} is running" else ERR=1 @@ -271,8 +271,8 @@ if [[ "${PLAT}" == 'Alpine' ]]; then fi fi - if rc-update show default | - grep -sEe "\s*${VPN_SERVICE} .*" &> /dev/null; then + if rc-update show default \ + | grep -sEe "\s*${VPN_SERVICE} .*" &> /dev/null; then echo -n ":: [OK] ${VPN_PRETTY_NAME} is enabled " echo "(it will automatically start on reboot)" else diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 1a1ba91..824556a 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -26,8 +26,8 @@ setupConfigDir="/etc/pivpn" pivpnFilesDir="/usr/local/src/pivpn" pivpnScriptDir="/opt/pivpn" -PLAT="$(grep -sEe '^NAME\=' /etc/os-release | - sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/")" +PLAT="$(grep -sEe '^NAME\=' /etc/os-release \ + | sed -E -e "s/NAME\=[\'\"]?([^ ]*).*/\1/")" if [[ "${PLAT}" == 'Alpine' ]]; then PKG_MANAGER='apk' @@ -36,8 +36,8 @@ fi UPDATE_PKG_CACHE="${PKG_MANAGER} update" -if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]] && - [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then +if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]] \ + && [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then vpnStillExists=1 # Two protocols have been installed, check if the script has passed diff --git a/scripts/update.sh b/scripts/update.sh index 7c922d4..32709bc 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -90,8 +90,8 @@ updatepivpnscripts() { echo fi - if [[ -d "${pivpnlocalpath}" ]] && - [[ -n "${pivpnlocalpath}" ]]; then + if [[ -d "${pivpnlocalpath}" ]] \ + && [[ -n "${pivpnlocalpath}" ]]; then rm -rf "${pivpnlocalpath}/../.pivpn" fi diff --git a/scripts/wireguard/clientSTAT.sh b/scripts/wireguard/clientSTAT.sh index 248db38..a9a740b 100755 --- a/scripts/wireguard/clientSTAT.sh +++ b/scripts/wireguard/clientSTAT.sh @@ -49,8 +49,8 @@ listClients() { BYTES_RECEIVED="$(awk '{ print $6 }' <<< "${LINE}")" BYTES_SENT="$(awk '{ print $7 }' <<< "${LINE}")" LAST_SEEN="$(awk '{ print $5 }' <<< "${LINE}")" - CLIENT_NAME="$(grep "${PUBLIC_KEY}" "${CLIENTS_FILE}" | - awk '{ print $1 }')" + CLIENT_NAME="$(grep "${PUBLIC_KEY}" "${CLIENTS_FILE}" \ + | awk '{ print $1 }')" printf "%s \t %s \t %s \t " \ "${CLIENT_NAME}" \ "${REMOTE_IP}" \ diff --git a/scripts/wireguard/makeCONF.sh b/scripts/wireguard/makeCONF.sh index c396125..6bbc6f8 100755 --- a/scripts/wireguard/makeCONF.sh +++ b/scripts/wireguard/makeCONF.sh @@ -40,9 +40,9 @@ while [[ "$#" -gt 0 ]]; do _val="${_key##--name=}" if [[ "${_val}" == "${_key}" ]]; then - [[ "$#" -lt 2 ]] && - err "::: Missing value for the optional argument '${_key}'." && - exit 1 + [[ "$#" -lt 2 ]] \ + && err "::: Missing value for the optional argument '${_key}'." \ + && exit 1 _val="${2}" shift @@ -94,9 +94,9 @@ elif [[ -f "configs/${CLIENT_NAME}.conf" ]]; then exit 1 fi -wg genkey | - tee "keys/${CLIENT_NAME}_priv" | - wg pubkey > "keys/${CLIENT_NAME}_pub" +wg genkey \ + | tee "keys/${CLIENT_NAME}_priv" \ + | wg pubkey > "keys/${CLIENT_NAME}_pub" wg genpsk | tee "keys/${CLIENT_NAME}_psk" &> /dev/null echo "::: Client Keys generated" @@ -104,8 +104,8 @@ echo "::: Client Keys generated" for i in {2..254}; do if ! grep -q " ${i}$" configs/clients.txt; then COUNT="${i}" - echo "${CLIENT_NAME} $(< keys/"${CLIENT_NAME}"_pub) $(date +%s) ${COUNT}" | - tee -a configs/clients.txt > /dev/null + echo "${CLIENT_NAME} $(< keys/"${CLIENT_NAME}"_pub) $(date +%s) ${COUNT}" \ + | tee -a configs/clients.txt > /dev/null break fi done @@ -167,12 +167,12 @@ echo "::: Client config generated" echo "::: Updated server config" if [[ -f /etc/pivpn/hosts.wireguard ]]; then - echo "${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn" | - tee -a /etc/pivpn/hosts.wireguard > /dev/null + echo "${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn" \ + | tee -a /etc/pivpn/hosts.wireguard > /dev/null if [[ "${pivpnenableipv6}" == 1 ]]; then - echo "${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn" | - tee -a /etc/pivpn/hosts.wireguard > /dev/null + echo "${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn" \ + | tee -a /etc/pivpn/hosts.wireguard > /dev/null fi if killall -SIGHUP pihole-FTL; then diff --git a/scripts/wireguard/removeCONF.sh b/scripts/wireguard/removeCONF.sh index 9f15b21..c10b10c 100755 --- a/scripts/wireguard/removeCONF.sh +++ b/scripts/wireguard/removeCONF.sh @@ -100,11 +100,11 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do # Grab the least significant octed of the client IP address COUNT="$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $4}')" # The creation date of the client - CREATION_DATE="$(grep "^${CLIENT_NAME} " configs/clients.txt | - awk '{print $3}')" + CREATION_DATE="$(grep "^${CLIENT_NAME} " configs/clients.txt \ + | awk '{print $3}')" # And its public key - PUBLIC_KEY="$(grep "^${CLIENT_NAME} " configs/clients.txt | - awk '{print $2}')" + PUBLIC_KEY="$(grep "^${CLIENT_NAME} " configs/clients.txt \ + | awk '{print $2}')" # Then remove the client matching the variables above sed \ From 6d6ab86fd2eed6b5df60186a9cdef9ecbc9085ba Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Sat, 6 Aug 2022 05:15:28 +0200 Subject: [PATCH 08/14] fix(tarvis ci): fix a bug Fix a code style bug into the style stage --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bd12be8..9ebb975 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,8 +41,8 @@ jobs: virt: vm arch: amd64 script: - - shfmt -d -i 2 -ci -sr auto_install/install.sh - - find scripts/ -type f -exec shfmt -d -i 2 -ci -sr {} \; + - shfmt -d -i 2 -ci -sr -bn auto_install/install.sh + - find scripts/ -type f -exec shfmt -d -i 2 -ci -sr -bn {} \; - stage: test name: "OpenVPN Xenial & Commands" From 10d9610bdc2e43ea65d548d22f0225ff2e401022 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Sat, 6 Aug 2022 05:16:13 +0200 Subject: [PATCH 09/14] docs(CONTRIBUTING): fix a code style error Fix a code style error into the shfmt command --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92bbc6d..14eb247 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Please report unacceptable behavior to any project maintainer. * Use the following [commit rules](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format). * Use the following [code style rules](https://google.github.io/styleguide/shellguide.html). -We suggest you to use `shfmt` with the options `-i 2 -ci -sr -w` +We suggest you to use `shfmt` with the options `-i 2 -ci -sr -w -bn` ### **PiVPN Website** From d049755f8242e3dd51a42c5cde613b7e27b6abad Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Sat, 6 Aug 2022 05:33:14 +0200 Subject: [PATCH 10/14] fix(installer): fix code style Fix a code style bug --- auto_install/install.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/auto_install/install.sh b/auto_install/install.sh index e2a66da..4e74ebb 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -661,30 +661,37 @@ preconfigurePackages() { # to install wireguard-tools. [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] \ || + # If the package is not available, on Debian and Raspbian we can # add it via Bullseye repository. [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] \ || + # If the module is not builtin, on Raspbian we know the headers # package: raspberrypi-kernel-headers [[ "${PLAT}" == 'Raspbian' ]] \ || + # On Alpine, the kernel must be linux-lts or linux-virt if we want to # load the kernel module [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] \ || + # On Alpine Docker Container, the responsibility to have a WireGuard # module on the host system is at user side [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] \ || + # On Debian (and Ubuntu), we can only reliably assume the headers package # for amd64: linux-image-amd64 [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] \ || + # On Ubuntu, additionally the WireGuard package needs to be available, # since we didn't test mixing Ubuntu repositories. [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] \ || + # Ubuntu focal has wireguard support [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'arm64' && "${OSCN}" == 'focal' && -n "${AVAILABLE_WIREGUARD}" ]] then From 97eda159fd3e6ca79c8270ff3b46c5cfcbef0550 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Sat, 6 Aug 2022 05:35:30 +0200 Subject: [PATCH 11/14] refactor(travis ci): add files to lint and style stages Add the scripts into the ciscripts/ directory to the lint and the style stages --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9ebb975..c83f447 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ jobs: arch: amd64 script: - shellcheck -x auto_install/install.sh + - find ciscripts/ -type f -regex '.*\.sh' -exec shellcheck -x {} \; - find scripts/ -type f -exec shellcheck -x {} \; - stage: style @@ -42,6 +43,7 @@ jobs: arch: amd64 script: - shfmt -d -i 2 -ci -sr -bn auto_install/install.sh + - find ciscripts/ -type f -regex '.*\.sh' -exec shfmt -d -i 2 -ci -sr -bn {} \; - find scripts/ -type f -exec shfmt -d -i 2 -ci -sr -bn {} \; - stage: test From 9a7b7dfe7206ca1555b41640b7b79c1537ca6fdb Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Sat, 6 Aug 2022 05:52:31 +0200 Subject: [PATCH 12/14] fix(installer): fix code style Fix a code style bug --- auto_install/install.sh | 63 ++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/auto_install/install.sh b/auto_install/install.sh index 4e74ebb..49a0a0f 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -656,45 +656,30 @@ preconfigurePackages() { fi fi - if - # If the module is builtin and the package available, we only need - # to install wireguard-tools. - [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] \ - || - - # If the package is not available, on Debian and Raspbian we can - # add it via Bullseye repository. - [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] \ - || - - # If the module is not builtin, on Raspbian we know the headers - # package: raspberrypi-kernel-headers - [[ "${PLAT}" == 'Raspbian' ]] \ - || - - # On Alpine, the kernel must be linux-lts or linux-virt if we want to - # load the kernel module - [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] \ - || - - # On Alpine Docker Container, the responsibility to have a WireGuard - # module on the host system is at user side - [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] \ - || - - # On Debian (and Ubuntu), we can only reliably assume the headers package - # for amd64: linux-image-amd64 - [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] \ - || - - # On Ubuntu, additionally the WireGuard package needs to be available, - # since we didn't test mixing Ubuntu repositories. - [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] \ - || - - # Ubuntu focal has wireguard support - [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'arm64' && "${OSCN}" == 'focal' && -n "${AVAILABLE_WIREGUARD}" ]] - then + # case 1: If the module is builtin and the package available, + # we only need to install wireguard-tools. + # case 2: If the package is not available, on Debian and + # Raspbian we can add it via Bullseye repository. + # case 3: If the module is not builtin, on Raspbian we know + # the headers package: raspberrypi-kernel-headers + # case 4: On Alpine, the kernel must be linux-lts or linux-virt + # if we want to load the kernel module + # case 5: On Alpine Docker Container, the responsibility to have + # a WireGuard module on the host system is at user side + # case 6: On Debian (and Ubuntu), we can only reliably assume the + # headers package for amd64: linux-image-amd64 + # case 7: On Ubuntu, additionally the WireGuard package needs to + # be available, since we didn't test mixing Ubuntu repositories. + # case 8: Ubuntu focal has wireguard support + + if [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] \ + || [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] \ + || [[ "${PLAT}" == 'Raspbian' ]] \ + || [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] \ + || [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] \ + || [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] \ + || [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] \ + || [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'arm64' && "${OSCN}" == 'focal' && -n "${AVAILABLE_WIREGUARD}" ]]; then WIREGUARD_SUPPORT=1 fi From dda4d99f102dee7e789b243c94ee3d0938599b61 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Tue, 9 Aug 2022 10:09:25 +0200 Subject: [PATCH 13/14] refactor: improve code style Improve the code style of some pieces of code --- auto_install/install.sh | 134 ++++++++++---------------------- scripts/openvpn/clientStat.sh | 4 +- scripts/openvpn/makeOVPN.sh | 6 +- scripts/openvpn/pivpnDebug.sh | 3 +- scripts/openvpn/removeOVPN.sh | 3 +- scripts/self_check.sh | 12 +-- scripts/uninstall.sh | 6 +- scripts/wireguard/pivpnDEBUG.sh | 3 +- scripts/wireguard/removeCONF.sh | 11 +-- 9 files changed, 54 insertions(+), 128 deletions(-) diff --git a/auto_install/install.sh b/auto_install/install.sh index 49a0a0f..1d1885b 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -32,8 +32,7 @@ PKG_MANAGER="apt-get" ### shellcheck SC2086 UPDATE_PKG_CACHE="${PKG_MANAGER} update -y" PKG_INSTALL="${PKG_MANAGER} --yes --no-install-recommends install" -PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | " -PKG_COUNT="${PKG_COUNT} grep -c ^Inst || true" +PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true" CHECK_PKG_INSTALLED='dpkg-query -s' # Dependencies that are required by the script, @@ -53,9 +52,7 @@ INSTALLED_PACKAGES=() ######## URLs ######## easyrsaVer="3.1.0" -easyrsaRel="https://github.com/OpenVPN/easy-rsa" -easyrsaRel="${easyrsaRel}/releases/download/v${easyrsaVer}" -easyrsaRel="${easyrsaRel}/EasyRSA-${easyrsaVer}.tgz" +easyrsaRel="https://github.com/OpenVPN/easy-rsa/releases/download/v${easyrsaVer}/EasyRSA-${easyrsaVer}.tgz" ######## Undocumented Flags. Shhh ######## runUnattended=false @@ -722,10 +719,7 @@ preconfigurePackages() { fi ## download binaries - curl \ - -f \ - -L \ - -o "${down_dir}/master.tar.gz" \ + curl -fLo "${down_dir}/master.tar.gz" \ https://github.com/pivpn/grepcidr/archive/master.tar.gz tar -xzC "${down_dir}" -f "${down_dir}/master.tar.gz" @@ -1385,12 +1379,10 @@ chooseUser() { # Choose the user for the ovpns if [[ "${runUnattended}" == 'true' ]]; then if [[ -z "${install_user}" ]]; then - if [[ "$(awk \ - -F':' \ + if [[ "$(awk -F ':' \ 'BEGIN {count=0} $3>=1000 && $3<=60000 { count++ } END{ print count }' \ /etc/passwd)" -eq 1 ]]; then - install_user="$(awk \ - -F':' \ + install_user="$(awk -F ':' \ '$3>=1000 && $3<=60000 {print $1}' \ /etc/passwd)" echo -n "::: No user specified, but only ${install_user} is available, " @@ -1438,8 +1430,7 @@ chooseUser() { "${r}" \ "${c}" # First, let's check if there is a user available. - numUsers="$(awk \ - -F':' \ + numUsers="$(awk -F ':' \ 'BEGIN {count=0} $3>=1000 && $3<=60000 { count++ } END{ print count }' \ /etc/passwd)" @@ -1562,8 +1553,7 @@ updateRepo() { # Go back to /usr/local/src otherwise git will complain when the current # working directory has just been deleted (/usr/local/src/pivpn). cd /usr/local/src \ - && ${SUDO} git clone \ - -q \ + && ${SUDO} git clone -q \ --depth 1 \ --no-single-branch \ "${2}" \ @@ -1600,8 +1590,7 @@ makeRepo() { # Go back to /usr/local/src otherwhise git will complain when the current # working directory has just been deleted (/usr/local/src/pivpn). cd /usr/local/src \ - && ${SUDO} git clone \ - -q \ + && ${SUDO} git clone -q \ --depth 1 \ --no-single-branch \ "${2}" \ @@ -2761,14 +2750,12 @@ confOpenVPN() { ${SUDOE} cp vars.example pki/vars # Set elliptic curve certificate or traditional rsa certificates - ${SUDOE} sed \ - -i \ + ${SUDOE} sed -i \ "s/#set_var EASYRSA_ALGO.*/set_var EASYRSA_ALGO ${pivpnCERT}/" \ pki/vars # Set expiration for the CRL to 10 years - ${SUDOE} sed \ - -i \ + ${SUDOE} sed -i \ 's/#set_var EASYRSA_CRL_DAYS.*/set_var EASYRSA_CRL_DAYS 3650/' \ pki/vars @@ -2776,10 +2763,7 @@ confOpenVPN() { # Set custom key size if different from the default sed_pattern="s/#set_var EASYRSA_KEY_SIZE.*/" sed_pattern="${sed_pattern} set_var EASYRSA_KEY_SIZE ${pivpnENCRYPT}/" - ${SUDOE} sed \ - -i \ - "${sed_pattern}" \ - pki/vars + ${SUDOE} sed -i "${sed_pattern}" pki/vars else # If less than 2048, then it must be 521 or lower, # which means elliptic curve certificate was selected. @@ -2791,10 +2775,7 @@ confOpenVPN() { sed_pattern="s/#set_var EASYRSA_CURVE.*/" sed_pattern="${sed_pattern} set_var EASYRSA_CURVE" sed_pattern="${sed_pattern} ${ECDSA_MAP["${pivpnENCRYPT}"]}/" - ${SUDOE} sed \ - -i \ - "${sed_pattern}" \ - pki/vars + ${SUDOE} sed -i "${sed_pattern}" pki/vars fi # Build the certificate authority @@ -2843,9 +2824,7 @@ and HMAC key will now be generated." \ file_pattern="${pivpnFilesDir}/files/etc/openvpn" file_pattern="${file_pattern}/easy-rsa/pki/ffdhe${pivpnENCRYPT}.pem" # Use Diffie-Hellman parameters from RFC 7919 (FFDHE) - ${SUDOE} install \ - -m 644 \ - "${file_pattern}" \ + ${SUDOE} install -m 644 "${file_pattern}" \ "pki/dh${pivpnENCRYPT}.pem" else # Generate Diffie-Hellman key exchange @@ -2863,9 +2842,7 @@ and HMAC key will now be generated." \ if ! getent passwd "${ovpnUserGroup%:*}"; then if [[ "${PLAT}" == 'Alpine' ]]; then - ${SUDOE} adduser \ - -S \ - -D \ + ${SUDOE} adduser -SD \ -h /var/lib/openvpn/ \ -s /sbin/nologin \ "${ovpnUserGroup%:*}" @@ -2881,29 +2858,25 @@ and HMAC key will now be generated." \ ${SUDOE} chown "${ovpnUserGroup}" /etc/openvpn/crl.pem # Write config file for server using the template.txt file - ${SUDO} install \ - -m 644 \ + ${SUDO} install -m 644 \ "${pivpnFilesDir}/files/etc/openvpn/server_config.txt" \ /etc/openvpn/server.conf # Apply client DNS settings - ${SUDOE} sed \ - -i \ + ${SUDOE} sed -i \ "0,/\(dhcp-option DNS \)/ s/\(dhcp-option DNS \).*/\1${pivpnDNS1}\"/" \ /etc/openvpn/server.conf if [[ -z "${pivpnDNS2}" ]]; then ${SUDOE} sed -i '/\(dhcp-option DNS \)/{n;N;d}' /etc/openvpn/server.conf else - ${SUDOE} sed \ - -i \ + ${SUDOE} sed -i \ "0,/\(dhcp-option DNS \)/! s/\(dhcp-option DNS \).*/\1${pivpnDNS2}\"/" \ /etc/openvpn/server.conf fi # Set the user encryption key size - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s#\\(dh /etc/openvpn/easy-rsa/pki/dh\\).*#\\1${pivpnENCRYPT}.pem#" \ /etc/openvpn/server.conf @@ -2923,14 +2896,12 @@ and HMAC key will now be generated." \ sed_pattern="s/\(dh \/etc\/openvpn\/easy-rsa\/pki\/dh\).*/dh" sed_pattern="${sed_pattern} none\necdh-curve" sed_pattern="${sed_pattern} ${ECDSA_MAP["${pivpnENCRYPT}"]}/" - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "${sed_pattern}" \ /etc/openvpn/server.conf elif [[ "${pivpnCERT}" == "rsa" ]]; then # Otherwise set the user encryption key size - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s#\\(dh /etc/openvpn/easy-rsa/pki/dh\\).*#\\1${pivpnENCRYPT}.pem#" \ /etc/openvpn/server.conf fi @@ -2942,8 +2913,7 @@ and HMAC key will now be generated." \ # if they modified VPN subnet class put value in server.conf if [[ "$(cidrToMask "${subnetClass}")" != "255.255.255.0" ]]; then - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s/255.255.255.0/$(cidrToMask "${subnetClass}")/g" \ /etc/openvpn/server.conf fi @@ -2962,19 +2932,16 @@ and HMAC key will now be generated." \ sed_pattern="0,/\\(.*dhcp-option.*\\)/" sed_pattern="${sed_pattern}s//push \"dhcp-option " sed_pattern="${sed_pattern}DOMAIN ${pivpnSEARCHDOMAIN}\" \\n&/" - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "${sed_pattern}" \ /etc/openvpn/server.conf fi # write out server certs to conf file - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s#\\(key /etc/openvpn/easy-rsa/pki/private/\\).*#\\1${SERVER_NAME}.key#" \ /etc/openvpn/server.conf - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s#\\(cert /etc/openvpn/easy-rsa/pki/issued/\\).*#\\1${SERVER_NAME}.crt#" \ /etc/openvpn/server.conf @@ -2982,9 +2949,7 @@ and HMAC key will now be generated." \ # "/etc/openvpn/openvpn.conf". # To avoid crash thorugh OpenRC, we symlink this file. if [[ "${PLAT}" == 'Alpine' ]]; then - ${SUDO} ln -s \ - -f \ - -T \ + ${SUDO} ln -sfT \ /etc/openvpn/server.conf \ /etc/openvpn/openvpn.conf \ > /dev/null @@ -2992,42 +2957,36 @@ and HMAC key will now be generated." \ } confOVPN() { - ${SUDO} install \ - -m 644 \ + ${SUDO} install -m 644 \ "${pivpnFilesDir}/files/etc/openvpn/easy-rsa/pki/Default.txt" \ /etc/openvpn/easy-rsa/pki/Default.txt - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s/IPv4pub/${pivpnHOST}/" \ /etc/openvpn/easy-rsa/pki/Default.txt # if they modified port put value in Default.txt for clients to use if [[ "${pivpnPORT}" -ne 1194 ]]; then - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s/1194/${pivpnPORT}/g" \ /etc/openvpn/easy-rsa/pki/Default.txt fi # if they modified protocol put value in Default.txt for clients to use if [[ "${pivpnPROTO}" != "udp" ]]; then - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s/proto udp/proto tcp/g" \ /etc/openvpn/easy-rsa/pki/Default.txt fi # verify server name to strengthen security - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "s/SRVRNAME/${SERVER_NAME}/" \ /etc/openvpn/easy-rsa/pki/Default.txt if [[ "${pivpnTLSPROT}" == "tls-crypt" ]]; then # If they enabled 2.4 remove key-direction options since it's not required - ${SUDO} sed \ - -i \ + ${SUDO} sed -i \ "/key-direction 1/d" \ /etc/openvpn/easy-rsa/pki/Default.txt fi @@ -3038,8 +2997,7 @@ confWireGuard() { # Ubuntu 20.04 if [[ "${PLAT}" == 'Alpine' ]]; then echo '::: Adding wg-quick unit' - ${SUDO} install \ - -m 0755 \ + ${SUDO} install -m 0755 \ "${pivpnFilesDir}/files/etc/init.d/wg-quick" \ /etc/init.d/wg-quick else @@ -3049,9 +3007,7 @@ confWireGuard() { wireguard_service_path="${wireguard_service_path}/wg-quick@.service.d" wireguard_service_path="${wireguard_service_path}/override.conf" echo "::: Adding additional reload job type for wg-quick unit" - ${SUDO} install \ - -D \ - -m 644 \ + ${SUDO} install -Dm 644 \ "${wireguard_service_path}" \ /etc/systemd/system/wg-quick@.service.d/override.conf ${SUDO} systemctl daemon-reload @@ -3562,8 +3518,7 @@ confUnattendedUpgrades() { # https://github.com/mvo5/unattended-upgrades/blob/master/data/50unattended-upgrades.Raspbian # Add the remaining settings for all other distributions if [[ "${PLAT}" == "Raspbian" ]]; then - ${SUDO} install \ - -m 644 \ + ${SUDO} install -m 644 \ "${pivpnFilesDir}/files${aptConfDir}/50unattended-upgrades.Raspbian" \ "${aptConfDir}/50unattended-upgrades" fi @@ -3594,9 +3549,7 @@ confUnattendedUpgrades() { # when installing from debian package if [[ "${VPN}" == "wireguard" ]]; then if [[ -f /etc/apt/sources.list.d/pivpn-bullseye-repo.list ]]; then - if ! grep \ - -q \ - "\"o=${PLAT},n=bullseye\";" \ + if ! grep -q "\"o=${PLAT},n=bullseye\";" \ "${aptConfDir}/50unattended-upgrades"; then local sed_pattern sed_pattern=" {/a\"o=${PLAT},n=bullseye\";" @@ -3617,10 +3570,7 @@ confUnattendedUpgrades() { fi ## download binaries - curl \ - -f \ - -L \ - -o "${down_dir}/master.zip" \ + curl -fLo "${down_dir}/master.zip" \ https://github.com/jirutka/apk-autoupdate/archive/refs/heads/master.zip unzip -qd "${down_dir}" "${down_dir}/master.zip" @@ -3639,8 +3589,7 @@ confUnattendedUpgrades() { fi ) || exit 1 - ${SUDO} install \ - -m 0755 \ + ${SUDO} install -m 0755 \ "${pivpnFilesDir}/files/etc/apk/personal_autoupdate.conf" \ /etc/apk/personal_autoupdate.conf ${SUDO} apk-autoupdate /etc/apk/personal_autoupdate.conf @@ -3683,15 +3632,10 @@ installScripts() { # Only one protocol is installed, symlink bash completion, the pivpn script # and the script directory - ${SUDO} ln -s \ - -f \ - -T \ + ${SUDO} ln -sfT \ "${pivpnFilesDir}/scripts/${VPN}/bash-completion" \ /etc/bash_completion.d/pivpn - ${SUDO} ln \ - -s \ - -f \ - -T \ + ${SUDO} ln -sfT \ "${pivpnFilesDir}/scripts/${VPN}/pivpn.sh" \ /usr/local/bin/pivpn ${SUDO} ln -sf "${pivpnFilesDir}/scripts/" "${pivpnScriptDir}" diff --git a/scripts/openvpn/clientStat.sh b/scripts/openvpn/clientStat.sh index c810d79..b2da6f8 100755 --- a/scripts/openvpn/clientStat.sh +++ b/scripts/openvpn/clientStat.sh @@ -58,9 +58,7 @@ listClients() { printf "- %s\n" "${array[9]}" done < "${STATUS_LOG}" else - awk \ - -F' ' \ - -v s='CLIENT_LIST' \ + awk -F ' ' -v s='CLIENT_LIST' \ '$1 == s { print $2"\t\t"$3"\t"$4"\t"$5"\t\t"$6"\t\t"$8" "$9" "$11" - "$10"\n" }' \ diff --git a/scripts/openvpn/makeOVPN.sh b/scripts/openvpn/makeOVPN.sh index 2d3a1a9..2e5d209 100755 --- a/scripts/openvpn/makeOVPN.sh +++ b/scripts/openvpn/makeOVPN.sh @@ -443,8 +443,7 @@ echo "tls Private Key found: ${TA}" # Next append the client Public Cert echo "" - sed \ - -n \ + sed -n \ -e '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' \ < "issued/${NAME}${CRT}" echo "" @@ -517,8 +516,7 @@ for i in {2..254}; do # disabling SC2514, variable sourced externaly # shellcheck disable=SC2154 if [[ -z "$(ls -A /etc/openvpn/ccd)" ]] \ - || ! find /etc/openvpn/ccd \ - -type f \ + || ! find /etc/openvpn/ccd -type f \ -exec grep -q "${NET_REDUCED}.${i}" {} +; then COUNT="${i}" echo -n "ifconfig-push ${NET_REDUCED}.${i}" >> /etc/openvpn/ccd/"${NAME}" diff --git a/scripts/openvpn/pivpnDebug.sh b/scripts/openvpn/pivpnDebug.sh index 0df047c..bff2b8f 100755 --- a/scripts/openvpn/pivpnDebug.sh +++ b/scripts/openvpn/pivpnDebug.sh @@ -22,8 +22,7 @@ echo -n "Branch: " git --git-dir /usr/local/src/pivpn/.git rev-parse --abbrev-ref HEAD git \ - --git-dir /usr/local/src/pivpn/.git log \ - -n 1 \ + --git-dir /usr/local/src/pivpn/.git log -n 1 \ --format='Commit: %H%nAuthor: %an%nDate: %ad%nSummary: %s' printf "=============================================\n" diff --git a/scripts/openvpn/removeOVPN.sh b/scripts/openvpn/removeOVPN.sh index bc523c6..168a53b 100755 --- a/scripts/openvpn/removeOVPN.sh +++ b/scripts/openvpn/removeOVPN.sh @@ -181,8 +181,7 @@ for ((ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do if [[ -f /etc/pivpn/hosts.openvpn ]]; then sed \ -e "\#${STATIC_IP} ${CERTS_TO_REVOKE[ii]}.pivpn#d" \ - -i \ - /etc/pivpn/hosts.openvpn + -i /etc/pivpn/hosts.openvpn if killall -SIGHUP pihole-FTL; then echo "::: Updated hosts file for Pi-hole" diff --git a/scripts/self_check.sh b/scripts/self_check.sh index 218b502..6b0ed1f 100755 --- a/scripts/self_check.sh +++ b/scripts/self_check.sh @@ -37,8 +37,7 @@ if [[ "$(< /proc/sys/net/ipv4/ip_forward)" -eq 1 ]]; then echo ":: [OK] IP forwarding is enabled" else ERR=1 - read \ - -r \ + read -r \ -p ":: [ERR] IP forwarding is not enabled, attempt fix now? [Y/n] " \ REPLY @@ -98,8 +97,7 @@ if [[ "${USING_UFW}" -eq 0 ]]; then echo ":: [OK] Iptables INPUT rule set" else ERR=1 - read \ - -r \ + read -r \ -p ":: [ERR] Iptables INPUT rule is not set, attempt fix now? [Y/n] " \ REPLY @@ -219,8 +217,7 @@ else echo ":: [OK] Ufw input rule set" else ERR=1 - read \ - -r \ + read -r \ -p ":: [ERR] Ufw input rule is not set, attempt fix now? [Y/n] " \ REPLY @@ -240,8 +237,7 @@ else echo ":: [OK] Ufw forwarding rule set" else ERR=1 - read \ - -r \ + read -r \ -p ":: [ERR] Ufw forwarding rule is not set, attempt fix now? [Y/n] " \ REPLY diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 824556a..9acec1b 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -358,13 +358,11 @@ removeAll() { ${SUDO} unlink /usr/local/bin/pivpn ${SUDO} ln \ - -s \ - -T "${pivpnFilesDir}/scripts/${othervpn}/pivpn.sh" \ + -sT "${pivpnFilesDir}/scripts/${othervpn}/pivpn.sh" \ /usr/local/bin/pivpn ${SUDO} ln \ - -s \ - -T "${pivpnFilesDir}/scripts/${othervpn}/bash-completion" \ + -sT "${pivpnFilesDir}/scripts/${othervpn}/bash-completion" \ /etc/bash_completion.d/pivpn # shellcheck disable=SC1091 diff --git a/scripts/wireguard/pivpnDEBUG.sh b/scripts/wireguard/pivpnDEBUG.sh index 2ecc011..28e44f3 100755 --- a/scripts/wireguard/pivpnDEBUG.sh +++ b/scripts/wireguard/pivpnDEBUG.sh @@ -22,8 +22,7 @@ echo -n "Branch: " git --git-dir /usr/local/src/pivpn/.git rev-parse --abbrev-ref HEAD git \ - --git-dir /usr/local/src/pivpn/.git log \ - -n 1 \ + --git-dir /usr/local/src/pivpn/.git log -n 1 \ --format='Commit: %H%nAuthor: %an%nDate: %ad%nSummary: %s' printf "=============================================\n" diff --git a/scripts/wireguard/removeCONF.sh b/scripts/wireguard/removeCONF.sh index c10b10c..50c2492 100755 --- a/scripts/wireguard/removeCONF.sh +++ b/scripts/wireguard/removeCONF.sh @@ -109,8 +109,7 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do # Then remove the client matching the variables above sed \ -e "\#${CLIENT_NAME} ${PUBLIC_KEY} ${CREATION_DATE} ${COUNT}#d" \ - -i \ - configs/clients.txt + -i configs/clients.txt # Remove the peer section from the server config sed_pattern="/### begin ${CLIENT_NAME} ###/," @@ -136,10 +135,7 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do rm "${CONFIG}" fi done < <(find "${install_home}" \ - -maxdepth 3 \ - -type f \ - -name '*.conf' \ - -print0) + -maxdepth 3 -type f -name '*.conf' -print0) ((DELETED_COUNT++)) echo "::: Successfully deleted ${CLIENT_NAME}" @@ -152,8 +148,7 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do sed \ -e "\#${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn#d" \ -e "\#${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn#d" \ - -i \ - /etc/pivpn/hosts.wireguard + -i /etc/pivpn/hosts.wireguard if killall -SIGHUP pihole-FTL; then echo "::: Updated hosts file for Pi-hole" From 8100537a7cb2a450723ac82fee616100dd2f0d50 Mon Sep 17 00:00:00 2001 From: Giulio Coa Date: Thu, 11 Aug 2022 17:28:31 +0200 Subject: [PATCH 14/14] fix: revert code style Revert the code style of a piece of code --- auto_install/install.sh | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/auto_install/install.sh b/auto_install/install.sh index 1d1885b..66c1df3 100755 --- a/auto_install/install.sh +++ b/auto_install/install.sh @@ -3395,26 +3395,22 @@ confLogging() { program_name='ovpn-server' fi - { - echo "if \$programname == '${program_name}' then /var/log/openvpn.log" - echo "if \$programname == '${program_name}' then stop" - } | ${SUDO} tee /etc/rsyslog.d/30-openvpn.conf > /dev/null + echo "if \$programname == '${program_name}' then /var/log/openvpn.log +if \$programname == '${program_name}' then stop" | ${SUDO} tee /etc/rsyslog.d/30-openvpn.conf > /dev/null - { - echo "/var/log/openvpn.log" - echo "{" - echo $'\t'"rotate 4" - echo $'\t'"weekly" - echo $'\t'"missingok" - echo $'\t'"notifempty" - echo $'\t'"compress" - echo $'\t'"delaycompress" - echo $'\t'"sharedscripts" - echo $'\t'"postrotate" - echo $'\t'$'\t'"invoke-rc.d rsyslog rotate >/dev/null 2>&1 || true" - echo $'\t'"endscript" - echo "}" - } | ${SUDO} tee /etc/logrotate.d/openvpn > /dev/null + echo "/var/log/openvpn.log +{ + rotate 4 + weekly + missingok + notifempty + compress + delaycompress + sharedscripts + postrotate + invoke-rc.d rsyslog rotate >/dev/null 2>&1 || true + endscript +}" | ${SUDO} tee /etc/logrotate.d/openvpn > /dev/null # Restart the logging service case "${PLAT}" in