I'm looking to find the preferred VPN Server for SurfShark. I have done something similar before with NordVPN too. But I cannot get my script to work in a modular parameterised fashion.
The following command line queries all surfshark vpn servers and will return the one in GB with the lowest load.
Example
vpncmd=https://api.surfshark.com/v3/server/clusters
location='"GB"'
limit=10
capacity=101
echo "$0: Server: Looking for recommended server in '$location' with less than '$capacity%' load"
mycmd=(curl --silent "$vpncmd" | jq -r -c 'map(select(.countryCode =='$location' and .load <= '$capacity')) | sort_by(.load) | limit('$limit';.[]) | [.countryCode, .connectionName, .load] | "(.[1])"')
echo "*** RECOMMENDED SERVER: $mycmd"
Can anyone help with the variable substitution in the subcommand. $vpncmd is substituted, but I cannot find a way to substitute the other parameters in the jq command after the pipe. I've tried using $() to execute the subcommand, and eval. I've also tried using an arrange to pass the command sue to the spaces etc where it could treated as separate input variables. Any help would really be appreciated. I'm clearly doing something really stupid.
It is running on my OpenWRT router so it kind of is relevant. The script requires features of BASH so it will not operate under ash. Bash is declared as the shell within the script and Bash is installed via opkg.
Lots of users connect to the VPN at the router so I'm sure other people will be interested
That part does actually work. It is the fact I cannot replace $limit or $capacity with a shell variable or arg from getops. An ideas how to get BASH to substitute these variables in the subcommand?
on bash under openers the script works - I'll do what you say, but its not replacing the variables. Taking these double quotes out screws its up, it needs those to format the output.
jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at , line 1:
map(select(.countryCode == "GB")) | sort_by(.load) | limit(1;.[]) | [.countryCode, .connectionName, .load] | (.[1])
jq: 1 compile error
Hardcoded it all works....
root: /usr/local/bin/surfshark-server-find: Starting...
root: /usr/local/bin/surfshark-server-find: Server: Looking for recommended server in '"GB"' with less than '101%' load uk-lon.prod.surfshark.com
Can you substitute a variable within the jq command in order to definite the location or server load?
This fully works on OpenWRT:
curl --silent "https://api.surfshark.com/v3/server/clusters" | jq -r -c 'map(select(.countryCode == "GB")) | sort_by(.load) | limit(1;.[]) | [.countryCode, .connectionName, .load] | "(.[1])"'
But instead of GB, say I wanted to search in the US - how can I get a variable in the command and get Bash to interpret .e.g. $location (where location = US)
It's not working for you as you're trying to do variable expansion within single quotes. In bash, variables will not be expanded when they're in single quotes.
#!/bin/ash
myvar="some test data"
# expands
echo "this is my test data: $myvar"
# expands
echo "this is my test data: '$myvar'"
# does not expand
echo 'this is my test data: $myvar'
# does not expand
echo 'this is my test data: "$myvar"'
Sorry for the delay - been trying to get it all working. The overall script is below. What I have noticed is that I need to encapsulate the location (-l in args) with double quotes or it doesn't work. This is jg is called in the function get_servers
Also, I use bash because I check location against an array. That said, It would be better if I could extract the country codes from the list of available server i.e. US, UK. NL etc. This I think can be done using jg, but haven't quite worked it out yet...output=$(curl --silent "https://api.surfshark.com/v3/server/clusters" | jq -s -c '.[] | map(.countryCode) | reduce .[] as $i ({}; setpath([$i]; getpath([$i]) + 1) )
| to_entries | .[] | { "ISO Country Code": .key, "No of Servers" } '
Would really welcome any feedback, comments or tidying up!
# Script: surfshark-server-find
# Author: Ian Jones ian@ianjones.org.uk
# Date: December 2017
logger -s "$0: Starting..."
# Dependencies check
if ((BASH_VERSINFO[0] < 4))
then
2>&1 echo "Error: This script requires at least Bash 4.0"
exit 1
fi
type curl >/dev/null 2>&1 || { 2>&1 echo "Missing dependency: This script requires curl"; exit 1; }
type jq >/dev/null 2>&1 || { 2>&1 echo "Missing dependency: This script requires jq 1.5"; exit 1; }
# Reset in case getopts has been used previously in the shell.
OPTIND=1
#Declare variables
vpncmd=https://api.surfshark.com/v3/server/clusters
location="GB"
bare=false
technology=openvpn_udp
capacity="50" # Default capacity value is 30%
limit="10" # Default limit value is 10
usage() {
cat <<EOF
Usage: [-r] [-b -l -c -n -v -o -t]
where:
-r output the recommended server for your location and exit
-l 2-letter ISO 3166-1 country code (ex: us, uk, de)
-c current server load, integer between 1-100 (defaults to 30)
-n limits number of results, integer between 1-100 (defaults to 20)
-o output list of SurfShark country identifiers
-b returns a single server name for specified country code
-t server type (ex: openvpn_udp, openvpn_tcp)
EOF
exit 0
}
get_servers() {
if [[ "$bare" == false ]];
then
if [[ "$limit" -gt 1 ]];
then
plural="s"
else
plural=""
fi
logger -s "$0: Looking for up to $limit server$plural located in ${location^^} with server load lower than $capacity%..."
shellcmd="$(curl --silent "${vpncmd}" \
| jq -r -c "map(select(.countryCode == \"${location}\" \
and .load <= \"${capacity}\")) \
| sort_by(.load) \
| limit(${limit};.[]) \
| [.countryCode, .connectionName, .load] \
| (.[1])")"
echo "${shellcmd}"
logger -s "$0: Server: $shellcmd"
logger -s "$0: Completed"
fi
}
iso_codes=(
'ad' 'ae' 'af' 'ag' 'ai' 'al' 'am' 'ao' 'aq' 'ar' 'as' 'at' 'au' 'aw' 'ax'
'az' 'ba' 'bb' 'bd' 'be' 'bf' 'bg' 'bh' 'bi' 'bj' 'bl' 'bm' 'bn' 'bo' 'bq'
'br' 'bs' 'bt' 'bv' 'bw' 'by' 'bz' 'ca' 'cc' 'cd' 'cf' 'cg' 'ch' 'ci' 'ck'
'cl' 'cm' 'cn' 'co' 'cr' 'cu' 'cv' 'cw' 'cx' 'cy' 'cz' 'de' 'dj' 'dk' 'dm'
'do' 'dz' 'ec' 'ee' 'eg' 'eh' 'er' 'es' 'et' 'fi' 'fj' 'fk' 'fm' 'fo' 'fr'
'ga' 'gb' 'gd' 'ge' 'gf' 'gg' 'gh' 'gi' 'gl' 'gm' 'gn' 'gp' 'gq' 'gr' 'gs'
'gt' 'gu' 'gw' 'gy' 'hk' 'hm' 'hn' 'hr' 'ht' 'hu' 'id' 'ie' 'il' 'im' 'in'
'io' 'iq' 'ir' 'is' 'it' 'je' 'jm' 'jo' 'jp' 'ke' 'kg' 'kh' 'ki' 'km' 'kn'
'kp' 'kr' 'kw' 'ky' 'kz' 'la' 'lb' 'lc' 'li' 'lk' 'lr' 'ls' 'lt' 'lu' 'lv'
'ly' 'ma' 'mc' 'md' 'me' 'mf' 'mg' 'mh' 'mk' 'ml' 'mm' 'mn' 'mo' 'mp' 'mq'
'mr' 'ms' 'mt' 'mu' 'mv' 'mw' 'mx' 'my' 'mz' 'na' 'nc' 'ne' 'nf' 'ng' 'ni'
'nl' 'no' 'np' 'nr' 'nu' 'nz' 'om' 'pa' 'pe' 'pf' 'pg' 'ph' 'pk' 'pl' 'pm'
'pn' 'pr' 'ps' 'pt' 'pw' 'py' 'qa' 're' 'ro' 'rs' 'ru' 'rw' 'sa' 'sb' 'sc'
'sd' 'se' 'sg' 'sh' 'si' 'sj' 'sk' 'sl' 'sm' 'sn' 'so' 'sr' 'ss' 'st' 'sv'
'sx' 'sy' 'sz' 'tc' 'td' 'tf' 'tg' 'th' 'tj' 'tk' 'tl' 'tm' 'tn' 'to' 'tr'
'tt' 'tv' 'tw' 'tz' 'ua' 'ug' 'uk' 'um' 'us' 'uy' 'uz' 'va' 'vc' 've' 'vg'
'vi' 'vn' 'vu' 'wf' 'ws' 'ye' 'yt' 'za' 'zm' 'zw'
)
array_contains () {
local array="$1[@]"
local seeking=$2
local in=1
for element in "${!array}"; do
if [[ $element == "$seeking" ]]; then
in=0
break
fi
done
return $in
}
while getopts ":l:c:n:t:orbh" opt; do
case "$opt" in
h)
usage
exit 0
;;
o)
output=$(curl --silent "https://api.surfshark.com/v3/server/clusters" | jq -s -c '.[] | map(.countryCode) | reduce .[] as $i ({}; setpath([$i]; getpath([$i]) + 1) ) | to_entries | .[] | { "ISO Country Code": .key, "No of Servers"
: .value }')
logger -s "$0: Server: $output"
echo "$output"
logger -s "$0: Completed"
exit 0
;;
t)
technology=$OPTARG >&2;
;;
b)
bare=true
;;
l)
location=printf("$OPTARG") >&2;
array_contains iso_codes "${OPTARG,,}" && location=${OPTARG,,} >&2 || \
{
>&2 echo
>&2 echo "Invalid iso country code parameter."
>&2 echo "Please provide a valid ISO 3166-1 country code to -l."
>&2 echo "(ex: -l us)"
exit 1
}
;;
c)
if [[ "$OPTARG" =~ ^[0-9]+$ ]] && [ "$OPTARG" -ge 1 -a "$OPTARG" -le 100 ];
then
capacity=$OPTARG >&2;
else
{
>&2 echo
>&2 echo "Invalid capacity parameter."
>&2 echo "Please provide an integer between 1-100 to -c."
>&2 echo "(ex: -c 90)"
exit 1
}
fi
;;
n)
if [[ "$OPTARG" =~ ^[0-9]+$ ]] && [ "$OPTARG" -ge 1 -a "$OPTARG" -le 100 ];
then
limit=$OPTARG >&2;
else
{
>&2 echo "Invalid limit parameter."
>&2 echo "Please provide an integer between 1-100 to -n."
>&2 echo "(ex: -n 50)"
exit 1
}
fi
;;
r)
#Set Limit to 1 and utilisation to 101 for Recommended Server
limit="1"
capacity="101"
logger -s "$0: Server: Looking for recommended server in '$location' with less than '$capacity%' load"
shellcmd="$(curl --silent "${vpncmd}" \
| jq -r -c "map(select(.countryCode == \"${location}\" \
and .load <= \"${capacity}\")) \
| sort_by(.load) \
| limit(${limit};.[]) \
| [.countryCode, .connectionName, .load] \
| (.[1])")"
echo "${shellcmd}"
logger -s "$0: Server: $shellcmd"
logger -s "$0: Completed"
exit 0
;;
:)
>&2 echo
>&2 echo "Option -$OPTARG requires an argument."
>&2 usage
;;
\?)
>&2 echo
>&2 echo "Invalid option: -$OPTARG"
>&2 usage
;;
esac
done
get_servers
exit 0