Remove WG_CONFIG_MAX_AGE environment variable and update README to reflect changes in WireGuard configuration handling. Refactor rotation logic in rotate.py to always generate a new keypair and simplify state management.
Build and Push Docker Images / build-and-push (push) Successful in 20s
Build and Push Docker Images / build-and-push (push) Successful in 20s
This commit is contained in:
@@ -23,7 +23,6 @@ ENV SERVERLIST_CACHE_MAX_AGE=168h
|
|||||||
ENV TOKEN_CACHE_PATH=/config/cache/pia-token.json
|
ENV TOKEN_CACHE_PATH=/config/cache/pia-token.json
|
||||||
ENV TOKEN_CACHE_TTL=20h
|
ENV TOKEN_CACHE_TTL=20h
|
||||||
ENV PIA_CA_PATH=/config/cache/ca.rsa.4096.crt
|
ENV PIA_CA_PATH=/config/cache/ca.rsa.4096.crt
|
||||||
ENV WG_CONFIG_MAX_AGE=7d
|
|
||||||
ENV LATENCY_SWITCH_MARGIN_MS=15
|
ENV LATENCY_SWITCH_MARGIN_MS=15
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ docker run --rm --entrypoint /opt/venv/bin/python \
|
|||||||
| `TOKEN_CACHE_PATH` | `/config/cache/pia-token.json` | Disk-cache voor PIA auth-token |
|
| `TOKEN_CACHE_PATH` | `/config/cache/pia-token.json` | Disk-cache voor PIA auth-token |
|
||||||
| `TOKEN_CACHE_TTL` | `20h` | Hergebruik token i.p.v. opnieuw inloggen |
|
| `TOKEN_CACHE_TTL` | `20h` | Hergebruik token i.p.v. opnieuw inloggen |
|
||||||
| `PIA_CA_PATH` | `/config/cache/ca.rsa.4096.crt` | Gecachete PIA CA voor `addKey` TLS |
|
| `PIA_CA_PATH` | `/config/cache/ca.rsa.4096.crt` | Gecachete PIA CA voor `addKey` TLS |
|
||||||
| `WG_CONFIG_MAX_AGE` | `7d` | Geen nieuwe token/`addKey` zolang endpoint gelijk blijft en config jonger is |
|
|
||||||
| `FORCE_ROTATE` | `false` | `true` = altijd nieuwe config + restarts |
|
|
||||||
| `FORCE_TOKEN_REFRESH` | `false` | `true` = token-cache negeren |
|
| `FORCE_TOKEN_REFRESH` | `false` | `true` = token-cache negeren |
|
||||||
| `RATE_LIMIT_WAIT_SECONDS` | `3600` | Wachttijd bij PIA rate-limit vóór retry |
|
| `RATE_LIMIT_WAIT_SECONDS` | `3600` | Wachttijd bij PIA rate-limit vóór retry |
|
||||||
| `TZ` | `Europe/Brussels` | Tijdzone voor scheduling |
|
| `TZ` | `Europe/Brussels` | Tijdzone voor scheduling |
|
||||||
@@ -113,8 +111,8 @@ Behoud minimaal:
|
|||||||
|
|
||||||
1. Latency meten over WG-servers in `PIA_REGIONS` (of random)
|
1. Latency meten over WG-servers in `PIA_REGIONS` (of random)
|
||||||
2. **Pin** de snelste server-IP (niet alleen regio)
|
2. **Pin** de snelste server-IP (niet alleen regio)
|
||||||
3. Keypair lokaal genereren; token + `addKey` alleen bij echte refresh
|
3. Altijd nieuw keypair + `addKey` (reauth), daarna containers herstarten
|
||||||
4. Caches: serverlist, token (~20u), CA-cert
|
4. Caches: serverlist, token (~20u), CA-cert — alleen om overbodige API-calls te beperken
|
||||||
5. Skip token/`addKey`/restarts als endpoint gelijk blijft én config jonger dan `WG_CONFIG_MAX_AGE`
|
5. Bij rate-limit: `RATE_LIMIT_WAIT_SECONDS` (default 1 uur) wachten en opnieuw proberen
|
||||||
|
|
||||||
**Let op:** zet gluetun **niet** in `RESTART_CONTAINERS`; gebruik `GLUETUN_CONTAINER`. Elke echte rotatie geeft korte downtime.
|
**Let op:** zet gluetun **niet** in `RESTART_CONTAINERS`; gebruik `GLUETUN_CONTAINER`. Elke echte rotatie geeft korte downtime.
|
||||||
|
|||||||
@@ -236,83 +236,13 @@ def restart_containers(containers: list[str]) -> None:
|
|||||||
raise SystemExit(f"docker restart failed for container={container}")
|
raise SystemExit(f"docker restart failed for container={container}")
|
||||||
|
|
||||||
|
|
||||||
def config_age_seconds(wg_path: Path) -> float | None:
|
|
||||||
if not wg_path.is_file():
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return time.time() - wg_path.stat().st_mtime
|
|
||||||
except OSError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def should_skip_regeneration(
|
|
||||||
server: pia.WgServer,
|
|
||||||
state: dict[str, Any],
|
|
||||||
wg_path: Path,
|
|
||||||
latency_results: list[dict[str, Any]],
|
|
||||||
) -> bool:
|
|
||||||
if pia.env_bool("FORCE_ROTATE", False):
|
|
||||||
log("FORCE_ROTATE=true; regenerating WireGuard config")
|
|
||||||
return False
|
|
||||||
|
|
||||||
previous_region = state.get("region")
|
|
||||||
previous_server_ip = state.get("server_ip")
|
|
||||||
if previous_region != server.region:
|
|
||||||
return False
|
|
||||||
|
|
||||||
age = config_age_seconds(wg_path)
|
|
||||||
if age is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
max_age = pia.parse_duration_seconds(os.environ.get("WG_CONFIG_MAX_AGE", "7d"), 604800)
|
|
||||||
if age > max_age:
|
|
||||||
log(f"Existing WireGuard config is stale (age {int(age)}s > max {max_age}s); regenerating")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not re.search(
|
|
||||||
r"^\[Interface\]",
|
|
||||||
wg_path.read_text(encoding="utf-8", errors="replace"),
|
|
||||||
flags=re.MULTILINE,
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Same region, different server: only regenerate if clearly faster.
|
|
||||||
if previous_server_ip and server.ip != previous_server_ip:
|
|
||||||
margin = float(os.environ.get("LATENCY_SWITCH_MARGIN_MS", "15"))
|
|
||||||
previous_latency = state.get("latency_ms")
|
|
||||||
winner = next((item for item in latency_results if item.get("server_ip") == server.ip), None)
|
|
||||||
new_latency = winner.get("latency_ms") if winner else None
|
|
||||||
if (
|
|
||||||
isinstance(previous_latency, (int, float))
|
|
||||||
and isinstance(new_latency, (int, float))
|
|
||||||
and (previous_latency - new_latency) < margin
|
|
||||||
):
|
|
||||||
log(
|
|
||||||
f"Keeping current server {previous_server_ip}; "
|
|
||||||
f"{server.ip} only {previous_latency - new_latency:.2f}ms faster "
|
|
||||||
f"(margin {margin:g}ms)"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
log(f"Switching server within {server.region}: {previous_server_ip} -> {server.ip}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"Skipping PIA token/addKey; endpoint unchanged "
|
|
||||||
f"({server.region}/{server.ip}) and config age {int(age)}s <= {max_age}s"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def write_state(
|
def write_state(
|
||||||
state_path: Path,
|
state_path: Path,
|
||||||
server: pia.WgServer,
|
server: pia.WgServer,
|
||||||
mode: str,
|
mode: str,
|
||||||
restarted: list[str],
|
restarted: list[str],
|
||||||
latency_results: list[dict[str, Any]],
|
latency_results: list[dict[str, Any]],
|
||||||
*,
|
|
||||||
skipped: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
previous = read_state(state_path)
|
|
||||||
winner = next(
|
winner = next(
|
||||||
(
|
(
|
||||||
item
|
item
|
||||||
@@ -329,17 +259,11 @@ def write_state(
|
|||||||
"selection": mode,
|
"selection": mode,
|
||||||
"wg_config": os.environ.get("WG_CONFIG_PATH", "/config/wireguard/wg0.conf"),
|
"wg_config": os.environ.get("WG_CONFIG_PATH", "/config/wireguard/wg0.conf"),
|
||||||
"restarted_containers": restarted,
|
"restarted_containers": restarted,
|
||||||
|
"rotated_at": now,
|
||||||
"last_checked_at": now,
|
"last_checked_at": now,
|
||||||
"skipped_regeneration": skipped,
|
|
||||||
}
|
}
|
||||||
if skipped and isinstance(previous.get("rotated_at"), str):
|
|
||||||
payload["rotated_at"] = previous["rotated_at"]
|
|
||||||
else:
|
|
||||||
payload["rotated_at"] = now
|
|
||||||
if winner and winner.get("latency_ms") is not None:
|
if winner and winner.get("latency_ms") is not None:
|
||||||
payload["latency_ms"] = winner["latency_ms"]
|
payload["latency_ms"] = winner["latency_ms"]
|
||||||
elif skipped and previous.get("latency_ms") is not None:
|
|
||||||
payload["latency_ms"] = previous["latency_ms"]
|
|
||||||
if latency_results:
|
if latency_results:
|
||||||
payload["latency_results"] = latency_results
|
payload["latency_results"] = latency_results
|
||||||
|
|
||||||
@@ -355,21 +279,10 @@ def rotate_once() -> None:
|
|||||||
|
|
||||||
state_path = Path(os.environ.get("ROTATOR_STATE_PATH", "/config/rotator-state.json"))
|
state_path = Path(os.environ.get("ROTATOR_STATE_PATH", "/config/rotator-state.json"))
|
||||||
wg_path = Path(os.environ.get("WG_CONFIG_PATH", "/config/wireguard/wg0.conf"))
|
wg_path = Path(os.environ.get("WG_CONFIG_PATH", "/config/wireguard/wg0.conf"))
|
||||||
state = read_state(state_path)
|
|
||||||
|
|
||||||
server, mode, latency_results = pick_server(state_path)
|
server, mode, latency_results = pick_server(state_path)
|
||||||
log(f"Selected endpoint: {server.region} / {server.cn} / {server.ip} (mode={mode})")
|
log(f"Selected endpoint: {server.region} / {server.cn} / {server.ip} (mode={mode})")
|
||||||
|
|
||||||
if should_skip_regeneration(server, state, wg_path, latency_results):
|
|
||||||
# If we decided to keep the previous server IP, persist that identity.
|
|
||||||
keep_ip = state.get("server_ip") if isinstance(state.get("server_ip"), str) else server.ip
|
|
||||||
keep_cn = state.get("server_cn") if isinstance(state.get("server_cn"), str) else server.cn
|
|
||||||
if keep_ip != server.ip:
|
|
||||||
server = pia.WgServer(region=server.region, ip=keep_ip, cn=str(keep_cn or server.cn))
|
|
||||||
write_state(state_path, server, mode, [], latency_results, skipped=True)
|
|
||||||
log(f"Rotation skipped for {server.region}/{server.ip}")
|
|
||||||
return
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory(prefix="pia-rotate-") as tmp:
|
with tempfile.TemporaryDirectory(prefix="pia-rotate-") as tmp:
|
||||||
tmp_conf = Path(tmp) / "wg0.conf"
|
tmp_conf = Path(tmp) / "wg0.conf"
|
||||||
log("Generating WireGuard config via native PIA client")
|
log("Generating WireGuard config via native PIA client")
|
||||||
@@ -391,7 +304,7 @@ def rotate_once() -> None:
|
|||||||
|
|
||||||
restarted = parse_restart_containers()
|
restarted = parse_restart_containers()
|
||||||
restart_containers(restarted)
|
restart_containers(restarted)
|
||||||
write_state(state_path, server, mode, restarted, latency_results, skipped=False)
|
write_state(state_path, server, mode, restarted, latency_results)
|
||||||
log(f"Rotation complete for {server.region}/{server.ip}")
|
log(f"Rotation complete for {server.region}/{server.ip}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user