I tightened HERMES_WEBUI_HOST to 127.0.0.1 on the reasoning that the compose service had published loopback and the unit had widened it. That reasoning missed the consumer: this unit is host-networked (it shares the CDP daemon's loopback), so Caddy cannot reach it by container name and fronts the guarded vhost hermes.franksdank.cloud with `reverse_proxy 192.168.0.143:8787` — recorded in dankclaw/Caddyfile with the same explanation. With the app on loopback that upstream is dead, so the vhost broke. The wildcard bind is what serves BOTH consumers: Caddy on the LAN address and the phone's SSH tunnel on the host's localhost. Controls are the auth chain in front of the vhost plus the webui password. The unit comment now states this instead of the incorrect 'as before' claim, and the near-miss is worth the paragraph: the e2e gate stayed green because no spec covers that vhost.
64 lines
2.9 KiB
INI
64 lines
2.9 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 0.0.0.0:8787 of the host netns, and that is deliberate:
|
|
# this unit is host-networked (the CDP daemon above needs a shared loopback), so
|
|
# Caddy cannot reach it by container name and fronts the guarded vhost
|
|
# hermes.franksdank.cloud with `reverse_proxy 192.168.0.143:8787` (see the
|
|
# hermes block in dankclaw/Caddyfile, which records the same constraint). A
|
|
# loopback bind was tried on 2026-09-16 for a tighter posture and broke that
|
|
# vhost, because a bind is also the only way an off-host process can reach a
|
|
# host-networked container: the LAN address and the tunnel's localhost hop are
|
|
# both served by the wildcard, and a loopback-only bind serves only the latter.
|
|
# Controls are the auth chain in front of the vhost plus the password.
|
|
|
|
[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=0.0.0.0
|
|
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
|