From 2b25780f3e96ca51c319850db8a6789ad6465d70 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 4 Jun 2026 18:29:43 +0200 Subject: [PATCH] pia wireguard rotator --- .../gluetun-pia-wireguard-rotator/Dockerfile | 21 ++++ .../gluetun-pia-wireguard-rotator/README.md | 109 ++++++++++++++++++ .../entrypoint.sh | 40 +++++++ .../gluetun-pia-wireguard-rotator/rotate.sh | 101 ++++++++++++++++ Dockers/gluetun-pia-wireguard-rotator/version | 1 + 5 files changed, 272 insertions(+) create mode 100644 Dockers/gluetun-pia-wireguard-rotator/Dockerfile create mode 100644 Dockers/gluetun-pia-wireguard-rotator/README.md create mode 100644 Dockers/gluetun-pia-wireguard-rotator/entrypoint.sh create mode 100644 Dockers/gluetun-pia-wireguard-rotator/rotate.sh create mode 100644 Dockers/gluetun-pia-wireguard-rotator/version diff --git a/Dockers/gluetun-pia-wireguard-rotator/Dockerfile b/Dockers/gluetun-pia-wireguard-rotator/Dockerfile new file mode 100644 index 0000000..25a7121 --- /dev/null +++ b/Dockers/gluetun-pia-wireguard-rotator/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:1.23-alpine AS builder + +RUN apk add --no-cache git ca-certificates \ + && go install github.com/kylegrantlucas/pia-wg-config@v1.1.1 + +FROM docker:cli + +RUN apk add --no-cache bash ca-certificates tzdata jq + +COPY --from=builder /go/bin/pia-wg-config /usr/local/bin/pia-wg-config +COPY rotate.sh entrypoint.sh /usr/local/bin/ + +RUN chmod +x /usr/local/bin/rotate.sh /usr/local/bin/entrypoint.sh + +ENV TZ=Europe/Brussels +ENV WG_CONFIG_PATH=/config/wireguard/wg0.conf +ENV ROTATOR_STATE_PATH=/config/rotator-state.json +ENV GLUETUN_CONTAINER=m3u-filter-vpn +ENV ROTATE_AT=03:00 + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/Dockers/gluetun-pia-wireguard-rotator/README.md b/Dockers/gluetun-pia-wireguard-rotator/README.md new file mode 100644 index 0000000..0a638e0 --- /dev/null +++ b/Dockers/gluetun-pia-wireguard-rotator/README.md @@ -0,0 +1,109 @@ +# gluetun-pia-wireguard-rotator + +Sidecar die dagelijks (en bij container-start) een willekeurige PIA WireGuard-region kiest via [pia-wg-config](https://github.com/kylegrantlucas/pia-wg-config), `wg0.conf` op het gedeelde gluetun-volume schrijft, en de gluetun-container herstart. + +## Vereisten + +- Gluetun met `VPN_SERVICE_PROVIDER=custom` en `VPN_TYPE=wireguard` +- Gedeeld volume met gluetun (bijv. `/var/dockers/m3u-filter-pia:/gluetun` op gluetun, `/config` op de rotator) +- Docker socket (alleen voor `docker restart` van de gluetun-container) +- Actief PIA-abonnement + +## Environment variables + +### Required + +| Variable | Description | +|----------|-------------| +| `PIA_USER` | PIA-gebruikersnaam | +| `PIA_PASS` | PIA-wachtwoord | +| `PIA_REGIONS` | CSV (`netherlands,france,belgium`) of JSON-array (`["netherlands","france"]`) | + +Region-codes moeten overeenkomen met `pia-wg-config` (niet de OpenVPN-namen uit Gluetun's ingebouwde PIA-provider). + +Lijst opvragen: + +```bash +docker run --rm --entrypoint pia-wg-config bramkel/gluetun-pia-wireguard-rotator:latest regions +``` + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `GLUETUN_CONTAINER` | `m3u-filter-vpn` | Container name/id om te herstarten | +| `WG_CONFIG_PATH` | `/config/wireguard/wg0.conf` | Pad waar `wg0.conf` wordt geschreven | +| `ROTATOR_STATE_PATH` | `/config/rotator-state.json` | Laatste rotatie-metadata | +| `ROTATE_AT` | `03:00` | Dagelijks rotatietijdstip (`HH:MM`, in `TZ`) | +| `TZ` | `Europe/Brussels` | Tijdzone voor scheduling | + +## Output + +- `wireguard/wg0.conf` op het gedeelde volume — Gluetun leest dit als `/gluetun/wireguard/wg0.conf` en dit **overschrijft** `WIREGUARD_*` environment variables +- `rotator-state.json` — laatste gekozen region en timestamp + +## Compose-integratie + +Zie [`docker-compose.example.yml`](docker-compose.example.yml) voor een volledig voorbeeld met `m3u-filter-vpn`, m3u-editor en xtream-proxy. + +### Nieuwe service toevoegen + +```yaml + gluetun-pia-wireguard-rotator: + image: bramkel/gluetun-pia-wireguard-rotator:latest + container_name: gluetun-pia-wireguard-rotator + restart: unless-stopped + environment: + - TZ=Europe/Brussels + - PIA_USER=${PIA_USER} + - PIA_PASS=${PIA_PASSWORD} + - PIA_REGIONS=netherlands,france,belgium + - GLUETUN_CONTAINER=m3u-filter-vpn + - ROTATE_AT=03:00 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /var/dockers/m3u-filter-pia:/config + depends_on: + - m3u-filter-vpn +``` + +Zet `PIA_USER` en `PIA_PASSWORD` in een host-`.env` (niet inline in compose). + +### Gluetun opschonen (aanbevolen na eerste succesvolle rotatie) + +Zodra `wg0.conf` bestaat, heeft het bestand voorrang op env-vars. Verwijder uit `m3u-filter-vpn` om verwarring te voorkomen: + +- `WIREGUARD_ENDPOINT_IP` +- `WIREGUARD_PUBLIC_KEY` +- `WIREGUARD_PRIVATE_KEY` +- `WIREGUARD_ADDRESSES` + +Behoud minimaal: + +```yaml + environment: + - VPN_SERVICE_PROVIDER=custom + - VPN_TYPE=wireguard +``` + +Optioneel host-`.env`-keys (`WIREGUARD_*`) opruimen als die niet meer gebruikt worden. + +## Deploy + +1. Push/build image (`Dockers/gluetun-pia-wireguard-rotator/**` triggert Gitea CI → `bramkel/gluetun-pia-wireguard-rotator:latest`) +2. `docker compose up -d gluetun-pia-wireguard-rotator` +3. Controleer logs: `docker logs gluetun-pia-wireguard-rotator` en `docker logs m3u-filter-vpn` + +## Gedrag + +1. Bij start: direct roteren (nieuwe config + gluetun restart) +2. Daarna: elke dag om `ROTATE_AT` opnieuw +3. Willekeurige region uit `PIA_REGIONS` + +**Let op:** `docker restart` op gluetun veroorzaakt kort VPN-verlies voor alle containers met `network_mode: service:m3u-filter-vpn`. + +## Security + +- De Docker socket geeft de rotator rechten om containers te herstarten; mount read-only waar mogelijk +- `wg0.conf` bevat private keys (`chmod 600`) +- Bewaar PIA-credentials in `.env`, niet in version control diff --git a/Dockers/gluetun-pia-wireguard-rotator/entrypoint.sh b/Dockers/gluetun-pia-wireguard-rotator/entrypoint.sh new file mode 100644 index 0000000..5ae750a --- /dev/null +++ b/Dockers/gluetun-pia-wireguard-rotator/entrypoint.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { echo "[$(date -Is)] $*"; } + +sleep_until_next_rotate() { + local rotate_at="${ROTATE_AT:-03:00}" + local target_h target_m + IFS=: read -r target_h target_m <<<"$rotate_at" + target_h=$((10#$target_h)) + target_m=$((10#$target_m)) + + local now_h now_m now_s target_s wait_s + now_h=$(date +%H) + now_m=$(date +%M) + now_h=$((10#$now_h)) + now_m=$((10#$now_m)) + now_s=$((now_h * 3600 + now_m * 60)) + target_s=$((target_h * 3600 + target_m * 60)) + + if ((now_s < target_s)); then + wait_s=$((target_s - now_s)) + else + wait_s=$((86400 - now_s + target_s)) + fi + + log "Next rotation at ${rotate_at} (${TZ:-UTC}) in ${wait_s}s" + sleep "$wait_s" +} + +log "Starting gluetun PIA WireGuard rotator (TZ=${TZ:-UTC}, ROTATE_AT=${ROTATE_AT:-03:00})" + +log "Running rotation on startup" +/usr/local/bin/rotate.sh + +while true; do + sleep_until_next_rotate + log "Running scheduled daily rotation" + /usr/local/bin/rotate.sh +done diff --git a/Dockers/gluetun-pia-wireguard-rotator/rotate.sh b/Dockers/gluetun-pia-wireguard-rotator/rotate.sh new file mode 100644 index 0000000..7768af4 --- /dev/null +++ b/Dockers/gluetun-pia-wireguard-rotator/rotate.sh @@ -0,0 +1,101 @@ +#!/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 +} + +parse_regions() { + # CSV: "netherlands,france,belgium" or JSON: '["netherlands","france"]' + 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' +} + +pick_random_region() { + local regions=() + while IFS= read -r region; do + [[ -n "$region" ]] && regions+=("$region") + done < <(parse_regions) + + if [[ "${#regions[@]}" -eq 0 ]]; then + >&2 echo "PIA_REGIONS is empty after parsing" + exit 2 + fi + + printf '%s\n' "${regions[@]}" | shuf -n 1 +} + +write_state() { + local region="$1" + local state_path="${ROTATOR_STATE_PATH:-/config/rotator-state.json}" + local rotated_at + rotated_at="$(date -Is)" + mkdir -p "$(dirname "$state_path")" + jq -n \ + --arg region "$region" \ + --arg rotated_at "$rotated_at" \ + --arg wg_config "${WG_CONFIG_PATH:-/config/wireguard/wg0.conf}" \ + '{region: $region, rotated_at: $rotated_at, wg_config: $wg_config}' >"$state_path" + chmod 644 "$state_path" +} + +rotate_once() { + require_env PIA_USER + require_env PIA_PASS + require_env PIA_REGIONS + + local region wg_path tmp_dir gluetun_container + region="$(pick_random_region)" + wg_path="${WG_CONFIG_PATH:-/config/wireguard/wg0.conf}" + gluetun_container="${GLUETUN_CONTAINER:-m3u-filter-vpn}" + + log "Selected region: $region" + + tmp_dir="$(mktemp -d)" + trap 'rm -rf "$tmp_dir"' RETURN + + local tmp_conf="${tmp_dir}/wg0.conf" + log "Generating WireGuard config with pia-wg-config" + if ! pia-wg-config -v -r "$region" -o "$tmp_conf" "$PIA_USER" "$PIA_PASS"; then + >&2 echo "pia-wg-config failed for region=$region" + exit 1 + fi + + if ! grep -q '^\[Interface\]' "$tmp_conf"; then + >&2 echo "Generated config missing [Interface] section" + exit 1 + fi + + mkdir -p "$(dirname "$wg_path")" + mv "$tmp_conf" "$wg_path" + chmod 600 "$wg_path" + log "Wrote $wg_path" + + write_state "$region" + + log "Restarting gluetun container: $gluetun_container" + if ! docker restart "$gluetun_container"; then + >&2 echo "docker restart failed for container=$gluetun_container" + exit 1 + fi + + log "Rotation complete for region=$region" +} + +rotate_once diff --git a/Dockers/gluetun-pia-wireguard-rotator/version b/Dockers/gluetun-pia-wireguard-rotator/version new file mode 100644 index 0000000..a0f9a4b --- /dev/null +++ b/Dockers/gluetun-pia-wireguard-rotator/version @@ -0,0 +1 @@ +latest