diff --git a/api/tests/test_invariants.py b/api/tests/test_invariants.py index 87a865b..bd43263 100644 --- a/api/tests/test_invariants.py +++ b/api/tests/test_invariants.py @@ -425,9 +425,28 @@ def test_i05_jobs_get_a_network_per_job_not_a_shared_bridge(): def test_i05_jobs_cannot_bind_mount_from_the_daemon_host(): - cfg = (ROOT / "deploy" / "runner" / "config.yaml").read_text() - assert "valid_volumes: []" in cfg - assert 'docker_host: "-"' in cfg + """Narrowed 2026-09-23 (orchestrator-approved): a job may bind-mount EXACTLY + one daemon path, windy-pro's non-secret build inputs, and only because dind + itself has that path READ-ONLY. Anything more (a second path, a writable + one, a glob) reopens the host to CI code. Still no docker socket for jobs.""" + import re + + import yaml + + rd = ROOT / "deploy" / "runner" + cfg = yaml.safe_load((rd / "config.yaml").read_text()) + allowed = cfg["container"]["valid_volumes"] + assert allowed in ([], ["/ci-inputs/windy-pro"]), f"I-5: jobs may mount nothing else: {allowed}" + assert cfg["container"]["docker_host"] == "-" + if allowed: + compose = yaml.safe_load((rd / "docker-compose.yml").read_text()) + binds = [v for v in compose["services"]["dind"]["volumes"] if v.startswith("/")] + assert binds == ["/home/user1-gpu/ci-inputs/windy-pro:/ci-inputs/windy-pro:ro"], ( + f"I-5: dind's only host bind must be the ci-inputs path, READ-ONLY: {binds}") + for name, svc in compose["services"].items(): + if name != "dind": + for v in svc.get("volumes") or []: + assert not re.match(r"^/home/user1-gpu/ci-inputs", v), f"I-5: {name} mounts ci-inputs" def test_i05_no_ci_container_can_reach_the_forge_network(): diff --git a/deploy/runner/config.yaml b/deploy/runner/config.yaml index 490fda9..cea1bf6 100644 --- a/deploy/runner/config.yaml +++ b/deploy/runner/config.yaml @@ -54,6 +54,9 @@ container: privileged: false options: workdir_parent: /workspace - valid_volumes: [] # a job cannot bind-mount anything from the daemon host + # A job may bind-mount exactly ONE daemon path: the read-only windy-pro build + # inputs (mounted :ro into dind itself). Per-runner, not per-repo (act_runner + # limit): any windyadmin repo could mount it; it is non-secret and read-only. + valid_volumes: ["/ci-inputs/windy-pro"] docker_host: "-" # do NOT expose the runner's own docker socket to jobs force_pull: false diff --git a/deploy/runner/docker-compose.yml b/deploy/runner/docker-compose.yml index 01eaa63..eb874cd 100644 --- a/deploy/runner/docker-compose.yml +++ b/deploy/runner/docker-compose.yml @@ -49,6 +49,11 @@ services: # A fresh volume: Sysbox shifts ownership to its own uid range. The old # `dind-storage` is kept untouched for the privileged rollback. - dind-storage-sysbox:/var/lib/docker + # READ-ONLY, non-secret build inputs for windy-pro's desktop jobs (models, + # linux-x64 portable bundle, enter-monitor build), copied from the frozen + # release clone by deploy/runner/refresh-ci-inputs.sh. Jobs may mount ONLY + # this path (config.yaml valid_volumes). Orchestrator-approved 09-23. + - /home/user1-gpu/ci-inputs/windy-pro:/ci-inputs/windy-pro:ro # G1.5 — bounded so a fork-bomb workflow cannot starve Grant's interactive # session. Veron 1 is his workstation, not a dedicated build box. cpus: 12.0 # 12 of 24 cores diff --git a/deploy/runner/refresh-ci-inputs.sh b/deploy/runner/refresh-ci-inputs.sh new file mode 100755 index 0000000..99f4db1 --- /dev/null +++ b/deploy/runner/refresh-ci-inputs.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Refresh the READ-ONLY CI input cache for windy-pro desktop jobs from the FROZEN +# release clone on Veron. Reads ~/windy-pro-release only; never writes to it. +# Run when the release lane says engines / wheels / the portable bundle changed. +# Linux inputs only: 3 models + requirements-bundle.txt, bundled-portable/linux-x64, +# native/enter-monitor/build. Result is chmod a-w and mounted :ro into dind. +set -euo pipefail +SRC=/home/user1-gpu/windy-pro-release +DST=/home/user1-gpu/ci-inputs/windy-pro +models=$(ls "$SRC/extraResources/model" | grep -E '^windy-(nano|lite|core)-ct2$') +[ "$(wc -w <<<"$models")" = 3 ] || { echo "expected 3 models, got: $models"; exit 1; } +mkdir -p "$DST/extraResources/model" "$DST/bundled-portable" "$DST/native-enter-monitor-build" +chmod -R u+w "$DST" +R="ionice -c3 nice -n 19 rsync -a --delete" +for m in $models; do $R "$SRC/extraResources/model/$m/" "$DST/extraResources/model/$m/"; done +$R "$SRC/extraResources/requirements-bundle.txt" "$DST/extraResources/requirements-bundle.txt" +$R "$SRC/bundled-portable/linux-x64/" "$DST/bundled-portable/linux-x64/" +$R "$SRC/native/enter-monitor/build/" "$DST/native-enter-monitor-build/" +date -u +%FT%TZ > "$DST/.refreshed-from-windy-pro-release" +chmod -R a-w "$DST" +du -sh --apparent-size "$DST"