341 lines
10 KiB
Bash
Executable file
341 lines
10 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Get the directory where the script is located
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
BASE="$SCRIPT_DIR/providers"
|
|
ROFI="rofi -dmenu -i"
|
|
NOTIFY="notify-send"
|
|
VPN_CMD="sudo openvpn"
|
|
|
|
# Helpers
|
|
die() {
|
|
$NOTIFY "MultiVPN" "$1"
|
|
exit 1
|
|
}
|
|
pick_random() { printf "%s\n" "$@" | shuf -n1; }
|
|
|
|
# Provider menu
|
|
providers=()
|
|
while IFS= read -r d; do
|
|
[[ -d "$BASE/$d" ]] && providers+=("$d")
|
|
done < <(ls "$BASE")
|
|
|
|
[[ ${#providers[@]} -eq 0 ]] && die "No providers found in $BASE"
|
|
|
|
provider_menu="[Disconnect]\n[Random Provider]\n$(printf "%s\n" "${providers[@]}" | sort)"
|
|
provider=$(echo -e "$provider_menu" | $ROFI -p "Select Provider")
|
|
[[ -z "$provider" ]] && exit 0
|
|
|
|
if [[ "$provider" == "[Disconnect]" ]]; then
|
|
sudo pkill openvpn 2>/dev/null && $NOTIFY "MultiVPN" "Disconnected ✓" || $NOTIFY "MultiVPN" "No active VPN connection"
|
|
exit 0
|
|
elif [[ "$provider" == "[Random Provider]" ]]; then
|
|
provider=$(pick_random "${providers[@]}")
|
|
random_provider=true
|
|
else
|
|
random_provider=false
|
|
fi
|
|
|
|
AUTH="$BASE/$provider/auth"
|
|
SERVER_DIR="$BASE/$provider/servers"
|
|
|
|
# Dynamic city map
|
|
declare -A countries
|
|
declare -A city_country_map
|
|
|
|
# Scan all .ovpn files and extract city-country info
|
|
while IFS= read -r f; do
|
|
filename=$(basename "$f" .ovpn)
|
|
|
|
# Parse filename format: city-country-###-scramble or city-country-###
|
|
if [[ "$filename" =~ ^([a-z0-9-]+)-([a-z]{2})-([0-9]{3})(-scramble)?$ ]]; then
|
|
city="${BASH_REMATCH[1]}"
|
|
country="${BASH_REMATCH[2]}"
|
|
# Server number is in BASH_REMATCH[3]
|
|
# Scramble flag is in BASH_REMATCH[4]
|
|
|
|
# Capitalize city name (replace hyphens with spaces)
|
|
city_display=$(echo "$city" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
|
|
|
# Get full country name from country code
|
|
case "$country" in
|
|
# North America
|
|
us) country_full="United States" ;;
|
|
ca) country_full="Canada" ;;
|
|
mx) country_full="Mexico" ;;
|
|
|
|
# Europe - Western
|
|
uk | gb) country_full="United Kingdom" ;;
|
|
de) country_full="Germany" ;;
|
|
fr) country_full="France" ;;
|
|
nl) country_full="Netherlands" ;;
|
|
ch) country_full="Switzerland" ;;
|
|
es) country_full="Spain" ;;
|
|
it) country_full="Italy" ;;
|
|
be) country_full="Belgium" ;;
|
|
at) country_full="Austria" ;;
|
|
ie) country_full="Ireland" ;;
|
|
pt) country_full="Portugal" ;;
|
|
lu) country_full="Luxembourg" ;;
|
|
|
|
# Europe - Northern
|
|
se) country_full="Sweden" ;;
|
|
no) country_full="Norway" ;;
|
|
dk) country_full="Denmark" ;;
|
|
fi) country_full="Finland" ;;
|
|
is) country_full="Iceland" ;;
|
|
|
|
# Europe - Eastern
|
|
pl) country_full="Poland" ;;
|
|
cz) country_full="Czech Republic" ;;
|
|
hu) country_full="Hungary" ;;
|
|
ro) country_full="Romania" ;;
|
|
bg) country_full="Bulgaria" ;;
|
|
sk) country_full="Slovakia" ;;
|
|
si) country_full="Slovenia" ;;
|
|
hr) country_full="Croatia" ;;
|
|
rs) country_full="Serbia" ;;
|
|
ua) country_full="Ukraine" ;;
|
|
|
|
# Europe - Southern
|
|
gr) country_full="Greece" ;;
|
|
tr) country_full="Turkey" ;;
|
|
cy) country_full="Cyprus" ;;
|
|
mt) country_full="Malta" ;;
|
|
|
|
# Asia - East
|
|
jp) country_full="Japan" ;;
|
|
kr) country_full="South Korea" ;;
|
|
cn) country_full="China" ;;
|
|
tw) country_full="Taiwan" ;;
|
|
hk) country_full="Hong Kong" ;;
|
|
mo) country_full="Macau" ;;
|
|
|
|
# Asia - Southeast
|
|
sg) country_full="Singapore" ;;
|
|
my) country_full="Malaysia" ;;
|
|
th) country_full="Thailand" ;;
|
|
id) country_full="Indonesia" ;;
|
|
ph) country_full="Philippines" ;;
|
|
vn) country_full="Vietnam" ;;
|
|
|
|
# Asia - South
|
|
in) country_full="India" ;;
|
|
pk) country_full="Pakistan" ;;
|
|
bd) country_full="Bangladesh" ;;
|
|
lk) country_full="Sri Lanka" ;;
|
|
|
|
# Middle East
|
|
ae) country_full="United Arab Emirates" ;;
|
|
sa) country_full="Saudi Arabia" ;;
|
|
il) country_full="Israel" ;;
|
|
qa) country_full="Qatar" ;;
|
|
kw) country_full="Kuwait" ;;
|
|
bh) country_full="Bahrain" ;;
|
|
om) country_full="Oman" ;;
|
|
jo) country_full="Jordan" ;;
|
|
|
|
# Oceania
|
|
au) country_full="Australia" ;;
|
|
nz) country_full="New Zealand" ;;
|
|
|
|
# South America
|
|
br) country_full="Brazil" ;;
|
|
ar) country_full="Argentina" ;;
|
|
cl) country_full="Chile" ;;
|
|
co) country_full="Colombia" ;;
|
|
pe) country_full="Peru" ;;
|
|
uy) country_full="Uruguay" ;;
|
|
py) country_full="Paraguay" ;;
|
|
ve) country_full="Venezuela" ;;
|
|
ec) country_full="Ecuador" ;;
|
|
|
|
# Central America & Caribbean
|
|
cr) country_full="Costa Rica" ;;
|
|
pa) country_full="Panama" ;;
|
|
gt) country_full="Guatemala" ;;
|
|
jm) country_full="Jamaica" ;;
|
|
tt) country_full="Trinidad and Tobago" ;;
|
|
|
|
# Africa
|
|
za) country_full="South Africa" ;;
|
|
eg) country_full="Egypt" ;;
|
|
ng) country_full="Nigeria" ;;
|
|
ke) country_full="Kenya" ;;
|
|
ma) country_full="Morocco" ;;
|
|
gh) country_full="Ghana" ;;
|
|
|
|
# Baltic States
|
|
ee) country_full="Estonia" ;;
|
|
lv) country_full="Latvia" ;;
|
|
lt) country_full="Lithuania" ;;
|
|
|
|
# Other European
|
|
md) country_full="Moldova" ;;
|
|
al) country_full="Albania" ;;
|
|
mk) country_full="North Macedonia" ;;
|
|
ba) country_full="Bosnia and Herzegovina" ;;
|
|
me) country_full="Montenegro" ;;
|
|
|
|
# Fallback
|
|
*) country_full=$(echo "$country" | tr '[:lower:]' '[:upper:]') ;;
|
|
esac
|
|
|
|
# Add to country map
|
|
if [[ -z "${countries[$country_full]:-}" ]]; then
|
|
countries[$country_full]="$city"
|
|
else
|
|
# Only add if city not already in list
|
|
if [[ ! " ${countries[$country_full]} " =~ " $city " ]]; then
|
|
countries[$country_full]+=" $city"
|
|
fi
|
|
fi
|
|
|
|
# Map city -> country for later lookup
|
|
city_country_map[$city]="$country_full"
|
|
fi
|
|
done < <(find "$SERVER_DIR" -name "*.ovpn")
|
|
|
|
[[ ${#countries[@]} -eq 0 ]] && die "No valid .ovpn files found in $SERVER_DIR"
|
|
|
|
# Country menu
|
|
country_menu="[Random Country]\n"
|
|
country_menu+="$(printf "%s\n" "${!countries[@]}" | sort)"
|
|
country=$(echo -e "$country_menu" | $ROFI -p "Select Country")
|
|
[[ -z "$country" ]] && exit 0
|
|
if [[ "$country" == "[Random Country]" ]]; then
|
|
country=$(pick_random "${!countries[@]}")
|
|
random_country=true
|
|
else
|
|
random_country=false
|
|
fi
|
|
|
|
# City menu
|
|
cities=(${countries[$country]})
|
|
|
|
if [[ "$random_country" == true ]]; then
|
|
city=$(pick_random "${cities[@]}")
|
|
city_display=$(echo "$city" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
|
else
|
|
city_menu="[Random City]\n"
|
|
for c in "${cities[@]}"; do
|
|
city_display=$(echo "$c" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
|
city_menu+="$city_display\n"
|
|
done
|
|
|
|
city_display=$(echo -e "$city_menu" | $ROFI -p "$country")
|
|
[[ -z "$city_display" ]] && exit 0
|
|
|
|
if [[ "$city_display" == "[Random City]" ]]; then
|
|
city=$(pick_random "${cities[@]}")
|
|
city_display=$(echo "$city" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
|
else
|
|
# Convert display name back to filename format
|
|
city=$(echo "$city_display" | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g')
|
|
fi
|
|
fi
|
|
|
|
# Check if scrambled servers exist for this city
|
|
mapfile -t scrambled_servers < <(ls "$SERVER_DIR" | grep "^$city-[a-z]\{2\}-[0-9]\{3\}-scramble\.ovpn$" || true)
|
|
mapfile -t regular_servers < <(ls "$SERVER_DIR" | grep "^$city-[a-z]\{2\}-[0-9]\{3\}\.ovpn$" || true)
|
|
|
|
has_scrambled=$([[ ${#scrambled_servers[@]} -gt 0 ]] && echo true || echo false)
|
|
has_regular=$([[ ${#regular_servers[@]} -gt 0 ]] && echo true || echo false)
|
|
|
|
if [[ "$has_scrambled" == true && "$has_regular" == true ]]; then
|
|
# Both types available, ask user
|
|
scramble_menu="Regular\nScrambled\n[Random]"
|
|
scramble_choice=$(echo -e "$scramble_menu" | $ROFI -p "Connection Type")
|
|
[[ -z "$scramble_choice" ]] && exit 0
|
|
|
|
case "$scramble_choice" in
|
|
"Regular")
|
|
use_scramble=false
|
|
;;
|
|
"Scrambled")
|
|
use_scramble=true
|
|
;;
|
|
"[Random]")
|
|
use_scramble=$([[ $((RANDOM % 2)) -eq 0 ]] && echo true || echo false)
|
|
;;
|
|
esac
|
|
elif [[ "$has_scrambled" == true ]]; then
|
|
# Only scrambled available
|
|
use_scramble=true
|
|
elif [[ "$has_regular" == true ]]; then
|
|
# Only regular available
|
|
use_scramble=false
|
|
else
|
|
die "No servers found for $city_display"
|
|
fi
|
|
|
|
# Server menu
|
|
if [[ "$use_scramble" == true ]]; then
|
|
server_files=("${scrambled_servers[@]}")
|
|
connection_type="🔒 Scrambled"
|
|
else
|
|
server_files=("${regular_servers[@]}")
|
|
connection_type="Regular"
|
|
fi
|
|
|
|
[[ "${#server_files[@]}" -eq 0 ]] && die "No $connection_type servers for $city_display"
|
|
|
|
if [[ "$random_provider" == true || "$random_country" == true ]]; then
|
|
server_file=$(pick_random "${server_files[@]}")
|
|
else
|
|
server_menu="[Random Server]\n"
|
|
for s in "${server_files[@]}"; do
|
|
# Extract server number (it's always before -scramble or .ovpn)
|
|
if [[ "$s" =~ -([0-9]{3})(-scramble)?\.ovpn$ ]]; then
|
|
server_menu+="Server ${BASH_REMATCH[1]}\n"
|
|
else
|
|
server_menu+="$s\n"
|
|
fi
|
|
done
|
|
server_menu+="[Disconnect]"
|
|
|
|
choice=$(echo -e "$server_menu" | $ROFI -p "$city_display ($connection_type)")
|
|
[[ -z "$choice" ]] && exit 0
|
|
|
|
if [[ "$choice" == "[Disconnect]" ]]; then
|
|
sudo killall openvpn && exit 0
|
|
elif [[ "$choice" == "[Random Server]" ]]; then
|
|
server_file=$(pick_random "${server_files[@]}")
|
|
else
|
|
num=$(echo "$choice" | grep -o '[0-9]\{3\}')
|
|
if [[ "$use_scramble" == true ]]; then
|
|
server_file=$(printf "%s\n" "${server_files[@]}" | grep -- "-$num-scramble\.ovpn$")
|
|
else
|
|
server_file=$(printf "%s\n" "${server_files[@]}" | grep -- "-$num\.ovpn$")
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Ask for sudo
|
|
SUDO_PASS=$(rofi -dmenu -password -p "Enter sudo password")
|
|
[[ -z "$SUDO_PASS" ]] && exit 0
|
|
|
|
# Kill old VPN if existing
|
|
echo "$SUDO_PASS" | sudo -S pkill openvpn 2>/dev/null || true
|
|
|
|
# Notify
|
|
$NOTIFY "MultiVPN" "Connecting…\n$provider → $country → $city_display\nType: $connection_type"
|
|
|
|
# Launch openvpn
|
|
echo "$SUDO_PASS" | sudo -S openvpn --config "$SERVER_DIR/$server_file" --auth-user-pass "$AUTH" &
|
|
|
|
# Wait for VPN to establish and routing to settle
|
|
sleep 8
|
|
|
|
# Get public IP
|
|
# Retry a few times to ensure we get the VPN IP
|
|
for i in {1..3}; do
|
|
ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || echo "")
|
|
[[ -n "$ip" ]] && break
|
|
sleep 2
|
|
done
|
|
|
|
[[ -z "$ip" ]] && ip="Unknown"
|
|
|
|
$NOTIFY "MultiVPN" "Connected ✓\n$provider → $country → $city_display\nType: $connection_type\nIP: $ip"
|