From a2daca61ef75b9c4a9167f99bcb863ec46694df0 Mon Sep 17 00:00:00 2001 From: Kit OC5 Date: Wed, 23 Sep 2026 14:56:48 -0400 Subject: [PATCH] ci: mount windy-pro's read-only build inputs into dind; allow exactly that path for jobs Non-secret inputs git-ignored in windy-pro (models, linux-x64 portable bundle, enter-monitor build) that build-desktop needs. Mounted :ro into dind; valid_volumes allows only /ci-inputs/windy-pro; refresh-ci-inputs.sh copies them from the frozen release clone (read-only on the source). Invariant I-5 narrowed, not dropped: exactly that one path, read-only in dind, no other service mounts it, still no docker socket (proven to fail on :rw). Orchestrator-approved (option a). Applied in an idle window. Co-Authored-By: Claude Opus 5.5 --- api/tests/test_invariants.py | 25 ++++++++++++++++++++++--- deploy/runner/config.yaml | 5 ++++- deploy/runner/docker-compose.yml | 5 +++++ deploy/runner/refresh-ci-inputs.sh | 21 +++++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100755 deploy/runner/refresh-ci-inputs.sh 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"