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.
209 lines
10 KiB
Markdown
209 lines
10 KiB
Markdown
# 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 <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):
|
|
```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. **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):
|
|
```bash
|
|
# 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$def` → `260000$$abc$$def`).
|
|
|
|
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).
|
|
|
|
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` (Gateway/Messaging-Prozess) und `hermes-agent-dashboard`.
|
|
Falls `HERMES_DASHBOARD_PASSWORD_HASH` noch leer ist (Schritt 4 noch nicht
|
|
gemacht): `hermes-agent-dashboard` bricht beim Start ab, die anderen drei
|
|
Services laufen trotzdem — Hash nachtragen, dann
|
|
`docker compose up -d hermes-agent-dashboard`.
|
|
|
|
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 <HERMES_GATEWAY_TOKEN aus .env>" \
|
|
-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 direkt im Browser oeffnen: `http://<diese-maschine>:9119`
|
|
— bindet auf `0.0.0.0`, kein SSH-Tunnel mehr noetig, dafuer fragt es jetzt
|
|
den Basic-Auth-Login aus Schritt 4 ab. Traffic ist weiterhin unverschluesseltes
|
|
HTTP (siehe Sicherheitshinweis unten) — fuer mehr als "kurz testen" gehoert
|
|
ein TLS-Reverse-Proxy davor.
|
|
|
|
## 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 19.07.2026 bewusst auf `0.0.0.0` (Stefans
|
|
Entscheidung — ein SSH-Tunnel machte mit dem SSL-Setup Probleme) und ist per
|
|
Basic-Auth gesichert (siehe Setup-Schritt 4, Hermes verweigert den 0.0.0.0-Bind
|
|
sonst ohnehin hart). Das ist Testzustand, kein Dauerbetrieb-Haerten: das
|
|
Passwort geht als HTTP Basic-Auth unverschluesselt raus (kein TLS auf Port
|
|
9119), Keys/Config/Sessions liegen dahinter offen. Fuer Dauerbetrieb gehoert
|
|
ein TLS-Reverse-Proxy (Caddy/nginx) davor, der Basic-Auth + Zertifikat
|
|
uebernimmt — Dashboard-Port dann wieder `127.0.0.1`, nur der Proxy von aussen
|
|
erreichbar.
|
|
|
|
Generell gilt: wenn Du `HERMES_GATEWAY_PORT`, `API_SERVER_HOST` oder den
|
|
Dashboard-Port doch mal direkt nach aussen exposest, ist der Traffic
|
|
unverschluesseltes HTTP, kein TLS. Nicht direkt ins offene Internet haengen —
|
|
SSH-Tunnel, WireGuard/Tailscale oder einen TLS-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`
|
|
- `.env` — Secrets (Token, ggf. API_SERVER_KEY)
|
|
|
|
## 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`.
|