Skip to content

Instantly share code, notes, and snippets.

@jonasjancarik
Last active February 9, 2023 12:38
Show Gist options
  • Save jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf to your computer and use it in GitHub Desktop.
Save jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf to your computer and use it in GitHub Desktop.
ProtonVPN from Terminal - use a random config file
echo -e "\n\nDownloading custom ProtonVPN scripts...\n\n"
curl -s -o ~/protonvpn.sh "https://gist.githubusercontent.com/jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf/raw/protonvpn.sh?$RANDOM"
chmod +x ~/protonvpn.sh
curl -s -o ~/Desktop/protonvpn_script.desktop "https://gist.githubusercontent.com/jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf/raw/protonvpn_script.desktop?$RANDOM"
chmod 755 ~/Desktop/protonvpn_script.desktop
gio set ~/Desktop/protonvpn_script.desktop metadata::trusted true
curl -s -o ~/Desktop/protonvpn_disconnect.desktop "https://gist.githubusercontent.com/jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf/raw/protonvpn_disconnect.desktop?$RANDOM"
chmod 755 ~/Desktop/protonvpn_disconnect.desktop
gio set ~/Desktop/protonvpn_disconnect.desktop metadata::trusted true
echo -e "\n\n\e[32m$(tput bold)The script is now installed.$(tput sgr0)\e[0m \nUse the \"ProtonVPN (Free)\" shortcut on the desktop to launch it.\n\n"
#!/bin/bash
################
# Auto-update #
################
# Get the path of the currently installed script
current_script_path=~/protonvpn.sh
# Get the hash of the currently installed script
current_script_hash=$(sha256sum $current_script_path | awk '{print $1}')
# Download the latest version of this script
latest_script_hash=$(set -o pipefail && curl -s https://gist.githubusercontent.com/jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf/raw/protonvpn.sh?$RANDOM | sha256sum | awk '{print $1}')
# Check if the curl command was successful
if [ $? -ne 0 ]; then
echo "${YELLOW}WARNING${ENDCOLOR}: unable to check for updates."
else
# Compare the hashes of the two scripts
if [ ! "$current_script_hash" == "$latest_script_hash" ]; then
read -p "Update available, do you want to download it? (yes/no)" response
if [ "$(echo "$response" | tr '[:upper:]' '[:lower:]')" == "yes" ]; then
echo -e "\nDownloading update to ~/Downloads/protonvpn_install.sh..."
curl -s https://gist.githubusercontent.com/jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf/raw/install.sh?$RANDOM > ~/Downloads/protonvpn_install.sh
chmod +x ~/Downloads/protonvpn_install.sh
echo -e "\nUpdate downloaded, running the install script..."
exec ~/Downloads/protonvpn_install.sh # also exits the current script
else
echo "Update skipped."
fi
fi
fi
################
# Main script #
################
zip_file=~/Downloads/ProtonVPN_server_configs.zip
config_dir=~/protonvpn_configs
checksum_file="$config_dir/checksum.txt"
auth_fail_counter_file="$config_dir/auth_fail_counter.txt"
# colors
RED="\e[31m"
GREEN="\e[32m"
YELLOW="\e[33m"
ENDCOLOR="\e[0m"
child_process_exit_code=""
# https://www.baeldung.com/linux/background-process-get-exit-code
handle_sigchld() {
if [ -n "$pid" -a ! -d "/proc/$pid" ]; then
wait $pid
child_process_exit_code=$?
# echo pid $pid terminated with exit code $child_process_exit_code
unset pid
fi
}
extract_files() {
# create the target directory if it doesn't exist
mkdir -p "$config_dir"
# remove all .ovpn files from the target directory
find "$config_dir" -type f -name "*.ovpn" -delete 2>/dev/null
# extract the files, silently
# unzip -q -o "$zip_file" -d "$config_dir" 'jp*'
unzip -q -o "$zip_file" -d "$config_dir"
# store the new checksum
echo "$new_checksum" >"$checksum_file"
}
retry_prompt_result=""
retry_prompt() {
# ask user to retry or exit
read -p "Retry? (yes/no)" response
if [ "$(echo "$response" | tr '[:upper:]' '[:lower:]')" == "yes" ]; then
retry_prompt_result="true"
else
retry_prompt_result="false"
echo "Exiting..."
exit 1
fi
}
check_zip_file() {
config_files=$(find "$config_dir" -type f -name "*.ovpn" 2>/dev/null)
if [ -f "$zip_file" ]; then
# zip file exists
# calculate checksum of the zip file
new_checksum=$(sha256sum "$zip_file" | awk '{print $1}')
if [[ $(find "$zip_file" -mtime +30) ]]; then
echo -e "${YELLOW}WARNING${ENDCOLOR}: The ProtonVPN server configs zip file is older than 30 days. It is recommended to download a new one."
fi
if [ -f "$checksum_file" ]; then
# read stored checksum
old_checksum=$(cat "$checksum_file")
# compare new and stored checksums
if [ "$new_checksum" != "$old_checksum" ]; then
# checksums are different, extract the files
extract_files
fi
else
# checksum file doesn't exist, extract the files
extract_files
fi
else
# zip file doesn't exist
if [[ -z "$config_files" ]]; then
echo -e "\n${RED}ERROR${ENDCOLOR}: No .ovpn config files found in the $config_dir directory. The configs zip file has to be in ~/Downloads to create those."
echo -e "\nPlease download it from https://account.protonvpn.com/downloads#openvpn-configuration-files under OpenVPN configuration files. \n\n Choose these options: \n 1. Select platform: GNU/Linux \n 2. Select protocol: UDP \n 3. Select config file and download: Free server configs \n 4. Click on Download all configurations under the table. \n\n Make sure the downloaded file is in the Downloads folder and that its name is ProtonVPN_server_configs.zip. \n "
# ask user to retry or exit
retry_prompt
if [ "$retry_prompt_result" == "true" ]; then
check_zip_file
fi
else
echo -e "\n${YELLOW}WARNING${ENDCOLOR}: The ProtonVPN server configs zip file was not found. It should be in the ~/Downloads directory to help keep the configs up-to-date. \n\n Please download it from https://account.protonvpn.com/downloads#openvpn-configuration-files under OpenVPN configuration files. \n\n Choose these options: \n 1. Select platform: GNU/Linux \n 2. Select protocol: UDP \n 3. Select config file and download: Free server configs \n 4. Click on Download all configurations under the table. \n\n Make sure the downloaded file is in the Downloads folder and that its name is ProtonVPN_server_configs.zip. \n "
fi
fi
}
check_zip_file
if ! ls "$config_dir"/*.ovpn 1>/dev/null 2>&1; then
echo -e "\n${YELLOW}WARNING${ENDCOLOR}: No configuration files found in the directory $config_dir. Trying to get them from the downloaded configs file...\n"
extract_files
fi
# Check if the current version of update-resolv-conf script is different from the one at GitHub
update_resolv_conf_file="/etc/openvpn/update-resolv-conf"
update_resolv_conf_url="https://raw.githubusercontent.com/ProtonVPN/scripts/master/update-resolv-conf.sh"
if [ -f "$update_resolv_conf_file" ]; then
local_hash=$(sha256sum "$update_resolv_conf_file" | awk '{print $1}')
remote_hash=$(wget -qO- "$update_resolv_conf_url" | sha256sum | awk '{print $1}')
if [ "$local_hash" != "$remote_hash" ]; then
echo "Backing up the current version of update-resolv-conf script to $update_resolv_conf_file.bak"
sudo cp "$update_resolv_conf_file" "$update_resolv_conf_file.bak"
sudo wget "$update_resolv_conf_url" -O "$update_resolv_conf_file"
sudo chmod +x "$update_resolv_conf_file"
fi
else
sudo wget "$update_resolv_conf_url" -O "$update_resolv_conf_file"
sudo chmod +x "$update_resolv_conf_file"
fi
# check if the credentials.txt file exists, if not, ask the user to provide credentials
credentials_file="$config_dir/credentials.txt"
if [ ! -f "$credentials_file" ]; then
echo -e "We need special login details for this - i.e. not your regular ProtonVPN password. Get them from the OpenVPN/IKEv2 username section at https://account.protonvpn.com/account#openvpn\n"
read -p "Username: " username
read -p "Password: " password
echo "$username" >"$credentials_file"
echo "$password" >>"$credentials_file"
rm -f "$auth_fail_counter_file"
fi
if ! sudo -n true 2>/dev/null; then
echo -e "Please enter your password to enable the connection: "
# dummy sudo command to enable sudo for this session
sudo -v
fi
attempt_counter=0
random_config_file=""
no_config_files_left() {
# echo a message starting with an empty line
echo -e "\n${RED}ERROR${ENDCOLOR}: No configuration files left in the directory $config_dir."
echo -e "Removing the configs zip file to prevent reusing outdated configs..."
rm -f $zip_file
echo -e "\nPlease download a new one from https://account.protonvpn.com/downloads#openvpn-configuration-files under OpenVPN configuration files. \n\n Choose these options: \n 1. Select platform: GNU/Linux \n 2. Select protocol: UDP \n 3. Select config file and download: Free server configs \n 4. Click on Download all configurations under the table. \n\n Make sure the downloaded file is in the Downloads folder and that its name is ProtonVPN_server_configs.zip. \n "
retry_prompt
if [ "$retry_prompt_result" == "true" ]; then
exec $current_script_path
else
exit 1
fi
}
choose_country() {
# ask the user what country they want to connect through, JP, US or NL:
echo -e "\nPlease choose a country to connect through: \n 1. Japan \n 2. United States \n 3. Netherlands \n"
read -p "Enter a number: " country_number
if [ "$country_number" == "1" ]; then
country="jp"
country_full="Japan"
elif [ "$country_number" == "2" ]; then
country="us"
country_full="the United States"
elif [ "$country_number" == "3" ]; then
country="nl"
country_full="the Netherlands"
else
echo -e "\n${RED}ERROR${ENDCOLOR}: Invalid input. Please enter a number between 1 and 3.\n"
choose_country
fi
}
choose_country
echo -e "\n"
# start OpenVPN using the credentials file and the randomly chosen .ovpn file
while true; do
if ls "$config_dir"/*.ovpn 1>/dev/null 2>&1; then
# store a random config file in the variable random_config_file
random_config_file=$(find $config_dir -name "$country*.ovpn" | shuf -n 1)
else
no_config_files_left
fi
if [ "$attempt_counter" -ge 1 ]; then
echo -e "Trying configuration file: $random_config_file..."
fi
# create the logs directory if it doesn't exist
mkdir -p $config_dir/log
# clear the log file
truncate -s 0 $config_dir/log/openvpn.log
# kill any previously open OpenVPN processes - redirect errors to /dev/null
sudo killall openvpn 2>/dev/null
# count the number of lines in the config file which start with "remote "
# number_of_servers=$(grep -c "^remote " "$random_config_file")
# start OpenVPN in the background
# sudo openvpn --mute-replay-warnings --config "$random_config_file" --auth-user-pass $credentials_file --connect-timeout 10 --connect-retry-max 1 --resolv-retry $number_of_servers --log $config_dir/log/openvpn.log &
sudo openvpn --mute-replay-warnings --config "$random_config_file" --auth-user-pass $credentials_file --connect-timeout 10 --connect-retry 0 --connect-retry-max 1 --log $config_dir/log/openvpn.log &
# save the PID of the OpenVPN process
pid=$!
# echo $pid # for debugging
# Trap signals and kill the subprocess when the script is interrupted or terminated
trap "if ps -p $pid > /dev/null; then kill $pid; fi" SIGINT SIGTERM INT TERM SIGHUP EXIT
trap handle_sigchld SIGCHLD
connection_detected="false"
counter=0
# wait for the OpenVPN process to finish
while kill -0 $pid 2> /dev/null; do
if ! $connection_detected; then
echo -ne "\r${YELLOW}Connecting through ${country_full} using ProtonVPN... (${counter} seconds)${ENDCOLOR}"
# increment counter
counter=$((counter+1))
fi
# check if the connection was successful
if ! $connection_detected && sudo grep -q "Initialization Sequence Completed" "$config_dir/log/openvpn.log"; then
connection_detected="true"
echo -ne "\r"
tput el
echo -e "${GREEN}You are now connected through ${country_full} using ProtonVPN.${ENDCOLOR}"
echo -e "\r- To disconnect, press Ctrl+C"
echo -e "\r- If you just close this window, you may have to use the 'Disconnect ProtonVPN' shortcut on the desktop to actually disconnect."
echo "0" >> "$auth_fail_counter_file"
ip_api_response=""
get_ip_details() {
ip_api_response=$(curl -s http://ip-api.com/json/?fields=country,regionName,city,zip,lat,lon,isp,query)
}
api_details_obtained="false"
# try to get the IP details 5 times
for i in {1..5}; do
get_ip_details
if [ $? -eq 0 ]; then
api_details_obtained="true"
echo -e "\n\r"
# clear line
tput el
echo -e "You are connected through this location:"
query=$(echo $ip_api_response | jq -r '.query')
country=$(echo $ip_api_response | jq -r '.country')
regionName=$(echo $ip_api_response | jq -r '.regionName')
city=$(echo $ip_api_response | jq -r '.city')
zip=$(echo $ip_api_response | jq -r '.zip')
lat=$(echo $ip_api_response | jq -r '.lat')
lon=$(echo $ip_api_response | jq -r '.lon')
isp=$(echo $ip_api_response | jq -r '.isp')
org=$(echo $ip_api_response | jq -r '.org')
echo -en "\n\rIP:\t\t$query"
echo -en "\n\rCountry:\t$country"
echo -en "\n\rRegion:\t\t$regionName"
echo -en "\n\rCity:\t\t$city"
echo -en "\n\rZip:\t\t$zip"
echo -en "\n\rLat:\t\t$lat, lon: $lon"
echo -en "\n\rISP:\t\t$isp"
echo -en "\n\rOrg.:\t\t$org"
echo -e "\n\r"
break
fi
sleep 1
done
fi
if ! $connection_detected; then
sleep 1
fi
done
echo -e "\n"
echo -ne "\r"
# echo $pid # for debugging
# echo $! # for debugging
# echo $? # for debugging
# reset trap
trap - SIGINT SIGTERM
echo -e "\n"
if [ $child_process_exit_code -ne 0 ]; then
echo "OpenVPN connection failed."
if sudo grep -qE "(All connections have been connect-retry-max.*times unsuccessful, exiting)|(Cannot resolve host address)" "$config_dir/log/openvpn.log"; then
echo "The current configuration file is not working. Renaming it to ${random_config_file}_old"
mv "$random_config_file" "${random_config_file}_old"
if [ $(ls "$config_dir"/*.ovpn | wc -l) -eq 0 ]; then # todo: redirect errors
no_config_files_left
else
echo -e "\n\r"
read -p "Do you want to try again with a different configuration file? (yes/no)" answer
if [ "$(echo "$answer" | tr '[:upper:]' '[:lower:]')" != "yes" ]; then
echo "Exiting."
exit 0
fi
attempt_counter=$((attempt_counter + 1))
fi
else
echo -e "\n\rAn unknown error occurred. Exiting.\n\r"
read -n1 -p "Press any key to exit."
echo -e "\n\r"
exit 1
fi
else
# for some reason auth fail does not exit with a non-zero exit code
if sudo grep -q "AUTH: Received control message: AUTH_FAILED" "$config_dir/log/openvpn.log"; then
echo -e "\n\rAuthentication failed."
# load the counter file
if [ ! -f "$auth_fail_counter_file" ]; then
echo "1" >> "$auth_fail_counter_file"
auth_fail_count=1
echo -e "\rSometimes this is temporary. Please try again.\n"
else
auth_fail_count=$(cat "$auth_fail_counter_file")
auth_fail_count=$((auth_fail_count + 1))
echo "$auth_fail_count" > "$auth_fail_counter_file"
read -p "This may be temporary, but your saved login details have already failed ${auth_fail_count} times. Would you like to remove them? (yes/no)" answer
if [ "$answer" = "yes" ]; then
mv "$credentials_file" "$credentials_file"_old
rm "$auth_fail_counter_file"
echo -e "Your login details have been removed (backed up to ${credentials_file}_old). Please try again."
exit 0
fi
fi
read -n1 -p "Press any key to exit."
echo -e "\n"
exit 1
fi
echo -e "\nOpenVPN connection closed.\n\r"
read -n1 -p "Press any key to exit."
echo -e "\n\r"
exit 0
fi
done
[Desktop Entry]
Type=Application
Name=Disconnect ProtonVPN (Free)
Exec=sh -c "sudo killall openvpn 2>/dev/null"
Icon=gnome-globe-net
Terminal=true
[Desktop Entry]
Type=Application
Name=ProtonVPN (Free)
Exec=sh -c "~/protonvpn.sh"
Icon=gnome-globe-net
Terminal=true
@jonasjancarik
Copy link
Author

Install with

curl -s "https://gist.githubusercontent.com/jonasjancarik/d5b73a1c1274defd290304db1cb0dfaf/raw/install.sh?$RANDOM" | bash

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment