fix: bulk challenge enable path (compose YAML, base images, async toggle, tooling)
- fix_dup_volumes.py: 4 canonical templates had TWO volumes: keys inside one service (invalid YAML -> 'mapping key volumes already defined'), which broke every enable for anti-alchemy/burvesigner/gemas-notes/kode-viewer. - fjb: ghcr.io base is not anonymously pullable on this host; swapped to the official httpd:2.4 (its httpd.conf only uses stock modules). Added onlyBuiltDependencies to package.json (pnpm >=10 blocks esbuild's postinstall). - xl + kode-viewer: node:20-slim-bookworm is not a real tag; use node:20-bookworm-slim. gift-voucher: buster -> bookworm. - prebuild_images.py: build each challenge's shared services-<name> image once in parallel (passes a placeholder PASSWORD build-arg, since several Dockerfiles run chpasswd and fail on an empty arg). - set_enabled.py / sync_all_challenges.py: batch registry flip + runtime apply that survives panel restarts and reports per-team results. - Challenge toggle is now async: PATCH returns a job id, the client polls /api/challenges/jobs/<id> so a multi-minute build no longer blocks the panel. Added _SYNC_LOCK to serialize concurrent compose rewrites.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
#!/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/<name> 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())
|
||||
Reference in New Issue
Block a user