Files
serverAppConfigs/serverAppConfigs-docs/Dankclaw/Hermes-WebUI.md
T
Frank 2e179b024d docs(hermes): document namespace addressability and fix stale claims
New page Architecture/Container Network Namespaces.md: which local services are
addressable from which Podman network namespace on Dankclaw (rootless podman +
netavark). Verified live 2026-09-02: gateway/webui/CDP run in the host netns and
dial loopback; hermes-dashboard is the only Hermes container on a bridge network
(dankclaw_default, 10.89.1.0/24 gw 10.89.1.1) and can dial published ports only —
unpublished host-netns listeners (signal-cli :8080, webui :8787) are refused from
that namespace even though both bind 0.0.0.0.

Corrected while verifying the above:
- Hermes.md: data root is /home/dank/Containers/hermes/data (not the retired
  /home/dank/hermes-data); gateway dials the Signal daemon on loopback; the
  daemon itself is a host user unit (signal-cli-daemon.service) bound to
  0.0.0.0:8080, so the Signal HTTP API is LAN-reachable.
- Hermes-Dashboard.md: it is a Quadlet unit (Network=dankclaw_default), not a
  compose service; data root + /opt/data anonymous volume corrected.
- Hermes-WebUI.md: binds 0.0.0.0:8787 in the host netns (verified: unauthenticated
  GET /health from another LAN machine returned 200) — the SSH tunnel is an access
  path, not a containment boundary; HealthCmd and data-path corrections.
- Network Architecture.md: no hermes vhost exists in either the tracked or the live
  Caddyfile (hermes.franksdank.cloud resolves but routes nowhere); SearXNG exits the
  tunnel via the shared named network searxng.network, not service:gluetun.

Docs only — deploy is a no-op for this change.
2026-09-02 16:10:57 -07:00

7.2 KiB

type, title, description, tags, timestamp
type title description tags timestamp
configuration Hermes-WebUI (Hermex server) The hermes-webui container on Dankclaw — the API server that the Hermex iPhone app talks to, reachable over an SSH tunnel and, because it binds all interfaces, from the whole LAN.
hermes
hermes-webui
hermex
dankclaw
ssh-tunnel
2026-09-14

Hermes-WebUI (Hermex server)

hermes-webui is the hermes-webui server (MIT, third-party) that the Hermex iPhone app connects to. It runs the Hermes agent in-process from the shared HERMES_HOME, so it is the same agent — same memory, skills, and sessions — as the Hermes gateway and the Hermes-Dashboard.

Host: Dankclaw — Port: 0.0.0.0:8787 (all interfaces) — Ingress: SSH tunnel for the phone app; direct LAN access also works

Facts

Item Value
Image docker.io/nousresearch/hermes-agent:latest (same as the gateway)
Command python3 /webui/bootstrap.py --foreground
Source checkout /home/dank/Containers/hermes/webui on Dankclaw, bind-mounted at /webui
Port 8787, bound to 0.0.0.0 in the host network namespace (Network=host) — LAN-reachable, not loopback-only
Auth HERMES_WEBUI_PASSWORD (host .env; Hermex signs in with it)
Data bind mount /home/dank/Containers/hermes/data → /data, shared with the gateway and dashboard; anonymous volume at /opt/data
Health HealthCmd= in the Quadlet unit probes http://127.0.0.1:8787/health from inside the unit (works because the webui runs in the host netns); the deployer probes the same path from the host

Why a container, not a host process

The gateway's state files are owned by the container-mapped uid (109999, from rootless Podman's 1 → 100000/65536 subuid range). A fresh rootless namespace on Dankclaw maps identically (verified with podman unshare cat /proc/self/uid_map), so a second container using the same image reads and writes the shared volume with exactly the ownership the gateway already uses. A host-side process running as dank (uid 1000) could not read the gateway's 0600 state files.

How it works

  • bootstrap.py creates /webui/.venv on first boot, pip-installs the WebUI and agent requirements, then execs server.py as one foreground process (--foreground). systemd-free: Podman supervises it via restart: unless-stopped.
  • HERMES_WEBUI_SKIP_ONBOARDING=1 keeps the first-run wizard off; a profile already exists in /data from the gateway.
  • The healthcheck start_period is 600 s because the very first boot spends minutes pip-installing into a fresh venv.

