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.
This commit is contained in:
1 parent
92c7442df7
commit
ddf678fb9c
6 files changed
+180
-127
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
|
||||
@@ -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
|
||||
@@ -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 <<EOF
|
||||
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||
EOF
|
||||
chown node:node /home/node/.hermes/.env
|
||||
chmod 600 /home/node/.hermes/.env
|
||||
"
|
||||
fi
|
||||
|
||||
# Verifikation im Container
|
||||
log "Verifiziere Hermes im PaperClip-Container:"
|
||||
docker exec -u node "${PAPERCLIP_CONTAINER}" hermes --version 2>&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 <<EOF
|
||||
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||
EOF
|
||||
chown -R hermes:hermes /opt/data/.env 2>/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 "========================================================="
|
||||
@@ -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
|
||||
|
||||
@@ -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:"
|
||||
|
||||
Reference in new issue
Block a user