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