feat(rank6): node-exporter renders from a template; guard chain is one snippet

- service-map render node-exporter-unit --host H: the MIRROR pair comment
  ('keep this file in step') becomes a template. Per-host title, LAN
  address from the hosts map, port from the node-<host> scrape job. Both
  tracked quadlets are now rendered artifacts; check_drift gates them.
- dankclaw/Caddyfile: the nine verbatim Anubis pre-auth chains
  (reverse_proxy anubis:8923 + X-Real-Ip) collapse into the (guarded)
  named snippet; guarded vhosts are 'import guarded'. Validated with real
  caddy 2.11.7 (import resolution clean; the stock image stops at the
  porkbun module and the git basicauth placeholder, as designed).
- vhost-scoped guard greps (authentik, homepage_caddy, marreta_quadlet)
  now pin 'import guarded' in the vhost block.
- Service Inventory + Authentik docs carry the new laws.
This commit is contained in:
Frank committed 2026-10-13 22:40:50 -07:00
1 parent 5976ad0550
commit 65bd5ccea7
11 files changed
+206 -78

No files matched your search

+1 -1
View File
@@ -122,7 +122,7 @@ UNIT_DIR="$REPO_ROOT/dankclaw/authentik/quadlet"
@test "the homepage vhost is www, guarded by Anubis" {
local block
block=$(awk '/^www\.franksdank\.cloud[[:space:]]*\{/,/^}/' "$CADDYFILE")
echo "$block" | grep -q 'reverse_proxy anubis:8923'
echo "$block" | grep -q 'import guarded'
}
@test "the naked domain redirects to www at the TLS edge" {
+11
View File
@@ -251,3 +251,14 @@ CADDYFILE="$REPO_ROOT/dankclaw/Caddyfile"
grep -q -- '--server.http.listen-addr=0.0.0.0:12345' "$unit"
grep -q '^PublishPort=127.0.0.1:12345:12345$' "$unit"
}
@test "the guard chain is a named snippet the guarded vhosts import" {
# Rank 6: the Anubis pre-auth chain (reverse_proxy anubis:8923 with the
# X-Real-Ip header_up) existed as nine verbatim copies. One snippet,
# per-vhost imports; a chain edit is a one-line change.
grep -q '^(guarded) {' "$CADDYFILE"
local imports
imports=$(grep -c $'^\timport guarded$' "$CADDYFILE")
[ "$imports" -ge 9 ]
[ "$(grep -c 'reverse_proxy anubis:8923' "$CADDYFILE")" -eq 1 ]
}
+1 -1
View File
@@ -51,7 +51,7 @@ setup() {
# domain never enters the chain (it redirects at the TLS edge first).
local block
block=$(awk '/^www\.franksdank\.cloud[[:space:]]*\{/,/^}/' "$CADDY")
echo "$block" | grep -q 'reverse_proxy anubis:8923'
echo "$block" | grep -q 'import guarded'
# forwards accepted traffic to; Host survives the hop, so per-vhost
# routing resumes there — guarded vhosts proxy the whole request
# through Authentik, which forwards authenticated traffic back on the
+2 -2
View File
@@ -112,11 +112,11 @@ GLUETUN="$REPO_ROOT/dankclaw/searxng/quadlet/gluetun.container"
# Public edge: the vhost must proxy to the challenge proxy, not straight to the app.
awk '
/^marreta\.franksdank\.cloud \{/ { inblk=1 }
inblk && /reverse_proxy anubis:8923/ { challenge=1 }
inblk && /import guarded/ { challenge=1 }
inblk && /^\}/ { inblk=0 }
END { exit challenge ? 0 : 1 }
' "$REPO_ROOT/dankclaw/Caddyfile" \
|| { echo "marreta vhost must reverse_proxy anubis:8923"; return 1; }
|| { echo "marreta vhost must import the guarded chain (the one named Anubis snippet)"; return 1; }
}
@test "the Caddyfile post-auth listener routes marreta to the published 8082" {
+13
View File
@@ -123,3 +123,16 @@ if problems:
sys.exit(1)
PYEOF
}
@test "inventory: the node-exporter unit is a per-host rendered template" {
# Rank 6: the two node-exporter quadlets were a MIRROR pair kept in
# step by a comment. The render is now the law on both hosts.
for host in dankclaw dankserver; do
run bash -c "cd '$REPO_ROOT' && python3 scripts/service-map.py render node-exporter-unit --host $host | \
diff -u $host/observability/quadlet/node-exporter.container -"
[ "$status" -eq 0 ] || { echo "$host unit drift"; return 1; }
done
# Hand-edited copies fail the drift gate:
grep -q "Keep this file in step" dankserver/observability/quadlet/node-exporter.container && return 1
true
}