#!/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