fix: get all imported challenges building and running
Root causes found by prebuilding every challenge image in parallel:
- fjb: ghcr.io base is not anonymously pullable here -> official httpd:2.4.
pnpm 12 (via corepack on node:20) fails the install with
ERR_PNPM_IGNORED_BUILDS unless build scripts are approved; neither
onlyBuiltDependencies in pnpm-workspace.yaml nor --no-ignore-scripts
suppresses it. The working sequence is:
pnpm install --ignore-scripts && pnpm approve-builds --all && pnpm rebuild
- xl + kode-viewer: node:20-slim-bookworm is not a real tag -> node:20-bookworm-slim.
- burvesigner: python-dev no longer exists in bookworm -> dropped (python3-dev
was already there and the source has no py2 syntax).
- burvesigner/hirnfick/s3: apt update and install were separate RUN layers;
with the bundled apt-insecure.conf the second invocation re-resolved against
the EOL bullseye-security mirror and 404'd every package. Merged into one
'update && install' layer (fix_apt_layers.py, idempotent).
- consolidate_images.sh: teams used to build a private image per team
(team1-x ... team4-x) because no shared image existed. Since the password is
applied at runtime via chpasswd, one shared services-<name> build is enough;
this reclaims ~1.5 GB, which matters on a 79 GB disk.
- reconcile_team_state(): a challenge enabled while a team was down left
state.json without ports/flag/password, so the next compose render died with
KeyError. Now both the API and the CLI tools reconcile first.
This commit is contained in:
@@ -84,6 +84,63 @@ def set_challenge_enabled(name: str, enabled: bool) -> dict:
|
||||
_SYNC_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def reconcile_team_state() -> list[str]:
|
||||
"""Make every team's state.json agree with the registry's enabled set.
|
||||
|
||||
Needed whenever the registry is edited directly (panel/set_enabled.py) or
|
||||
a challenge is enabled but a team was offline/stopped while it happened:
|
||||
state.json must carry ports + a chall password + a flag for every enabled
|
||||
challenge, otherwise render_team_compose() raises KeyError on ports[name].
|
||||
|
||||
Returns a list of human-readable actions taken.
|
||||
"""
|
||||
reg = load_registry()
|
||||
enabled = enabled_challenges()
|
||||
# rebuild the derived CHALLENGES for fresh creates
|
||||
global CHALLENGES
|
||||
CHALLENGES = [(c["name"], c["chall_offset"], c["ssh_offset"]) for c in enabled]
|
||||
notes: list[str] = []
|
||||
for d in sorted(TEAMS_DIR.glob("team*")):
|
||||
sf = d / "state.json"
|
||||
if not sf.exists():
|
||||
continue
|
||||
st = json.loads(sf.read_text())
|
||||
idx = st["index"]
|
||||
dirty = False
|
||||
st.setdefault("ports", {})
|
||||
st.setdefault("chall_passwords", {})
|
||||
st.setdefault("flags", {})
|
||||
for ch in enabled:
|
||||
name = ch["name"]
|
||||
if name not in st["ports"]:
|
||||
st["ports"][name] = {"chall": 30000 + idx * 1000 + ch["chall_offset"],
|
||||
"ssh": 30000 + idx * 1000 + ch["ssh_offset"]}
|
||||
dirty = True
|
||||
notes.append(f"team{idx}: allocated ports for {name}")
|
||||
if not st["chall_passwords"].get(name):
|
||||
st["chall_passwords"][name] = f"chall{idx}_{name}_{secrets.token_hex(4)}"
|
||||
dirty = True
|
||||
if not st["flags"].get(name):
|
||||
flag = f"GEMASTIK18{{TEAM{idx}_{name.upper()}_{secrets.token_hex(6)}}}"
|
||||
st["flags"][name] = flag
|
||||
fd = d / "receiver" / "flags"
|
||||
fd.mkdir(parents=True, exist_ok=True)
|
||||
(fd / f"{name}.txt").write_text(flag)
|
||||
dirty = True
|
||||
notes.append(f"team{idx}: minted flag for {name}")
|
||||
# make sure the source tree is present for a later `build:`
|
||||
ts = d / "services" / name
|
||||
if not ts.exists():
|
||||
src = SERVICES_SRC / name
|
||||
if src.exists():
|
||||
shutil.copytree(src, ts,
|
||||
ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".git"))
|
||||
notes.append(f"team{idx}: copied source for {name}")
|
||||
if dirty:
|
||||
sf.write_text(json.dumps(st, indent=2))
|
||||
return notes
|
||||
|
||||
|
||||
def sync_challenge_runtime(name: str, enabled: bool) -> dict:
|
||||
"""Apply one challenge's enabled flag to every live team.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user