#!/usr/bin/env bash set -euo pipefail # ── Hermes Worker Provisioner ────────────────────────────── # Adds a new Paperclip Hermes worker to the ai compose stack. # # Usage: # ./provision-hermes-worker.sh # # Example: # ./provision-hermes-worker.sh worker-1 WORKER_1_DISCORD_BOT_TOKEN # # The script APPENDS only — never modifies or removes existing # content, even commented lines. # # Post-provision steps (manual): # 1. Add secrets to agenix .env file # 2. systemctl restart ai_stack.service # 3. Configure Paperclip agent # ───────────────────────────────────────────────────────────── NAME="${1:?Usage: $0 }" TOKEN_VAR="${2:?Usage: $0 }" # ── Paths ─────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" COMPOSE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" COMPOSE_FILE="${COMPOSE_DIR}/compose.yml" # Each Hermes worker gets its own volume on the NFS HoardingCow VOLUME_BASE="/mnt/HoardingCow_docker_data/Hermes" VOLUME_DIR="${VOLUME_BASE}/${NAME}" # The Hermes container runs as UID 10000 (hermes user from Dockerfile) HERMES_UID=10000 # ── Validation ────────────────────────────────────────────── if ! [ -f "$COMPOSE_FILE" ]; then echo "❌ compose.yml not found at $COMPOSE_FILE" exit 1 fi if grep -q "^ ${NAME}:" "$COMPOSE_FILE"; then echo "❌ Service '${NAME}' already exists in ${COMPOSE_FILE}" exit 1 fi # ── Generate unique API key ───────────────────────────────── # Used by Paperclip to authenticate against this worker's # Hermes API server (/v1/chat/completions) API_KEY="pc_worker_$(openssl rand -hex 16)" # ── Find next available API port ──────────────────────────── # Workers get sequential ports starting at 8650. # Scans compose.yml for existing API_SERVER_PORT values and # picks the next one. BASE_PORT=8650 MAX_PORT=0 while IFS= read -r line; do port="${line#*API_SERVER_PORT: \"}" port="${port%%\"*}" if [ -n "$port" ] && [ "$port" -gt "$MAX_PORT" ]; then MAX_PORT="$port" fi done < <(grep -oP 'API_SERVER_PORT:\s*"\d+"' "$COMPOSE_FILE" 2>/dev/null) NEW_PORT=$((MAX_PORT + 1)) if [ "$NEW_PORT" -lt "$BASE_PORT" ]; then NEW_PORT=$BASE_PORT fi # ── Create volume directory (on NFS) ──────────────────────── echo "📁 Creating volume directory: ${VOLUME_DIR}" mkdir -p "$VOLUME_DIR" # Hermes container runs as UID 10000 — set ownership so the # container can write its config, sessions, skills if command -v chown &>/dev/null; then chown -R "${HERMES_UID}:${HERMES_UID}" "$VOLUME_DIR" 2>/dev/null || \ echo "⚠ Could not chown ${VOLUME_DIR} — run with sudo if needed" fi # Make it group-readable for debugging chmod 755 "$VOLUME_DIR" 2>/dev/null || true # ── Append service to compose.yml ─────────────────────────── echo "📝 Appending service '${NAME}' to compose.yml ..." TMPFILE=$(mktemp) awk -v name="$NAME" \ -v port="$NEW_PORT" \ -v api_key="$API_KEY" \ -v token_var="$TOKEN_VAR" \ ' # Insert new worker service block just before the networks: section /^networks:/ { print "" print " " name ":" print " <<: *hermes-worker" print " container_name: " name print " environment:" print " API_SERVER_PORT: \"" port "\"" print " API_SERVER_KEY: \"" api_key "\"" print " DISCORD_BOT_TOKEN: ${" token_var "}" print " volumes:" print " - /mnt/HoardingCow_docker_data/Hermes/" name ":/opt/data" print "" } { print } ' "$COMPOSE_FILE" > "$TMPFILE" && mv "$TMPFILE" "$COMPOSE_FILE" # ── Done ──────────────────────────────────────────────────── echo "" echo "✅ Worker '${NAME}' provisioned successfully" echo "" echo "────────────────────────────────────────────" echo " NEXT STEPS" echo "────────────────────────────────────────────" echo "" echo "1. Add secrets to the agenix .env stack file:" echo "" echo " # ${NAME}" echo " ${TOKEN_VAR}=" echo "" echo "2. Restart the AI stack:" echo "" echo " systemctl restart ai_stack.service" echo "" echo "3. In Paperclip, create an agent with HTTP adapter:" echo "" echo " Endpoint: http://${NAME}:${NEW_PORT}/v1/chat/completions" echo " API Key: ${API_KEY}" echo "" echo "────────────────────────────────────────────"