ARIA 68ca43e5ac Caddy: Zertifikat wird automatisch im Container erzeugt statt manuell per openssl auf dem Host
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).
2026-07-19 22:11:49 +00:00

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 (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 <TOKEN>, 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-dashboard (0.0.0.0:9119, Hermes' eigenes Web-UI/Setup, per
                         Basic-Auth gesichert seit 19.07.2026 — Stefans
                         Entscheidung, siehe Sicherheitshinweis)

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 <TOKEN>-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 <-> <tool_call>-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):

    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:

    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):

    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. Dashboard-Auth einrichten (Pflicht seit 19.07.2026 — hermes-agent-dashboard laeuft mit --host 0.0.0.0 und Hermes verweigert diesen Bind hart, wenn kein Auth-Provider registriert ist: Refusing to bind dashboard to 0.0.0.0 — the auth gate engages on non-loopback binds, but no auth providers are registered. Ohne diesen Schritt kommt der Dashboard- Container beim naechsten Schritt gar nicht hoch):

    # HERMES_DASHBOARD_USER in .env ist Klartext, HERMES_DASHBOARD_PASSWORD_HASH
    # ist NIEMALS das Klartext-Passwort, sondern der Hash daraus. Erzeugen kannst
    # Du den erst NACHDEM Schritt 5 unten (docker compose up -d) mindestens
    # hermes-agent hochgefahren hat -- gegen "hermes-agent", NICHT
    # "hermes-agent-dashboard" (der braucht den Hash schon zum Start, Henne-Ei):
    docker exec -it hermes-agent python -c \
      "from plugins.dashboard_auth.basic import hash_password; print(hash_password('DEIN-PASSWORT'))"
    

    ACHTUNG DOLLARZEICHEN-FALLE: der Hash hat das Format pbkdf2:sha256:<n>$<salt>$<hash> — zwei rohe $-Zeichen drin. Docker Compose interpretiert $xyz in .env-Werten selbst als Variablen- Referenz und ersetzt nicht aufloesbare Teile stillschweigend durch Leerstring (sichtbar als WARN The "xyz" variable is not set beim docker-compose up). Ohne Escaping kommt im Container ein verstuemmelter Hash an und das Dashboard bleibt bei "no auth providers are registered" haengen, obwohl alles "richtig" eingetragen aussieht. Jedes $ im Hash durch $$ ersetzen, bevor Du ihn in .env eintraegst (z.B. 260000$abc$def260000$$abc$$def). Anfuehrungszeichen um den Wert helfen dabei NICHT — Compose expandiert $ in .env-Werten unabhaengig davon ob sie gequotet sind.

    Am einfachsten den Hash direkt per sed in der Zeile escapen, statt ihn von Hand nochmal abzutippen (verdoppelt NUR die $ in genau dieser einen Zeile):

    sed -i '/^HERMES_DASHBOARD_PASSWORD_HASH=/ s/\$/$$/g' .env
    

    Zur Kontrolle, ob noch unverdoppelte $ uebrig sind (sollte leer sein):

    grep -o '\$[^$]' .env
    

    Output (den escapeten Hash, nicht das Passwort) in .env bei HERMES_DASHBOARD_PASSWORD_HASH eintragen, HERMES_DASHBOARD_USER nach Belieben setzen (Default admin). Der dashboard.basic_auth-Block dazu liegt schon fertig in hermes-agent-config/config.yaml.example (Platzhalter, die Hermes selbst aus den Container-Env-Vars expandiert — kein manuelles Ausfuellen noetig, siehe Schritt 3).

    WICHTIG, wenn Du Schritt 3 schon VOR dem 19.07.2026 gemacht hast: Schritt 3 kopiert config.yaml.example nur EINMALIG nach hermes-data/agent-home/config.yaml — kein automatischer Re-Sync danach. Der dashboard:-Block wurde erst nachtraeglich in die .example-Datei ergaenzt, landet also NICHT von selbst in einer bereits existierenden config.yaml. Ohne ihn bleibt Hermes bei "no auth providers are registered" haengen, egal wie sauber .env escaped ist (weil Hermes den Block ja gar nicht sieht). Pruefen:

    grep -A3 '^dashboard:' hermes-data/agent-home/config.yaml
    

    Kommt nichts zurueck → Block manuell anhaengen:

    cat >> hermes-data/agent-home/config.yaml <<'EOF'
    
    dashboard:
      basic_auth:
        username: "${HERMES_DASHBOARD_USER}"
        password_hash: "${HERMES_DASHBOARD_PASSWORD_HASH}"
    EOF
    

    Danach neu hochziehen: docker compose up -d --build hermes-agent-dashboard

  5. TLS-Reverse-Proxy (Caddy) einrichten — macht Port 443 von aussen erreichbar, Dashboard bleibt intern auf 127.0.0.1:

    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:

    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 (EIGENER Hash, nicht derselbe wie bei Schritt 4 — Caddy will bcrypt, Hermes' Hash ist pbkdf2):

    docker run --rm caddy:2-alpine caddy hash-password --plaintext 'DEIN-PASSWORT'
    

    Output in .env bei CADDY_BASIC_AUTH_HASH eintragen, danach — gleiche Dollarzeichen-Falle wie bei Schritt 4 (bcrypt-Hashes haben auch rohe $-Zeichen drin) — escapen:

    sed -i '/^CADDY_BASIC_AUTH_HASH=/ s/\$/$$/g' .env
    

    HERMES_DASHBOARD_USER aus Schritt 4 wird wiederverwendet (dasselbe Login gilt fuer beide Auth-Schichten).

  6. Stack starten:

    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 (Gateway/Messaging-Prozess), hermes-agent-dashboard und caddy. Falls HERMES_DASHBOARD_PASSWORD_HASH oder CADDY_BASIC_AUTH_HASH noch leer sind: der jeweilige Service bricht beim Start ab, die anderen laufen trotzdem — Werte nachtragen, dann docker compose up -d hermes-agent-dashboard caddy.

  7. Einmalig Claude-CLI-Login (oeffnet Browser-OAuth mit deiner Claude-Max-Subscription):

    docker exec -it hermes-proxy claude
    

    Danach liegen die Credentials persistent in hermes-data/claude-config/ — ueberlebt Container-Restarts.

  8. Testen, dass der Proxy laeuft:

    curl -s http://127.0.0.1:${HERMES_GATEWAY_PORT:-8447}/v1/chat/completions \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer <HERMES_GATEWAY_TOKEN aus .env>" \
      -d '{"model":"sonnet","messages":[{"role":"user","content":"Sag Hallo"}]}'
    

    Ohne oder mit falschem Bearer-Token gibt's 401 unauthorized.

  9. Mit Hermes chatten:

    docker exec -it hermes-agent hermes chat
    

    oder das Dashboard im Browser oeffnen: https://<diese-maschine> (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 5b ab. Traffic ist ab hier durchgehend TLS-verschluesselt, kein SSH-Tunnel mehr noetig.

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).

