#!/usr/bin/env python3 """Batch-apply the enabled challenge set to every live team, one challenge at a time, then report per-challenge results. This is the CLI equivalent of hitting PATCH /api/challenges/ for each registry entry, but it survives panel restarts and reports progress, which matters because a full sync is N challenges x 4 teams x (docker build ~1-3 min). Usage: python3 panel/sync_all_challenges.py # sync registry -> runtime python3 panel/sync_all_challenges.py --status # just show what is up """ from __future__ import annotations import json import subprocess import sys import time from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import teams as orch # noqa: E402 def container_count(name: str) -> int: out = subprocess.run( ["docker", "ps", "--format", "{{.Names}}"], capture_output=True, text=True, ).stdout.splitlines() return sum(1 for n in out if n.startswith(f"{name}_container_team")) def main() -> int: if "--status" in sys.argv: reg = orch.load_registry() enabled = [c["name"] for c in reg["challenges"] if c.get("enabled")] print(f"registry enabled ({len(enabled)}): {', '.join(enabled)}\n") running = 0 for name in enabled: n = container_count(name) running += 1 if n else 0 print(f" {name:16s} {n}/4 teams {'OK' if n == 4 else ('PARTIAL' if n else 'MISSING')}") print(f"\n{running}/{len(enabled)} challenges have at least one container") return 0 reg = orch.load_registry() enabled = [c for c in reg["challenges"] if c.get("enabled")] print(f"syncing {len(enabled)} enabled challenges across live teams\n") t_all = time.time() for c in enabled: name = c["name"] if container_count(name) == 4: print(f"== {name}: already up on 4 teams, skipping") continue t0 = time.time() print(f"== {name}: applying (this builds one image, then reuses it per team)", flush=True) try: report = orch.sync_challenge_runtime(name, True) except Exception as e: # a single bad challenge must not abort the batch print(f" FAILED after {time.time()-t0:.0f}s: {e}", flush=True) continue for t in report.get("teams", []): last = (t.get("detail") or "").strip().splitlines()[-1:] or [""] mark = "ok" if t.get("ok") else "FAIL" print(f" team{t.get('team')}: {mark} {last[0][:110]}", flush=True) print(f" done in {time.time()-t0:.0f}s", flush=True) print(f"\nall done in {(time.time()-t_all)/60:.1f} min") return 0 if __name__ == "__main__": raise SystemExit(main())