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:
1 parent
06d3502a2b
commit
aaa1f82313
5 files changed
+83
-228
No files matched your search
@@ -1,147 +0,0 @@
|
||||
# Dankclaw stack — AI, search, development & observability services.
|
||||
# Deploy: cd /home/dank/serverAppConfigs && podman-compose -f dankclaw/compose.yaml up -d
|
||||
# See ../docs/Dankclaw/ and ../docs/Infrastructure/ for service documentation.
|
||||
|
||||
services:
|
||||
# --- SearXNG stack (gluetun + searxng + valkey) --------------------------
|
||||
# These three services are now boot-persistent Quadlet units, not compose
|
||||
# services. They live in searxng/quadlet/ and install to
|
||||
# ~/.config/containers/systemd/ on the host. searxng reuses gluetun's netns
|
||||
# (Network=gluetun.container) so its traffic exits the WireGuard tunnel.
|
||||
# Deploy with: scripts/deploy-searxng-quadlet.sh dankclaw
|
||||
# See ../docs/Dankclaw/SearXNG.md, Gluetun.md, Valkey.md.
|
||||
|
||||
# --- AI image generation (ROCm GPU backend, gfx1151) ---------------------
|
||||
# ComfyUI is a boot-persistent Quadlet unit since 2026-09-16:
|
||||
# comfyui/quadlet/comfyui.container, installed by deploy-remote.sh. It left
|
||||
# compose after the container it had started went stale — created before the
|
||||
# GPU declarations existed, then restarted by `restart: unless-stopped`
|
||||
# without /dev/kfd or /dev/dri until it had failed 9,146 times on
|
||||
# "No HIP GPUs are available". Built from Containerfile.comfyui (AMD's
|
||||
# gfx1151 base), models and data under /home/dank/comfyui-data.
|
||||
# See ../docs/Dankclaw/ComfyUI.md.
|
||||
|
||||
# --- Chat UI (open-webui) ------------------------------------------------
|
||||
# Now a boot-persistent Quadlet unit (quadlet, slirp4netns, port 3005), not
|
||||
# a compose service. See open-webui/quadlet/open-webui.container and
|
||||
# ../docs/Dankclaw/OpenWebUI.md.
|
||||
|
||||
hermes:
|
||||
image: docker.io/nousresearch/hermes-agent:latest
|
||||
container_name: hermes
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
# The image's default s6 entrypoint runs the interactive TUI, which exits
|
||||
# without a TTY and crash-loops the container. Override it to run the
|
||||
# headless gateway (the documented Docker mode). The gateway drops to the
|
||||
# unprivileged hermes user (uid 10000), so the data dir must be writable by it.
|
||||
entrypoint: ["bash", "-c", "hermes gateway run --force -v"]
|
||||
environment:
|
||||
HERMES_DATA_DIR: /data
|
||||
# Point hermes's config/.env at the mounted data dir (persistent).
|
||||
HERMES_HOME: /data
|
||||
# Signal adapter -> the signal-cli daemon, a HOST process listening on
|
||||
# :8080. The service runs with network_mode: host below, so it dials
|
||||
# loopback directly; from a bridge network only DNAT'd published ports
|
||||
# accept connections to the host LAN IP, and un-forwarded ports refuse
|
||||
# instantly (verified live 2026-08-30).
|
||||
SIGNAL_HTTP_URL: "http://127.0.0.1:8080"
|
||||
SIGNAL_ACCOUNT: "${SIGNAL_ACCOUNT}"
|
||||
SIGNAL_ALLOWED_USERS: "${SIGNAL_ALLOWED_USERS}"
|
||||
ports:
|
||||
- "8090:8090"
|
||||
volumes:
|
||||
- /home/dank/hermes-data:/data
|
||||
# No healthcheck: 'hermes gateway run' starts the messaging gateway (Signal
|
||||
# SSE), not the :8090 web dashboard, so there is no HTTP endpoint to probe.
|
||||
# The container's running state + the daemon's SSE connection are the signal.
|
||||
|
||||
# --- Hermes web dashboard (management UI: config, API keys, sessions) -----
|
||||
# A separate process from the Signal gateway (the gateway entrypoint does not
|
||||
# start the dashboard). Bound to the LAN with basic auth: the username + a
|
||||
# scrypt password_hash live in the git-ignored /home/dank/hermes-data config.
|
||||
# Port 9119 is published so a browser on the LAN can reach it.
|
||||
hermes-dashboard:
|
||||
image: docker.io/nousresearch/hermes-agent:latest
|
||||
container_name: hermes-dashboard
|
||||
restart: unless-stopped
|
||||
entrypoint: ["bash", "-c", "hermes dashboard --host 0.0.0.0 --port 9119 --no-open --skip-build"]
|
||||
environment:
|
||||
HERMES_DATA_DIR: /data
|
||||
HERMES_HOME: /data
|
||||
ports:
|
||||
- "9119:9119"
|
||||
volumes:
|
||||
- /home/dank/hermes-data:/data
|
||||
|
||||
# --- Hermes web UI (Hermex server; phone-facing API for the agent) --------
|
||||
# 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.
|
||||
# Loopback-only publish: the SSH tunnel (phone -> Dankclaw:22) is the sole
|
||||
# ingress. The password gates Hermex logins and lives in the host .env.
|
||||
hermes-webui:
|
||||
image: docker.io/nousresearch/hermes-agent:latest
|
||||
container_name: hermes-webui
|
||||
restart: unless-stopped
|
||||
entrypoint: ["bash", "-c", "python3 /webui/bootstrap.py --foreground"]
|
||||
environment:
|
||||
HERMES_HOME: /data
|
||||
# Container-internal bind: 0.0.0.0 so Podman's port forwarder (which
|
||||
# dials the container's bridge IP) can reach it. The *host-side*
|
||||
# publish below stays on 127.0.0.1, so loopback-only ingress holds.
|
||||
HERMES_WEBUI_HOST: 0.0.0.0
|
||||
HERMES_WEBUI_PORT: "8787"
|
||||
HERMES_WEBUI_PASSWORD: "${HERMES_WEBUI_PASSWORD}"
|
||||
# Headless host: never show the first-run wizard (a profile already
|
||||
# exists in /data from the gateway).
|
||||
HERMES_WEBUI_SKIP_ONBOARDING: "1"
|
||||
ports:
|
||||
- "127.0.0.1:8787:8787"
|
||||
volumes:
|
||||
- /home/dank/hermes-data:/data
|
||||
# rw on purpose: bootstrap.py creates /webui/.venv in the repo root.
|
||||
- /home/dank/hermes-webui:/webui
|
||||
healthcheck:
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/health')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
# First boot pip-installs WebUI + agent deps into a fresh venv.
|
||||
start_period: 600s
|
||||
|
||||
# --- Git hosting (Gitea) -------------------------------------------------
|
||||
# Now boot-persistent Quadlet units (private bridge network, DNS enabled),
|
||||
# not compose services. See gitea/quadlet/ and ../docs/Dankclaw/Gitea.md.
|
||||
|
||||
# --- Observability -------------------------------------------------------
|
||||
# Now boot-persistent Quadlet units, not compose services: prometheus,
|
||||
# grafana, loki, alloy and node-exporter install from
|
||||
# dankclaw/observability/quadlet/ and are dispatched by deploy_observability
|
||||
# in scripts/deploy-remote.sh. They left compose on 2026-09-11, after a host
|
||||
# reboot proved that compose-only containers do not come back on their own —
|
||||
# the same gap that had already cost the fleet Caddy and Pi-hole.
|
||||
#
|
||||
# The three HTTP UIs publish on 127.0.0.1 only. Off-host access is through
|
||||
# the guarded franksdank.cloud vhosts (Caddy reaches these containers by
|
||||
# name over this network) and the host's own homepage status timer probes
|
||||
# them over loopback. See
|
||||
# serverAppConfigs-docs/Infrastructure/Observability.md.
|
||||
|
||||
# --- Authentik (SSO / OIDC provider) -------------------------------------
|
||||
# Now boot-persistent Quadlet units, not compose services: authentik-
|
||||
# postgres -> authentik-server -> authentik-worker start in dependency order
|
||||
# via ~/.config/containers/systemd/*.container on the host (repo source:
|
||||
# dankclaw/authentik/quadlet/; deploy-remote.sh deploy_authentik installs
|
||||
# and starts them). Redis was removed in 2025.10 (sessions/caching moved to
|
||||
# PostgreSQL). The Caddyfile's forward_auth vhosts proxy to
|
||||
# authentik-server:9000 over the dankclaw_default network (A2A DNS).
|
||||
# See ../docs/Infrastructure/Authentik.md.
|
||||
|
||||
# Caddy (the homelab's single reverse proxy) left compose on 2026-08-26:
|
||||
# it is boot-persistent via its Quadlet unit (dankclaw/caddy/quadlet/
|
||||
# caddy.container), installed by deploy_caddy in scripts/deploy-remote.sh.
|
||||
# The rendered Caddyfile, certs, and config still live under
|
||||
# /home/dank/Containers/caddy (rootless-writable).
|
||||
@@ -14,9 +14,13 @@
|
||||
# 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 therefore listens on 0.0.0.0:8787 of the host netns (LAN-facing as
|
||||
# before); access control is the password gate, and the SSH tunnel
|
||||
# (phone -> Dankclaw:22) remains the sole off-network ingress path.
|
||||
# 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
|
||||
@@ -27,7 +31,7 @@ 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_HOST=127.0.0.1
|
||||
Environment=HERMES_WEBUI_PORT=8787
|
||||
Environment=HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD}
|
||||
Environment=HERMES_HOME=/data
|
||||
|
||||
Reference in new issue
Block a user