$HOME/.bash.d/.bash_vpn :
#!/bin/bash
BASH_HOME=$HOME/.bash.d
source $BASH_HOME/.bash_geo
source $BASH_HOME/.bash_parse
IPLOG_PATH="$HOME/.iplog"
VPN_CONF_PATH="/etc/openvpn"
VPN_MAIN_CONF_FILE="vpnx.conf"
VPN_MAIN_CONF_FILE_PATH="${VPN_CONF_PATH}/${VPN_MAIN_CONF_FILE}"
DEBUG_LOG_FILE_PATH="$HOME/.iplogcrondebug.log"
GEOLOC_LOG_FILE_PATH="$HOME/.gloc.log"
LAST_NATURAL_IP_FILE="$HOME/.last_known_natural_ip"
if [ -f "$LAST_NATURAL_IP_FILE" ]; then SXIP=$(cat $LAST_NATURAL_IP_FILE) && export SXIP; fi;
TRDAEMON="transmission-daemon"
TRDAEMONBIN=/usr/bin/${TRDAEMON}
TRUSER=debian-transmission
VPNLIST=( france germany sweden switzerland luxembourg finland nl1 nl2 nl3 italy lithuania poland portugal romania spain coventry london portsmouth )
if [ -z $CURRENT_VPN_INDEX ]
then
if [ -f "$VPN_MAIN_CONF_FILE_PATH" ]
then;
current_vpn_target_path=$(readlink "$VPN_MAIN_CONF_FILE_PATH")
current_vpn_file=$(basename "$current_vpn_target_path")
current_vpn_name="${current_vpn_file%.ovpn}"
arr_count=0
for vpn_entry in "${VPNLIST[@]}"
do;
if [ "${vpn_entry}" == "${current_vpn_name}" ]
then;
CURRENT_VPN_INDEX=$arr_count ; export CURRENT_VPN_INDEX;
CURRENT_VPN_NAME="${VPNLIST[$CURRENT_VPN_INDEX]}" ; export CURRENT_VPN_NAME;
else;
arr_count="$(( ++arr_count ))"
fi;
done;
else;
CURRENT_VPN_INDEX=0 ; export CURRENT_VPN_INDEX;
CURRENT_VPN_NAME="" ; export CURRENT_VPN_NAME;
fi;
fi;
function getExtIp {
echo "$(curl -s icanhazip.com)";
}
function storeNaturalExtIpInFile {
naturalExtIp="$(getNaturalExtIp)"
if [ "$?" -eq 0 ]
then;
echo "$naturalExtIp" > $LAST_NATURAL_IP_FILE;
return 0;
else;
return 1;
fi;
}
function getNaturalExtIp {
naturalExtIp="$(ssh other@machine.on.your.network curl -s icanhazip.com)"
if $(testArgumentIsIPv4Address "$naturalExtIp")
then;
echo "$naturalExtIp"
return 0;
else;
return 1;
fi;
}
function iplogcron {
local ipnow="$(getExtIp)"
storeNaturalExtIpInFile
SXIP="$(cat $LAST_NATURAL_IP_FILE)"
if [ -n "$SXIP" ]
then;
export SXIP;
fi;
echo -e "$(date) ::\t ${ipnow} ::\t Natural IP:[${SXIP}]" >> "${IPLOG_PATH}";
if [ $(chkvpnargs ${ipnow}) -ne 0 ]
then;
vpnDownMsg=$(echo "VPN DOWN:\t IP=${ipnow} \t VPN_INDEX=${CURRENT_VPN_INDEX} \t VPN_NAME=${VPNLIST[$CURRENT_VPN_INDEX]}");
echo -e "$vpnDownMsg" >> "$DEBUG_LOG_FILE_PATH";
echo -e "$vpnDownMsg";
forceSwitchVpnConnection;
else;
vpnOkMsg=$(echo "VPN OK:\t IP=${ipnow} \t VPN_INDEX=${CURRENT_VPN_INDEX} \t VPN_NAME=${VPNLIST[$CURRENT_VPN_INDEX]}");
echo -e "$vpnOkMsg";
fi;
}
function parseForceSwitchArgs {
local changeIndex
if [ "$#" -eq 1 ]
then;
if [ $(testArgumentIsNumeric "$1") ]
then;
changeIndex=$1;
else;
inputArg="$1"
CLEANSTRING="$(sanitizeArgument ${inputArg})"
element_index=$(getElementIndex "$CLEANSTRING" "${VPNLIST[@]}")
if [ "$?" -eq 0 ]
then;
changeIndex="$element_index";
else;
echo " > > > INPUT [$CLEANSTRING] INVALID CHOICE :: Choose value from [${VPNLIST[@]}] < < <";
return 1;
fi;
fi;
if [ -n "$changeIndex" ]
then;
if [ "$changeIndex" -eq 0 ]
then;
CURRENT_VPN_INDEX="$(( ${#VPNLIST[@]} - 1 ))" ; export CURRENT_VPN_INDEX ; echo " > > > CHANGING VPN TO [${VPNLIST[$changeIndex]}] :: index [$changeIndex] > > >";
elif [ "$changeIndex" -lt "${#VPNLIST[@]}" ]
then;
CURRENT_VPN_INDEX="$(( $changeIndex - 1 ))" ; export CURRENT_VPN_INDEX ; echo " > > > CHANGING VPN TO [${VPNLIST[$changeIndex]}] :: index [$changeIndex] > > >";
else;
echo " > > > INDEX [$changeIndex] OUT OF RANGE :: CHOOSE VALUE FROM 0 TO $(( ${#VPNLIST[@]} - 1 )) < < <";
return 1;
fi;
fi;
fi;
return 0;
}
function forceSwitchVpnConnection {
local numArgs="$#"
local calledByUser
local calledStateCode="$(ps -o stat= -p $$)" &>/dev/null
if [[ "$calledStateCode" =~ .*\+ ]] && [ "$numArgs" -eq 0 ]
then;
calledByUser="true";
local nextVpnIndex;
[[ $CURRENT_VPN_INDEX -ne $(( ${#VPNLIST[@]} - 1 )) ]] && nextVpnIndex=$(( $CURRENT_VPN_INDEX + 1 )) || nextVpnIndex=0;
echo " > > > CHANGING VPN TO [${VPNLIST[$nextVpnIndex]}] :: index [$nextVpnIndex] > > >";
fi;
if [ -n "$numArgs" ] && [ "$numArgs" -gt 0 ]
then;
parseForceSwitchArgs $@;
if [ $? -ne 0 ]
then
return 1;
fi;
calledByUser="true";
fi;
echo -e "\n\n:\t:\t:\t:\t FORCE SWITCH VPN CONNECTION\t :\t:\t:\t:\n" >> "$DEBUG_LOG_FILE_PATH";
echo "$(date) :: *** Enter vpnRotateConfig" >> "$DEBUG_LOG_FILE_PATH";
vpnRotateConfig;
echo "$(date) :: *** Enter vpnReload" >> "$DEBUG_LOG_FILE_PATH";
if [ "$(vpnReload)" = 0 ]
then;
echo "$(date) :: > > > SUCCESS *** vpnReload returned 0, so we can start $TRDAEMON again" >> "$DEBUG_LOG_FILE_PATH";
sudo service "$TRDAEMON" start >> "$DEBUG_LOG_FILE_PATH" 2>&1;
local extIp="$(getExtIp)";
geoLocMsg=$(echo "New VPN Location: $(getGeoLocFromIp $extIp) :: [IP Address: $extIp]");
echo "$geoLocMsg" >> "$DEBUG_LOG_FILE_PATH";
if [ "$calledByUser" = "true" ]
then;
echo "$geoLocMsg";
fi;
fi;
}
function stopTransmissionDaemon {
local retval=2
local counter=0
while [ $retval -gt 1 ]
do;
echo "$(date) :: *** Enter isServiceRunning: $TRDAEMON ... count = $counter" >> "$DEBUG_LOG_FILE_PATH";
result="$(isServiceRunning $TRDAEMON)";
if [ "$result" = 0 ]
then;
echo "$(date) :: > > > isServiceRunning returned 0 (not running) - so now we return 0" >> "$DEBUG_LOG_FILE_PATH";
retval=0;
elif [ $counter -gt 4 ]
then;
echo "$(date) :: > > > isServiceRunning returned 1 (running) 5 times in a row - so now we return 1" >> "$DEBUG_LOG_FILE_PATH";
retval=1;
else;
echo "$(date) :: > > > isServiceRunning returned 1 (running) - so we try to stop $TRDAEMON and then sleep for 1 second (count = $counter)" >> "$DEBUG_LOG_FILE_PATH";
sudo service "$TRDAEMON" stop >> "$DEBUG_LOG_FILE_PATH" 2>&1;
sleep 1;
counter="$(( ++counter ))";
fi;
done;
echo $retval;
}
function vpnReload {
echo "$(date) :: *** Enter stopTransmissionDaemon" >> "$DEBUG_LOG_FILE_PATH";
stopTDResult="$(stopTransmissionDaemon)";
echo "$(date) :: --- Leaving stopTransmissionDaemon. retval = $stopTDResult" >> "$DEBUG_LOG_FILE_PATH";
if [ "$stopTDResult" = 0 ]
then;
echo "$(date) :: > > > stopTransmissionDaemon returned 0 (successfully stoppped) so now we restart openvpn service..." >> "$DEBUG_LOG_FILE_PATH";
local retval=2;
local counter=0;
while [ $retval -gt 1 ]
do;
echo "$(date) :: > > > Restart openvpn and then wait 10 seconds before checking IP Address (attempt no. $counter)" >> "$DEBUG_LOG_FILE_PATH";
sudo service openvpn restart >> "$DEBUG_LOG_FILE_PATH" 2>&1;
sleep 16;
echo "$(date) :: *** Enter checkvpn [check for non domestic IP Address...]" >> "$DEBUG_LOG_FILE_PATH";
if [ "$(chkvpn)" = 0 ]
then;
echo "$(date) :: > > > SUCCESS *** IP Address is: [$(getExtIp)] *** return 0" >> "$DEBUG_LOG_FILE_PATH";
retval=0;
elif [ $counter -gt 4 ]
then;
echo "$(date) :: ~ ~ ~ FAILURE --- IP Address inaccessible/unchanging [$SXIP] after 5 attempts --- return 1" >> "$DEBUG_LOG_FILE_PATH";
retval=1;
else;
sleep 4;
counter="$(( ++counter ))";
fi;
done;
echo $retval;
fi;
}
# Check ext IP isnt $SXIP
# Human readable
function checkvpn {
if [ -f "$LAST_NATURAL_IP_FILE" ]; then SXIP=$(cat $LAST_NATURAL_IP_FILE) && export SXIP; fi;
local xip="$(getExtIp)";
if [ -z "$xip" ]
then;
echo "$(date) :: *** VPN Momentary Failure - External IP Cannot be Acquired";
elif [[ "$xip" == "$SXIP" ]]
then;
echo "$(date) :: *** VPN Failure - External IP = [$xip]";
else;
echo "$(date) :: *** VPN Success - External IP = [$xip]";
fi;
}
# Returns 0 (success) or 1 (fail)
function chkvpn {
if [ -f "$LAST_NATURAL_IP_FILE" ]; then SXIP=$(cat $LAST_NATURAL_IP_FILE) && export SXIP; fi;
local xip="$(getExtIp)";
if [ -n "$xip" ] && [[ "$xip" != "$SXIP" ]]
then;
echo 0;
else;
echo 1;
fi;
}
# As above but takes IP argument to compare with
function chkvpnargs {
if [ -n "$1" ]
then;
if [ -f "$LAST_NATURAL_IP_FILE" ]; then SXIP=$(cat $LAST_NATURAL_IP_FILE) && export SXIP; fi;
local xip="$1";
if [[ "$xip" != "$SXIP" ]]
then;
echo 0;
else;
echo 1;
fi;
else;
echo 1;
fi;
}
function vpnRotateConfig {
if [ -n $CURRENT_VPN_INDEX ]
then;
echo "$(date) :: > > > CURRENT_VPN_INDEX: $CURRENT_VPN_INDEX" >> "$DEBUG_LOG_FILE_PATH";
(( ++CURRENT_VPN_INDEX )); export CURRENT_VPN_INDEX;
if [ $CURRENT_VPN_INDEX -eq ${#VPNLIST[@]} ]
then;
CURRENT_VPN_INDEX=0; export CURRENT_VPN_INDEX;
fi;
echo "$(date) :: > > > NEW_VPN_INDEX: $CURRENT_VPN_INDEX" >> "$DEBUG_LOG_FILE_PATH";
nextVpn="${VPNLIST[$CURRENT_VPN_INDEX]}";
echo "$(date) :: > > > nextVpn: $nextVpn" >> "$DEBUG_LOG_FILE_PATH";
nextVpnConfPath="${VPN_CONF_PATH}/${nextVpn}.ovpn";
echo "$(date) :: > > > nextVpnConfPath: $nextVpnConfPath" >> "$DEBUG_LOG_FILE_PATH";
rm -f "${VPN_MAIN_CONF_FILE_PATH}";
ln -s "${nextVpnConfPath}" "${VPN_MAIN_CONF_FILE_PATH}";
CURRENT_VPN_NAME=${nextVpn}; export CURRENT_VPN_NAME;
fi;
}
function isServiceRunning {
if [ -n $1 ]
then;
local service="$1";
echo "$(date) :: > > > isServiceRunning? service: $service" >> "$DEBUG_LOG_FILE_PATH";
if (( $(ps -ef | grep -v grep | grep $service | wc -l) > 0 ))
then;
echo "$(date) :: > > > YES *** $service IS running." >> "$DEBUG_LOG_FILE_PATH";
echo 1;
else;
echo "$(date) :: ~ ~ ~ NO --- $service is NOT running." >> "$DEBUG_LOG_FILE_PATH";
echo 0;
fi;
else;
>&2 echo "$(date) :: *** Usage: $0 <name_of_service>";
fi;
}
###############################################################################################
# ALIASES #
###############################################################################################
alias extip='curl -s icanhazip.com'
alias gextip='curl freegeoip.net/json/$(extip)'
alias fsvpn='forceSwitchVpnConnection'
$HOME/.bash.d/.bash_parse :
#########################################################################
# >>> .bash_parse #
#-----------------------------------------------------------------------#
# bash utility functions to aid parsing input / manipulating arrays #
#########################################################################
function getElementIndex {
local e
local arr_count=0
for e in "${@:2}"
do;
if [[ "$e" == "$1" ]]
then;
echo $arr_count;
return 0;
else;
arr_count="$(( ++arr_count ))";
fi;
done;
echo "";
return 1;
}
function sanitizeArgument {
#-----------------------------------------------------------#
# NB: ${string//substring/replacement} #
# Replace all matches of $substring with $replacement. #
#-----------------------------------------------------------#
local retval
if [ "$#" -ne 1 ]
then;
return 1;
fi;
suspectArgument="$1";
cleanArgument="${suspectArgument//[^a-zA-Z0-9]/}";
echo "$cleanArgument";
return 0;
}
function testArgumentIsNumeric {
local retval;
if [ "$#" -ne 1 ]
then;
retval=1;
fi;
if [ "$1" -eq "$1" ] 2> /dev/null
then;
retval=0;
else;
retval=2;
fi;
return $retval;
}
function testArgumentIsIPv4Address {
if [ "$#" -ne 1 ]
then;
return 1;
fi;
local ip=$1;
local retval=1;
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
then;
OIFS=$IFS;
IFS='.';
ip=($ip);
IFS=$OIFS;
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]];
retval=$?;
fi;
return $retval;
}
$HOME/.bash.d/.bash_geo :
#!/bin/bash
###############################################################################################
# IP UTILITY FUNCTIONS #
###############################################################################################
function 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='.';
ip=($ip);
IFS=$OIFS;
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]];
stat=$?;
fi;
return $stat;
}
function getWanIp() {
echo "$(curl -s icanhazip.com)";
}
###############################################################################################
# GEOCODING FUNCTIONS #
###############################################################################################
function getGeoLocFromIp()
{
local print_lat_lon
if [ "$#" -eq 1 ] && [ "$1" != "-h" ] && $(validip $1)
then;
print_lat_lon=false;
elif [ "$#" -eq 2 ] && [ "$2" = "-l" ] && $(validip $1)
then;
print_lat_lon=true;
else;
echo -e "getGeoLocFromIp (alias: glocip)\nReturns: reverse geocoded city and country of IP address\nUsage: getGeoLocFromIp <valid ipv4 address> [-h] [-l]\n -h: Print this usage stub\n -l: Additionally output latitude and longitude" >&2;
return 1;
fi;
local ip="$1";
local lat_lon=$(getLatLonFromIp ${ip});
local stat=$?;
if [ $stat -ne 0 ]; then return $stat; fi;
local city_country=$(getCityCountryFromLatLon "${lat_lon}");
local stat=$?;
if [ $stat -ne 0 ]; then return $stat; fi;
if [ "$print_lat_lon" = true ]
then;
echo "${city_country} [${lat_lon}]";
else;
echo "${city_country}";
fi;
return 0;
}
function getLatLonFromIp()
{
if [ "$#" -ne 1 ] || [ $(validip $1) ]
then;
echo -e "Usage: getLatLonFromIp <valid ipv4 address>" >&2;
return 1;
fi;
local ip="$1";
local lat_lon=$(curl -s -m 1 "freegeoip.net/json/${ip}");
if [ -z "${lat_lon}" ] || [[ "${lat_lon}" =~ Backend\ not\ available ]] || [[ ! "${lat_lon}" =~ .*latitude.* ]]
then;
echo -e "Failure to access geocoding API; either:\n - Network connectivity is compromised\n - API calls to freegeoip.net are being blocked\n - freegeoip.net have changed their API\nInvestigate." >&2;
return 1;
else;
lat_lon="$(echo ${lat_lon} | jq -r '.latitude, .longitude')";
lat_lon="$(echo ${lat_lon} | tr ' ' ',')";
fi;
echo "${lat_lon}";
return 0;
}
function getCityCountryFromLatLon()
{
local latlon="";
if [ "$#" -eq 1 ] && [ -n "$1" ]
then;
latlon="$1";
elif [ "$#" -eq 2 ] && [ -n "$1" ] && [ -n "$2" ]
then;
local lat="$1";
local lon="$2";
latlon="${lat},${lon}";
else;
echo -e "Usage: getCityCountryFromLatLon {<latitude> <longitude>|<latitude,longitude>}" >&2;
return 1;
fi;
mapfile -t city_country < <(curl -G -k --data "latlng=$latlon&sensor=false" http://maps.googleapis.com/maps/api/geocode/xml 2>/dev/null | xmlstarlet sel -t -v '/GeocodeResponse/result[1]/address_component[type="political"][type="country"]/long_name' -n -t -v '/GeocodeResponse/result[1]/address_component[type="political"][type="locality"]/long_name');
if [ "${#city_country[@]}" -lt 2 ]
then;
echo -e "Failed to parse geocoding API, either:\n - Network connectivity is compromised\n - API calls to maps.googleapis.com are being blocked\n - maps.googleapis.com has changed its API\nInvestigate." >&2;
return 1;
fi;
local country="${city_country[0]}";
local city="${city_country[1]}";
echo "${city}, ${country}";
return 0;
}
###############################################################################################
# ALIASES #
###############################################################################################
alias gloc='getGeoLocFromIp $(getExtIp)'
alias glocip='getGeoLocFromIp'
alias latlonip='getLatLonFromIp'
These should all be sourced in your $HOME/.bashrc
or $HOME/.bash_profile
initialisation scripts, for example:
$HOME/.bashrc :
if [ -d ~/.bash.d ]
then;
. ~/.bash.d/.bash_aliases;
. ~/.bash.d/.bash_vpn;
. ~/.bash.d/.bash_parse;
. ~/.bash.d/.bash_geo;
fi;
Then, running crontab -e
as a regular user, the cron function in the .bash_vpn
source file can be called (in this example, every 5 minutes) by inserting the following:
SHELL=/bin/bash
###############################################################
# m h dom mon dow command
#==============================================================
# very frequent (every minute or 5)
*/5 * * * * . $HOME/.bash.d/.bash_vpn; iplogcron
Next, for the script to work completely, a couple of changes need to be made to your sudoers
file, which should always be edited using the visudo
command. Once this editor is opened, the following entries need to be inserted:
%vpnadmin ALL=(root) NOPASSWD: /usr/sbin/openvpn
%vpnadmin ALL=(ALL) NOPASSWD: /usr/sbin/service openvpn *
vpnuser ALL=(ALL) NOPASSWD: /usr/sbin/service transmission-daemon *
%vpnadmin
is a group that must be created - and then vpnuser
is the user who will be running all these scripts, and they must be a member of the %vpnadmin
group, this can be done with the following commands:
root@srv:~# adduser vpnuser
root@srv:~# addgroup vpnadmin
root@srv:~# usermod -a -G vpnadmin vpnuser
We will also need acl
(Access Control List) utilities for manipulating extended attributes of /etc/openvpn
and it’s contents (specifically allowing vpnuser
to access them with elevated privileges). First:
root@srv:~# apt update
root@srv:~# apt install acl libacl
Then:
root@srv:~# cd /etc
root@srv:~# getfacl --all-effective openvpn
# file: openvpn/
# owner: root
# group: root
user::rwx
group::r-x
other::r-x
root@srv:~# setfacl -dm group:vpnadmin:rwx /etc/openvpn
root@srv:~# setfacl -dm group:vpnadmin:rwx /run/openvpn
root@srv:~# getfacl --all-effective openvpn
# file: openvpn/
# owner: root
# group: root
user::rwx
group::r-x #effective:r-x
group:vpnadmin:rwx #effective:rwx
mask::rwx
other::r-x
default:user::rwx
default:group::r-x #effective:r-x
default:group:vpnadmin:rwx #effective:rwx
default:mask::rwx
default:other::r-x
At this point, everything is well set up. All that remains to be done is to populate your /etc/openvpn
directory with a set of placename.ovpn
files, where each placename
maps directly to an element from the VPNLIST
array declared at the top of $HOME/.bash.d/.bash_vpn
:
VPNLIST=( france germany sweden switzerland luxembourg finland nl1 nl2 nl3 italy lithuania poland portugal romania spain coventry london portsmouth )
So, for instance, in my /etc/openvpn
directory:
dev@srv:/etc/openvpn$ ls -l
total 84
-rwxrwx--- 1 root root 2992 Sep 1 02:45 coventry.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:15 finland.ovpn
-rwxrwx--- 1 root root 3099 Apr 10 02:31 france.ovpn
-rwxrwx--- 1 root root 3110 Apr 10 02:32 germany.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:38 italy.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:38 lithuania.ovpn
-rwxrwx--- 1 root root 2992 Sep 1 02:45 london.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:15 luxembourg.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:15 nl1.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:15 nl2.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:16 nl3.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:38 poland.ovpn
-rwxrwx--- 1 root root 2992 Sep 1 02:46 portsmouth.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:39 portugal.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:39 romania.ovpn
-rwxrwx--- 1 root root 2992 Jun 25 03:39 spain.ovpn
-rwxrwx--- 1 root root 2981 Jun 8 04:51 sweden.ovpn
-rwxrwx--- 1 root root 3014 Jun 8 04:52 switzerland.ovpn
lrwxrwxrwx 1 vpnuser vpnuser 25 Sep 13 19:44 vpnx.conf -> /etc/openvpn/poland.ovpn
And contained within each of these ovpn files are:
Thus each ovpn file is self-contained and contains all necessary information to create the openvpn connection.
In the Linux shell, you can run:
dev@srv:~$ fsvpn portugal
> > > CHANGING VPN TO [portugal] :: index [12] > > >
New VPN Location: Lisboa, Portugal :: [IP Address: 94.1.2.3]
Finally, to use the geoCoding / location capabilities, you’ll need to install jq
and xmlstarlet
which are used to parse the API responses:
dev@srv:~$ sudo apt update
dev@srv:~$ sudo apt install jq xmlstarlet
Then try out the functions by themselves:
dev@srv:~$ gloc
Stockholm, Sweden
dev@srv:~$ glocip 8.8.8.8
Mount Hope, United States
Any questions, just drop a comment below.