openboatmobile-ai/templates/userdata-hermes.tpl
CeeLo Greenheart a593af9b27 Initial commit - Clean public release
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
2026-04-22 19:13:28 +00:00

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 ""