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_TTL=20h
|
||||
ENV PIA_CA_PATH=/config/cache/ca.rsa.4096.crt
|
||||
ENV WG_CONFIG_MAX_AGE=7d
|
||||
ENV LATENCY_SWITCH_MARGIN_MS=15
|
||||
|
||||
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_TTL` | `20h` | Hergebruik token i.p.v. opnieuw inloggen |
|
||||
| `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 |
|
||||
| `RATE_LIMIT_WAIT_SECONDS` | `3600` | Wachttijd bij PIA rate-limit vóór retry |
|
||||
| `TZ` | `Europe/Brussels` | Tijdzone voor scheduling |
|
||||
@@ -113,8 +111,8 @@ Behoud minimaal:
|
||||
|
||||
1. Latency meten over WG-servers in `PIA_REGIONS` (of random)
|
||||
2. **Pin** de snelste server-IP (niet alleen regio)
|
||||
3. Keypair lokaal genereren; token + `addKey` alleen bij echte refresh
|
||||
4. Caches: serverlist, token (~20u), CA-cert
|
||||
5. Skip token/`addKey`/restarts als endpoint gelijk blijft én config jonger dan `WG_CONFIG_MAX_AGE`
|
||||
3. Altijd nieuw keypair + `addKey` (reauth), daarna containers herstarten
|
||||
4. Caches: serverlist, token (~20u), CA-cert — alleen om overbodige API-calls te beperken
|
||||
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.
|
||||
|
||||
@@ -236,83 +236,13 @@ def restart_containers(containers: list[str]) -> None:
|
||||
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(
|
||||
state_path: Path,
|
||||
server: pia.WgServer,
|
||||
mode: str,
|
||||
restarted: list[str],
|
||||
latency_results: list[dict[str, Any]],
|
||||
*,
|
||||
skipped: bool = False,
|
||||
) -> None:
|
||||
previous = read_state(state_path)
|
||||
winner = next(
|
||||
(
|
||||
item
|
||||
@@ -329,17 +259,11 @@ def write_state(
|
||||
"selection": mode,
|
||||
"wg_config": os.environ.get("WG_CONFIG_PATH", "/config/wireguard/wg0.conf"),
|
||||
"restarted_containers": restarted,
|
||||
"rotated_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:
|
||||
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:
|
||||
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"))
|
||||
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)
|
||||
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:
|
||||
tmp_conf = Path(tmp) / "wg0.conf"
|
||||
log("Generating WireGuard config via native PIA client")
|
||||
@@ -391,7 +304,7 @@ def rotate_once() -> None:
|
||||
|
||||
restarted = parse_restart_containers()
|
||||
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}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user