# Hermes Agent — Claude-Max-Proxy + Hermes selbst, alles auf einer Maschine Repliziert den Proxy-Trick, den wir fuer ARIA nutzen: `claude-max-api-proxy` (npm) wrappt die Claude Code CLI — angemeldet mit deiner Claude-Max- Subscription — als OpenAI-kompatiblen `/v1/chat/completions`-Endpoint. [Hermes Agent](https://github.com/NousResearch/hermes-agent) (Nous Research, CLI/Desktop-Agent) redet darueber mit Claude, ohne API-Kosten — laeuft ueber die Subscription, nicht ueber Anthropic-API-Billing. **Entscheidung (Stefan):** Hermes Agent selbst laeuft auf DERSELBEN Maschine wie dieser Proxy-Stack — alles was zu Hermes gehoert auf eine Box, minimale Latenz, nur ein Rechner zum Pflegen. ARIA bleibt komplett unangetastet (eigenes Docker-Netz `hermes-net`, eigenes Claude-CLI-Login-Volume, kein gemeinsamer Container, laeuft ja ohnehin auf einer anderen Maschine als Hermes). ## Architektur ``` hermes-agent (Nous Research, network_mode: host) | HTTP, Authorization: Bearer , http://localhost:8447/v1 v hermes-gateway (Port 8447, NUR auf 127.0.0.1 gebunden) | prueft Bearer-Token, sonst 401 v hermes-proxy (Port 3456, NUR docker-intern im hermes-net erreichbar) | claude-max-api-proxy -> Claude Code CLI (Claude-Max-Login) v Anthropic (ueber deine Claude-Max-Subscription) 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 (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 unbemerkt Deine Claude-Max-Subscription mitbenutzen. Das Gateway (`proxy-patches/gateway.js`, pure Node, keine Extra-Deps) prueft den `Authorization: Bearer `-Header gegen ein Shared Secret, bevor irgendwas an `hermes-proxy` weitergeleitet wird. Seit "alles auf einer Maschine" bindet das Gateway nur noch auf `127.0.0.1`, nicht mehr nach aussen — kein anderer Rechner kommt mehr direkt ran. ## Was uebernommen wurde (1:1 aus ARIAs Proxy-Setup) - **Sed-Patches** an der npm-CLI-Bridge (host-binding, `--dangerously-skip-permissions`, echter `--system-prompt`-Kanal, 24h-Timeout, Prompt via stdin) — generische Infra-Fixes, nicht ARIA-spezifisch. - **Adapter-Patches** (`proxy-patches/openai-to-cli.js`, `cli-to-openai.js`) — die OpenAI-`tools` <-> ``-Text-Bruecke. Generisch, 1:1 uebernommen. - **NICHT** uebernommen: ARIAs `routes.js`-Patch (Live-Tool-Stream/Not-Aus fuers Diagnostic-UI, ARIA-Bridge-spezifisch). Stattdessen gibt's hier das eigene `gateway.js` fuer die Auth, die ARIA in der Form nicht braucht. ## Setup 1. **Hermes-Agent-Sourcecode daneben klonen** (das offizielle Docker-Image liegt nicht auf Docker Hub, sondern wird per `build:` aus dem Repo gebaut): ```bash cd /pfad/zum/deploy-ziel git clone https://github.com/NousResearch/hermes-agent.git hermes-agent-src ``` (dieser Clone ist in `.gitignore` — bleibt lokal, wandert nicht in unser Repo) 2. **`.env` anlegen:** ```bash cp .env.example .env openssl rand -hex 32 # Ausgabe in .env bei HERMES_GATEWAY_TOKEN eintragen ``` 3. **Model-Config fuer Hermes seeden** (einmalig, VOR dem ersten Start — danach fasst Hermes die Datei nicht mehr an): ```bash mkdir -p hermes-data/agent-home cp hermes-agent-config/config.yaml.example hermes-data/agent-home/config.yaml ``` Die Datei zeigt Hermes per `provider: custom` auf unseren `hermes-gateway` (`http://localhost:${HERMES_GATEWAY_PORT}/v1`) — `${HERMES_GATEWAY_PORT}`/`${HERMES_GATEWAY_TOKEN}` werden von Hermes selbst beim Config-Laden aus den Container-Env-Vars expandiert, die kommen aus Deiner `.env`. Kein manuelles Ausfuellen noetig. 4. **TLS-Reverse-Proxy (Caddy) einrichten — macht Port 443 von aussen erreichbar, Dashboard bleibt intern auf `127.0.0.1`:** Hermes' eigenes Dashboard-Auth-Gate (`dashboard.basic_auth` in `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 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). **Erzeugt sich automatisch** — kein manueller Schritt mehr noetig: der `caddy`-Container prueft beim Start, ob in `hermes-data/caddy-certs/` schon ein Zertifikat liegt, und wenn nicht, installiert er sich kurz `openssl` (per `apk`) und erzeugt es selbst (siehe `command:`-Block im `caddy`-Service in `docker-compose.yml`). Einmalig generiert, danach persistent im Volume — ueberlebt `docker compose up`/Neustarts, wird nicht bei jedem Start neu gebaut. Falls Du's doch manuell/mit eigenem `/CN` (z.B. Server-IP) erzeugen willst, geht das weiterhin von Hand — dann einfach VOR dem ersten Hochziehen selbst nach `hermes-data/caddy-certs/dashboard.{crt,key}` legen, der Auto-Generate-Check ueberspringt die Erzeugung dann: ```bash mkdir -p hermes-data/caddy-certs openssl req -x509 -nodes -newkey rsa:4096 \ -keyout hermes-data/caddy-certs/dashboard.key \ -out hermes-data/caddy-certs/dashboard.crt \ -days 36500 \ -subj "/CN=hermes-dashboard" ``` b. Caddy-Basic-Auth-Hash erzeugen (bcrypt, das einzige Auth-Secret das es jetzt noch braucht): ```bash docker run --rm caddy:2-alpine caddy hash-password --plaintext 'DEIN-PASSWORT' ``` Output in `.env` bei `CADDY_BASIC_AUTH_HASH` eintragen, danach — **Dollarzeichen-Falle** (bcrypt-Hashes haben rohe `$`-Zeichen drin, Docker Compose interpretiert `$xyz` in `.env`-Werten sonst als eigene Variablen-Referenz und ersetzt still durch Leerstring) — escapen: ```bash sed -i '/^CADDY_BASIC_AUTH_HASH=/ s/\$/$$/g' .env ``` `HERMES_DASHBOARD_USER` aus Schritt 2 (`.env`) wird als Login-Name wiederverwendet. 5. **Stack starten:** ```bash docker compose up -d ``` Baut beim ersten Mal das Hermes-Image aus `hermes-agent-src` (dauert ein paar Minuten), startet dann `hermes-proxy`, `hermes-gateway`, `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): ```bash docker exec -it hermes-proxy claude ``` Danach liegen die Credentials persistent in `hermes-data/claude-config/` — ueberlebt Container-Restarts. 7. **Testen, dass der Proxy laeuft:** ```bash curl -s http://127.0.0.1:${HERMES_GATEWAY_PORT:-8447}/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"model":"sonnet","messages":[{"role":"user","content":"Sag Hallo"}]}' ``` Ohne oder mit falschem Bearer-Token gibt's `401 unauthorized`. 8. **Mit Hermes chatten:** ```bash docker exec -it hermes-agent hermes chat ``` oder das Dashboard im Browser oeffnen: `https://` (Port 443, kein `:9119` mehr — der ist jetzt nur noch intern). Browser meldet beim ersten Aufruf "Zertifikat nicht vertrauenswuerdig" (weil selbstsigniert, nicht von einer oeffentlichen CA) — einmalig als Ausnahme akzeptieren ("Trotzdem fortfahren" o.ae.), danach fragt Caddy den Basic-Auth-Login aus Schritt 4b ab. Traffic ist ab hier durchgehend TLS-verschluesselt, kein SSH-Tunnel mehr noetig. ## Toolsets aktivieren (Spotify & Co.) **Neu, sonst fragt Hermes nie nach Zugangsdaten und antwortet stattdessen mit "ich hab keinen Zugriff auf Spotify/dein Handy/etc." — kein Bug, nur unkonfiguriert:** Hermes' Toolsets (Spotify, Kalender, Browser, …) sind bei einer frischen Installation ALLE deaktiviert, komplett Opt-in pro Plugin. Solange nichts eingeschaltet ist, schickt Hermes gar kein `tools`-Feld an unseren Proxy — Claude sieht ehrlich keine Tools und antwortet entsprechend ehrlich "kann ich nicht", obwohl der Proxy selbst (`openai-to-cli.js`) technisch laengst faehig ist, ``-Tools zu injizieren (genau derselbe Mechanismus wie bei ARIA). **Toolset einschalten** (interaktives TUI im Container): ```bash docker exec -it hermes-agent hermes tools ``` Zum gewuenschten Plugin runterscrollen (z.B. `🎵 Spotify`), mit Leertaste an, mit `s` speichern. **Login/OAuth fuers Toolset:** ```bash docker exec -it hermes-agent hermes auth spotify ``` Bei Spotify z.B. keine geteilte Drittanbieter-App — Hermes fuehrt Dich durch das Anlegen einer eigenen minimalen Spotify-Dev-App (nur Client-ID, kein Secret, PKCE-Flow) auf `developer.spotify.com/dashboard` inkl. der einzutragenden Redirect-URI (`http://127.0.0.1:43827/spotify/callback`). Der Login-Callback-Server laeuft dabei IM Container/auf der Remote-Maschine — fuer den Abschluss im lokalen Browser einmalig ein SSH-Port-Forward aufmachen: ```bash ssh -N -L 43827:127.0.0.1:43827 @ ``` **Kein SSH-Port-Forward moeglich?** Hermes validiert `redirect_uri` hart auf `127.0.0.1`/`localhost` (kein oeffentlicher Callback moeglich), aber der lokale Listener laesst sich umgehen: `scripts/spotify_manual_auth.py` baut dieselbe PKCE-Autorisierungs-URL, wartet aber nicht auf den Callback, sondern fragt Dich nach dem `code`-Parameter, den Du von Hand aus der (fehlschlagenden) Redirect-URL in der Adressleiste kopierst. Das Ergebnis landet 1:1 im selben Format in `~/.hermes/auth.json` wie beim normalen Login. ```bash docker cp scripts/spotify_manual_auth.py hermes-agent:/tmp/spotify_manual_auth.py docker exec -it hermes-agent python3 /tmp/spotify_manual_auth.py ``` Details/Ablauf stehen im Docstring des Skripts. Du kannst dabei dieselbe Spotify-Dev-App wie fuer ARIA wiederverwenden — dafuer im [Spotify-Dashboard](https://developer.spotify.com/dashboard) bei der bestehenden App unter *Settings → Redirect URIs* zusaetzlich `http://127.0.0.1:43827/spotify/callback` eintragen (kein neues Client-Secret noetig, PKCE braucht nur die Client-ID). Hinweis: Free-Spotify-Account reicht fuer Suche/Playlists/Library, fuer echte Playback-Steuerung (play/pause/skip auf einem Geraet) braucht's Spotify Premium. Gilt analog fuer jedes andere Toolset (Kalender, Browser, etc.) — immer zuerst `hermes tools` (einschalten), dann `hermes auth ` falls das Plugin OAuth/Login braucht. ### Toolset aktiviert, Claude sagt trotzdem "kenn ich nicht" / nennt eigene Skills/MCP-Server Root Cause (20.07.2026, im echten Code + per Live-Test verifiziert, nicht geraten): `hermes-proxy` laeuft mit einer **echten Claude-Code-CLI-Session** (npm-Paket `claude-max-api-proxy` wrappt `@anthropic-ai/claude-code` tatsaechlich als Subprocess). Diese Session hat ein eigenes Volume (`./hermes-data/claude-config`), kann aber auf **demselben Anthropic-Account** eingeloggt sein wie ARIA. Ein Account hat aber account-weite claude.ai- Connectors (z.B. Gmail/Calendar/Drive) und Claude Codes eigenes natives Skill-System — die werden in JEDER CLI-Session auf diesem Account automatisch mitgeladen, auch hier. Wenn man Claude dann explizit nach einem "Skill" fragt (z.B. "nutze Skill spotify_albums"), checkt Claude zuerst sein EIGENES, natives Skill-/MCP-System (findet dort nichts zu Spotify, dafuer Gmail/ Calendar/Drive) und ignoriert dabei die per Prompt injizierten ``-Tools (Hermes' eigentliche Plugins wie Spotify) komplett — es sind zwei parallele, konkurrierende Tool-Konzepte in derselben Session. **Fix:** `docker-compose.yml` startet die CLI seit `367ff64+` (Sed-Patch auf `manager.js`) zusaetzlich mit `--safe-mode` — das deaktiviert fuer diese Session ALLE Customizations (Skills, Plugins, MCP-Server, CLAUDE.md, ...), eingebaute Tools (Bash/Read/...) bleiben zwar technisch an, werden aber nicht gebraucht. Danach sieht Claude in der Hermes-Proxy-Session ausschliesslich die per System-Prompt injizierten externen Tools und sollte sie auch fuer "Skill"-artige Anfragen (Spotify etc.) nutzen. ```bash git pull docker-compose up -d --build hermes-proxy ``` Test: nach dem Neustart erneut nach einem aktivierten Toolset fragen (z.B. "spiel meine Playlist Fliegen auf dem Handy ab") — Claude sollte jetzt einen ``-Block ausgeben statt "kenn ich nicht" bzw. eigene Connectors zu nennen. Falls es weiter hakt: `docker logs hermes-proxy --tail 100 -f` waehrend der Reproduktion pruefen ob `--safe-mode` tatsaechlich im gestarteten Prozess ankam (`docker exec -it hermes-proxy ps aux | grep claude`). ### Verbindungstest ohne Chat/Claude (`spotify_test_connection.py`) Wenn Claude im Dashboard-Chat was sagt wie "Spotify-Tools sind zwar gelistet, aber nicht verbunden/aktiv" ist unklar OB das stimmt oder ob Claude nur raet — eine LLM-Textantwort ist kein verlaesslicher Diagnosewert. `scripts/spotify_test_connection.py` prueft stattdessen direkt in Hermes' eigenem Code (denselben Funktionen, die der echte Tool-Dispatch benutzt), read-only, ohne irgendwas zu veraendern: 1. Ist das Toolset `spotify` ueber `hermes tools` fuer irgendeine Plattform aktiviert? (`~/.hermes/config.yaml` -> `platform_toolsets`) 2. Ist Spotify eingeloggt? (`~/.hermes/auth.json` -> `providers.spotify`) 3. Funktioniert ein ECHTER API-Call mit dem gespeicherten Token (`GET /me/player/devices`, inkl. Auto-Refresh falls abgelaufen)? ```bash docker cp scripts/spotify_test_connection.py hermes-agent:/tmp/spotify_test_connection.py docker exec -it hermes-agent python3 /tmp/spotify_test_connection.py ``` Sind alle drei Punkte gruen und der Chat sagt trotzdem "kenn ich nicht" / "nicht verbunden", liegt's am Proxy bzw. am System-Prompt (siehe Abschnitt oben), nicht an Spotify selbst — dann `docker logs hermes-proxy` waehrend der Reproduktion pruefen. Gleiches Nutzungsmuster wie `scripts/spotify_manual_auth.py` (Login-Skript weiter oben). ## Mobiler Client (Android)? Zwei GRUNDVERSCHIEDENE Richtungen, nicht verwechseln: - **Unser Proxy oben** (`hermes-gateway` -> `hermes-proxy`): Claude ALS Backend FUER Hermes. Kein Client redet direkt damit ausser Hermes selbst. - **Hermes' EIGENER OpenAI-kompatibler API-Server** (`API_SERVER_HOST` / `API_SERVER_KEY`, auskommentiert im `hermes-agent`-Service): Hermes ALS Server FUER einen Chat-Client. Aktivieren + `API_SERVER_KEY` in `.env` setzen, dann kann ein Client wie **gpt_mobile** oder **Maid** (Open Source, Android, freier `base_url`+Key, kein Messenger — siehe Chat-Verlauf) sich direkt gegen Hermes verbinden. Fuer sowas wie die ARIA-App (Wake-Word, Sprachsteuerung, TTS) braeuchte man dagegen einen eigenen App-Build — kein fertiges Produkt zum Runterladen. ## Sicherheitshinweis `hermes-gateway` bindet nur auf `127.0.0.1` — von aussen kommt niemand mehr 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). 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 Port ist 443, durchgehend verschluesselt. Das Zertifikat ist selbstsigniert (keine oeffentliche CA dahinter) — Browser zeigen beim ersten Aufruf eine Warnung, die Du einmalig als Ausnahme akzeptierst (Details siehe Setup-Schritt 8). Wer eine "echte", warnungsfreie CA-Kette will, braucht dafuer eine oeffentliche Domain + Let's Encrypt — war hier bewusst nicht der Plan. Generell gilt: wenn Du `HERMES_GATEWAY_PORT`, `API_SERVER_HOST` oder den Dashboard-Port (`9119`) doch mal direkt nach aussen exposest (statt ueber Caddy), ist der Traffic unverschluesseltes HTTP, kein TLS. Nicht direkt ins offene Internet haengen — SSH-Tunnel, WireGuard/Tailscale oder eben den Caddy-Reverse-Proxy davorschalten. ## Troubleshooting **`{"detail":"Invalid Host header. Dashboard requests must use the hostname the server was bound to."}` beim Aufruf ueber Caddy:** Hermes' Dashboard- Backend prueft den eingehenden `Host`-Header gegen den Host, an den es gebunden ist (`127.0.0.1:9119`) — Schutz gegen DNS-Rebinding. Caddy reicht per Default den ORIGINALEN Host-Header vom Browser durch (Deine Server-IP/Domain), den lehnt Hermes dann ab. Fix ist im `Caddyfile` bereits drin: der `reverse_proxy`-Block ueberschreibt den Host-Header explizit (`header_up Host 127.0.0.1:9119`), bevor die Anfrage ans Dashboard geht. Falls der Fehler trotzdem auftritt: pruefen ob Dein `Caddyfile` den `header_up`-Block im `reverse_proxy 127.0.0.1:9119 { ... }` wirklich enthaelt (`git pull` + `docker-compose up -d --build caddy`, kein Neubuild noetig, 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 '`, 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: - 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 (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 ``` **Chat im Dashboard bricht dauerhaft mit "Chat connection interrupted (code 1006). Reconnecting..." + "events feed disconnected — tool calls may not appear" ab, obwohl die Seite selbst (Login, Session-History) normal laedt und `docker logs hermes-agent` waehrend des Reconnects RUHIG bleibt (kein Guard-Token, kein Fehler, gar nichts):** Root Cause im echten Hermes-Sourcecode gefunden (`hermes_cli/web_server.py`, Funktion `_ws_host_origin_reason`), nicht geraten: der Host-Header-Fix von weiter oben loest nur die **HTTP**-Seite. WebSocket-Upgrades (`/api/ws`, `/api/pub`, `/api/events` — genau die Endpunkte hinter Chat + Events-Feed) haben einen ZWEITEN, unabhaengigen Guard, der zusaetzlich den `Origin`-Header prueft. Der Browser schickt als Origin immer die echte aufgerufene Adresse (Server-IP/Domain), Hermes ist aber auf `127.0.0.1` gebunden und lehnt jeden Origin ab, der nicht `127.0.0.1`/`localhost`/`::1` ist -> `origin_mismatch` -> WS wird sofort mit Code 4403 geschlossen (Browser zeigt das oft generisch als 1006). Der Grund warum die Log-Suche vorher ins Leere lief: anders als `/api/console` und `/api/pty` loggen `/api/ws`, `/api/pub` und `/api/events` diese Ablehnung **nicht** — sie schliessen still. Gleicher Bug-Mechanismus wie der Host-Header-Fehler, nur eine Ebene tiefer und ohne jede Fehlermeldung. **Fix (bereits im `Caddyfile`):** Origin-Header komplett entfernen statt ihn umzuschreiben — fehlt der Header, ueberspringt Hermes den Origin-Check komplett: ``` reverse_proxy 127.0.0.1:9119 { header_up Host 127.0.0.1:9119 header_up -Origin } ``` Nach `git pull`: `docker-compose up -d --build caddy` (Config-Reload reicht, kein Neubuild von hermes-agent noetig). ## Verzeichnisse (nicht committet, siehe .gitignore) - `hermes-agent-src/` — geklonter Hermes-Agent-Sourcecode (Docker-Build-Context) - `hermes-data/claude-config/` — Claude-CLI-Login (Browser-OAuth-Session) - `hermes-data/agent-home/` — Hermes' `~/.hermes`-Aequivalent: Config (`config.yaml`, aus `hermes-agent-config/config.yaml.example` geseedet), Sessions, Memory, Skills, `.env` - `hermes-data/caddy-certs/` — selbstsigniertes 100-Jahre-Zertifikat (`dashboard.crt`/`dashboard.key`, Setup-Schritt 4a) — Private Key, niemals committen - `hermes-data/caddy-data/`, `hermes-data/caddy-config/` — Caddys eigener Runtime-State (nicht von Dir angefasst) - `.env` — Secrets (Token, ggf. API_SERVER_KEY, Basic-Auth-Hashes) ## Wo deployen? Diese Compose-Datei ist ortsunabhaengig. Stefan deployt sie selbst auf der Ziel-Maschine (nicht aria-wohnung — komplett getrennt von ARIA). Repo klonen, die drei Setup-Schritte oben (Hermes-Source klonen, `.env`, Config seeden), dann `docker compose up -d`.