From ddf678fb9c5681711a4a5c72dc4d43e10fe5b10a Mon Sep 17 00:00:00 2001 From: orfelorfel23 Date: Fri, 2 Oct 2026 05:10:10 +0200 Subject: [PATCH] feat: Add Hermes setup sub-script and in-container CLI installation Fixes Hermes CLI not found in PATH by installing hermes-agent inside paperclip_app container. Symlinks hermes executable to PATH, mounts persistent volumes for venv and hermes-data, configures shared API keys, and integrates setup script into deployment. --- README.md | 24 +++-- deploy_script.sh | 4 + services/hermes/service.init | 126 ++--------------------- services/hermes/setup_hermes.sh | 138 ++++++++++++++++++++++++++ services/paperclip/docker-compose.yml | 2 + services/paperclip/service.init | 13 ++- 6 files changed, 180 insertions(+), 127 deletions(-) create mode 100644 services/hermes/setup_hermes.sh diff --git a/README.md b/README.md index a281e04..4158cde 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,25 @@ Paperclip ist in seiner Benutzeroberfläche bei der Erstinstallation stark auf o ### Hermes in PaperClip einbinden -Nachdem der Service gestartet ist, kann PaperClip Hermes als Agenten einstellen: +Das Setup-Unterskript `services/hermes/setup_hermes.sh` wird automatisch beim Deployment ausgeführt und richtet die `hermes` CLI vollständig im PaperClip-Container ein (`pip install hermes-agent`). -1. Im PaperClip-Dashboard → **"Hire Agent"** -2. Adapter wählen: **`Hermes Gateway`** -3. Gateway-URL eintragen: `https://hermes.paperclip.orfel.de` -4. Falls nach einem API Key gefragt wird: `hermes_secret_paperclip_7a8f9b2c3d4e5f60` (definiert in `shared.env` als `HERMES_API_KEY`) -5. Hermes-Agent konfigurieren und Tasks zuweisen. +In PaperClip kann Hermes auf zwei Arten eingestellt werden: + +1. **`hermes_local` (Standard & empfohlen):** + * Im PaperClip-Dashboard → **"Hire Agent"** + * Adapter: **`hermes_local`** + * PaperClip ruft die CLI `hermes` direkt im Container auf. API Keys werden automatisch aus `services/shared.env` bereitgestellt. + +2. **`hermes_gateway` (Standalone):** + * Adapter: **`Hermes Gateway`** + * Gateway-URL: `http://hermes:8642` (intern) oder `https://hermes.paperclip.orfel.de` + +### Manuelles Setup + +Falls Hermes manuell neu eingerichtet werden soll: +```bash +sudo bash /opt/PaperClip/services/hermes/setup_hermes.sh +``` ## Weitere Adapter einbinden diff --git a/deploy_script.sh b/deploy_script.sh index daff84d..43c0f77 100644 --- a/deploy_script.sh +++ b/deploy_script.sh @@ -353,6 +353,10 @@ create_volumes() { for svc_name in "${ACTIVE_SERVICES[@]}"; do mkdir -p "${DATA_BASE}/${svc_name}" done + mkdir -p "${DATA_BASE}/paperclip-hermes-venv" + mkdir -p "${DATA_BASE}/paperclip-hermes-data" + # Node-User (UID 1000) benötigt Schreibrechte auf .hermes + chown -R 1000:1000 "${DATA_BASE}/paperclip-hermes-data" 2>/dev/null || true } ############################################################################### diff --git a/services/hermes/service.init b/services/hermes/service.init index 20a1755..8e72b56 100644 --- a/services/hermes/service.init +++ b/services/hermes/service.init @@ -1,128 +1,14 @@ #!/usr/bin/env bash ############################################################################### -# hermes service.init — läuft nach dem Start des Hermes-Containers -# Richtet Hermes vollständig ein: -# 1. Wartet, bis die API antwortet -# 2. Konfiguriert den Standard-Provider (OpenRouter) -# 3. Gibt Status und Dashboard-URL aus +# hermes service.init — aufgerufen durch deploy_script.sh (run_service_inits) +# Delegiert die vollständige Einrichtung an setup_hermes.sh ############################################################################### set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SHARED_ENV="${SCRIPT_DIR}/../shared.env" -CONTAINER="hermes" -API_PORT="8642" -DASHBOARD_PORT="9119" -API_BASE="http://127.0.0.1:${API_PORT}" +SETUP_SCRIPT="${SCRIPT_DIR}/setup_hermes.sh" -log() { echo -e "\e[32m[HERMES-INIT]\e[0m $*"; } -warn() { echo -e "\e[33m[HERMES-INIT][WARN]\e[0m $*"; } -err() { echo -e "\e[31m[HERMES-INIT][ERROR]\e[0m $*" >&2; } - -############################################################################### -# 1. Shared API Keys laden -############################################################################### -OPENROUTER_API_KEY="" -MISTRAL_API_KEY="" - -HERMES_API_KEY="" - -if [[ -f "${SHARED_ENV}" ]]; then - # shellcheck source=/dev/null - source "${SHARED_ENV}" +if [[ -f "${SETUP_SCRIPT}" ]]; then + chmod +x "${SETUP_SCRIPT}" + bash "${SETUP_SCRIPT}" fi - -if [[ -z "${OPENROUTER_API_KEY}" && -z "${MISTRAL_API_KEY}" ]]; then - warn "Kein API Key in shared.env gefunden. Hermes wird ohne Provider-Konfiguration gestartet." -fi - -############################################################################### -# 2. Auf Hermes-API warten -############################################################################### -log "Warte auf Hermes API (${API_BASE}/health)..." -for i in {1..45}; do - if docker exec "${CONTAINER}" curl -sf -H "Authorization: Bearer ${HERMES_API_KEY:-}" "${API_BASE}/health" >/dev/null 2>&1 || \ - docker exec "${CONTAINER}" curl -sf "${API_BASE}/health" >/dev/null 2>&1 || \ - docker exec "${CONTAINER}" wget -qO- --header="Authorization: Bearer ${HERMES_API_KEY:-}" "${API_BASE}/health" >/dev/null 2>&1 || \ - docker exec "${CONTAINER}" wget -qO- "${API_BASE}/health" >/dev/null 2>&1; then - log "Hermes API antwortet!" - break - fi - if [[ $i -eq 45 ]]; then - err "Hermes API hat nach 90s nicht geantwortet. Logs:" - docker logs "${CONTAINER}" --tail 20 >&2 - exit 1 - fi - sleep 2 -done - -############################################################################### -# 3. Provider konfigurieren (idempotent) -############################################################################### -configure_provider() { - local provider_name="$1" - local api_key="$2" - local base_url="$3" - local model="$4" - - log "Konfiguriere Provider: ${provider_name} (${base_url})..." - - # Hermes REST API: Provider setzen - # POST /api/config/provider — setzt den Standard-LLM-Provider - local response - response=$(docker exec "${CONTAINER}" wget -qO- \ - --header="Content-Type: application/json" \ - --post-data="{\"provider\":\"${provider_name}\",\"apiKey\":\"${api_key}\",\"baseUrl\":\"${base_url}\",\"model\":\"${model}\"}" \ - "${API_BASE}/api/config/provider" 2>/dev/null || echo "FAILED") - - if [[ "${response}" == "FAILED" ]]; then - # Fallback: hermes CLI direkt im Container - log "REST-API-Konfiguration fehlgeschlagen — versuche hermes CLI..." - (docker exec "${CONTAINER}" hermes config set \ - --provider "${provider_name}" \ - --api-key "${api_key}" \ - --base-url "${base_url}" \ - --model "${model}" 2>/dev/null || \ - warn "CLI-Konfiguration ebenfalls fehlgeschlagen. Manuelle Konfiguration über ${DASHBOARD_URL} nötig.") || true - else - log "Provider ${provider_name} erfolgreich konfiguriert." - fi -} - -# Provider wählen: OpenRouter hat Vorrang vor Mistral -if [[ -n "${OPENROUTER_API_KEY}" ]]; then - configure_provider \ - "openrouter" \ - "${OPENROUTER_API_KEY}" \ - "https://openrouter.ai/api/v1" \ - "openrouter/auto" -elif [[ -n "${MISTRAL_API_KEY}" ]]; then - configure_provider \ - "mistral" \ - "${MISTRAL_API_KEY}" \ - "https://api.mistral.ai/v1" \ - "mistral-large-latest" -else - warn "Kein Provider konfiguriert — Hermes läuft ohne LLM." -fi - -############################################################################### -# 4. Status & Dashboard-URL ausgeben -############################################################################### -DASHBOARD_URL="https://hermes.paperclip.orfel.de" - -log "=========================================================" -log " HERMES SETUP ABGESCHLOSSEN" -log "=========================================================" -log " Dashboard: ${DASHBOARD_URL}" -log " Interne API: http://hermes:${API_PORT}" -log " PaperClip Adapter-URL: http://hermes:${API_PORT}" -log "=========================================================" -log " In PaperClip einbinden:" -log " → Hire Agent → Adapter: hermes_gateway" -log " → Gateway URL: http://hermes:${API_PORT}" -log "=========================================================" - -echo "" -echo "Hermes Logs (letzte 10):" -docker logs "${CONTAINER}" --tail 10 2>&1 diff --git a/services/hermes/setup_hermes.sh b/services/hermes/setup_hermes.sh new file mode 100644 index 0000000..f124882 --- /dev/null +++ b/services/hermes/setup_hermes.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +############################################################################### +# PaperClip — Hermes Agent Setup Sub-Script +# Kann eigenständig ausgeführt oder vom Deploy-Skript aufgerufen werden. +# +# Aufgaben: +# 1. Richtet den Hermes CLI Client innerhalb des PaperClip-Containers ein, +# damit PaperClip via 'hermes_local' Adapter direkt Hermes-Agenten +# erstellen und ausführen kann (behebt 'Hermes CLI not found in PATH'). +# 2. Konfiguriert die Umgebungsvariablen und API-Keys (OpenRouter etc.) +# für den Node-User von PaperClip. +# 3. Überprüft die Installation mit 'hermes --version' und 'hermes config'. +# 4. Stellt sicher, dass das eigenständige Hermes-Dashboard bereit ist. +############################################################################### +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SERVICES_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +REPO_DIR="$(cd "${SERVICES_DIR}/.." && pwd)" +SHARED_ENV="${SERVICES_DIR}/shared.env" +PAPERCLIP_CONTAINER="paperclip_app" +HERMES_CONTAINER="hermes" + +log() { echo -e "\e[32m[HERMES-SETUP]\e[0m $*"; } +warn() { echo -e "\e[33m[HERMES-SETUP][WARN]\e[0m $*"; } +err() { echo -e "\e[31m[HERMES-SETUP][ERROR]\e[0m $*" >&2; } + +log "=========================================================" +log " Starte vollständige Hermes-Einrichtung für PaperClip..." +log "=========================================================" + +# ----------------------------------------------------------------------------- +# 1. Shared API Keys laden +# ----------------------------------------------------------------------------- +OPENROUTER_API_KEY="" +MISTRAL_API_KEY="" + +if [[ -f "${SHARED_ENV}" ]]; then + # shellcheck source=/dev/null + source "${SHARED_ENV}" +fi + +# ----------------------------------------------------------------------------- +# 2. PaperClip Container prüfen +# ----------------------------------------------------------------------------- +if ! docker ps --format '{{.Names}}' | grep -q "^${PAPERCLIP_CONTAINER}$"; then + warn "Container '${PAPERCLIP_CONTAINER}' läuft nicht. Überspringe In-Container-Setup." +else + log "1/3 Prüfe Hermes CLI im PaperClip-Container (${PAPERCLIP_CONTAINER})..." + + hermes_installed=false + if docker exec "${PAPERCLIP_CONTAINER}" which hermes >/dev/null 2>&1; then + if docker exec "${PAPERCLIP_CONTAINER}" hermes --version >/dev/null 2>&1; then + hermes_installed=true + fi + fi + + # Wenn der Venv-Mount existiert, aber nur der Symlink fehlt: + if [[ "${hermes_installed}" == "false" ]] && docker exec "${PAPERCLIP_CONTAINER}" test -x /opt/hermes-venv/bin/hermes; then + log "Existierende Hermes-Venv gefunden. Stelle Symlink her..." + docker exec -u root "${PAPERCLIP_CONTAINER}" ln -sf /opt/hermes-venv/bin/hermes /usr/local/bin/hermes + if docker exec "${PAPERCLIP_CONTAINER}" hermes --version >/dev/null 2>&1; then + hermes_installed=true + fi + fi + + # Vollständige Installation wenn noch nicht vorhanden + if [[ "${hermes_installed}" == "false" ]]; then + log "Hermes CLI nicht gefunden — installiere in ${PAPERCLIP_CONTAINER} (pip install hermes-agent)..." + + docker exec -u root "${PAPERCLIP_CONTAINER}" bash -c ' + set -e + apt-get update -qq + apt-get install -y -qq --no-install-recommends python3-pip python3-venv curl git + mkdir -p /opt/hermes-venv + python3 -m venv /opt/hermes-venv + /opt/hermes-venv/bin/pip install --no-cache-dir hermes-agent + ln -sf /opt/hermes-venv/bin/hermes /usr/local/bin/hermes + chmod -R a+rx /opt/hermes-venv + rm -rf /var/lib/apt/lists/* + ' + log "Hermes CLI erfolgreich in ${PAPERCLIP_CONTAINER} installiert!" + else + log "Hermes CLI ist bereits in ${PAPERCLIP_CONTAINER} installiert." + fi + + # ------------------------------------------------------------------------- + # 3. Hermes Konfiguration & Keys für PaperClip User (node) vorbereiten + # ------------------------------------------------------------------------- + log "2/3 Konfiguriere Hermes-Umgebung für PaperClip..." + + docker exec -u root "${PAPERCLIP_CONTAINER}" bash -c ' + mkdir -p /home/node/.hermes + chown -R node:node /home/node/.hermes + ' + + # API Keys in ~/.hermes/.env schreiben, damit hermes sie immer findet + if [[ -n "${OPENROUTER_API_KEY}" ]]; then + docker exec -u root "${PAPERCLIP_CONTAINER}" bash -c "cat > /home/node/.hermes/.env <&1 || true +fi + +# ----------------------------------------------------------------------------- +# 4. Standalone Hermes Container (Dashboard & Gateway) prüfen +# ----------------------------------------------------------------------------- +if docker ps --format '{{.Names}}' | grep -q "^${HERMES_CONTAINER}$"; then + log "3/3 Standalone Hermes Container (${HERMES_CONTAINER}) ist aktiv." + + # Sicherstellen, dass .env im Volume existiert + if [[ -n "${OPENROUTER_API_KEY}" ]]; then + docker exec -u root "${HERMES_CONTAINER}" bash -c "cat > /opt/data/.env </dev/null || true +chmod 600 /opt/data/.env +" + fi +else + log "3/3 Standalone Hermes Container (${HERMES_CONTAINER}) nicht aktiv (wird bei Bedarf gestartet)." +fi + +log "=========================================================" +log " HERMES VOLLSTÄNDIG EINGERICHTET!" +log "=========================================================" +log " In PaperClip kannst du nun direkt einen Hermes-Agenten anlegen:" +log " → Adapter: hermes_local (empfohlen, läuft lokal im Container)" +log " → Oder Adapter: hermes_gateway (http://hermes:8642)" +log " Dashboard: https://hermes.paperclip.orfel.de (falls hermes-Service aktiv)" +log "=========================================================" diff --git a/services/paperclip/docker-compose.yml b/services/paperclip/docker-compose.yml index bf3721a..b6ea222 100644 --- a/services/paperclip/docker-compose.yml +++ b/services/paperclip/docker-compose.yml @@ -38,6 +38,8 @@ services: PAPERCLIP_OPENCODE_PROVIDERS: '{"openrouter":{"npm":"@ai-sdk/openai-compatible","options":{"baseURL":"https://openrouter.ai/api/v1","apiKey":"{env:OPENROUTER_API_KEY}"},"models":{"anthropic/claude-sonnet-4-5":{},"anthropic/claude-3.5-sonnet":{},"google/gemini-2.5-pro":{},"deepseek/deepseek-r1":{},"openai/gpt-4o":{}}},"mistral":{"npm":"@ai-sdk/openai-compatible","options":{"apiKey":"{env:MISTRAL_API_KEY}"},"models":{"mistral/mistral-large":{}}},"minimax":{"npm":"@ai-sdk/openai-compatible","options":{"apiKey":"{env:MINIMAX_API_KEY}"},"models":{"minimax/abab6.5s":{}}},"poe":{"npm":"@ai-sdk/openai-compatible","options":{"apiKey":"{env:POE_API_KEY}"},"models":{"poe/claude-3-opus":{}}}}' volumes: - /opt/PaperClip-Data/paperclip:/paperclip + - /opt/PaperClip-Data/paperclip-hermes-venv:/opt/hermes-venv + - /opt/PaperClip-Data/paperclip-hermes-data:/home/node/.hermes depends_on: paperclip-postgres: condition: service_healthy diff --git a/services/paperclip/service.init b/services/paperclip/service.init index 7728eee..d968c04 100644 --- a/services/paperclip/service.init +++ b/services/paperclip/service.init @@ -74,7 +74,18 @@ else fi ############################################################################### -# 4. Diagnose-Logs +# 4. Hermes Agent Setup (falls Hermes-Service vorhanden) +############################################################################### +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +HERMES_SETUP="${SCRIPT_DIR}/../hermes/setup_hermes.sh" +if [[ -f "${HERMES_SETUP}" ]]; then + log "Führe Hermes-Setup für PaperClip aus..." + chmod +x "${HERMES_SETUP}" + bash "${HERMES_SETUP}" || warn "Hermes-Setup hatte Warnungen (nicht fatal)." +fi + +############################################################################### +# 5. Diagnose-Logs ############################################################################### log "=========================================================" log " DEBUG DIAGNOSTICS:"