Sanitized for public release: - Removed all API keys, tokens, and secrets - Removed personal Discord IDs from hermes-openclaw.json - Updated git URLs to be generic placeholders - All sensitive data uses environment variable interpolation
492 lines
No EOL
15 KiB
Smarty
492 lines
No EOL
15 KiB
Smarty
#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 "" |