hermes: the webui was LAN-facing, not loopback — and Dankclaw's compose is gone

Two things, found while checking whether the hermes trio still needed migrating.

1. **The phone-facing webui was exposed to the LAN.** Its unit runs with
   Network=host, so there is no Podman port forwarder and the app's own bind is
   the exposure. The compose service it replaced published
   `127.0.0.1:8787:8787` (loopback); the unit bound `HERMES_WEBUI_HOST=0.0.0.0`,
   and the LAN address answered 200 — password-gated, but on every interface,
   which is not the documented "SSH tunnel is the sole ingress" posture. It now
   binds 127.0.0.1, which serves the tunnel just as well (sshd dials
   localhost:8787 from the host). The unit comment claiming "LAN-facing as
   before" was wrong about "before", and is corrected.

2. **Dankclaw's compose.yaml is retired.** Auditing ownership showed every
   running container on that host reports a PODMAN_SYSTEMD_UNIT label — the
   hermes trio's units have owned their containers since 2026-09-12 — so the
   three service blocks left in compose.yaml were dead weight that could only
   create a duplicate container fighting a unit for a name. deploy_hermes was
   already fully unit-based. The file is staged in .AGENTTRASH/; the planner
   plans nothing for it, an unknown service on that target now errors instead of
   silently no-oping through `podman-compose`, and DANKCLAW_COMPOSE_SERVICES is
   empty.

The hermes-webui seam now reads the unit — it read compose, which is exactly how
the bind drifted while the test kept passing against a file nobody ran.
This commit is contained in:
Frank
2026-09-16 07:22:20 -07:00
parent 06d3502a2b
commit aaa1f82313
5 changed files with 83 additions and 228 deletions
+16 -19
View File
@@ -125,21 +125,12 @@ FRANKBOOK_SERVICES = {
"readarr",
}
DANKCLAW_COMPOSE_SERVICES = {
"comfyui",
"hermes",
"hermes-dashboard",
"hermes-webui",
"prometheus",
"grafana",
"loki",
"alloy",
"node-exporter",
"authentik-postgres",
"authentik-server",
"authentik-worker",
"caddy",
}
DANKCLAW_COMPOSE_SERVICES = set()
# Dankserver's compose file is still the source of truth for its media stack;
# Dankclaw's is gone (deleted 2026-09-16). Every service on Dankclaw is a
# Quadlet unit now — the last compose services were the hermes trio, whose units
# have owned their containers since 2026-09-12 and whose compose blocks were
# dead weight that could only create a duplicate container.
DANKSERVER_COMPOSE_SERVICES = {
"mariadb",
@@ -198,8 +189,14 @@ def _yaml_services(contents: str | None) -> dict[str, Any]:
def compose_changes(repo: str, target: str, base: str, head: str) -> set[str]:
"""Return only compose services whose YAML definitions or dependencies changed."""
"""Return only compose services whose YAML definitions or dependencies changed.
A target with no compose file (Dankclaw since 2026-09-16) plans nothing:
its services are Quadlet units, selected by their own paths.
"""
path = f"{target}/compose.yaml"
if not _git_show(repo, head, path) and not _git_show(repo, base, path):
return set()
old_doc = _yaml_document(_git_show(repo, base, path))
new_doc = _yaml_document(_git_show(repo, head, path))
old = old_doc.get("services", {}) if isinstance(old_doc.get("services", {}), dict) else {}
@@ -264,9 +261,9 @@ def services_for_path(path: str, target: str) -> set[str]:
if parts[0] == "dankclaw":
if len(parts) >= 2 and parts[1] == "compose.yaml":
# Revision-aware callers replace this broad fallback with the
# service-level YAML comparison in compose_changes().
_add(services, *DANKCLAW_COMPOSE_SERVICES)
# Dankclaw has no compose file since 2026-09-16: every service is a
# Quadlet unit, each selected by its own path. A change (or a
# deletion) of this path therefore restarts nothing.
return services
if len(parts) >= 2 and parts[1] == "Caddyfile":
_add(services, "caddy")