#cloud-config # Hermes Agent Bootstrap (Nous Research) # Update packages package_update: true package_upgrade: true # Install required packages packages: - curl - git - jq - gnupg - ca-certificates - software-properties-common %{ if docker_enabled ~} # Docker-specific packages %{ else ~} # Direct installation packages - python3 - python3-pip - python3-venv - build-essential - libffi-dev - libssl-dev %{ endif ~} # Create admin user (if different from root) users: - name: ${admin_user} sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh_authorized_keys: ${jsonencode(admin_ssh_keys)} groups: [sudo, systemd-journal] # Write system configuration files write_files: # Hermes environment file - path: /home/${admin_user}/.hermes/.env content: | # Hermes Agent Configuration - Generated by Terraform # Inference API (Venice AI via OpenAI-compatible endpoint) # Venice API uses OPENAI_API_KEY + OPENAI_BASE_URL for custom endpoints OPENAI_API_KEY=${venice_api_key} OPENAI_BASE_URL=${venice_base_url} # Discord Bot %{if discord_bot_token != ""} DISCORD_BOT_TOKEN=${discord_bot_token} %{endif} %{if discord_home_channel != ""} DISCORD_HOME_CHANNEL=${discord_home_channel} %{endif} %{if discord_allowed_users != ""} DISCORD_ALLOWED_USERS=${discord_allowed_users} %{endif} # Brave Search %{if brave_search_api_key != ""} BRAVE_API_KEY=${brave_search_api_key} %{endif} # Gateway Token HERMES_GATEWAY_TOKEN=${gateway_token} # Authorization %{if gateway_allowed_users != ""} GATEWAY_ALLOWED_USERS=${gateway_allowed_users} %{endif} %{if gateway_allow_all_users} GATEWAY_ALLOW_ALL_USERS=true %{endif} permissions: '0600' # Hermes config.yaml - path: /home/${admin_user}/.hermes/config.yaml content: | # Hermes Agent Configuration # Framework: Nous Research Hermes Agent # Venice AI via OpenAI-compatible endpoint model: base_url: ${venice_base_url} model: ${primary_model} auth: mode: allowlist %{if discord_bot_token != ""} channels: discord: enabled: true auto_thread: ${discord_auto_thread} %{if discord_server_id != ""} guilds: "${discord_server_id}": require_mention: false %{if length(discord_user_id) > 0} users: %{ for id in discord_user_id ~} - "${id}" %{ endfor ~} %{endif} %{endif} %{endif} # Configure auxiliary tasks to use Venice AI explicitly # This avoids "no auxiliary provider" warning auxiliary: compression: base_url: ${venice_base_url} api_key: ${venice_api_key} model: ${primary_model} approvals: mode: smart gateway: port: 18789 bind: "0.0.0.0" permissions: '0644' # SOUL.md - Agent personality - path: /home/${admin_user}/.hermes/SOUL.md content: | # SOUL.md - ${agent_name} You are ${agent_name}, an AI agent running on the Hermes Agent framework from Nous Research. ## Identity **Name:** ${agent_name} **Framework:** Hermes Agent (Nous Research) **Model:** ${primary_model_name} ## Behavior - Be helpful and direct - Explain your reasoning clearly - Ask for clarification when needed - Follow security guardrails ## Notes - Running on ${server_name} - Provider: Hetzner Cloud - Location: ${location} permissions: '0644' %{ if docker_enabled ~} # Docker Compose for Hermes (Docker mode only) - path: /home/${admin_user}/docker-compose.yml content: | services: hermes: image: nousresearch/hermes-agent:latest container_name: ${agent_name} restart: unless-stopped command: gateway run volumes: - /home/${admin_user}/.hermes:/opt/data ports: - "18789:18789" env_file: - /home/${admin_user}/.hermes/.env deploy: resources: limits: memory: 4G cpus: "2.0" permissions: '0644' %{ endif ~} # Systemd service for Hermes - path: /etc/systemd/system/hermes.service content: | [Unit] Description=Hermes Agent Service %{ if docker_enabled ~} After=docker.service Requires=docker.service %{ else ~} After=network.target Wants=network-online.target %{ endif ~} [Service] Type=simple WorkingDirectory=/home/${admin_user} User=${admin_user} %{ if docker_enabled ~} ExecStartPre=/bin/bash -c 'sleep 5 && docker ps > /dev/null' ExecStart=/bin/sh -c 'cd /home/${admin_user} && exec docker compose -f docker-compose.yml up' ExecStop=/bin/sh -c 'cd /home/${admin_user} && exec docker compose -f docker-compose.yml down' %{ else ~} Environment=PATH=/home/${admin_user}/hermes-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ExecStart=/usr/local/bin/hermes gateway run ExecStop=/bin/kill -TERM $MAINPID %{ endif ~} Restart=on-failure RestartSec=15 StandardOutput=journal StandardError=journal SyslogIdentifier=hermes [Install] WantedBy=multi-user.target permissions: '0644' # Health check and diagnostics script - path: /usr/local/bin/hermes-health-check.sh content: | #!/bin/bash set -e echo "=== Hermes Agent Health Check ===" echo "" %{ if docker_enabled ~} # Docker-based checks # Check if Docker is running if systemctl is-active --quiet docker; then echo "✓ Docker daemon running" else echo "✗ Docker daemon not running" exit 1 fi # Check if Hermes container exists if docker ps -a | grep -q "${agent_name}"; then echo "✓ Hermes container exists" else echo "✗ Hermes container not found" exit 1 fi # Check if Hermes container is running if docker ps | grep -q "${agent_name}"; then echo "✓ Hermes container running" CONTAINER_ID=$(docker ps -q -f name=${agent_name}) UPTIME=$(docker inspect --format='{{.State.StartedAt}}' $CONTAINER_ID) echo " Started: $UPTIME" else echo "✗ Hermes container not running" echo " Last status:" docker ps -a --format "table {{.Names}}\t{{.Status}}" | grep ${agent_name} exit 1 fi %{ else ~} # Direct installation checks # Check if hermes binary exists if [ -x "/usr/local/bin/hermes" ]; then echo "✓ Hermes binary installed" else echo "✗ Hermes binary not found" exit 1 fi # Check if hermes venv exists if [ -d "/home/${admin_user}/hermes-venv" ]; then echo "✓ Hermes virtual environment exists" else echo "✗ Hermes virtual environment not found" exit 1 fi # Check if hermes process is running if pgrep -f "hermes gateway run" > /dev/null; then echo "✓ Hermes process running" HERMES_PID=$(pgrep -f "hermes gateway run") echo " PID: $HERMES_PID" else echo "✗ Hermes process not running" exit 1 fi %{ endif ~} # Check if port is listening if netstat -tlnp 2>/dev/null | grep -q ":18789 " || lsof -i :18789 > /dev/null 2>&1; then echo "✓ Gateway listening on port 18789" else echo "✗ Gateway not listening on port 18789" exit 1 fi # Check if config files exist if [ -f /home/${admin_user}/.hermes/config.yaml ]; then echo "✓ config.yaml exists" else echo "✗ config.yaml missing" exit 1 fi if [ -f /home/${admin_user}/.hermes/.env ]; then echo "✓ .env file exists" else echo "✗ .env file missing" exit 1 fi # Check systemd service if systemctl is-active --quiet hermes.service; then echo "✓ Hermes systemd service active" else echo "✗ Hermes systemd service not active" systemctl status hermes.service || true exit 1 fi # Check recent logs echo "" echo "Recent logs:" %{ if docker_enabled ~} docker logs --tail=10 ${agent_name} 2>&1 | head -20 || echo " (No logs available)" %{ else ~} journalctl -u hermes.service -n 10 --no-pager || echo " (No logs available)" %{ endif ~} # Check Discord configuration if grep -q "DISCORD_BOT_TOKEN" /home/${admin_user}/.hermes/.env; then if [ -s /home/${admin_user}/.hermes/.env ]; then BOT_TOKEN=$(grep "DISCORD_BOT_TOKEN" /home/${admin_user}/.hermes/.env | cut -d= -f2 | wc -c) echo "" echo "Discord configuration:" echo " Bot token configured: $([ $BOT_TOKEN -gt 10 ] && echo "✓ Yes" || echo "✗ No")" grep "DISCORD_SERVER_ID" /home/${admin_user}/.hermes/.env > /dev/null && echo " Server ID configured: ✓" || echo " Server ID configured: ✗" fi fi echo "" echo "=== Health Check Complete ===" echo "" echo "For more details:" echo " systemctl status hermes.service" %{ if docker_enabled ~} echo " docker logs -f ${agent_name}" %{ else ~} echo " journalctl -u hermes.service -f" echo " hermes --help" %{ endif ~} echo "" permissions: '0755' %{ if docker_enabled == false ~} # Direct installation script - avoids YAML escaping issues in runcmd - path: /usr/local/bin/install-hermes-direct.sh content: | #!/bin/bash set -e ADMIN_USER="${admin_user}" echo "=== Installing Hermes Agent (Direct Mode) ===" # Ensure home directory exists mkdir -p /home/$ADMIN_USER chown -R $ADMIN_USER:$ADMIN_USER /home/$ADMIN_USER chmod 755 /home/$ADMIN_USER # Install dependencies apt-get update apt-get install -y git curl python3 python3-pip python3-venv build-essential libffi-dev libssl-dev # Install uv (running as root during cloud-init) # Install uv system-wide so all users can access it UV_INSTALL_DIR=/usr/local/bin curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=$UV_INSTALL_DIR sh export PATH="$UV_INSTALL_DIR:$PATH" # Clone Hermes Agent repository echo "Cloning Hermes Agent repository..." su - $ADMIN_USER -c "cd /home/$ADMIN_USER && git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git" # Create virtual environment with Python 3.11 echo "Creating Python 3.11 virtual environment..." su - $ADMIN_USER -c "cd /home/$ADMIN_USER/hermes-agent && /usr/local/bin/uv venv venv --python 3.11" # Install Hermes with messaging extras echo "Installing Hermes Agent (this may take a few minutes)..." su - $ADMIN_USER -c "cd /home/$ADMIN_USER/hermes-agent && export VIRTUAL_ENV=/home/$ADMIN_USER/hermes-agent/venv && /usr/local/bin/uv pip install -e '.[messaging]'" # Create hermes wrapper script echo "Creating wrapper script..." cat > /usr/local/bin/hermes << WRAPPER_EOF #!/bin/bash # Hermes wrapper script - uv is installed during cloud-init export PATH="/home/$ADMIN_USER/.local/bin:\$PATH" export VIRTUAL_ENV="/home/$ADMIN_USER/hermes-agent/venv" exec "/home/$ADMIN_USER/hermes-agent/venv/bin/hermes" "\$@" WRAPPER_EOF chmod +x /usr/local/bin/hermes # Verify installation echo "Verifying installation..." /usr/local/bin/hermes version || { echo "ERROR: Hermes Agent installation failed" exit 1 } # Create config directory structure su - $ADMIN_USER -c "mkdir -p /home/$ADMIN_USER/.hermes/{cron,sessions,logs,memories,skills,pairing,hooks,image_cache,audio_cache}" chown -R $ADMIN_USER:$ADMIN_USER /home/$ADMIN_USER/.hermes chmod 755 /home/$ADMIN_USER/.hermes echo "=== Installation Complete ===" permissions: '0755' %{ endif ~} # Run commands runcmd: # Create directories - mkdir -p /home/${admin_user}/.hermes - chown -R ${admin_user}:${admin_user} /home/${admin_user}/.hermes %{ if docker_enabled ~} # Docker-based installation - curl -fsSL https://get.docker.com | sh # Install Docker Compose plugin (BEFORE pulling images) - apt-get update - apt-get install -y docker-compose-plugin # Ensure home directory exists with correct ownership - mkdir -p /home/${admin_user} - chown -R ${admin_user}:${admin_user} /home/${admin_user} - chmod 755 /home/${admin_user} # Add user to docker group for later use - usermod -aG docker ${admin_user} # Wait for Docker daemon to be ready - sleep 5 - docker ps > /dev/null || (sleep 10 && docker ps) # Pull Hermes image (runs as root) - docker pull nousresearch/hermes-agent:latest # Ensure .hermes directory has correct permissions for files written by docker - mkdir -p /home/${admin_user}/.hermes - chown -R ${admin_user}:${admin_user} /home/${admin_user}/.hermes - chmod 755 /home/${admin_user}/.hermes - chown ${admin_user}:${admin_user} /home/${admin_user}/docker-compose.yml - chmod 644 /home/${admin_user}/docker-compose.yml %{ else ~} # Direct installation - call the install script - /usr/local/bin/install-hermes-direct.sh %{ endif ~} # Enable and start Hermes service - systemctl daemon-reload - systemctl enable hermes.service # Start the service with a slight delay to ensure all prerequisites are ready - sleep 2 - systemctl start hermes.service - sleep 3 # Verify service started - systemctl is-active hermes.service || systemctl status hermes.service # Print completion message - | echo "" echo "=======================================" echo " Hermes Agent Bootstrap Complete!" echo "=======================================" echo "" echo "Server: ${server_name}" echo "Framework: Hermes Agent (Nous Research)" echo "Model: ${primary_model}" %{ if docker_enabled ~} echo "Deployment: Docker Container" %{ else ~} echo "Deployment: Direct Installation" %{ endif ~} echo "" echo "Verify deployment:" echo " systemctl status hermes.service" %{ if docker_enabled ~} echo " docker ps" echo " docker logs ${agent_name}" %{ else ~} echo " hermes --version" echo " journalctl -u hermes.service -f" %{ endif ~} echo "" echo "For Discord connectivity:" echo " Check bot has 'online' status and is in your server" echo ""