fix: port scheme 30000 (avoid syncthing 22000), reuse base images (no per-team rebuild), recover corrupted receiver/main.py, compose -p project isolation

- PORT_BASE 20000->30000: team1=31xxx team2=32xxx; syncthing owns 22000
- create_team replaces build: with image: services-<name> so teams reuse base images (was rebuilding 6 images per team, disk 100%)
- recovery: receiver/main.py was corrupted by bad patch (write_file with read_file format); restored from team1 copy + original GitHub
- docker compose -p teamN: project isolation so team compose doesn't overlap (was showing team1 containers for team2)
This commit is contained in:
root
2026-09-23 15:44:53 +08:00
parent 1ca963b2b7
commit 8c061ba1b7
2 changed files with 178 additions and 43 deletions
+21 -8
View File
@@ -15,9 +15,9 @@ Team N layout:
state.json (team metadata: ports, admin user/pass, ssh creds, created ts)
Port scheme (base offset per team index, index 1-based):
team i: chall ports = 20000 + i*1000 + 0..5 (blogpost,carbeat,cdn,phew,sheesh,warmup)
ssh ports = 20000 + i*1000 + 22..27 (10022-style)
receiver = 20000 + i*1000 + 80 (receiver API, e.g. 21080, 22080)
team i: chall ports = 30000 + i*1000 + 0..5 (blogpost,carbeat,cdn,phew,sheesh,warmup)
ssh ports = 30000 + i*1000 + 22..27 (10022-style)
receiver = 30000 + i*1000 + 80 (receiver API, e.g. 31080, 32080)
"""
import json
import os
@@ -45,7 +45,7 @@ CHALLENGES = [
("warmup", 5, 27),
]
PORT_BASE = 20000
PORT_BASE = 30000
STEP = 1000
def team_ports(idx: int) -> dict:
@@ -100,6 +100,19 @@ def create_team(idx: int, label: str = None):
"host.docker.internal:18080", f"host.docker.internal:{recv_port}"))
compose = svc_dir / "docker-compose.yml"
text = compose.read_text()
# --- replace build: blocks with image: so teams reuse the base images (no rebuild) ---
# Each service's build block looks like:
# build:
# context: <name>
# args:
# - PASSWORD=$PASSWORD_XXXXX
# Replace the whole block with " image: services-<name>".
for name, coff, soff in CHALLENGES:
text = re.sub(
rf" build:\n context: {name}\n args:\n - PASSWORD=\$PASSWORD_[0-9]+\n",
f" image: services-{name}\n",
text)
compose.write_text(text)
# rewrite container names + ports per challenge
for name, coff, soff in CHALLENGES:
cont_old = f"{name}_container"
@@ -167,7 +180,7 @@ def start_team(idx: int):
if not (team_dir / "state.json").exists():
raise FileNotFoundError(f"Team {idx} not created")
svc_dir = team_dir / "services"
subprocess.run(["docker", "compose", "-f", svc_dir / "docker-compose.yml", "up", "-d", "--build"],
subprocess.run(["docker", "compose", "-p", f"team{idx}", "-f", svc_dir / "docker-compose.yml", "up", "-d", "--build"],
cwd=str(svc_dir), check=False, capture_output=True)
_start_receiver(idx)
st = json.loads((team_dir / "state.json").read_text())
@@ -178,7 +191,7 @@ def start_team(idx: int):
def stop_team(idx: int):
team_dir = TEAMS_DIR / f"team{idx}"
svc_dir = team_dir / "services"
subprocess.run(["docker", "compose", "-f", svc_dir / "docker-compose.yml", "down"],
subprocess.run(["docker", "compose", "-p", f"team{idx}", "-f", svc_dir / "docker-compose.yml", "down"],
cwd=str(svc_dir), check=False, capture_output=True)
_stop_receiver(idx)
st = json.loads((team_dir / "state.json").read_text())
@@ -271,9 +284,9 @@ def randomize_flags(idx: int) -> dict:
# rotate into containers: compose mounts the flag files read-only, so
# drop+recreate the challenge containers to pick up new flags
svc_dir = team_dir / "services"
subprocess.run(["docker", "compose", "-f", svc_dir / "docker-compose.yml",
subprocess.run(["docker", "compose", "-p", f"team{idx}", "-f", svc_dir / "docker-compose.yml",
"down"], cwd=str(svc_dir), check=False, capture_output=True)
subprocess.run(["docker", "compose", "-f", svc_dir / "docker-compose.yml",
subprocess.run(["docker", "compose", "-p", f"team{idx}", "-f", svc_dir / "docker-compose.yml",
"up", "-d"], cwd=str(svc_dir), check=False, capture_output=True)
_start_receiver(idx)
st = json.loads((team_dir / "state.json").read_text())