services: # ─── Claude Max API Proxy (fuer Hermes) ───────────────── # Gleicher Trick wie beim ARIA-Proxy (/root/ARIA-AGENT/docker-compose.yml, # Service "proxy"): das npm-Paket claude-max-api-proxy wrappt die Claude # Code CLI (angemeldet mit Claude-Max-Subscription) als OpenAI-kompatiblen # /v1/chat/completions-Endpoint. Die Sed-Patches unten sind identisch zu # ARIA — noetig damit: # - der Server auf 0.0.0.0 bindet (sonst nur localhost im Container) # - Claude ohne interaktive Permission-Prompts laeuft (--dangerously-skip-permissions, # zwingend fuer non-interactive Container-Betrieb als root) # - der System-Prompt komplett ersetzt wird (--system-prompt) statt nur # angehaengt — sonst gewinnt Claude Codes eigene "You are Claude Code"- # Identitaet bei duennem Kontext # - lange Agent-Loops nicht nach 5min timeouten (DEFAULT_TIMEOUT -> 24h) # - der Prompt via stdin statt CLI-Arg uebergeben wird (Arg-Laengenlimit/ # Escaping-Probleme bei langen Konversationen) # # Adapter-Patches (proxy-patches/*.js) sind 1:1 aus dem ARIA-Proxy # uebernommen — sie sind generisch (implementieren nur die # OpenAI-tools <-> -Text-Bridge, kein ARIA-Code drin): # - openai-to-cli.js: haengt `tools`-Definitionen als Anleitung in den # System-Prompt ("gib {json} aus") # - cli-to-openai.js: parsed genau dieses Format aus Claudes Antwort # wieder raus und baut daraus OpenAI-konforme tool_calls[] # # BEWUSST NICHT uebernommen: ARIAs routes.js-Patch. Der haengt zusaetzlich # an aria-bridge:8090 (Live-Tool-Stream + Not-Aus-Killswitch) und liest # /shared/config/models.json — beides ARIA-Bridge-spezifisch. Hier laeuft # die NPM-Stock-Version von routes.js (macht die Kern-Route trotzdem, # nur ohne die Bridge-Extras). Falls Hermes spaeter auch Live-Stream/ # Killswitch braucht: sag Bescheid, dann bauen wir das nach. hermes-proxy: image: node:22-alpine container_name: hermes-proxy command: >- sh -c "apk add --no-cache bash curl && npm install -g @anthropic-ai/claude-code claude-max-api-proxy && DIST=$$(find /usr/local/lib -path '*/claude-max-api-proxy/dist' -type d | head -1) && sed -i 's/startServer({ port })/startServer({ port, host: process.env.HOST || \"127.0.0.1\" })/' $$DIST/server/standalone.js && sed -i 's/\"--no-session-persistence\",/\"--no-session-persistence\",\"--dangerously-skip-permissions\",/' $$DIST/subprocess/manager.js && sed -i 's/\"--dangerously-skip-permissions\",/\"--dangerously-skip-permissions\",\"--system-prompt\",options.systemPrompt,/' $$DIST/subprocess/manager.js && sed -i 's/const DEFAULT_TIMEOUT = 300000;/const DEFAULT_TIMEOUT = 86400000;/' $$DIST/subprocess/manager.js && sed -i '/prompt, \\/\\/ Pass prompt as argument/d' $$DIST/subprocess/manager.js && sed -i 's|this\\.process\\.stdin?\\.end();|this.process.stdin?.end(prompt);|' $$DIST/subprocess/manager.js && cp /proxy-patches/openai-to-cli.js $$DIST/adapter/openai-to-cli.js && cp /proxy-patches/cli-to-openai.js $$DIST/adapter/cli-to-openai.js && claude-max-api" volumes: # WICHTIG: eigenes, von ARIA getrenntes Volume fuer die Claude-CLI-Session. # Einmalig `docker exec -it hermes-proxy claude` fuer den Browser-Login # mit der Claude-Max-Subscription (kann derselbe Account wie ARIA sein — # es ist ja dieselbe Subscription, nur eine eigene CLI-Session dafuer). - ./hermes-data/claude-config:/root/.claude # optional: nur noetig falls Hermes Dateien mit Dir austauschen soll - ./hermes-shared:/shared - ./proxy-patches:/proxy-patches:ro tmpfs: # wie bei ARIA: Claude Codes eigenes Auto-Memory nicht persistieren - /root/.claude/projects environment: - HOST=0.0.0.0 - SHELL=/bin/bash - CLAUDE_CODE_BUBBLEWRAP=1 # erlaubt --dangerously-skip-permissions als root restart: unless-stopped networks: - hermes-net # Bewusst KEIN ports:-Mapping nach aussen — genau wie bei ARIAs Proxy. # Nur Container im selben Docker-Netz (hier: hermes-gateway) sollen ihn # erreichen koennen. Das eigentliche Netzwerk-Exposing + Auth macht das # Gateway unten, damit hier niemals ein unauthentifizierter Endpoint # offen im Netz haengt. # Zum manuellen Testen direkt im Container: docker exec -it hermes-proxy curl -s localhost:3456/v1/chat/completions ... # ─── Hermes Gateway (Auth + Netzwerk-Expose) ─────────────── # Stefan deployt den eigentlichen Hermes-Agent-Client (Nous Research, # https://github.com/NousResearch/hermes-agent, CLI/Desktop-App) SELBST # auf einer ANDEREN Maschine — hier steht daher kein hermes-agent-Service, # nur die Proxy-Seite, die Hermes von aussen ansprechen kann. # # Weil "von aussen" heisst: der Proxy braucht einen echten Netzwerk-Port. # hermes-proxy selbst hat aber keine Authentifizierung eingebaut (siehe # Kommentar oben) — ein offener Port waere ein frei nutzbarer Claude-Max- # Zugang fuer jeden der ihn erreicht. Deshalb dieses schlanke Gateway # (proxy-patches/gateway.js, pure Node, keine Extra-Deps): prueft den # Authorization: Bearer Header, leitet nur bei Match an # hermes-proxy weiter. Alles andere -> 401. # # In Hermes' ~/.hermes/config.yaml traegst du ein: # model: # provider: custom # model: "sonnet" # oder "opus" / "haiku" — siehe MODEL_MAP # # in proxy-patches/openai-to-cli.js # base_url: "http://:${HERMES_GATEWAY_PORT:-8447}/v1" # api_key: "" # OpenAI-kompatible Clients schicken api_key automatisch als # "Authorization: Bearer " — genau das prueft das Gateway, kein # Extra-Schritt auf Hermes-Seite noetig. hermes-gateway: image: node:22-alpine container_name: hermes-gateway command: ["node", "/gateway/gateway.js"] depends_on: - hermes-proxy volumes: - ./proxy-patches:/gateway:ro environment: - TOKEN=${HERMES_GATEWAY_TOKEN:?HERMES_GATEWAY_TOKEN muss in .env gesetzt sein - siehe .env.example} - UPSTREAM_HOST=hermes-proxy - UPSTREAM_PORT=3456 - LISTEN_PORT=8080 ports: # Bindet auf ALLE Interfaces, damit Hermes von der anderen Maschine # rankommt. HERMES_GATEWAY_PORT in .env setzen (Default 8447). # WICHTIG (lies auch README "Sicherheitshinweis"): wenn diese Maschine # direkt im Internet haengt statt in einem vertrauenswuerdigen # LAN/VPN, den Port zusaetzlich per Firewall auf Hermes' IP # einschraenken oder per VPN/SSH-Tunnel statt direktem Expose fahren # — Bearer-Token schuetzt vor Fremdnutzung, aber der Traffic selbst # laeuft hier unverschluesselt (HTTP, kein TLS). - "${HERMES_GATEWAY_PORT:-8447}:8080" restart: unless-stopped networks: - hermes-net networks: hermes-net: driver: bridge