Root Cause im echten Hermes-Sourcecode (hermes_cli/web_server.py,
_ws_host_origin_reason) verifiziert, nicht geraten: WebSocket-Upgrades
(/api/ws, /api/pub, /api/events) haben einen zweiten Guard neben dem
Host-Header, der zusaetzlich den Origin-Header gegen den Bind (127.0.0.1)
prueft. Browser schickt die echte Server-Adresse als Origin -> origin_mismatch
-> WS wird mit 4403 (Browser: 1006) geschlossen, komplett ohne Logging
(anders als /api/console + /api/pty). Fix: Origin-Header in Caddy entfernen,
dann ueberspringt Hermes den Check ganz.
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.
Hermes' Dashboard-Backend prueft den eingehenden Host-Header gegen den
Host, an den es gebunden ist -- Caddy reichte bisher den originalen
Host-Header vom Browser (Server-IP/Domain) durch, was Hermes als
'Invalid Host header' ablehnte. header_up ueberschreibt den Host jetzt
explizit. Plus Troubleshooting-Eintrag in der README.
Hermes' eigenes Auth-Gate (dashboard.basic_auth/HERMES_DASHBOARD_PASSWORD_HASH)
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 -- das Gate haette hier
nie gegriffen. Statt es als "Fallback falls Bind mal wieder wandert" zu
behalten, jetzt sauber raus: HERMES_DASHBOARD_PASSWORD_HASH aus .env.example
und docker-compose.yml, der auskommentierte dashboard:-Block aus
config.yaml.example, README-Setup auf 8 statt 9 Schritte umnummeriert.
Caddy (TLS + eigene Basic-Auth) ist damit die einzige noetige Auth-Schicht --
ein Layer statt zwei, weniger Setup, weniger Verwirrung. Stale Kommentare im
Caddy-Compose-Block (redeten noch von "zweiter Basic-Auth-Schicht noetig weil
unklar ob dashboard.basic_auth greift") ebenfalls korrigiert.
Dashboard bindet seit dem Caddy-Umbau nur noch auf 127.0.0.1 -- Hermes'
Auth-Gate engagiert sich laut eigener Fehlermeldung NUR bei einem
0.0.0.0-Bind, greift also gar nicht mehr. HERMES_DASHBOARD_PASSWORD_HASH
ist damit optional statt Pflicht (kein docker-compose-Abbruch mehr bei
leerem Wert), der dashboard: basic_auth-Block in config.yaml.example ist
standardmaessig auskommentiert. Caddy (TLS + eigene Basic-Auth) bleibt
die einzige im Normalbetrieb noetige Auth-Schicht. README/.env.example
entsprechend entschlackt -- Hash-Erzeugen + Dollarzeichen-Escaping nur
noch als Fallback dokumentiert, falls der Bind mal wieder auf 0.0.0.0
wandert.
Entrypoint im caddy-Service prueft beim Start ob /certs/dashboard.{crt,key}
schon existiert und erzeugt es sonst selbst (apk add openssl + req -x509,
100 Jahre). Persistent im Volume, kein manueller Schritt mehr noetig.
Manuelle Erzeugung bleibt als Fallback in der README dokumentiert (z.B.
fuer eigenes /CN).
Stefans Entscheidung: 0.0.0.0-Bind mit unverschluesseltem Basic-Auth war nur
Testzustand. Jetzt bindet hermes-agent-dashboard wieder auf 127.0.0.1, ein
neuer Caddy-Service (network_mode: host) terminiert TLS auf 443 mit einem
manuell erzeugten selbstsignierten Zertifikat (100 Jahre Laufzeit, kein
Let's-Encrypt-Domain-Zwang) und erzwingt eine zweite, eigene Basic-Auth-Schicht
(bcrypt-Hash in CADDY_BASIC_AUTH_HASH, getrennt von Hermes' pbkdf2-Hash) -
noetig weil Hermes' eigenes Auth-Gate laut Fehlermeldung nur bei 0.0.0.0-Binds
"engagiert". README + .env.example um die Setup-Schritte (Cert-Erzeugung,
Caddy-Hash, $-Escaping-Falle) ergaenzt.
docker-compose interpretiert rohe $-Zeichen im pbkdf2-Hash (.env) selbst
als Variablen-Referenzen und ersetzt sie stillschweigend durch Leerstring
(sichtbar als "WARN variable is not set"). Ergebnis: verstümmelter Hash,
Dashboard bleibt trotz "richtig" eingetragener Werte bei "no auth
providers are registered" haengen. Fix: jedes $ im Hash durch $$ ersetzen.
README dokumentierte den seit 19.07.2026 pflichtigen Dashboard-Auth-Block
(HERMES_DASHBOARD_USER/PASSWORD_HASH, dashboard.basic_auth in config.yaml)
noch gar nicht - neuer Setup-Schritt 4, Schritt 7/8 umnummeriert, Architektur-
Diagramm und Sicherheitshinweis auf den 0.0.0.0-Dashboard-Stand gebracht.
Nebenbei echten Bug gefunden: .env.example und config.yaml.example sagten,
den Passwort-Hash per 'docker exec hermes-agent-dashboard' zu erzeugen -
aber genau dieser Container startet ohne bereits gesetztes
HERMES_DASHBOARD_PASSWORD_HASH gar nicht (docker-compose :?-Pflichtvar).
Hash muss gegen 'hermes-agent' erzeugt werden, der teilt sich das Image
ohne die Pflicht-Var.
Stefan-Entscheidung: Hermes Agent (Nous Research) laeuft auf derselben
Maschine wie der Claude-Max-Proxy, nicht mehr remote. Ergaenzt:
- hermes-agent + hermes-agent-dashboard Services (offizielle Definition,
gebaut aus separat geklontem hermes-agent-src/), network_mode: host
- hermes-agent-config/config.yaml.example: seedet Hermes' model:-Block
automatisch auf provider: custom -> unser hermes-gateway, per ${VAR}-
Expansion aus .env (kein manuelles Config-Editieren mehr)
- hermes-gateway bindet jetzt nur noch auf 127.0.0.1 statt 0.0.0.0, da
Hermes lokal mitlaeuft und kein Remote-Zugriff mehr noetig ist
- .gitignore fuer hermes-agent-src/, hermes-data/, .env (Secrets/Sourcecode
gehoeren nicht ins Repo)
- README auf Single-Machine-Architektur aktualisiert, inkl. Setup-Schritten
und Einordnung des optionalen API_SERVER (fuer spaeteren mobilen Client,
andere Richtung als unser Proxy)