Files
MythEclipse ef385c3397 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.
2026-09-25 21:03:29 +08:00

82 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Merge a Dockerfile's split `apt-get update` + `apt-get install` into one RUN.
Why: several imported challenge Dockerfiles run
RUN apt-get -o Acquire::AllowInsecureRepositories=true update
RUN apt-get -y --allow-unauthenticated install -y ...
as two separate layers. The bundled apt-insecure.conf enables insecure/downgrade
repositories, and a second apt invocation in a later layer re-resolves against
whatever index the first one left behind — which on the EOL bullseye-security
mirror returns 404 for every package:
Err:1 http://deb.debian.org/debian-security bullseye-security/main amd64 libsystemd0
404 Not Found
Merging them into a single `update && install` layer (the pattern the working
gemastik18 challenges use) makes apt resolve once, from a fresh index.
Idempotent: a Dockerfile that already has the combined form is left untouched.
"""
import re
import sys
from pathlib import Path
SERVICES = Path("/opt/gemastik18-final/services")
UPDATE_RE = re.compile(
r"^RUN\s+apt-get\s+(?P<opts>(?:-o\s+\S+\s+)*)update\s*$"
)
INSTALL_RE = re.compile(r"^RUN\s+(?P<rest>apt-get\s+.*\binstall\b.*)$")
def fix(text: str) -> tuple[str, bool]:
lines = text.splitlines()
out: list[str] = []
i = 0
changed = False
while i < len(lines):
m = UPDATE_RE.match(lines[i])
if not m:
out.append(lines[i])
i += 1
continue
# look ahead: is the very next non-empty line an install?
j = i + 1
while j < len(lines) and not lines[j].strip():
j += 1
if j < len(lines):
mi = INSTALL_RE.match(lines[j])
if mi:
out.append(f"RUN apt-get {m.group('opts')}update && \\")
out.append(f" {mi.group('rest')}")
changed = True
i = j + 1
continue
out.append(lines[i])
i += 1
return "\n".join(out) + ("\n" if text.endswith("\n") else ""), changed
def main(argv):
names = argv[1:] or [p.name for p in sorted(SERVICES.iterdir())
if p.is_dir() and (p / "Dockerfile").exists()]
changed = []
for name in names:
p = SERVICES / name / "Dockerfile"
if not p.exists():
continue
text = p.read_text()
new, did = fix(text)
if did:
p.write_text(new)
changed.append(name)
print(f"{name}: merged apt update+install into one RUN")
print("rewritten:", ", ".join(changed) if changed else "(none)")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))