Gateway+Dashboard in einen Container zusammenfassen (HERMES_DASHBOARD=1)

hermes-agent-dashboard als separater Container ist raus. Stattdessen
supervised s6 Gateway und Dashboard jetzt als Geschwister-Prozesse im
selben hermes-agent-Container (HERMES_DASHBOARD=1 + HERMES_DASHBOARD_HOST=
127.0.0.1) -- laut Startup-Log selbst "the recommended setup for the s6
container image". Das ist der wahrscheinliche Fix fuer zwei offene Bugs:
Dashboard zeigte Gateway-Status immer als "Stopped" (lokale Prozess-
Inspektion sah den Gateway-Prozess im getrennten Container nie) und der
Restart-Button warf "no such gateway 'default'" (loeste s6-Services nur im
eigenen Container auf). README + Architektur-Diagramm entsprechend
aktualisiert.
This commit is contained in:
ARIA
2026-07-19 23:03:00 +00:00
parent 804b5192fc
commit 367ff648e8
2 changed files with 120 additions and 103 deletions
+70 -48
View File
@@ -28,15 +28,32 @@ hermes-agent (Nous Research, network_mode: host)
v
Anthropic (ueber deine Claude-Max-Subscription)
hermes-agent-dashboard (127.0.0.1:9119, Hermes' eigenes Web-UI/Setup, nur
intern erreichbar)
hermes-agent (EIN Container, s6 supervisiert Gateway + Dashboard als
Geschwister-Prozesse via HERMES_DASHBOARD=1):
- Gateway-Prozess: Messaging-Bruecke, redet mit Claude wie oben
- Dashboard-Prozess: 127.0.0.1:9119, Hermes' eigenes Web-UI, nur intern
erreichbar
^
| reverse_proxy
| reverse_proxy (Dashboard-Port)
v
caddy (0.0.0.0:443, TLS + Basic-Auth — einzige Auth-/TLS-Schicht,
siehe Sicherheitshinweis)
```
**Wichtig, Kurskorrektur 20.07.2026:** Gateway und Dashboard liefen anfangs
in ZWEI getrennten Containern (`hermes-agent` + `hermes-agent-dashboard`,
jeweils dasselbe Image mit anderem `command:`). Das verursachte zwei reale
Bugs: Dashboard zeigte den Gateway-Status immer als "Stopped" (die Status-
Pruefung ist eine LOKALE Prozess-Inspektion im eigenen Container, nicht per
Netzwerk-Call — im getrennten Container sah sie den Gateway-Prozess nie),
und der "Restart Gateway"-Button im Dashboard scheiterte mit `no such
gateway 'default'` (der Button loest s6-Services im EIGENEN Container auf,
im Dashboard-Container gab's aber keinen). Der Startup-Log hatte den
Hinweis die ganze Zeit schon dabei: *"dashboard supervised alongside if
HERMES_DASHBOARD is set. This is the recommended setup for the s6 container
image."* Jetzt EIN `hermes-agent`-Container mit `HERMES_DASHBOARD=1` +
`HERMES_DASHBOARD_HOST=127.0.0.1` — kein separater Dashboard-Service mehr.
Warum das Gateway trotzdem noch da ist, obwohl alles auf einer Maschine
laeuft: `hermes-proxy` selbst hat keine Authentifizierung (genau wie ARIAs
Proxy) — jeder andere Container im selben Docker-Host koennte sonst
@@ -94,10 +111,11 @@ aussen — kein anderer Rechner kommt mehr direkt ran.
`config.yaml` + `HERMES_DASHBOARD_PASSWORD_HASH`) ist bewusst NICHT mehr
Teil des Setups (aufgeraeumt am 19.07.2026, spaeter Abend/20.07.2026) —
das Gate engagiert sich laut eigener Fehlermeldung ohnehin nur bei einem
`0.0.0.0`-Bind, und der Dashboard-Container bindet fest auf `127.0.0.1`
(siehe `command:` im `hermes-agent-dashboard`-Service). Die komplette
Absicherung nach aussen laeuft ausschliesslich ueber Caddy unten —
ein Auth-Layer statt zwei, weniger Setup-Schritte, weniger Verwirrung.
`0.0.0.0`-Bind, und das Dashboard bindet fest auf `127.0.0.1`
(`HERMES_DASHBOARD_HOST` im `hermes-agent`-Service, siehe
`docker-compose.yml`). Die komplette Absicherung nach aussen laeuft
ausschliesslich ueber Caddy unten — ein Auth-Layer statt zwei, weniger
Setup-Schritte, weniger Verwirrung.
a. Selbstsigniertes Zertifikat, 100 Jahre gueltig (bewusst kein Let's
Encrypt — kein Domain-/Port-80-Zwang, kein Renewal je wieder noetig).
@@ -143,9 +161,10 @@ aussen — kein anderer Rechner kommt mehr direkt ran.
```
Baut beim ersten Mal das Hermes-Image aus `hermes-agent-src` (dauert ein
paar Minuten), startet dann `hermes-proxy`, `hermes-gateway`,
`hermes-agent` (Gateway/Messaging-Prozess), `hermes-agent-dashboard` und
`caddy`. Falls `CADDY_BASIC_AUTH_HASH` noch leer ist: `caddy` bricht beim
Start ab, die anderen laufen trotzdem — Wert nachtragen (Schritt 4b), dann
`hermes-agent` (EIN Container, s6 supervisiert darin Gateway UND
Dashboard zusammen, siehe Architektur oben) und `caddy`. Falls
`CADDY_BASIC_AUTH_HASH` noch leer ist: `caddy` bricht beim Start ab, die
anderen laufen trotzdem — Wert nachtragen (Schritt 4b), dann
`docker compose up -d caddy`.
6. **Einmalig Claude-CLI-Login** (oeffnet Browser-OAuth mit deiner Claude-Max-Subscription):
@@ -197,17 +216,19 @@ Zwei GRUNDVERSCHIEDENE Richtungen, nicht verwechseln:
ran, solange Du nicht selbst was aendertst (z.B. fuer den mobilen API-Server
oben, oder falls Hermes doch mal auf eine zweite Maschine zieht).
`hermes-agent-dashboard` bindet fest auf `127.0.0.1` — von aussen nicht mehr
direkt erreichbar. Davor steht `caddy` (Service in `docker-compose.yml`,
Config in `Caddyfile`): terminiert TLS auf Port 443 mit einem selbstsignierten
100-Jahre-Zertifikat (Setup-Schritt 4a) und erzwingt eine Basic-Auth-Schicht
(Setup-Schritt 4b, bcrypt-Hash in `CADDY_BASIC_AUTH_HASH`). Caddy ist die
EINZIGE Auth-/TLS-Schicht — Hermes' eigenes Dashboard-Auth-Gate
(`dashboard.basic_auth`/`HERMES_DASHBOARD_PASSWORD_HASH`) wurde bewusst
komplett rausgenommen (aufgeraeumt 20.07.2026): es engagiert sich laut eigener
Fehlermeldung ohnehin nur bei einem 0.0.0.0-Bind, der Dashboard-Container
bindet aber fest auf 127.0.0.1 — ein zweiter, nie greifender Auth-Layer haette
nur Setup-Schritte und Verwirrung gekostet, ohne echten Sicherheitsgewinn.
Das Dashboard (s6-supervisierter Geschwister-Prozess im `hermes-agent`-
Container, `HERMES_DASHBOARD_HOST=127.0.0.1`) bindet fest auf `127.0.0.1` —
von aussen nicht mehr direkt erreichbar. Davor steht `caddy` (Service in
`docker-compose.yml`, Config in `Caddyfile`): terminiert TLS auf Port 443 mit
einem selbstsignierten 100-Jahre-Zertifikat (Setup-Schritt 4a) und erzwingt
eine Basic-Auth-Schicht (Setup-Schritt 4b, bcrypt-Hash in
`CADDY_BASIC_AUTH_HASH`). Caddy ist die EINZIGE Auth-/TLS-Schicht — Hermes'
eigenes Dashboard-Auth-Gate (`dashboard.basic_auth`/
`HERMES_DASHBOARD_PASSWORD_HASH`) wurde bewusst komplett rausgenommen
(aufgeraeumt 20.07.2026): es engagiert sich laut eigener Fehlermeldung
ohnehin nur bei einem 0.0.0.0-Bind, das Dashboard bindet aber fest auf
127.0.0.1 — ein zweiter, nie greifender Auth-Layer haette nur Setup-Schritte
und Verwirrung gekostet, ohne echten Sicherheitsgewinn.
Das war vorher (kurzzeitig, testweise) `0.0.0.0` OHNE TLS — Passwort ging als
Basic-Auth im Klartext raus. Ist jetzt behoben: einziger von aussen offene
@@ -242,38 +263,39 @@ Caddy liest die Config beim Start neu ein).
**"Restart Gateway"-Button im Dashboard zeigt
`✗ no such gateway 'default': register it with 'hermes profile create default'
first, or pass an existing profile name via '-p <name>'`:** Kein Bug bei uns,
sondern eine Architektur-Inkompatibilitaet zwischen unserem Deploy-Stil und
dem Dashboard-Button. Hintergrund (im Hermes-Sourcecode nachgesehen,
`hermes_cli/service_manager.py` + `hermes_cli/container_boot.py`):
first, or pass an existing profile name via '-p <name>'`, und/oder das
Dashboard zeigt "Gateway Status: Stopped" obwohl der Gateway laeuft:**
Root Cause war eine Architektur-Inkompatibilitaet: Gateway und Dashboard
liefen anfangs in ZWEI GETRENNTEN Containern (`hermes-agent` mit
`command: ["gateway", "run"]` + ein separater `hermes-agent-dashboard` mit
`command: ["dashboard", ...]`, beide aus demselben Image). Zwei Symptome,
eine Ursache:
- Unser `hermes-agent`-Service startet mit `command: ["gateway", "run"]`
direkt als Container-Kommando. Das Image erkennt dieses Muster als
"Legacy-Start" und supervised den Prozess ueber den **statischen**
s6-Service `main-hermes` (fest im Image, siehe
`/etc/s6-overlay/s6-rc.d/`).
- Der Dashboard-Restart-Button ruft dagegen immer `hermes gateway restart`
auf (ohne `-p` = Profil `default`). Dieser Befehl sucht einen **dynamisch
registrierten** s6-Service `gateway-default` unter `/run/service/` — der
entsteht aber nur durch `hermes profile create <name>` (Per-Profile-
Gateway-System). Bei uns wurde das nie ausgefuehrt, `main-hermes` und
`gateway-default` sind zwei komplett getrennte Dinge im selben Image.
- Ergebnis: der Button findet "sein" `gateway-default` nicht und wirft genau
diesen Fehler — der eigentliche Gateway-Prozess (`main-hermes`) laeuft
davon voellig unbeeindruckt normal weiter.
- Der Gateway-Status im Dashboard kommt aus einer **lokalen Prozess-
Inspektion** (PID-Check) im eigenen Container, NICHT aus einem Netzwerk-
Call — im getrennten Dashboard-Container sah dieser Check den
Gateway-Prozess nie, daher immer "Stopped".
- Der Restart-Button ruft `hermes gateway restart` auf, das s6-Services
**im eigenen Container** aufloest. Im Dashboard-Container gab's dafuer
keinen registrierten Service — daher `no such gateway 'default'`. Der
echte Gateway-Prozess lief davon unbeeindruckt weiter.
**Fix / Workaround:** Den Dashboard-Restart-Button bei uns einfach nicht
benutzen, sondern von aussen den Container neu starten:
**Fix (seit 20.07.2026, `docker-compose.yml`):** Gateway und Dashboard laufen
jetzt als s6-supervisierte Geschwister-Prozesse in EINEM `hermes-agent`-
Container (`HERMES_DASHBOARD=1` + `HERMES_DASHBOARD_HOST=127.0.0.1` im
`environment:`-Block) — genau der vom Image selbst empfohlene Weg, siehe
Startup-Log: *"dashboard supervised alongside if HERMES_DASHBOARD is set.
This is the recommended setup for the s6 container image."* Kein separater
`hermes-agent-dashboard`-Service mehr, kein `hermes profile create default`
noetig. Nach `git pull` + `docker-compose up -d --build` sollten Gateway-
Status UND Restart-Button im Dashboard den echten Zustand zeigen — bitte
nach dem Redeploy einmal ausprobieren.
Sollte der Restart-Button trotzdem noch spinnen: als Fallback von aussen
neu starten (bei s6-Auto-Restart selten noetig):
```bash
docker-compose restart hermes-agent
```
Das genuegt fast nie: `main-hermes` laeuft unter s6-Supervision mit
Auto-Restart bei Crash (siehe Startup-Log "gateway is now running under s6
supervision (auto-restart on crash...)"), ein manueller Restart ist im
Normalbetrieb also selten noetig. `hermes profile create default`
NICHT ausfuehren, um den Button zu "reparieren" — das wuerde einen zweiten,
parallelen Gateway-Mechanismus fuer denselben Port aufmachen und ist nicht
das, wofuer unser Single-Profile-Setup gebaut ist.
## Verzeichnisse (nicht committet, siehe .gitignore)