diff --git a/Dockers/pia-wireguard/Dockerfile b/Dockers/pia-wireguard/Dockerfile new file mode 100644 index 0000000..818744d --- /dev/null +++ b/Dockers/pia-wireguard/Dockerfile @@ -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"] diff --git a/Dockers/pia-wireguard/README.md b/Dockers/pia-wireguard/README.md new file mode 100644 index 0000000..1138fbb --- /dev/null +++ b/Dockers/pia-wireguard/README.md @@ -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). + diff --git a/Dockers/pia-wireguard/ca.rsa.4096.crt b/Dockers/pia-wireguard/ca.rsa.4096.crt new file mode 100644 index 0000000..82dec69 --- /dev/null +++ b/Dockers/pia-wireguard/ca.rsa.4096.crt @@ -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----- diff --git a/Dockers/pia-wireguard/docker-compose.example.yml b/Dockers/pia-wireguard/docker-compose.example.yml new file mode 100644 index 0000000..fbb59ee --- /dev/null +++ b/Dockers/pia-wireguard/docker-compose.example.yml @@ -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 diff --git a/Dockers/pia-wireguard/entrypoint.sh b/Dockers/pia-wireguard/entrypoint.sh new file mode 100644 index 0000000..7306657 --- /dev/null +++ b/Dockers/pia-wireguard/entrypoint.sh @@ -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" </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 "$@" + diff --git a/Dockers/pia-wireguard/pia.sh b/Dockers/pia-wireguard/pia.sh new file mode 100644 index 0000000..55f1116 --- /dev/null +++ b/Dockers/pia-wireguard/pia.sh @@ -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" +} + diff --git a/Dockers/pia-wireguard/version b/Dockers/pia-wireguard/version new file mode 100644 index 0000000..b9bc2fd --- /dev/null +++ b/Dockers/pia-wireguard/version @@ -0,0 +1 @@ +latest \ No newline at end of file