Remote access (Hermex on iOS — persistent SSH tunnel)

Only port 22 is forwarded to Dankclaw at the router, so the phone reaches the webui through an SSH local forward — no extra router ports, no third-party relay (no Tailscale, no Cloudflare). Implemented 2026-08-31. The tunnel is an access path for the phone, not a containment boundary: the server binds 0.0.0.0:8787, so any host that can route to 192.168.0.143 can reach the API directly and needs only HERMES_WEBUI_PASSWORD to get in (verified 2026-09-02: an unauthenticated GET /health from another LAN machine returned HTTP 200). See Container Network Namespaces for why the webui is reachable this way and the sibling dashboard on 9119 is not.

  • Dedicated tunnel account on Dankclaw (/usr/sbin/nologin shell, password locked). Authentication is key-only; the account can do nothing except authenticate — ForceCommand /usr/sbin/nologin kills any session channel while the transport stays up for port forwarding.
  • sshd drop-in /etc/ssh/sshd_config.d/99-tunnel.conf scopes AllowTcpForwarding yes to that user only (plus PermitTTY no, X11Forwarding no).
  • Key options on the authorized key pin the blast radius: command="/usr/sbin/nologin",no-pty,restrict,port-forwarding,permitopen="localhost:8787" — a leaked tunnel key can only forward to localhost:8787, never act as a general SOCKS/SSH proxy.
  • On the phone (client side): create a key on-device (private key never leaves the device), hand the public key to the admin who appends it to /home/tunnel/.ssh/authorized_keys, then connect from the SSH client:
    • In-network target 192.168.0.143:22, off-network target the public hostname (router forwards only port 22 → Dankclaw) — same forward config otherwise.
    • Local forward 127.0.0.1:8787 → localhost:8787; Hermex server URL is then http://localhost:8787 in both locations.
    • For a persistent tunnel on iOS, Secure ShellFish's personal-VPN mode (Network Extension keeps the tunnel up and routes 127.0.0.0/8 through it); Termius works but only while its session is connected — iOS allows no background SSH daemon.
graph LR
    A[Hermex app] -->|http://localhost:8787| B[SSH local forward on iPhone<br/>persistent via personal VPN]
    B ==>|encrypted SSH to port 22| C[Dankclaw sshd<br/>tunnel user, forced nologin]
    C --> D["hermes-webui 0.0.0.0:8787 (host netns)"]
    D --> E["/home/dank/Containers/hermes/data (shared agent state)"]

Deployment

./scripts/deploy.sh dankclaw --service hermes-webui

deploy.sh copies dankclaw/hermes/quadlet/*.container into the quadlet unit directory and restarts hermes.service, hermes-dashboard.service and hermes-webui.service in the user manager of dank (linger must be on for them to come up at boot), so gateway/dashboard/webui restarts stay isolated from inference deploys. Before first deploy, set a strong HERMES_WEBUI_PASSWORD in the live dankclaw/.env.

Gotchas

  • The /webui mount is rw on purpose: bootstrap.py creates .venv in the repo root. Do not flip it to :ro.
  • The webui binds 0.0.0.0:8787 today, so the SSH tunnel is an access path for the phone, not a security boundary: any LAN host that can route to 192.168.0.143 reaches the API and needs only HERMES_WEBUI_PASSWORD. The webui can run agent commands, so treat that password as a credential with command-execution blast radius. To make loopback-only binding true again, set HERMES_WEBUI_HOST=127.0.0.1 in the unit and require the tunnel for every client — that is an exposure change to decide deliberately, not a silent fix.
  • The webui and the gateway share one data root (/data is a bind mount of /home/dank/Containers/hermes/data). A config change made on one side takes effect when the other restarts (same caveat as the dashboard).
  • bootstrap.py auto-installs a Hermes Agent if it cannot find one; in this image the agent is already present, so that path should never fire. If the log shows an install attempt, the image changed — re-verify the entrypoint.

Citations

See Also