This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
bash \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
iproute2 \
|
||||||
|
iptables \
|
||||||
|
jq \
|
||||||
|
procps \
|
||||||
|
wireguard-tools \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY ca.rsa.4096.crt /opt/pia/ca.rsa.4096.crt
|
||||||
|
COPY pia.sh /usr/local/lib/pia.sh
|
||||||
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
RUN chmod 0644 /opt/pia/ca.rsa.4096.crt \
|
||||||
|
&& chmod 0755 /usr/local/lib/pia.sh /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# `pia-wireguard` (PIA WireGuard gateway container)
|
||||||
|
|
||||||
|
Deze container zet **Private Internet Access (PIA)** op via **WireGuard** en laat andere containers dezelfde VPN-stack gebruiken via `network_mode: service:...`.
|
||||||
|
|
||||||
|
## Vereisten
|
||||||
|
|
||||||
|
- Docker/Compose
|
||||||
|
- Kernel WireGuard support op de host
|
||||||
|
- Container runtime permissies:
|
||||||
|
- `cap_add: [NET_ADMIN]`
|
||||||
|
- `devices: [/dev/net/tun]`
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
- **Required**
|
||||||
|
- `PIA_USER`: je PIA username
|
||||||
|
- `PIA_PASS`: je PIA password
|
||||||
|
- `PIA_REGIONS`: CSV (`nl_amsterdam,de_berlin`) of JSON array (`["nl_amsterdam","de_berlin"]`)
|
||||||
|
|
||||||
|
- **Optional**
|
||||||
|
- `PIA_PORT_FORWARD`: `true|false` (default `false`)
|
||||||
|
- `PIA_STATE_DIR`: default `/pia`
|
||||||
|
- `PIA_PF_FILE`: default `/pia/port_forward.json`
|
||||||
|
- `PIA_PF_PORT_FILE`: default `/pia/port_forward.port`
|
||||||
|
- `PIA_HANDSHAKE_MAX_AGE_SECONDS`: default `180`
|
||||||
|
- `PIA_HEALTH_URL`: default `https://api64.ipify.org`
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Wanneer `PIA_PORT_FORWARD=true`:
|
||||||
|
|
||||||
|
- `port_forward.json` (mountbaar naar host): bevat `port`, `region`, `expires_at`, `refreshed_at`
|
||||||
|
- `port_forward.port`: bevat enkel het poortnummer
|
||||||
|
|
||||||
|
## Compose voorbeeld
|
||||||
|
|
||||||
|
Zie [`docker-compose.example.yml`](docker-compose.example.yml).
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
|
||||||
|
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
|
||||||
|
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
|
||||||
|
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
|
||||||
|
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
|
||||||
|
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQw
|
||||||
|
MzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
|
||||||
|
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
|
||||||
|
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
|
||||||
|
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
|
||||||
|
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
|
||||||
|
bmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVk
|
||||||
|
hjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULN
|
||||||
|
De4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9K
|
||||||
|
V2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ
|
||||||
|
25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SND
|
||||||
|
fCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZyl
|
||||||
|
p7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7p
|
||||||
|
Kwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzj
|
||||||
|
tRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wi
|
||||||
|
jSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNz
|
||||||
|
meGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz
|
||||||
|
1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNV
|
||||||
|
HQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRt
|
||||||
|
yWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
|
||||||
|
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
|
||||||
|
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
|
||||||
|
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
|
||||||
|
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
|
||||||
|
aW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkq
|
||||||
|
hkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCt
|
||||||
|
pXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dv
|
||||||
|
Em89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1G
|
||||||
|
tF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZu
|
||||||
|
LfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs
|
||||||
|
6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj3
|
||||||
|
5xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnX
|
||||||
|
JUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJ
|
||||||
|
iyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l
|
||||||
|
8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW
|
||||||
|
+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
services:
|
||||||
|
piauplink:
|
||||||
|
build: .
|
||||||
|
container_name: piauplink
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
sysctls:
|
||||||
|
- net.ipv4.conf.all.src_valid_mark=1
|
||||||
|
environment:
|
||||||
|
- PIA_USER=${PIA_USER}
|
||||||
|
- PIA_PASS=${PIA_PASS}
|
||||||
|
- PIA_REGIONS=nl_amsterdam,de_berlin
|
||||||
|
- PIA_PORT_FORWARD=false
|
||||||
|
volumes:
|
||||||
|
- ./pia-state:/pia
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
whoami_behind_vpn:
|
||||||
|
image: traefik/whoami:latest
|
||||||
|
network_mode: service:piauplink
|
||||||
|
depends_on:
|
||||||
|
- piauplink
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source /usr/local/lib/pia.sh
|
||||||
|
|
||||||
|
PIA_IFACE="${PIA_IFACE:-wg0}"
|
||||||
|
PIA_STATE_DIR="${PIA_STATE_DIR:-/pia}"
|
||||||
|
PIA_PF_ENABLED="${PIA_PORT_FORWARD:-false}"
|
||||||
|
PIA_PF_FILE="${PIA_PF_FILE:-$PIA_STATE_DIR/port_forward.json}"
|
||||||
|
PIA_PF_PORT_FILE="${PIA_PF_PORT_FILE:-$PIA_STATE_DIR/port_forward.port}"
|
||||||
|
PIA_DNS_ENABLE="${PIA_DNS_ENABLE:-true}"
|
||||||
|
PIA_DNS_FALLBACK="${PIA_DNS_FALLBACK:-10.0.0.242}"
|
||||||
|
PIA_HEALTH_URL="${PIA_HEALTH_URL:-https://api64.ipify.org}"
|
||||||
|
PIA_HANDSHAKE_MAX_AGE_SECONDS="${PIA_HANDSHAKE_MAX_AGE_SECONDS:-180}"
|
||||||
|
PIA_RECONNECT_BACKOFF_SECONDS="${PIA_RECONNECT_BACKOFF_SECONDS:-5}"
|
||||||
|
|
||||||
|
CA_CERT_PATH="/opt/pia/ca.rsa.4096.crt"
|
||||||
|
|
||||||
|
pf_pid=""
|
||||||
|
current_region=""
|
||||||
|
current_wg_ip=""
|
||||||
|
current_wg_cn=""
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
set +e
|
||||||
|
if [[ -n "$pf_pid" ]]; then
|
||||||
|
kill "$pf_pid" 2>/dev/null || true
|
||||||
|
wait "$pf_pid" 2>/dev/null || true
|
||||||
|
pf_pid=""
|
||||||
|
fi
|
||||||
|
iptables -D OUTPUT -j PIA_KILLSWITCH 2>/dev/null || true
|
||||||
|
iptables -F PIA_KILLSWITCH 2>/dev/null || true
|
||||||
|
iptables -X PIA_KILLSWITCH 2>/dev/null || true
|
||||||
|
wg-quick down "$PIA_IFACE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
ensure_state_dir() {
|
||||||
|
mkdir -p "$PIA_STATE_DIR"
|
||||||
|
chmod 0755 "$PIA_STATE_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_resolv_conf() {
|
||||||
|
local dns="$1"
|
||||||
|
log "Setting /etc/resolv.conf to DNS $dns"
|
||||||
|
printf "nameserver %s\n" "$dns" > /etc/resolv.conf
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_killswitch() {
|
||||||
|
local endpoint_ip="$1"
|
||||||
|
local endpoint_port="$2"
|
||||||
|
|
||||||
|
iptables -N PIA_KILLSWITCH 2>/dev/null || true
|
||||||
|
iptables -F PIA_KILLSWITCH
|
||||||
|
|
||||||
|
iptables -A PIA_KILLSWITCH -o lo -j ACCEPT
|
||||||
|
iptables -A PIA_KILLSWITCH -o "$PIA_IFACE" -j ACCEPT
|
||||||
|
iptables -A PIA_KILLSWITCH -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
|
# Allow negotiating the tunnel itself (endpoint traffic leaves on eth0)
|
||||||
|
iptables -A PIA_KILLSWITCH -p udp -d "$endpoint_ip" --dport "$endpoint_port" -j ACCEPT
|
||||||
|
|
||||||
|
# Allow local traffic (docker internal + loopback ranges)
|
||||||
|
iptables -A PIA_KILLSWITCH -d 127.0.0.0/8 -j ACCEPT
|
||||||
|
iptables -A PIA_KILLSWITCH -d 172.16.0.0/12 -j ACCEPT
|
||||||
|
iptables -A PIA_KILLSWITCH -d 192.168.0.0/16 -j ACCEPT
|
||||||
|
iptables -A PIA_KILLSWITCH -d 10.0.0.0/8 -j ACCEPT
|
||||||
|
|
||||||
|
iptables -A PIA_KILLSWITCH -j REJECT
|
||||||
|
|
||||||
|
iptables -C OUTPUT -j PIA_KILLSWITCH 2>/dev/null || iptables -I OUTPUT 1 -j PIA_KILLSWITCH
|
||||||
|
}
|
||||||
|
|
||||||
|
is_tunnel_healthy() {
|
||||||
|
if ! wg show "$PIA_IFACE" >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local now latest age
|
||||||
|
now="$(date +%s)"
|
||||||
|
latest="$(wg show "$PIA_IFACE" latest-handshakes 2>/dev/null | awk '{print $2}' | sort -nr | head -n1 || true)"
|
||||||
|
if [[ -z "$latest" || "$latest" == "0" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
age="$(( now - latest ))"
|
||||||
|
if (( age > PIA_HANDSHAKE_MAX_AGE_SECONDS )); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -fsS --max-time 5 "$PIA_HEALTH_URL" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
start_port_forwarding_loop() {
|
||||||
|
local token="$1"
|
||||||
|
local wg_ip="$2"
|
||||||
|
local wg_cn="$3"
|
||||||
|
local region="$4"
|
||||||
|
|
||||||
|
(
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
local sigresp status payload signature port expires_at bindresp
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sigresp="$(pia_pf_get_signature "$wg_ip" "$wg_cn" "$token" "$CA_CERT_PATH")"
|
||||||
|
status="$(jq -r '.status' <<<"$sigresp")"
|
||||||
|
if [[ "$status" != "OK" ]]; then
|
||||||
|
>&2 echo "PF getSignature did not return OK: $sigresp"
|
||||||
|
sleep 5
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
payload="$(jq -r '.payload' <<<"$sigresp")"
|
||||||
|
signature="$(jq -r '.signature' <<<"$sigresp")"
|
||||||
|
port="$(echo "$payload" | base64 -d | jq -r '.port')"
|
||||||
|
expires_at="$(echo "$payload" | base64 -d | jq -r '.expires_at')"
|
||||||
|
|
||||||
|
bindresp="$(pia_pf_bind_port "$wg_ip" "$wg_cn" "$payload" "$signature" "$CA_CERT_PATH")"
|
||||||
|
if [[ "$(jq -r '.status' <<<"$bindresp")" != "OK" ]]; then
|
||||||
|
>&2 echo "PF bindPort did not return OK: $bindresp"
|
||||||
|
sleep 10
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_state_dir
|
||||||
|
jq -n \
|
||||||
|
--arg region "$region" \
|
||||||
|
--arg server_ip "$wg_ip" \
|
||||||
|
--arg server_cn "$wg_cn" \
|
||||||
|
--argjson port "$port" \
|
||||||
|
--arg expires_at "$expires_at" \
|
||||||
|
--arg refreshed_at "$(date -Is)" \
|
||||||
|
'{region:$region,server:{ip:$server_ip,cn:$server_cn},port:$port,expires_at:$expires_at,refreshed_at:$refreshed_at}' \
|
||||||
|
> "$PIA_PF_FILE.tmp" \
|
||||||
|
&& mv "$PIA_PF_FILE.tmp" "$PIA_PF_FILE"
|
||||||
|
|
||||||
|
echo "$port" > "$PIA_PF_PORT_FILE.tmp" && mv "$PIA_PF_PORT_FILE.tmp" "$PIA_PF_PORT_FILE"
|
||||||
|
|
||||||
|
sleep 900
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
|
||||||
|
pf_pid="$!"
|
||||||
|
log "Port-forward loop started (pid=$pf_pid)."
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_region() {
|
||||||
|
local region="$1"
|
||||||
|
local wg_ip="$2"
|
||||||
|
local wg_cn="$3"
|
||||||
|
local token="$4"
|
||||||
|
|
||||||
|
log "Connecting region=$region wg=${wg_cn} (${wg_ip})"
|
||||||
|
|
||||||
|
local privKey pubKey wgjson status peer_ip server_key server_port dns_server
|
||||||
|
privKey="$(wg genkey)"
|
||||||
|
pubKey="$(echo "$privKey" | wg pubkey)"
|
||||||
|
|
||||||
|
wgjson="$(pia_wireguard_add_key "$wg_ip" "$wg_cn" "$token" "$pubKey" "$CA_CERT_PATH")"
|
||||||
|
status="$(jq -r '.status' <<<"$wgjson")"
|
||||||
|
if [[ "$status" != "OK" ]]; then
|
||||||
|
>&2 echo "WireGuard addKey failed: $wgjson"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
peer_ip="$(jq -r '.peer_ip' <<<"$wgjson")"
|
||||||
|
server_key="$(jq -r '.server_key' <<<"$wgjson")"
|
||||||
|
server_port="$(jq -r '.server_port' <<<"$wgjson")"
|
||||||
|
dns_server="$(jq -r '.dns_servers[0] // empty' <<<"$wgjson")"
|
||||||
|
if [[ -z "$dns_server" ]]; then
|
||||||
|
dns_server="$PIA_DNS_FALLBACK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /etc/wireguard
|
||||||
|
cat >"/etc/wireguard/${PIA_IFACE}.conf" <<EOF
|
||||||
|
[Interface]
|
||||||
|
Address = ${peer_ip}
|
||||||
|
PrivateKey = ${privKey}
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PersistentKeepalive = 25
|
||||||
|
PublicKey = ${server_key}
|
||||||
|
AllowedIPs = 0.0.0.0/0
|
||||||
|
Endpoint = ${wg_ip}:${server_port}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [[ "$PIA_DNS_ENABLE" == "true" ]]; then
|
||||||
|
write_resolv_conf "$dns_server"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wg-quick down "$PIA_IFACE" 2>/dev/null || true
|
||||||
|
wg-quick up "$PIA_IFACE"
|
||||||
|
|
||||||
|
setup_killswitch "$wg_ip" "$server_port"
|
||||||
|
|
||||||
|
current_region="$region"
|
||||||
|
current_wg_ip="$wg_ip"
|
||||||
|
current_wg_cn="$wg_cn"
|
||||||
|
|
||||||
|
log "Connected. Verifying egress."
|
||||||
|
curl -fsS --max-time 10 "$PIA_HEALTH_URL" | tr -d '\n' | sed 's/^/PublicIP=/' || true
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ "$PIA_PF_ENABLED" == "true" ]]; then
|
||||||
|
start_port_forwarding_loop "$token" "$wg_ip" "$wg_cn" "$region"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_env PIA_USER
|
||||||
|
require_env PIA_PASS
|
||||||
|
require_env PIA_REGIONS
|
||||||
|
|
||||||
|
ensure_state_dir
|
||||||
|
log "Authenticating to PIA."
|
||||||
|
local token
|
||||||
|
token="$(pia_get_token)"
|
||||||
|
|
||||||
|
log "Fetching serverlist."
|
||||||
|
local serverlist
|
||||||
|
serverlist="$(pia_serverlist_v4)"
|
||||||
|
|
||||||
|
local candidates
|
||||||
|
candidates="$(pia_pick_region_candidates <<<"$serverlist")"
|
||||||
|
if [[ -z "$candidates" ]]; then
|
||||||
|
>&2 echo "No matching WireGuard servers found for PIA_REGIONS=$(printf %q "${PIA_REGIONS}")"
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Starting connect loop."
|
||||||
|
local line region wg_ip wg_cn connected=0
|
||||||
|
while IFS= read -r line; do
|
||||||
|
region="${line%%|*}"
|
||||||
|
wg_ip="$(cut -d'|' -f2 <<<"$line")"
|
||||||
|
wg_cn="$(cut -d'|' -f3 <<<"$line")"
|
||||||
|
|
||||||
|
if connect_region "$region" "$wg_ip" "$wg_cn" "$token"; then
|
||||||
|
connected=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done <<<"$candidates"
|
||||||
|
|
||||||
|
if (( connected == 0 )); then
|
||||||
|
>&2 echo "Failed to connect to any region in PIA_REGIONS."
|
||||||
|
exit 5
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Recovery loop (runs forever)
|
||||||
|
local idx=0 total
|
||||||
|
total="$(wc -l <<<"$candidates" | tr -d ' ')"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sleep 15
|
||||||
|
if is_tunnel_healthy; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Tunnel unhealthy. Reconnecting (rotate region)."
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
idx=$(( (idx + 1) % total ))
|
||||||
|
line="$(sed -n "$((idx + 1))p" <<<"$candidates")"
|
||||||
|
region="${line%%|*}"
|
||||||
|
wg_ip="$(cut -d'|' -f2 <<<"$line")"
|
||||||
|
wg_cn="$(cut -d'|' -f3 <<<"$line")"
|
||||||
|
|
||||||
|
until connect_region "$region" "$wg_ip" "$wg_cn" "$token"; do
|
||||||
|
log "Reconnect failed; sleeping ${PIA_RECONNECT_BACKOFF_SECONDS}s."
|
||||||
|
sleep "$PIA_RECONNECT_BACKOFF_SECONDS"
|
||||||
|
idx=$(( (idx + 1) % total ))
|
||||||
|
line="$(sed -n "$((idx + 1))p" <<<"$candidates")"
|
||||||
|
region="${line%%|*}"
|
||||||
|
wg_ip="$(cut -d'|' -f2 <<<"$line")"
|
||||||
|
wg_cn="$(cut -d'|' -f3 <<<"$line")"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log() { echo "[$(date -Is)] $*"; }
|
||||||
|
|
||||||
|
require_env() {
|
||||||
|
local name="$1"
|
||||||
|
if [[ -z "${!name:-}" ]]; then
|
||||||
|
>&2 echo "Missing required env var: $name"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pia_get_token() {
|
||||||
|
require_env PIA_USER
|
||||||
|
require_env PIA_PASS
|
||||||
|
|
||||||
|
local resp token
|
||||||
|
resp="$(curl -fsS --location --request POST \
|
||||||
|
'https://www.privateinternetaccess.com/api/client/v2/token' \
|
||||||
|
--form "username=${PIA_USER}" \
|
||||||
|
--form "password=${PIA_PASS}")"
|
||||||
|
|
||||||
|
token="$(jq -r '.token // empty' <<<"$resp")"
|
||||||
|
if [[ -z "$token" ]]; then
|
||||||
|
>&2 echo "Failed to authenticate to PIA (no token). Response:"
|
||||||
|
>&2 echo "$resp" | head -c 2000
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$token"
|
||||||
|
}
|
||||||
|
|
||||||
|
pia_serverlist_v4() {
|
||||||
|
curl -fsS 'https://serverlist.piaservers.net/vpninfo/servers/v4'
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_regions() {
|
||||||
|
# Supports:
|
||||||
|
# - CSV: "nl_amsterdam,de_berlin"
|
||||||
|
# - JSON: '["nl_amsterdam","de_berlin"]'
|
||||||
|
local raw="${PIA_REGIONS:-}"
|
||||||
|
if [[ -z "$raw" ]]; then
|
||||||
|
>&2 echo "Missing required env var: PIA_REGIONS"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$raw" =~ ^\[.*\]$ ]]; then
|
||||||
|
jq -r '.[]' <<<"$raw"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
tr ',' '\n' <<<"$raw" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | awk 'NF'
|
||||||
|
}
|
||||||
|
|
||||||
|
pia_pick_region_candidates() {
|
||||||
|
# Input: serverlist JSON via stdin
|
||||||
|
# Output: for each requested region id -> line "region_id|wg_ip|wg_cn"
|
||||||
|
local requested
|
||||||
|
requested="$(parse_regions | tr '\n' ' ')"
|
||||||
|
|
||||||
|
jq -r --arg requested "$requested" '
|
||||||
|
def wanted($id): ($requested | split(" ") | index($id)) != null;
|
||||||
|
.regions[]
|
||||||
|
| select(wanted(.id))
|
||||||
|
| . as $r
|
||||||
|
| ($r.servers.wg[0] // empty) as $wg
|
||||||
|
| select($wg != null)
|
||||||
|
| "\($r.id)|\($wg.ip)|\($wg.cn)"
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
pia_wireguard_add_key() {
|
||||||
|
# Args: WG_SERVER_IP WG_HOSTNAME PIA_TOKEN PUBKEY CA_CERT_PATH
|
||||||
|
local wg_ip="$1"
|
||||||
|
local wg_hostname="$2"
|
||||||
|
local token="$3"
|
||||||
|
local pubkey="$4"
|
||||||
|
local ca_cert="$5"
|
||||||
|
|
||||||
|
curl -fsS -G \
|
||||||
|
--connect-to "${wg_hostname}::${wg_ip}:" \
|
||||||
|
--cacert "$ca_cert" \
|
||||||
|
--data-urlencode "pt=${token}" \
|
||||||
|
--data-urlencode "pubkey=${pubkey}" \
|
||||||
|
"https://${wg_hostname}:1337/addKey"
|
||||||
|
}
|
||||||
|
|
||||||
|
pia_pf_get_signature() {
|
||||||
|
# Args: PF_GATEWAY PF_HOSTNAME PIA_TOKEN CA_CERT_PATH
|
||||||
|
local gateway="$1"
|
||||||
|
local hostname="$2"
|
||||||
|
local token="$3"
|
||||||
|
local ca_cert="$4"
|
||||||
|
|
||||||
|
curl -fsS -m 10 \
|
||||||
|
--connect-to "${hostname}::${gateway}:" \
|
||||||
|
--cacert "$ca_cert" \
|
||||||
|
-G --data-urlencode "token=${token}" \
|
||||||
|
"https://${hostname}:19999/getSignature"
|
||||||
|
}
|
||||||
|
|
||||||
|
pia_pf_bind_port() {
|
||||||
|
# Args: PF_GATEWAY PF_HOSTNAME PAYLOAD SIGNATURE CA_CERT_PATH
|
||||||
|
local gateway="$1"
|
||||||
|
local hostname="$2"
|
||||||
|
local payload="$3"
|
||||||
|
local signature="$4"
|
||||||
|
local ca_cert="$5"
|
||||||
|
|
||||||
|
curl -fsS -m 10 \
|
||||||
|
--connect-to "${hostname}::${gateway}:" \
|
||||||
|
--cacert "$ca_cert" \
|
||||||
|
-G \
|
||||||
|
--data-urlencode "payload=${payload}" \
|
||||||
|
--data-urlencode "signature=${signature}" \
|
||||||
|
"https://${hostname}:19999/bindPort"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
latest
|
||||||
Reference in New Issue
Block a user