All checks were successful
Check Flake / check-flake (push) Successful in 3m24s
230 lines
8.2 KiB
Nix
230 lines
8.2 KiB
Nix
let
|
|
caPath = ./ca.rsa.4096.crt;
|
|
pubKeyPath = ./pubkey.pem;
|
|
in
|
|
|
|
# Bash function library for PIA VPN WireGuard operations.
|
|
# All PIA API calls accept an optional $proxy variable:
|
|
# proxy="http://10.100.0.1:8888" fetchPIAToken
|
|
# When $proxy is set, curl uses --proxy "$proxy"; otherwise direct connection.
|
|
|
|
# Reference materials:
|
|
# https://serverlist.piaservers.net/vpninfo/servers/v6
|
|
# https://github.com/pia-foss/manual-connections
|
|
# https://github.com/thrnz/docker-wireguard-pia/blob/master/extra/wg-gen.sh
|
|
# https://www.wireguard.com/netns/#ordinary-containerization
|
|
|
|
{
|
|
scriptCommon = ''
|
|
proxy_args() {
|
|
if [[ -n "''${proxy:-}" ]]; then
|
|
echo "--proxy $proxy"
|
|
fi
|
|
}
|
|
|
|
# Debug: check if a PIA API response has trailing data beyond the JSON.
|
|
verifyPIAResponse() {
|
|
local raw=$1 label=$2
|
|
local total_lines
|
|
total_lines=$(echo "$raw" | wc -l)
|
|
if echo "$raw" | jq -e . >/dev/null 2>&1; then
|
|
echo "DEBUG $label: response is valid JSON ($total_lines lines, no trailing signature)"
|
|
else
|
|
echo "DEBUG $label: response has non-JSON content ($total_lines lines). Full response:"
|
|
echo "$raw"
|
|
fi
|
|
}
|
|
|
|
fetchPIAToken() {
|
|
local PIA_USER PIA_PASS resp
|
|
echo "Reading PIA credentials..."
|
|
PIA_USER=$(sed '1q;d' /run/agenix/pia-login.conf)
|
|
PIA_PASS=$(sed '2q;d' /run/agenix/pia-login.conf)
|
|
echo "Requesting PIA authentication token..."
|
|
resp=$(curl -s $(proxy_args) -u "$PIA_USER:$PIA_PASS" \
|
|
"https://www.privateinternetaccess.com/gtoken/generateToken")
|
|
PIA_TOKEN=$(echo "$resp" | jq -r '.token')
|
|
if [[ -z "$PIA_TOKEN" || "$PIA_TOKEN" == "null" ]]; then
|
|
echo "ERROR: Failed to fetch PIA token: $resp" >&2
|
|
return 1
|
|
fi
|
|
echo "PIA token acquired"
|
|
}
|
|
|
|
choosePIAServer() {
|
|
local serverLocation=$1
|
|
local raw servers_json totalservers serverindex
|
|
servers_json=$(mktemp)
|
|
echo "Fetching PIA server list..."
|
|
raw=$(curl -s $(proxy_args) \
|
|
"https://serverlist.piaservers.net/vpninfo/servers/v6")
|
|
verifyPIAResponse "$raw" "serverList"
|
|
echo "$raw" | head -n 1 | tr -d '\n' > "$servers_json"
|
|
|
|
totalservers=$(jq -r \
|
|
'.regions | .[] | select(.id=="'"$serverLocation"'") | .servers.wg | length' \
|
|
"$servers_json")
|
|
if ! [[ "$totalservers" =~ ^[0-9]+$ ]] || [ "$totalservers" -eq 0 ] 2>/dev/null; then
|
|
echo "ERROR: Location \"$serverLocation\" not found." >&2
|
|
rm -f "$servers_json"
|
|
return 1
|
|
fi
|
|
echo "Found $totalservers WireGuard servers in region '$serverLocation'"
|
|
serverindex=$(( RANDOM % totalservers ))
|
|
|
|
WG_HOSTNAME=$(jq -r \
|
|
'.regions | .[] | select(.id=="'"$serverLocation"'") | .servers.wg | .['"$serverindex"'].cn' \
|
|
"$servers_json")
|
|
WG_SERVER_IP=$(jq -r \
|
|
'.regions | .[] | select(.id=="'"$serverLocation"'") | .servers.wg | .['"$serverindex"'].ip' \
|
|
"$servers_json")
|
|
WG_SERVER_PORT=$(jq -r '.groups.wg | .[0] | .ports | .[0]' "$servers_json")
|
|
|
|
rm -f "$servers_json"
|
|
echo "Selected server $serverindex/$totalservers: $WG_HOSTNAME ($WG_SERVER_IP:$WG_SERVER_PORT)"
|
|
}
|
|
|
|
generateWireguardKey() {
|
|
PRIVATE_KEY=$(wg genkey)
|
|
PUBLIC_KEY=$(echo "$PRIVATE_KEY" | wg pubkey)
|
|
echo "Generated WireGuard keypair"
|
|
}
|
|
|
|
authorizeKeyWithPIAServer() {
|
|
local raw addKeyResponse
|
|
echo "Sending addKey request to $WG_HOSTNAME ($WG_SERVER_IP:$WG_SERVER_PORT)..."
|
|
raw=$(curl -s -G $(proxy_args) \
|
|
--connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" \
|
|
--cacert "${caPath}" \
|
|
--data-urlencode "pt=$PIA_TOKEN" \
|
|
--data-urlencode "pubkey=$PUBLIC_KEY" \
|
|
"https://$WG_HOSTNAME:$WG_SERVER_PORT/addKey")
|
|
verifyPIAResponse "$raw" "addKey"
|
|
addKeyResponse=$(echo "$raw" | head -n 1)
|
|
local status
|
|
status=$(echo "$addKeyResponse" | jq -r '.status')
|
|
if [[ "$status" != "OK" ]]; then
|
|
echo "ERROR: addKey failed: $addKeyResponse" >&2
|
|
return 1
|
|
fi
|
|
MY_IP=$(echo "$addKeyResponse" | jq -r '.peer_ip')
|
|
WG_SERVER_PUBLIC_KEY=$(echo "$addKeyResponse" | jq -r '.server_key')
|
|
WG_SERVER_PORT=$(echo "$addKeyResponse" | jq -r '.server_port')
|
|
echo "Key authorized — assigned VPN IP: $MY_IP, server port: $WG_SERVER_PORT"
|
|
}
|
|
|
|
writeWireguardQuickFile() {
|
|
local wgFile=$1
|
|
local listenPort=$2
|
|
rm -f "$wgFile"
|
|
touch "$wgFile"
|
|
chmod 700 "$wgFile"
|
|
cat > "$wgFile" <<WGEOF
|
|
[Interface]
|
|
PrivateKey = $PRIVATE_KEY
|
|
ListenPort = $listenPort
|
|
[Peer]
|
|
PersistentKeepalive = 25
|
|
PublicKey = $WG_SERVER_PUBLIC_KEY
|
|
AllowedIPs = 0.0.0.0/0
|
|
Endpoint = $WG_SERVER_IP:$WG_SERVER_PORT
|
|
WGEOF
|
|
echo "Wrote WireGuard config to $wgFile (listen=$listenPort)"
|
|
}
|
|
|
|
writeChosenServerToFile() {
|
|
local serverFile=$1
|
|
jq -n \
|
|
--arg hostname "$WG_HOSTNAME" \
|
|
--arg ip "$WG_SERVER_IP" \
|
|
--arg port "$WG_SERVER_PORT" \
|
|
'{hostname: $hostname, ip: $ip, port: $port}' > "$serverFile"
|
|
chmod 700 "$serverFile"
|
|
echo "Wrote server info to $serverFile"
|
|
}
|
|
|
|
loadChosenServerFromFile() {
|
|
local serverFile=$1
|
|
WG_HOSTNAME=$(jq -r '.hostname' "$serverFile")
|
|
WG_SERVER_IP=$(jq -r '.ip' "$serverFile")
|
|
WG_SERVER_PORT=$(jq -r '.port' "$serverFile")
|
|
echo "Loaded server info from $serverFile: $WG_HOSTNAME ($WG_SERVER_IP:$WG_SERVER_PORT)"
|
|
}
|
|
|
|
# Reset WG interface and tear down NAT/forwarding rules.
|
|
# Called on startup (clear stale state) and on exit via trap.
|
|
cleanupVpn() {
|
|
local interfaceName=$1
|
|
wg set "$interfaceName" listen-port 0 2>/dev/null || true
|
|
ip -4 address flush dev "$interfaceName" 2>/dev/null || true
|
|
ip route del default dev "$interfaceName" 2>/dev/null || true
|
|
iptables -t nat -F 2>/dev/null || true
|
|
iptables -F FORWARD 2>/dev/null || true
|
|
}
|
|
|
|
connectToServer() {
|
|
local wgFile=$1
|
|
local interfaceName=$2
|
|
|
|
echo "Applying WireGuard config to $interfaceName..."
|
|
wg setconf "$interfaceName" "$wgFile"
|
|
ip -4 address add "$MY_IP" dev "$interfaceName"
|
|
ip link set mtu 1420 up dev "$interfaceName"
|
|
echo "WireGuard interface $interfaceName is up with IP $MY_IP"
|
|
}
|
|
|
|
reservePortForward() {
|
|
local raw payload_and_signature
|
|
echo "Requesting port forward signature from $WG_HOSTNAME..."
|
|
raw=$(curl -s -m 5 \
|
|
--connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" \
|
|
--cacert "${caPath}" \
|
|
-G --data-urlencode "token=$PIA_TOKEN" \
|
|
"https://$WG_HOSTNAME:19999/getSignature")
|
|
verifyPIAResponse "$raw" "getSignature"
|
|
payload_and_signature=$(echo "$raw" | head -n 1)
|
|
local status
|
|
status=$(echo "$payload_and_signature" | jq -r '.status')
|
|
if [[ "$status" != "OK" ]]; then
|
|
echo "ERROR: getSignature failed: $payload_and_signature" >&2
|
|
return 1
|
|
fi
|
|
PORT_SIGNATURE=$(echo "$payload_and_signature" | jq -r '.signature')
|
|
PORT_PAYLOAD=$(echo "$payload_and_signature" | jq -r '.payload')
|
|
PORT=$(echo "$PORT_PAYLOAD" | base64 -d | jq -r '.port')
|
|
echo "Port forward reserved: port $PORT"
|
|
}
|
|
|
|
writePortRenewalFile() {
|
|
local portRenewalFile=$1
|
|
jq -n \
|
|
--arg signature "$PORT_SIGNATURE" \
|
|
--arg payload "$PORT_PAYLOAD" \
|
|
'{signature: $signature, payload: $payload}' > "$portRenewalFile"
|
|
chmod 700 "$portRenewalFile"
|
|
echo "Wrote port renewal data to $portRenewalFile"
|
|
}
|
|
|
|
readPortRenewalFile() {
|
|
local portRenewalFile=$1
|
|
PORT_SIGNATURE=$(jq -r '.signature' "$portRenewalFile")
|
|
PORT_PAYLOAD=$(jq -r '.payload' "$portRenewalFile")
|
|
echo "Loaded port renewal data from $portRenewalFile"
|
|
}
|
|
|
|
refreshPIAPort() {
|
|
local raw bindPortResponse
|
|
echo "Refreshing port forward binding with $WG_HOSTNAME..."
|
|
raw=$(curl -Gs -m 5 \
|
|
--connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" \
|
|
--cacert "${caPath}" \
|
|
--data-urlencode "payload=$PORT_PAYLOAD" \
|
|
--data-urlencode "signature=$PORT_SIGNATURE" \
|
|
"https://$WG_HOSTNAME:19999/bindPort")
|
|
verifyPIAResponse "$raw" "bindPort"
|
|
bindPortResponse=$(echo "$raw" | head -n 1)
|
|
echo "bindPort response: $bindPortResponse"
|
|
}
|
|
'';
|
|
}
|