hermes-agent-dashboard bindet seit dem 19.07.2026, spaeter Abend, wieder auf 127.0.0.1 — von aussen nicht mehr direkt erreichbar. Davor steht seitdem caddy (Service in docker-compose.yml, Config in Caddyfile): terminiert TLS auf Port 443 mit einem selbstsignierten 100-Jahre-Zertifikat (Setup-Schritt 5a) und erzwingt eine EIGENE Basic-Auth-Schicht (Setup-Schritt 5b, eigener bcrypt-Hash in CADDY_BASIC_AUTH_HASH — nicht derselbe wie Hermes' pbkdf2-Hash). Zwei Gruende fuer die doppelte Auth statt nur auf Hermes' eigene zu vertrauen:

  1. Hermes' Auth-Gate "engagiert" laut eigener Fehlermeldung nur bei einem 0.0.0.0-Bind — auf 127.0.0.1 (das Caddy anspricht) ist nicht garantiert, dass dashboard.basic_auth noch durchgesetzt wird.
  2. Selbst wenn doch: zwei unabhaengige Schichten sind ohnehin robuster als eine, falls in Zukunft mal eine davon falsch konfiguriert wird.

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 9). 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.

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 5a) — 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.

S
Description
No description provided
Readme
130 KiB
Languages
JavaScript 54.7%
Python 45.3%