Files
serverAppConfigs/dankclaw/hermes/quadlet/hermes-webui.container
T
Frank aaa1f82313 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.
2026-09-16 07:22:20 -07:00

61 lines
2.7 KiB
INI

# Quadlet unit: Hermex webui (Hermex) container (rootless, boot-persistent).
#
# Install path on dankclaw: ~/.config/containers/systemd/hermes-webui.container
# Unit name: hermes-webui.service (container name: hermes-webui)
#
# Runs the open-source hermes-webui (github.com/nesquena/hermes-webui) in
# foreground mode: bootstrap.py creates .venv inside the mounted checkout,
# installs WebUI + agent deps, then execs server.py as a single foreground
# process. It reads HERMES_HOME=/data, so it drives the SAME agent state
# (memory, skills, sessions) as the gateway container.
#
# Host networking on purpose: the agent's chrome-devtools MCP client spawns
# npx chrome-devtools-mcp with --browserUrl=http://127.0.0.1:9222, and the CDP
# daemon binds loopback-only in the host network namespace (see
# chrome-devtools-protocol.container). Only processes sharing that namespace
# can drive the browser; a bridge-network client could not reach it at all.
# The server binds LOOPBACK (HERMES_WEBUI_HOST=127.0.0.1). Host networking
# means there is no Podman port forwarder, so the bind IS the exposure: the
# compose service this replaced published 127.0.0.1:8787:8787, and the first
# version of this unit bound 0.0.0.0 instead, which put the phone-facing API on
# the whole LAN with only the password in front of it (measured 2026-09-16:
# 192.168.0.143:8787 answered 200). The SSH tunnel (phone -> Dankclaw:22) dials
# localhost:8787 from the host, so loopback serves it exactly as well.
[Unit]
Description=Hermes webui (Hermex) container
[Container]
ContainerName=hermes-webui
Image=localhost/hermes-agent:latest
Pull=never
Network=host
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
Environment=HERMES_WEBUI_HOST=127.0.0.1
Environment=HERMES_WEBUI_PORT=8787
Environment=HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD}
Environment=HERMES_HOME=/data
# Headless host: never show the first-run wizard (a profile already
# exists in /data from the gateway).
Environment=HERMES_WEBUI_SKIP_ONBOARDING=1
Volume=/home/dank/Containers/hermes/data:/data
Volume=/home/dank/Containers/hermes/webui:/webui
Exec=python3 /webui/bootstrap.py --foreground
# JSON exec form on purpose: this quadlet generator joins a bare HealthCmd
# into ONE argv element with \x20 escapes and then shell-executes it — the
# python one-liner's quoting would be mangled. A JSON array is passed through
# verbatim. Start period covers first-boot pip installs into /webui/.venv.
HealthCmd=["python3","-c","import urllib.request;urllib.request.urlopen('http://127.0.0.1:8787/health')"]
HealthInterval=30s
HealthTimeout=10s
HealthRetries=5
HealthStartPeriod=600s
[Service]
Restart=always
RestartSec=5
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
[Install]
WantedBy=default.target