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:
@@ -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