├── addon_vars ├── ui_install.sh ├── install.sh ├── README.md ├── nvpnmgr-menu.sh └── nordvpnmanager.sh /addon_vars: -------------------------------------------------------------------------------- 1 | # addon name 2 | MY_ADDON_NAME=nordvpnmanager 3 | # control script 4 | MY_ADDON_SCRIPT=nordvpnmanager.sh 5 | # addon page 6 | MY_ADDON_PAGE=NordVPNManager.asp 7 | # tab name 8 | MY_ADDON_TAB="NordVPN Manager" 9 | # Github repo name 10 | GIT_REPO="asusmerlin-nvpnmgr" 11 | # Github repo branch - modify to pull different branch 12 | # (fetch will overwrite local changes) 13 | GIT_REPO_BRANCH=master 14 | # Github dir 15 | GITHUB_DIR="https://raw.githubusercontent.com/h0me5k1n/$GIT_REPO/$GIT_REPO_BRANCH" 16 | # Local repo dir 17 | LOCAL_REPO="/jffs/scripts/$MY_ADDON_NAME" 18 | 19 | -------------------------------------------------------------------------------- /ui_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source /usr/sbin/helper.sh 4 | 5 | # Absolute path to this script, e.g. /home/user/bin/foo.sh 6 | SCRIPT=$(readlink -f "$0") 7 | # Absolute path this script is in, thus /home/user/bin 8 | SCRIPTPATH=$(dirname "$SCRIPT") 9 | # load standard variables 10 | source "$SCRIPTPATH/addon_vars" 11 | 12 | if [ ! -f "/jffs/addons/$MY_ADDON_NAME/$MY_ADDON_PAGE" ]; then 13 | echo "/jffs/addons/$MY_ADDON_NAME/$MY_ADDON_PAGE does not exist. Cannot enable UI without this file." 14 | exit 5 15 | fi 16 | 17 | # Does the firmware support addons? 18 | nvram get rc_support | grep -q am_addons 19 | if [ $? != 0 ] 20 | then 21 | echo "This firmware does not support addons!" 22 | logger "$MY_ADDON_NAME addon" "This firmware does not support addons!" 23 | exit 5 24 | fi 25 | 26 | # Obtain the first available mount point in $am_webui_page 27 | am_get_webui_page /jffs/addons/$MY_ADDON_NAME/$MY_ADDON_PAGE 28 | 29 | if [ "$am_webui_page" = "none" ] 30 | then 31 | echo "Unable to install $MY_ADDON_PAGE" 32 | logger "$MY_ADDON_NAME addon" "Unable to install $MY_ADDON_PAGE" 33 | exit 5 34 | fi 35 | logger "$MY_ADDON_NAME addon" "Mounting $MY_ADDON_PAGE as $am_webui_page" 36 | 37 | # Copy custom page 38 | cp /jffs/addons/$MY_ADDON_NAME/$MY_ADDON_PAGE /www/user/$am_webui_page 39 | 40 | # Copy menuTree (if no other script has done it yet) so we can modify it 41 | if [ ! -f /tmp/menuTree.js ] 42 | then 43 | cp /www/require/modules/menuTree.js /tmp/ 44 | mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js 45 | fi 46 | 47 | # Insert link at the end of the Tools menu. Match partial string, since tabname can change between builds (if using an AS tag) 48 | sed -i "/url: \"Tools_OtherSettings.asp\", tabName:/a {url: \"$am_webui_page\", tabName: \"$MY_ADDON_TAB\"}," /tmp/menuTree.js 49 | 50 | # sed and binding mounts don't work well together, so remount modified file 51 | umount /www/require/modules/menuTree.js && mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js 52 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Absolute path to this script, e.g. /home/user/bin/foo.sh 4 | SCRIPT=$(readlink -f "$0") 5 | # Absolute path this script is in, thus /home/user/bin 6 | SCRIPTPATH=$(dirname "$SCRIPT") 7 | # addon name 8 | MY_ADDON_NAME=nordvpnmanager 9 | # Github repo name 10 | GIT_REPO="asusmerlin-nvpnmgr" 11 | # Github repo branch - modify to pull different branch 12 | # (fetch will overwrite local changes) 13 | if [ -z "$1" ] 14 | then 15 | GIT_REPO_BRANCH=master 16 | else 17 | GIT_REPO_BRANCH=development 18 | fi 19 | # Github dir 20 | GITHUB_DIR="https://raw.githubusercontent.com/h0me5k1n/$GIT_REPO/$GIT_REPO_BRANCH" 21 | # Local repo dir 22 | LOCAL_REPO="/jffs/scripts/$MY_ADDON_NAME" 23 | 24 | # functions 25 | errorcheck(){ 26 | echo "$SCRIPTSECTION reported an error..." 27 | exit 1 28 | } 29 | 30 | # use to download the files from github 31 | GetFiles(){ 32 | echo "downloading $GIT_REPO using $GIT_REPO_BRANCH branch" 33 | GETFILENAME=addon_vars 34 | SCRIPTSECTION=get_$GETFILENAME 35 | [ -f "$LOCAL_REPO/$GETFILENAME" ] && rm "$LOCAL_REPO/$GETFILENAME" 36 | wget -O "$LOCAL_REPO/$GETFILENAME" "$GITHUB_DIR/$GETFILENAME" >/dev/null 2>&1 || errorcheck 37 | 38 | GETFILENAME=install.sh 39 | SCRIPTSECTION=get_$GETFILENAME 40 | [ -f "$LOCAL_REPO/$GETFILENAME" ] && rm "$LOCAL_REPO/$GETFILENAME" 41 | wget -O "$LOCAL_REPO/$GETFILENAME" "$GITHUB_DIR/$GETFILENAME" >/dev/null 2>&1 || errorcheck 42 | chmod 755 "$LOCAL_REPO/$GETFILENAME" 43 | 44 | GETFILENAME=nordvpnmanager.sh 45 | SCRIPTSECTION=get_$GETFILENAME 46 | [ -f "$LOCAL_REPO/$GETFILENAME" ] && rm "$LOCAL_REPO/$GETFILENAME" 47 | wget -O "$LOCAL_REPO/$GETFILENAME" "$GITHUB_DIR/$GETFILENAME" >/dev/null 2>&1 || errorcheck 48 | chmod 755 "$LOCAL_REPO/$GETFILENAME" 49 | 50 | GETFILENAME=ui_install.sh 51 | SCRIPTSECTION=get_$GETFILENAME 52 | [ -f "$LOCAL_REPO/$GETFILENAME" ] && rm "$LOCAL_REPO/$GETFILENAME" 53 | wget -O "$LOCAL_REPO/$GETFILENAME" "$GITHUB_DIR/$GETFILENAME" >/dev/null 2>&1 || errorcheck 54 | chmod 755 "$LOCAL_REPO/$GETFILENAME" 55 | 56 | GETFILENAME=nvpnmgr-menu.sh 57 | SCRIPTSECTION=get_$GETFILENAME 58 | [ -f "$LOCAL_REPO/$GETFILENAME" ] && rm "$LOCAL_REPO/$GETFILENAME" 59 | wget -O "$LOCAL_REPO/$GETFILENAME" "$GITHUB_DIR/$GETFILENAME" >/dev/null 2>&1 || errorcheck 60 | chmod 755 "$LOCAL_REPO/$GETFILENAME" 61 | 62 | SCRIPTSECTION= 63 | } 64 | 65 | # Check this is an Asus Merlin router 66 | nvram get buildinfo | grep merlin >/dev/null 2>&1 67 | if [ $? != 0 ] 68 | then 69 | echo "This script is only supported on an Asus Router running Merlin firmware!" 70 | exit 5 71 | fi 72 | 73 | # Does the firmware support addons? 74 | nvram get rc_support | grep -q am_addons 75 | if [ $? != 0 ] 76 | then 77 | echo "This firmware does not support addons!" 78 | logger "$MY_ADDON_NAME addon" "This firmware does not support addons!" 79 | exit 5 80 | fi 81 | 82 | # Check jffs is enabled 83 | JFFS_STATE=$(nvram get jffs2_on) 84 | if [ $JFFS_STATE != 1 ] 85 | then 86 | echo "This addon requires jffs to be enabled!" 87 | logger "$MY_ADDON_NAME addon" "This addon requires jffs to be enabled!" 88 | exit 5 89 | fi 90 | 91 | # create local repo folder 92 | mkdir -p "$LOCAL_REPO" 93 | 94 | # Get files 95 | GetFiles 96 | 97 | echo "installation complete... visit https://github.com/h0me5k1n/asusmerlin-nvpnmgr for CLI usage information or run \"nvpnmgr-menu.sh\" for menu driven configuration." -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asusmerlin-nvpnmgr 2 | Automatically update VPN client connection to recommended NordVPN server on Asus Merlin router firmware. Tested on RT-AC68U running v384.16 from https://www.asuswrt-merlin.net/download 3 | 4 | ## Prerequisites 5 | 6 | a NordVPN account is required to establish a connection. 7 | 8 | an Asus router running v384.15 or later of the Merlin firmware. 9 | 10 | The JFFS partition needs to be enabled. 11 | 12 | a VPN connection with the string "nordvpn" needs to exist in 1 of the 5 VPN client configurations on the router for the script to successfully run (install possible as long as the above prerequisites are in place). Configure this initially using the information from NordVPN about configuring the connection. Future executions of the script will also use the required naming convention. 13 | 14 | ## Installation (Script) 15 | To install the required files, run the following command on the Asus router running Merlin firmware: 16 | 17 | ``` 18 | wget -O - https://raw.githubusercontent.com/h0me5k1n/asusmerlin-nvpnmgr/master/install.sh | sh 19 | ``` 20 | 21 | ## Usage (Menu) 22 | run the following command 23 | ``` 24 | ./nvpnmgr-menu.sh 25 | ``` 26 | There is currently no validation on the input for scheduled entries - make sure you enter valid values! 27 | 28 | (menu approach inspired by https://github.com/jackyaz) 29 | 30 | ## Usage (CLI) 31 | - where [1|2|3|4|5] is noted, this refers to the instance of the VPN connection 32 | - where [openvpn_udp|openvpn_tcp] is noted, this refers to the VPN protocol (as named by NordVPN in their API). This is currently fixed to use the "openvpn_udp" VPN profiles. Future version may support multiple protocols (see "To Do" section). 33 | - where [minute] is noted, this refers to the minute in the hour when a scheduled update should take place 34 | - where [hour] is noted, this refers to the hour in the day/s when a scheduled update should take place 35 | - where [day numbers] is noted, this refers to the days when an update will take place from 0 to 6 (1 = Monday). Multiple days can be specified separating days with a comma (e.g. "1,5" is Monday and Friday). 36 | 37 | If a * is needed for minute, hour or day, the parameter must be enclosed in quotes (e.g. "*") 38 | 39 | ### Manual Server Update 40 | to manually trigger an update 41 | ``` 42 | ./nordvpnmanager.sh update [1|2|3|4|5] [openvpn_udp|openvpn_tcp] 43 | ``` 44 | if a connection is currently running, it will update and reconnect to the recommended server. 45 | 46 | This is currently fixed to use the "openvpn_udp" VPN profiles. Future version may support multiple protocols (see "To Do" section) 47 | 48 | ### Configure a Scheduled Server Update 49 | to schedule updates using cron/cru 50 | ``` 51 | ./nordvpnmanager.sh schedule [1|2|3|4|5] [openvpn_udp|openvpn_tcp] [minute] [hour] [day numbers] 52 | ``` 53 | This will not affect any existing connections until the scheduled time is reached and the VPN is connected 54 | 55 | This is currently fixed to use the "openvpn_udp" VPN profiles. Future version may support multiple protocols (see "To Do" section) 56 | 57 | ### Cancel Scheduled Server Update 58 | to cancel scheduled updates configured in cron/cru 59 | ``` 60 | ./nordvpnmanager.sh cancel [1|2|3|4|5] 61 | ``` 62 | This will not affect any existing connections 63 | 64 | ## Installation (WebUI - untested) 65 | This has not yet been created as the web page that needs to be installed has not yet been created. 66 | The actual script has been created but will not complete successfully without the web page. 67 | 68 | ## To Do 69 | Possible enhancements (when I get round to it!): 70 | 71 | - create a menu DONE 72 | - create menu entry validation 73 | - query available protocols via NordVPN api 74 | - handle tcp and udp protocols DONE 75 | - handle all protocols 76 | - write options to temp nvram (I haven't figured out how a web page passes parameters to an addon script. This might be needed instead of passing them from the page. e.g. page write temp nvram entries that are used by the script and then discarded?!?) 77 | - create web page for UI (I need help with this!) 78 | - test web page functions 79 | -------------------------------------------------------------------------------- /nvpnmgr-menu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # menu system 3 | # inspired by approaches in repos from https://github.com/jackyaz 4 | 5 | # Absolute path to this script, e.g. /home/user/bin/foo.sh 6 | SCRIPT=$(readlink -f "$0") 7 | # Absolute path this script is in, thus /home/user/bin 8 | SCRIPTPATH=$(dirname "$SCRIPT") 9 | # load standard variables and helper script 10 | source /usr/sbin/helper.sh 11 | source "$SCRIPTPATH/addon_vars" 12 | CONTROLSCRIPT="$LOCAL_REPO/$MY_ADDON_SCRIPT" 13 | 14 | # default variables for this script 15 | OPTIONCHECK=0 16 | 17 | PressEnter(){ 18 | while true; do 19 | printf "Press enter to continue..." 20 | read -r "key" 21 | case "$key" in 22 | *) 23 | break 24 | ;; 25 | esac 26 | done 27 | return 0 28 | } 29 | 30 | ReturnToMainMenu(){ 31 | OPTIONCHECK=1 32 | RETURNTEXT="$1" 33 | break 34 | ScriptHeader 35 | UpdateNowMenuHeader 36 | } 37 | 38 | SetVPNClient(){ 39 | printf "\\n\\e[1mPlease select a VPN client connection (x to cancel): \\e[0m" 40 | read -r "VPN_NO" 41 | if [ "$VPN_NO" = "x" ]; then 42 | OPTIONCHECK=1 43 | ReturnToMainMenu "previous operation cancelled" 44 | elif [ -z "$VPN_NO" ]; then 45 | ReturnToMainMenu "you must specify a valid VPN client" 46 | fi 47 | # validate VPN_NO here (must be a number from 1 to 5 have "nordvpn" in the name) 48 | } 49 | 50 | SetVPNProtocol(){ 51 | printf "\\n\\e[1mPlease select a VPN protocol (x to cancel): \\e[0m\\n" 52 | printf " 1. UDP\\n" 53 | printf " 2. TCP\\n" 54 | read -r "menu" 55 | 56 | while true; do 57 | case "$menu" in 58 | 1) 59 | # check for connections 60 | VPNPROT=openvpn_udp 61 | break 62 | ;; 63 | 2) 64 | # configure now 65 | VPNPROT=openvpn_tcp 66 | break 67 | ;; 68 | x) 69 | ReturnToMainMenu "previous operation cancelled" 70 | break 71 | ;; 72 | *) 73 | ReturnToMainMenu "you must choose a protocol option" 74 | break 75 | ;; 76 | esac 77 | done 78 | 79 | if [ -z "$VPNPROT" ]; then 80 | ReturnToMainMenu "you must choose a protocol option" 81 | fi 82 | } 83 | 84 | SetVPNType(){ 85 | printf "\\n\\e[1mPlease select a VPN Type (x to cancel): \\e[0m\\n" 86 | printf " 1. Standard VPN (default)\\n" 87 | printf " 2. Double VPN\\n" 88 | printf " 3. P2P\\n" 89 | read -r "menu" 90 | 91 | while true; do 92 | case "$menu" in 93 | 1) 94 | # check for connections 95 | VPNTYPE=standard 96 | break 97 | ;; 98 | 2) 99 | # configure now 100 | VPNTYPE=double 101 | break 102 | ;; 103 | 3) 104 | # configure now 105 | VPNTYPE=p2p 106 | break 107 | ;; 108 | x) 109 | ReturnToMainMenu "previous operation cancelled" 110 | break 111 | ;; 112 | *) 113 | VPNTYPE=standard 114 | break 115 | ;; 116 | esac 117 | done 118 | if [ -z "$VPNTYPE" ]; then 119 | ReturnToMainMenu "type not set or previous operation cancelled" 120 | fi 121 | } 122 | 123 | SetDays(){ 124 | printf "\\n\\e[1mPlease choose update day/s (x to cancel - blank for every day): \\e[0m" 125 | read -r "CRU_DAYNUMBERS" 126 | if [ "$CRU_DAYNUMBERS" = "x" ]; then 127 | ReturnToMainMenu "previous operation cancelled" 128 | elif [ -z "$CRU_DAYNUMBERS" ]; then 129 | CRU_DAYNUMBERS="*" 130 | printf "\\n\\e[1mSet to every day\\e[0m\\n" 131 | fi 132 | # validate DAYS here (must be a number from 0 to 7 or these numbers separated by comma/s) 133 | } 134 | 135 | SetHours(){ 136 | printf "\\n\\e[1mPlease choose update hour/s (x to cancel): \\e[0m" 137 | read -r "CRU_HOUR" 138 | if [ "$CRU_HOUR" = "x" ]; then 139 | ReturnToMainMenu "previous operation cancelled" 140 | elif [ -z "$CRU_HOUR" ]; then 141 | ReturnToMainMenu "you must specify a valid hour or hours separated by comma" 142 | fi 143 | # validate HOURS here (must be a number from 0 to 23) 144 | } 145 | 146 | SetMinutes(){ 147 | printf "\\n\\e[1mPlease choose update minute/s (x to cancel): \\e[0m" 148 | read -r "CRU_MINUTE" 149 | if [ "$CRU_MINUTE" = "x" ]; then 150 | OPTIONCHECK=1 151 | ReturnToMainMenu "previous operation cancelled" 152 | elif [ -z "$CRU_MINUTE" ]; then 153 | ReturnToMainMenu "you must specify a valid minute or minutes separated by comma" 154 | fi 155 | # validate MINUTES here (must be a number from 0 to 59) 156 | } 157 | 158 | ScriptHeader(){ 159 | clear 160 | printf "\\n" 161 | printf "\\e[1m############################################################\\e[0m\\n" 162 | printf "\\e[1m## $MY_ADDON_NAME Menu ##\\e[0m\\n" 163 | printf "\\e[1m############################################################\\e[0m\\n" 164 | printf "\\n" 165 | } 166 | 167 | MainMenu(){ 168 | printf " 1. Check for available NordVPN VPN client connections\\n" 169 | printf " 2. Update a VPN client connection NOW\\n" 170 | printf " 3. Schedule a VPN client connection update\\n" 171 | printf " d. Delete a scheduled VPN client connection update\\n" 172 | printf " u. Update $MY_ADDON_NAME\\n" 173 | printf " x. Exit $MY_ADDON_NAME menu\\n\\n" 174 | printf " z. Uninstall $MY_ADDON_NAME\\n" 175 | printf "\\n" 176 | printf "\\e[1m############################################################\\e[0m\\n" 177 | 178 | VPN_NO= 179 | VPNPROT= 180 | VPNTYPE= 181 | CRU_HOUR= 182 | CRU_DAYNUMBERS= 183 | CRU_MINUTE= 184 | 185 | while true; do 186 | if [ "$OPTIONCHECK" = "1" ] 187 | then 188 | printf "$RETURNTEXT\\n" 189 | OPTIONCHECK=0 190 | else 191 | printf "\\n" 192 | fi 193 | printf "Choose an option: " 194 | read -r "menu" 195 | case "$menu" in 196 | 1) 197 | printf "\\n" 198 | # check for connections 199 | ListMenu 200 | break 201 | ;; 202 | 2) 203 | printf "\\n" 204 | # configure now 205 | UpdateNowMenu 206 | break 207 | ;; 208 | 3) 209 | printf "\\n" 210 | # configure schedule 211 | ScheduleUpdateMenu 212 | break 213 | ;; 214 | d) 215 | printf "\\n" 216 | # remove schedule 217 | DeleteScheduleMenu 218 | break 219 | ;; 220 | u) 221 | printf "\\n" 222 | # update script from github 223 | "$LOCAL_REPO/install.sh" 224 | PressEnter 225 | break 226 | ;; 227 | x) 228 | ScriptHeader 229 | printf "\\n\\e[1mThanks for using $MY_ADDON_NAME!\\e[0m\\n\\n\\n" 230 | exit 0 231 | ;; 232 | z) 233 | printf "\\n\\e[1mAre you sure you want to uninstall $MY_ADDON_NAME (Y to confirm)?\\e[0m " 234 | read -r "confirm" 235 | if [ "$confirm" = "Y" ] 236 | then 237 | echo "Uninstalling $MY_ADDON_NAME..." 238 | # remove script 239 | Addon_Uninstall 240 | exit 0 241 | else 242 | ReturnToMainMenu "Uninstall of $MY_ADDON_NAME cancelled" 243 | fi 244 | ;; 245 | *) 246 | ReturnToMainMenu "Please choose a valid option" 247 | ;; 248 | esac 249 | done 250 | 251 | ScriptHeader 252 | MainMenu 253 | } 254 | 255 | UpdateNowMenuHeader(){ 256 | printf " Choose options as follows:\\n" 257 | printf " VPN client [1-5]\\n" 258 | printf " protocol to use (pick from list)\\n" 259 | printf " type to use (pick from list)\\n" 260 | printf "\\n" 261 | printf "\\e[1m############################################################\\e[0m\\n" 262 | } 263 | 264 | ScheduleUpdateMenuHeader(){ 265 | printf " Choose options as follows:\\n" 266 | printf " VPN client [1-5]\\n" 267 | printf " protocol to use (pick from list)\\n" 268 | printf " type to use (pick from list)\\n" 269 | printf " day/s to update [0-7]\\n" 270 | printf " hour/s to update [0-23]\\n" 271 | printf " minute/s to update [0-59]\\n" 272 | printf "\\n" 273 | printf "\\e[1m############################################################\\e[0m\\n" 274 | } 275 | 276 | DeleteScheduleMenuHeader(){ 277 | printf " Choose schedule entry to delete:\\n" 278 | printf " VPN client [1-5]\\n" 279 | printf "\\n" 280 | printf "\\e[1m############################################################\\e[0m\\n" 281 | } 282 | 283 | ListMenu(){ 284 | ScriptHeader 285 | 286 | $CONTROLSCRIPT list 287 | printf "\\n" 288 | PressEnter 289 | 290 | ReturnToMainMenu 291 | } 292 | 293 | UpdateNowMenu(){ 294 | ScriptHeader 295 | UpdateNowMenuHeader 296 | 297 | SetVPNClient 298 | SetVPNProtocol 299 | SetVPNType 300 | 301 | $CONTROLSCRIPT update "$VPN_NO" "$VPNPROT" "$VPNTYPE" 302 | PressEnter 303 | 304 | ReturnToMainMenu "Update VPN complete ($VPNTYPE)" 305 | } 306 | 307 | ScheduleUpdateMenu(){ 308 | ScriptHeader 309 | ScheduleUpdateMenuHeader 310 | 311 | SetVPNClient 312 | SetVPNProtocol 313 | SetVPNType 314 | SetDays 315 | SetHours 316 | SetMinutes 317 | 318 | $CONTROLSCRIPT schedule "$VPN_NO" "$VPNPROT" "$CRU_MINUTE" "$CRU_HOUR" "$CRU_DAYNUMBERS" "$VPNTYPE" 319 | PressEnter 320 | 321 | ReturnToMainMenu "Scheduled VPN update complete ($VPNTYPE)" 322 | } 323 | 324 | DeleteScheduleMenu(){ 325 | ScriptHeader 326 | DeleteScheduleMenuHeader 327 | 328 | SetVPNClient 329 | 330 | $CONTROLSCRIPT cancel "$VPN_NO" 331 | PressEnter 332 | 333 | ReturnToMainMenu "Delete VPN schedule complete" 334 | } 335 | 336 | Addon_Uninstall(){ 337 | printf "Uninstalling $MY_ADDON_NAME has not yet been tested...\\n" 338 | # printf "Uninstalling $MY_ADDON_NAME..." 339 | # cd ~ 340 | # rm -f "$LOCAL_REPO" 2>/dev/null 341 | # printf "Uninstall of $MY_ADDON_NAME completed" 342 | } 343 | 344 | ScriptHeader 345 | MainMenu -------------------------------------------------------------------------------- /nordvpnmanager.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # USAGE 4 | # 5 | # to check for and list current scheduled update entries 6 | # scriptname list 7 | # to manually trigger an update 8 | # scriptname update [1|2|3|4|5] [openvpn_udp|openvpn_tcp] [null|double|p2p] 9 | # to schedule updates using cron/cru 10 | # scriptname schedule [1|2|3|4|5] [openvpn_udp|openvpn_tcp] [minute] [hour] [day numbers] [null|double|p2p] 11 | # to cancel schedule updates using cron/cru 12 | # scriptname cancel [1|2|3|4|5] 13 | 14 | # Absolute path to this script, e.g. /home/user/bin/foo.sh 15 | SCRIPT=$(readlink -f "$0") 16 | # Absolute path this script is in, thus /home/user/bin 17 | SCRIPTPATH=$(dirname "$SCRIPT") 18 | # load standard variables and helper script 19 | source /usr/sbin/helper.sh 20 | source "$SCRIPTPATH/addon_vars" 21 | JSONSCRIPT="$SCRIPTPATH/JSON.sh" 22 | 23 | cd "$SCRIPTPATH" 24 | 25 | # variables 26 | EVENT=$MY_ADDON_NAME 27 | TYPE=$1 28 | VPN_NO=$2 29 | VPNPROT=$3 30 | # set VPN type (default "Standard VPN servers") 31 | VPNTYPE=legacy_standard 32 | # Other VPN types 33 | if [ "$TYPE" != "list" ] 34 | then 35 | if [ "$4" == "double" ] || [ "$7" == "double" ] 36 | then 37 | echo "configuring Double VPN..." 38 | VPNTYPE=legacy_double_vpn # Double VPN 39 | VPNTYPE_PARAM=double 40 | elif [ "$4" == "p2p" ] || [ "$7" == "p2p" ] 41 | then 42 | echo "configuring P2P VPN..." 43 | VPNTYPE=legacy_p2p # P2P 44 | VPNTYPE_PARAM=p2p 45 | else 46 | echo "configuring Standard VPN..." 47 | VPNTYPE_PARAM=standard 48 | fi 49 | fi 50 | #VPNPROT=openvpn_udp # use openvpn_udp or openvpn_tcp - this sets the default to openvpn_udp no matter what you pass to the script 51 | VPNPROT_SHORT=${VPNPROT/*_/} 52 | 53 | # check processing is for this addon 54 | # stop processing if event unmatched 55 | if [ "$EVENT" != "$MY_ADDON_NAME" ] 56 | then 57 | exit 0 58 | fi 59 | 60 | # functions 61 | errorcheck(){ 62 | echo "$SCRIPTSECTION reported an error..." 63 | logger -t "$MY_ADDON_NAME addon" "$SCRIPTSECTION reported an error" 64 | exit 1 65 | } 66 | 67 | # use to create content of vJSON variable 68 | getRecommended(){ 69 | SCRIPTSECTION=getRecommended 70 | curl -s -m 5 "https://api.nordvpn.com/v1/servers/recommendations?filters\[servers_groups\]\[identifier\]=$VPNTYPE&filters\[servers_technologies\]\[identifier\]=${VPNPROT}&limit=1" || errorcheck 71 | SCRIPTSECTION= 72 | } 73 | 74 | # use to download the JSON.sh script from github 75 | getJSONSH(){ 76 | SCRIPTSECTION=getJSONSH 77 | [ -f "$JSONSCRIPT" ] && rm "$JSONSCRIPT" 78 | wget -O "$JSONSCRIPT" "https://raw.githubusercontent.com/dominictarr/JSON.sh/master/JSON.sh" >/dev/null 2>&1 || errorcheck 79 | chmod +x "$JSONSCRIPT" 80 | SCRIPTSECTION= 81 | } 82 | 83 | # use to create content of OVPN_IP variable 84 | getIP(){ 85 | SCRIPTSECTION=getJSONSH 86 | # check vJSON variable contents exist 87 | [ -z "$vJSON" ] && errorcheck 88 | # check JSONSCRIPT script exists 89 | [ ! -f "$JSONSCRIPT" ] && errorcheck 90 | echo $vJSON | "$JSONSCRIPT" -b | grep station | cut -f2 | tr -d '"' 91 | SCRIPTSECTION= 92 | } 93 | 94 | # use to create content of OVPN_HOSTNAME variable 95 | getHostname(){ 96 | SCRIPTSECTION=getHostname 97 | [ -z "$vJSON" ] && errorcheck 98 | echo $vJSON | "$JSONSCRIPT" -b | grep hostname | cut -f2 | tr -d '"' 99 | SCRIPTSECTION= 100 | } 101 | 102 | # use to create content of OVPNFILE variable 103 | getOVPNFilename(){ 104 | SCRIPTSECTION=getOVPNFile 105 | [ -z "$OVPN_HOSTNAME" -o -z "$VPNPROT_SHORT" ] && errorcheck 106 | echo ${OVPN_HOSTNAME}.${VPNPROT_SHORT}.ovpn 107 | SCRIPTSECTION= 108 | } 109 | 110 | # use to create content of OVPN_DETAIL variable 111 | getOVPNcontents(){ 112 | SCRIPTSECTION=getOVPNcontents 113 | [ -z "$OVPNFILE" -o -z "$VPNPROT_SHORT" ] && errorcheck 114 | curl -s -m 5 "https://downloads.nordcdn.com/configs/files/ovpn_$VPNPROT_SHORT/servers/$OVPNFILE" || errorcheck 115 | SCRIPTSECTION= 116 | } 117 | 118 | # use to create content of CLIENT_CA variable 119 | getClientCA(){ 120 | SCRIPTSECTION=getOVPNcontents 121 | [ -z "$OVPN_DETAIL" ] && errorcheck 122 | echo "$OVPN_DETAIL" | awk '//{flag=1;next}/<\/ca>/{flag=0}flag' | sed '/^#/ d' 123 | SCRIPTSECTION= 124 | } 125 | 126 | # use to create content of CRT_CLIENT_STATIC variable 127 | getClientCRT(){ 128 | SCRIPTSECTION=getOVPNcontents 129 | [ -z "$OVPN_DETAIL" ] && errorcheck 130 | echo "$OVPN_DETAIL" | awk '//{flag=1;next}/<\/tls-auth>/{flag=0}flag' | sed '/^#/ d' 131 | SCRIPTSECTION= 132 | } 133 | 134 | # use to create content of EXISTING_NAME variable 135 | getConnName(){ 136 | SCRIPTSECTION=getConnName 137 | [ -z "$VPN_NO" ] && errorcheck 138 | nvram get vpn_client${VPN_NO}_desc || errorcheck 139 | SCRIPTSECTION= 140 | } 141 | 142 | # EXISTING_NAME check - it must contain "nordvpn" 143 | checkConnName(){ 144 | SCRIPTSECTION=checkConnName 145 | [ -z "$VPN_NO" ] && errorcheck 146 | EXISTING_NAME=$(getConnName) 147 | STR_COMPARE=nordvpn 148 | if echo $EXISTING_NAME | grep -v $STR_COMPARE >/dev/null 2>&1 149 | then 150 | logger -t "$MY_ADDON_NAME addon" "decription must contain nordvpn (VPNClient$VPN_NO)..." 151 | errorcheck 152 | fi 153 | SCRIPTSECTION= 154 | } 155 | 156 | # use to create content of EXISTING_IP variable 157 | getServerIP(){ 158 | SCRIPTSECTION=getServerIP 159 | [ -z "$VPN_NO" ] && errorcheck 160 | nvram get vpn_client${VPN_NO}_addr || errorcheck 161 | SCRIPTSECTION= 162 | } 163 | 164 | # use to create content of CONNECTSTATE variable - set to 2 if the VPN is connected 165 | getConnectState(){ 166 | SCRIPTSECTION=getConnectState 167 | [ -z "$VPN_NO" ] && errorcheck 168 | nvram get vpn_client${VPN_NO}_state || errorcheck 169 | SCRIPTSECTION= 170 | } 171 | 172 | # configure VPN 173 | setVPN(){ 174 | echo "updating VPN Client connection $VPN_NO now..." 175 | vJSON=$(getRecommended) 176 | getJSONSH 177 | OVPN_IP=$(getIP) 178 | OVPN_HOSTNAME=$(getHostname) 179 | OVPNFILE=$(getOVPNFilename) 180 | OVPN_DETAIL=$(getOVPNcontents) 181 | CLIENT_CA=$(getClientCA) 182 | CRT_CLIENT_STATIC=$(getClientCRT) 183 | EXISTING_NAME=$(getConnName) 184 | EXISTING_IP=$(getServerIP) 185 | CONNECTSTATE=$(getConnectState) 186 | 187 | SCRIPTSECTION=setVPN1 188 | [ -z "$OVPN_IP" -o -z "$OVPN_HOSTNAME" -o -z "$VPN_NO" ] && errorcheck 189 | SCRIPTSECTION=setVPN2 190 | [ -z "$CLIENT_CA" -o -z "$CRT_CLIENT_STATIC" ] && errorcheck 191 | SCRIPTSECTION=setVPN3 192 | [ -z "$CONNECTSTATE" ] && errorcheck 193 | # check that new VPN server IP is different 194 | if [ "$OVPN_IP" != "$EXISTING_IP" ] 195 | then 196 | SCRIPTSECTION=setVPN4 197 | echo "changing VPN Client connection $VPN_NO to $OVPN_HOSTNAME" 198 | nvram set vpn_client${VPN_NO}_addr=${OVPN_IP} || errorcheck 199 | nvram set vpn_client${VPN_NO}_desc=${OVPN_HOSTNAME} || errorcheck 200 | echo "$CLIENT_CA" > /jffs/openvpn/vpn_crt_client${VPN_NO}_ca 201 | echo "${CRT_CLIENT_STATIC}" > /jffs/openvpn/vpn_crt_client${VPN_NO}_static 202 | nvram commit 203 | # restart if connected - 2 is "connected" 204 | if [ "$CONNECTSTATE" = "2" ] 205 | then 206 | service stop_vpnclient${VPN_NO} 207 | sleep 3 208 | service start_vpnclient${VPN_NO} 209 | fi 210 | echo "complete" 211 | else 212 | echo "recommended server for VPN Client connection $VPN_NO is already the recommended server - $OVPN_HOSTNAME" 213 | fi 214 | SCRIPTSECTION= 215 | } 216 | 217 | # check for entries, connection state and schedule entry 218 | listEntries(){ 219 | # from 1 to 5 220 | for VPN_NO in 1 2 3 4 5 221 | do 222 | VPN_CLIENTDESC="$(nvram get vpn_client${VPN_NO}_desc | grep nordvpn)" 223 | if [ ! -z "$VPN_CLIENTDESC" ] 224 | then 225 | if [ "$(getConnectState)" = "2" ] 226 | then 227 | CONNECTSTATE=ACTIVE 228 | else 229 | CONNECTSTATE=INACTIVE 230 | fi 231 | cru l | grep "#${MY_ADDON_NAME}${VPN_NO}" >/dev/null 2>&1 232 | if [ $? -ne 0 ] 233 | then 234 | SCHEDULESTATE=UNSCHEDULED 235 | else 236 | SCHEDULESTATE=SCHEDULED 237 | fi 238 | echo "$VPN_NO. ${VPN_CLIENTDESC} (${CONNECTSTATE} and ${SCHEDULESTATE})" 239 | else 240 | echo "$VPN_NO. no nordvpn entry found" 241 | fi 242 | done 243 | } 244 | 245 | getCRONentry(){ 246 | SCRIPTSECTION=getCRONentry 247 | [ -z "$VPN_NO" -o -z "$MY_ADDON_NAME" ] && errorcheck 248 | cru l | grep "${MY_ADDON_NAME}${VPN_NO}" | sed 's/ sh.*//' 249 | [ $? -ne 0 ] && echo NOTFOUND 250 | SCRIPTSECTION= 251 | } 252 | 253 | setCRONentry(){ 254 | SCRIPTSECTION=setCRONentry 255 | echo "scheduling VPN Client connection $VPN_NO updating..." 256 | [ -z "$VPN_NO" -o -z "$MY_ADDON_NAME" -o -z "$SCRIPTPATH" -o -z "$MY_ADDON_SCRIPT" -o -z "$VPNPROT" -o -z "$VPNTYPE_PARAM" ] && errorcheck 257 | [ -z "$CRU_MINUTE" -o -z "$CRU_HOUR" -o -z "$CRU_DAYNUMBERS" ] && errorcheck 258 | # add new cru entry 259 | if cru l | grep "${MY_ADDON_NAME}${VPN_NO}" >/dev/null 2>&1 260 | then 261 | # replace existing 262 | cru d ${MY_ADDON_NAME}${VPN_NO} 263 | cru a ${MY_ADDON_NAME}${VPN_NO} "${CRU_MINUTE} ${CRU_HOUR} * * ${CRU_DAYNUMBERS} sh ${SCRIPTPATH}/${MY_ADDON_SCRIPT} update ${VPN_NO} ${VPNPROT} ${VPNTYPE_PARAM}" 264 | else 265 | # or add new if not exist 266 | cru a ${MY_ADDON_NAME}${VPN_NO} "${CRU_MINUTE} ${CRU_HOUR} * * ${CRU_DAYNUMBERS} sh ${SCRIPTPATH}/${MY_ADDON_SCRIPT} update ${VPN_NO} ${VPNPROT} ${VPNTYPE_PARAM}" 267 | fi 268 | # add persistent cru entry to /jffs/scripts/services-start for restarts 269 | if cat /jffs/scripts/services-start | grep "${MY_ADDON_NAME}${VPN_NO}" >/dev/null 2>&1 270 | then 271 | # remove and replace existing 272 | sed -i "/${MY_ADDON_NAME}${VPN_NO}/d" /jffs/scripts/services-start 273 | echo "cru a ${MY_ADDON_NAME}${VPN_NO} \"${CRU_MINUTE} ${CRU_HOUR} * * ${CRU_DAYNUMBERS} sh ${SCRIPTPATH}/${MY_ADDON_SCRIPT} update ${VPN_NO} ${VPNPROT} ${VPNTYPE_PARAM}\"" >> /jffs/scripts/services-start 274 | else 275 | # or add new if not exist 276 | echo "cru a ${MY_ADDON_NAME}${VPN_NO} \"${CRU_MINUTE} ${CRU_HOUR} * * ${CRU_DAYNUMBERS} sh ${SCRIPTPATH}/${MY_ADDON_SCRIPT} update ${VPN_NO} ${VPNPROT} ${VPNTYPE_PARAM}\"" >> /jffs/scripts/services-start 277 | fi 278 | am_settings_set nvpn_cron${VPN_NO} 1 279 | am_settings_set nvpn_cronstr${VPN_NO} "${CRU_MINUTE} ${CRU_HOUR} * * ${CRU_DAYNUMBERS}" 280 | echo "complete" 281 | SCRIPTSECTION= 282 | } 283 | 284 | delCRONentry(){ 285 | SCRIPTSECTION=delCRONentry 286 | echo "removing VPN Client connection $VPN_NO schedule entry..." 287 | [ -z "$VPN_NO" -o -z "$MY_ADDON_NAME" ] && errorcheck 288 | # remove cru entry 289 | if cru l | grep "${MY_ADDON_NAME}${VPN_NO}" >/dev/null 2>&1 290 | then 291 | # remove existing 292 | cru d ${MY_ADDON_NAME}${VPN_NO} 293 | fi 294 | # remove persistent cru entry from /jffs/scripts/services-start for restarts 295 | if cat /jffs/scripts/services-start | grep "${MY_ADDON_NAME}${VPN_NO}" >/dev/null 2>&1 296 | then 297 | # remove and replace existing 298 | sed -i "/${MY_ADDON_NAME}${VPN_NO}/d" /jffs/scripts/services-start 299 | fi 300 | am_settings_set nvpn_cron${VPN_NO} 301 | am_settings_set nvpn_cronstr${VPN_NO} 302 | echo "complete" 303 | SCRIPTSECTION= 304 | } 305 | 306 | # ---------------- 307 | # ---------------- 308 | # ---------------- 309 | 310 | # logic processing 311 | if [ "$TYPE" = "update" ] 312 | then 313 | checkConnName 314 | logger -t "$MY_ADDON_NAME addon" "Updating to recommended NORDVPN server (VPNClient$VPN_NO)..." 315 | setVPN 316 | logger -t "$MY_ADDON_NAME addon" "Update complete (VPNClient$VPN_NO - server $OVPN_HOSTNAME - type $VPNTYPE_PARAM)" 317 | fi 318 | 319 | if [ "$TYPE" = "schedule" ] 320 | then 321 | checkConnName 322 | CRU_MINUTE=$4 323 | CRU_HOUR=$5 324 | CRU_DAYNUMBERS=$6 325 | 326 | # default options 5:25am on Mondays and Thursdays 327 | [ -z "$CRU_MINUTE" ] && CRU_MINUTE=25 328 | [ -z "$CRU_HOUR" ] && CRU_HOUR=5 329 | [ -z "$CRU_DAYNUMBERS" ] && CRU_DAYNUMBERS=1,4 330 | 331 | # CRON entry format = 5 5 * * 1,3,5 sh /jffs/scripts/asusvpn-autoselectbest.sh #autoselectvpn# 332 | # command to add (in /jffs/scripts/services-start) cru a autoselectvpn "5 5 * * 1,3,5 sh /jffs/scripts/asusvpn-autoselectbest.sh" 333 | 334 | # cru command syntax to add, list, and delete cron jobs 335 | # id – Unique ID for each cron job. 336 | # min – Minute (0-59) 337 | # hour – Hours (0-23) 338 | # day – Day (0-31) 339 | # month – Month (0-12 [12 is December]) 340 | # week – Day of the week(0-7 [7 or 0 is Sunday]) 341 | # command – Script or command name to schedule. 342 | 343 | logger -t "$MY_ADDON_NAME addon" "Configuring scheduled update to recommended NORDVPN server (VPNClient$VPN_NO)..." 344 | setCRONentry 345 | logger -t "$MY_ADDON_NAME addon" "Scheduling complete (VPNClient$VPN_NO - type $VPNTYPE_PARAM)" 346 | fi 347 | 348 | if [ "$TYPE" = "cancel" ] 349 | then 350 | checkConnName 351 | [ -z "$VPN_NO" ] && errorcheck 352 | logger -t "$MY_ADDON_NAME addon" "Removing scheduled update to recommended NORDVPN server (VPNClient$VPN_NO)..." 353 | delCRONentry 354 | logger -t "$MY_ADDON_NAME addon" "Removal of schedule complete (VPNClient$VPN_NO)" 355 | fi 356 | 357 | if [ "$TYPE" = "list" ] 358 | then 359 | echo "VPN CLient List:" 360 | listEntries 361 | fi 362 | --------------------------------------------------------------------------------