Files
projects/Dockers/gluetun-pia-wireguard-rotator/rotate.sh
T
Bram 34ee842d34
Build and Push Docker Images / build-and-push (push) Successful in 23s
restart containers after rotation
2026-06-04 18:39:34 +02:00

133 lines
3.3 KiB
Bash

#!/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
}
parse_restart_containers() {
# CSV: "m3u-filter-vpn,m3u-editor,xtream-proxy" or JSON array.
# Falls back to GLUETUN_CONTAINER when unset.
local raw="${RESTART_CONTAINERS:-}"
if [[ -z "$raw" ]]; then
echo "${GLUETUN_CONTAINER:-m3u-filter-vpn}"
return
fi
if [[ "$raw" =~ ^\[.*\]$ ]]; then
jq -r '.[]' <<<"$raw"
return
fi
tr ',' '\n' <<<"$raw" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | awk 'NF'
}
restart_containers() {
local container restarted=()
while IFS= read -r container; do
[[ -z "$container" ]] && continue
log "Restarting container: $container"
if ! docker restart "$container"; then
>&2 echo "docker restart failed for container=$container"
exit 1
fi
restarted+=("$container")
done < <(parse_restart_containers)
if [[ "${#restarted[@]}" -eq 0 ]]; then
>&2 echo "No containers configured to restart"
exit 1
fi
}
write_state() {
local region="$1"
local state_path="${ROTATOR_STATE_PATH:-/config/rotator-state.json}"
local rotated_at restarted_json
rotated_at="$(date -Is)"
restarted_json="$(parse_restart_containers | jq -R -s 'split("\n") | map(select(length > 0))')"
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}" \
--argjson restarted_containers "$restarted_json" \
'{region: $region, rotated_at: $rotated_at, wg_config: $wg_config, restarted_containers: $restarted_containers}' >"$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
region="$(pick_random_region)"
wg_path="${WG_CONFIG_PATH:-/config/wireguard/wg0.conf}"
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"
restart_containers
write_state "$region"
log "Rotation complete for region=$region"
}
rotate_once