From c6d404ffde00af10dfe8c87dc045ce0d56f09f1a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 Aug 2026 23:26:18 -0700 Subject: [PATCH] AGENTS.md: External Exposure Policy (MANDATORY); comfyui_rocm seam retargeted Every Caddy-proxied service is now either behind the auth chain or explicitly internal-only, with the provider-first flip procedure codified. The comfyui GPU-access seam test now asserts the deployed contract (group_add keep-groups + udev MODE 0666 on kfd/renderD*) instead of the pre-fix 'video' group assumption. --- AGENTS.md | 28 ++++++++++++++++++++++++++++ tests/comfyui_rocm.bats | 37 ++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 52f4027..3b831b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -204,6 +204,34 @@ planning/dispatch. See [serverAppConfigs-docs/Architecture/Testing Policy.md](se are the auth mechanism. Headless sudo passes the password via the `FRANK_SUDO_PW` environment variable. The value never enters the repo. + +## External Exposure Policy (MANDATORY) + +Every service reverse-proxied through Caddy is either behind the auth chain +(Anubis :8923 → Authentik embedded outpost) or explicitly designated +internal-only — no public DNS name, no vhost. New services default to +internal-only until guarded. + +When guarding a new vhost, follow this order — provider first, then route: + +1. Create the outpost proxy provider FIRST (`external_host` = the exact vhost + URL including trailing slash; `internal_host = http://caddy:8017`). If the + vhost flips before the provider exists, authenticated traffic hits the + outpost error page instead of the app. +2. Flip the vhost to `reverse_proxy anubis:8923` with + `header_up X-Real-Ip {remote_host}`, and add the post-auth + `@ host ` handler on the `http://:8017` listener routing to the + real upstream. +3. The Anubis guard set, the Caddy TLS edge, and the outpost provider list + must stay in lockstep — they change together in one deploy. +4. Service-to-service traffic (e.g., Alloy → Loki push) targets internal + addresses (`loki:3100`), never the public hostname — non-browser clients + cannot solve challenges. + +Exempt-by-construction categories stay exempt with a documented reason in the +vhost comment: the IdP itself (`auth.` — its login flow must never sit behind a +challenge), git smart-HTTP endpoints (git clients cannot solve JS challenges), +and services with their own complete auth. ## Deletion & Trash Policy (MANDATORY) Never run `rm` to delete files. The command deletes permanently. Move every diff --git a/tests/comfyui_rocm.bats b/tests/comfyui_rocm.bats index 88a489e..913f2d1 100644 --- a/tests/comfyui_rocm.bats +++ b/tests/comfyui_rocm.bats @@ -5,13 +5,16 @@ # - build from a Containerfile based on the gfx1151 ROCm torch image # - pass /dev/kfd and /dev/dri to the container # - NOT pass --cpu -# - add the video and render groups +# - grant GPU access via world-writable compute nodes (udev MODE 0666), not +# named-group membership, because systemd user-unit spawns carry NO +# supplementary groups at spawn time # -# STATUS (2026-08-19): the GPU migration is applied. The compose builds +# STATUS (2026-08-31): the GPU migration is applied. The compose builds # comfyui-rocm:gfx1151 from Containerfile.comfyui (AMD's gfx1151 torch base), -# passes /dev/kfd + /dev/dri, adds the video group (render is unmappable in -# rootless), and drops --cpu. -# The four compose-wiring tests below are active (un-skipped). See +# passes /dev/kfd + /dev/dri, and relies on the udev rule at +# dankclaw/udev/99-comfyui-gpu-access.rules (MODE 0666 on kfd + renderD*) for +# compute-node access; group_add is keep-groups only. The four compose-wiring +# tests below are active (un-skipped). See # serverAppConfigs-docs/Research/ComfyUI ROCm and Vulkan.md. load helpers @@ -64,19 +67,23 @@ print("command OK:", cmd) PYEOF } -@test "comfyui adds the video group (render is unmappable in rootless)" { - python3 - "$REPO_ROOT/dankclaw/compose.yaml" <<'PYEOF' +@test "comfyui GPU access uses keep-groups + udev MODE 0666, not named groups" { + python3 - "$REPO_ROOT/dankclaw/compose.yaml" "$REPO_ROOT/dankclaw/udev/99-comfyui-gpu-access.rules" <<'PYEOF' import sys, yaml with open(sys.argv[1]) as fh: data = yaml.safe_load(fh) groups = data["services"]["comfyui"].get("group_add", []) -# 'video' is added (it resolves in the rootless namespace). 'render' (gid 990) -# is intentionally ABSENT: rootless podman cannot map it into the container -# namespace (the subgid range is 100000+), so --group-add render fails to start. -# GPU access comes from the /dev/kfd + /dev/dri passthrough, checked host-side -# (the run user is already a member of 'render' on the host). -assert "video" in groups, f"comfyui group_add must include video; got: {groups}" -assert "render" not in groups, f"comfyui group_add must NOT include render (unmappable in rootless); got: {groups}" -print("groups OK:", groups) +# User-unit spawns carry NO supplementary groups, so named group_add entries +# (video, render) can never be honored at spawn time. The deployed contract is +# keep-groups plus world-writable compute nodes via the udev rule; a future +# edit that drops either half must fail here. +assert list(groups) == ["keep-groups"], \ + f"comfyui group_add must be exactly ['keep-groups']; got: {groups}" +rule = open(sys.argv[2]).read() +assert 'KERNEL=="kfd", MODE="0666"' in rule, \ + "udev rule must grant MODE 0666 on kfd with NO SUBSYSTEM guard (misc-class devices have no subsystem to match)" +assert 'KERNEL=="renderD*", SUBSYSTEM=="drm", MODE="0666"' in rule, \ + "udev rule must grant MODE 0666 on renderD* (SUBSYSTEM drm guard)" +print("GPU access contract OK") PYEOF }