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.
This commit is contained in:
Frank
2026-08-30 23:26:18 -07:00
parent a382a3b883
commit c6d404ffde
2 changed files with 50 additions and 15 deletions
+28
View File
@@ -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 are the auth mechanism. Headless sudo passes the password via the
`FRANK_SUDO_PW` environment variable. The value never enters the repo. `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
`@<svc> host <vhost>` 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) ## Deletion & Trash Policy (MANDATORY)
Never run `rm` to delete files. The command deletes permanently. Move every Never run `rm` to delete files. The command deletes permanently. Move every
+22 -15
View File
@@ -5,13 +5,16 @@
# - build from a Containerfile based on the gfx1151 ROCm torch image # - build from a Containerfile based on the gfx1151 ROCm torch image
# - pass /dev/kfd and /dev/dri to the container # - pass /dev/kfd and /dev/dri to the container
# - NOT pass --cpu # - 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), # comfyui-rocm:gfx1151 from Containerfile.comfyui (AMD's gfx1151 torch base),
# passes /dev/kfd + /dev/dri, adds the video group (render is unmappable in # passes /dev/kfd + /dev/dri, and relies on the udev rule at
# rootless), and drops --cpu. # dankclaw/udev/99-comfyui-gpu-access.rules (MODE 0666 on kfd + renderD*) for
# The four compose-wiring tests below are active (un-skipped). See # 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. # serverAppConfigs-docs/Research/ComfyUI ROCm and Vulkan.md.
load helpers load helpers
@@ -64,19 +67,23 @@ print("command OK:", cmd)
PYEOF PYEOF
} }
@test "comfyui adds the video group (render is unmappable in rootless)" { @test "comfyui GPU access uses keep-groups + udev MODE 0666, not named groups" {
python3 - "$REPO_ROOT/dankclaw/compose.yaml" <<'PYEOF' python3 - "$REPO_ROOT/dankclaw/compose.yaml" "$REPO_ROOT/dankclaw/udev/99-comfyui-gpu-access.rules" <<'PYEOF'
import sys, yaml import sys, yaml
with open(sys.argv[1]) as fh: with open(sys.argv[1]) as fh:
data = yaml.safe_load(fh) data = yaml.safe_load(fh)
groups = data["services"]["comfyui"].get("group_add", []) groups = data["services"]["comfyui"].get("group_add", [])
# 'video' is added (it resolves in the rootless namespace). 'render' (gid 990) # User-unit spawns carry NO supplementary groups, so named group_add entries
# is intentionally ABSENT: rootless podman cannot map it into the container # (video, render) can never be honored at spawn time. The deployed contract is
# namespace (the subgid range is 100000+), so --group-add render fails to start. # keep-groups plus world-writable compute nodes via the udev rule; a future
# GPU access comes from the /dev/kfd + /dev/dri passthrough, checked host-side # edit that drops either half must fail here.
# (the run user is already a member of 'render' on the host). assert list(groups) == ["keep-groups"], \
assert "video" in groups, f"comfyui group_add must include video; got: {groups}" f"comfyui group_add must be exactly ['keep-groups']; got: {groups}"
assert "render" not in groups, f"comfyui group_add must NOT include render (unmappable in rootless); got: {groups}" rule = open(sys.argv[2]).read()
print("groups OK:", groups) 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 PYEOF
} }