shove into git

This commit is contained in:
2026-04-23 21:07:49 +00:00
commit 8e0d85b7f8
6 changed files with 1228 additions and 0 deletions

77
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,77 @@
pipeline {
agent any
parameters {
string(name: 'HERMES_VERSION', defaultValue: 'v0.7.0', description: 'Hermes Agent version to install (e.g. v0.7.0)')
string(name: 'VULTR_REGION', defaultValue: 'ewr', description: 'Vultr region for the build instance')
string(name: 'VULTR_PLAN', defaultValue: 'vc2-2c-4gb', description: 'Vultr plan for the build instance')
string(name: 'VULTR_OS_ID', defaultValue: '2284', description: 'Vultr OS ID (2284 = Ubuntu 24.04 LTS)')
}
stages {
stage('Validate') {
steps {
script {
if (!params.HERMES_VERSION?.trim()) {
error "HERMES_VERSION parameter is required."
}
}
}
}
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: '*/master']],
userRemoteConfigs: [[
url: 'https://git.vultropenclaw.com/vultr/hermes-agent-marketplace-packer.git'
]]
])
}
}
stage('Check Packer') {
steps {
sh 'packer --version'
}
}
stage('Packer Init') {
steps {
sh 'packer init hermes.pkr.hcl'
}
}
stage('Packer Build') {
steps {
withCredentials([string(
credentialsId: 'VULTR_API_KEY',
variable: 'VULTR_API_KEY'
)]) {
sh """
packer build \
-var 'vultr_api_key=${VULTR_API_KEY}' \
-var 'hermes_version=${params.HERMES_VERSION}' \
-var 'region_id=${params.VULTR_REGION}' \
-var 'plan_id=${params.VULTR_PLAN}' \
-var 'os_id=${params.VULTR_OS_ID}' \
-color=false \
hermes.pkr.hcl
"""
}
}
}
}
post {
success {
echo "✅ Snapshot built for Hermes Agent ${params.HERMES_VERSION}"
echo "✅ Region: ${params.VULTR_REGION} Plan: ${params.VULTR_PLAN}"
}
failure {
echo "❌ Packer build failed for Hermes Agent ${params.HERMES_VERSION}"
}
}
}

132
README.md Normal file
View File

@@ -0,0 +1,132 @@
# Hermes Agent Vultr Marketplace — Packer Image Build
One-shot install for Hermes Agent on Ubuntu 24.04 with Vultr Inference, ttyd, code-server, Docker, Caddy, and Homebrew.
## Architecture
Bare-metal install via Packer → Vultr snapshot.
### File layout
```
├── Jenkinsfile # Pipeline: validate → install packer → init → build
├── hermes.pkr.hcl # Packer HCL template (Vultr plugin)
├── scripts/
│ ├── provision.sh # Bake-time: system deps, hermes, docker, caddy, ttyd
│ └── cloud-init.yaml # Deploy-time: metadata fetch, config, caddy TLS
├── install-hermes.sh # Standalone installer (for manual deploys)
└── README.md
```
### Bake-time (Packer snapshot)
Everything slow and static:
- `apt` packages (build-essential, curl, git, jq, python3-venv, zsh, ttyd, …)
- Hermes Agent from GitHub (v0.7.0)
- Docker CE
- Caddy (disabled until configured)
- code-server
- Oh My Zsh + Homebrew
- UFW with ssh/http/https/7681/8080 allowed
- Dedicated `hermes` system user with `~/.hermes/` pre-created
- ttyd and code-server systemd services created
### Deploy-time (cloud-init)
Per-instance secrets and activation:
1. Fetch `app-password`, `app-inf_api_key`, `app-domain` from Vultr metadata
2. Write `/home/hermes/.hermes/.env` with Vultr Inference API key
3. Write `/home/hermes/.hermes/config.yaml` with model/provider config
4. Configure code-server with password
5. Start ttyd and code-server services
6. Write `/etc/caddy/Caddyfile` with domain, ZeroSSL TLS, basic auth
7. Start Caddy and wait for cert issuance
8. Shred the temp env file
### Access URLs (after deploy)
- **Hermes Terminal:** `https://your-domain.com/` (via ttyd)
- **VS Code:** `https://your-domain.com/code/`
- **Basic Auth:** Username `hermes`, password from Vultr metadata
### Jenkins parameters
| Parameter | Default | Description |
|---|---|---|
| `HERMES_VERSION` | `v0.7.0` | Hermes Agent git tag |
| `VULTR_REGION` | `ewr` | Build region |
| `VULTR_PLAN` | `vc2-2c-4gb` | Build instance size |
| `VULTR_OS_ID` | `2284` | Ubuntu 24.04 LTS |
### Credentials needed in Jenkins
| Credential ID | Type | Purpose |
|---|---|---|
| `VULTR_API_KEY` | Secret text | Vultr API key for Packer to provision + snapshot |
### Vultr Marketplace Metadata Variables
Configure these in the Vultr marketplace app:
| Variable | Description |
|----------|-------------|
| `app-password` | Password for basic auth and code-server |
| `app-inf_api_key` | Vultr Inference API key |
| `app-domain` | Domain for TLS certificate |
## Test VMs
### VM 1 (Basic - Manual Install)
```
Host: 137.220.58.94
User: root
Password: Uv!3XrMsq!45yQe*
```
### VM 2 (Full Dev + Caddy)
```
Host: 66.135.26.126
User: root
Password: 7_kGSd)5Jr%iK?M-
Domain: hermes-dev-001.vultropenclaw.com
```
## Available Models (Vultr Inference)
| Model ID | Context | Notes |
|----------|---------|-------|
| `zai-org/GLM-5-FP8` | 200K | Default, reasoning-capable |
| `deepseek-ai/DeepSeek-V3.2` | 128K | Reasoning-capable |
| `google/gemma-4-31B-it` | 256K | Reasoning-capable |
| `Qwen/Qwen2.5-Coder-32B-Instruct` | 131K | Code-focused |
| `MiniMaxAI/MiniMax-M2.5` | 128K | General purpose |
| `moonshotai/Kimi-K2.5` | 128K | General purpose |
## Manual Installation
For manual deploys without Packer, use the standalone installer:
```bash
# Without domain (HTTP only)
curl -fsSL https://raw.githubusercontent.com/.../install-hermes.sh | bash
# With domain (HTTPS via Caddy)
curl -fsSL https://raw.githubusercontent.com/.../install-hermes.sh | bash -s -- your-domain.com
```
## Services
| Service | Port | Description |
|---------|------|-------------|
| `ttyd-hermes` | 7681 | Hermes terminal (web) |
| `code-server-hermes` | 8080 | VS Code (web) |
| `caddy` | 80/443 | Reverse proxy with auto-HTTPS |
| `docker` | - | Container runtime |
## Notes
- ttyd runs as root, drops privileges to hermes user via `-u`/`-g` flags
- code-server runs as hermes user
- Homebrew installed at `/home/linuxbrew/.linuxbrew`
- `HOME` and `PATH` environment variables set in systemd services for brew compatibility
- Caddyfile uses ZeroSSL primary (avoids LE rate limits), Let's Encrypt fallback

91
hermes.pkr.hcl Normal file
View File

@@ -0,0 +1,91 @@
variable "vultr_api_key" {
type = string
default = env("VULTR_API_KEY")
sensitive = true
}
variable "hermes_version" {
type = string
default = "v0.7.0"
description = "Hermes Agent version to install (e.g. v0.7.0)"
}
variable "os_id" {
type = string
default = "2284" # Ubuntu 24.04 LTS x64
description = "Vultr OS ID"
}
variable "plan_id" {
type = string
default = "vc2-2c-4gb" # 2 vCPU / 4 GB — adjust to your marketplace tier
}
variable "region_id" {
type = string
default = "ewr" # New Jersey
}
packer {
required_plugins {
vultr = {
version = ">=v2.3.2"
source = "github.com/vultr/vultr"
}
}
}
source "vultr" "hermes" {
api_key = var.vultr_api_key
os_id = var.os_id
plan_id = var.plan_id
region_id = var.region_id
snapshot_description = "hermes-agent ${var.hermes_version} ${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
ssh_username = "root"
state_timeout = "25m"
}
build {
sources = ["source.vultr.hermes"]
# ── Bake-time provisioning ──
provisioner "shell" {
environment_vars = [
"DEBIAN_FRONTEND=noninteractive",
"HERMES_VERSION=${var.hermes_version}",
]
script = "scripts/provision.sh"
}
# ── Bake cloud-init into the image so it runs on first boot ──
provisioner "file" {
source = "scripts/cloud-init.yaml"
destination = "/etc/cloud/cloud.cfg.d/99-hermes.cfg"
}
# ── Vultr marketplace snapshot prep (must be last) ──
provisioner "shell" {
inline = [
"rm -rf /tmp/* /var/tmp/*",
"rm -f /root/.ssh/authorized_keys /etc/ssh/*key*",
"touch /etc/ssh/revoked_keys",
"chmod 600 /etc/ssh/revoked_keys",
"find /var/log -mtime -1 -type f -exec truncate -s 0 {} \\;",
"rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????",
"echo '' > /var/log/auth.log",
"cat /dev/null > /var/log/lastlog",
"cat /dev/null > /var/log/wtmp",
"rm -rf /var/lib/cloud/instances/*",
"rm -f /var/lib/systemd/random-seed",
"rm -f /etc/machine-id",
"touch /etc/machine-id",
"cat /dev/null > /root/.bash_history",
"updatedb 2>/dev/null || true",
"dd if=/dev/zero of=/zerofile bs=1M 2>/dev/null || true",
"sync",
"rm -f /zerofile",
"sync",
"fstrim / 2>/dev/null || true",
]
}
}

567
install-hermes.sh Executable file
View File

@@ -0,0 +1,567 @@
#!/bin/bash
# ============================================================================
# Hermes Agent Full Installer for Ubuntu 24.04
# ============================================================================
# Installs Hermes Agent with Vultr Inference provider pre-configured,
# plus Docker, zsh, Oh My Zsh, Homebrew, code-server, Caddy, ttyd, and UFW.
#
# Usage:
# ./install-hermes.sh [DOMAIN]
#
# If DOMAIN is provided, Caddy will be configured with HTTPS for that domain.
#
# ============================================================================
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'
BOLD='\033[1m'
# Configuration - Vultr Inference
VULTR_API_KEY="55AIOTBABUBU7PAZRZEKORCG4T2H2VVZQ3XQ"
VULTR_BASE_URL="https://api.vultrinference.com/v1"
DEFAULT_MODEL="zai-org/GLM-5-FP8"
TTYD_PORT="${TTYD_PORT:-7681}"
CODE_SERVER_PORT="${CODE_SERVER_PORT:-8080}"
# Domain for Caddy (optional)
DOMAIN="${1:-}"
# ============================================================================
# Helper functions
# ============================================================================
log_info() {
echo -e "${CYAN}${NC} $1"
}
log_success() {
echo -e "${GREEN}${NC} $1"
}
log_warn() {
echo -e "${YELLOW}${NC} $1"
}
log_error() {
echo -e "${RED}${NC} $1"
}
print_banner() {
echo ""
echo -e "${CYAN}${BOLD}"
echo "┌─────────────────────────────────────────────────────────┐"
echo "│ Hermes Agent + Dev Environment Installer │"
echo "└─────────────────────────────────────────────────────────┘"
echo -e "${NC}"
}
# ============================================================================
# Main installation
# ============================================================================
main() {
print_banner
# Check for Ubuntu/Debian
if [ -f /etc/os-release ]; then
. /etc/os-release
if [ "$ID" != "ubuntu" ] && [ "$ID" != "debian" ]; then
log_warn "This script is designed for Ubuntu/Debian. Proceeding anyway..."
fi
fi
# Ensure we're running as root or have sudo
if [ "$(id -u)" -ne 0 ] && ! command -v sudo &> /dev/null; then
log_error "This script requires root access or sudo"
exit 1
fi
# Run as root if we're not already
if [ "$(id -u)" -ne 0 ]; then
log_info "Requesting sudo for system package installation..."
SUDO="sudo"
else
SUDO=""
fi
export DEBIAN_FRONTEND=noninteractive
# -------------------------------------------------------------------------
# Step 1: Install system dependencies
# -------------------------------------------------------------------------
log_info "Installing system packages..."
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq \
build-essential procps curl file git ca-certificates \
gnupg software-properties-common ufw jq python3 python3-venv \
zsh docker.io ttyd ri rgrep ffmpeg > /dev/null 2>&1 || true
log_success "System packages installed"
# -------------------------------------------------------------------------
# Step 2: Install Docker (official repo)
# -------------------------------------------------------------------------
log_info "Installing Docker..."
if ! command -v docker &> /dev/null; then
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker
systemctl start docker
fi
log_success "Docker installed ($(docker --version))"
# -------------------------------------------------------------------------
# Step 3: Install Caddy
# -------------------------------------------------------------------------
log_info "Installing Caddy..."
if ! command -v caddy &> /dev/null; then
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-stable.list
chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
chmod o+r /etc/apt/sources.list.d/caddy-stable.list
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq caddy
fi
log_success "Caddy installed ($(caddy version | head -1))"
# -------------------------------------------------------------------------
# Step 4: Install code-server
# -------------------------------------------------------------------------
log_info "Installing code-server..."
if ! command -v code-server &> /dev/null; then
curl -fsSL https://code-server.dev/install.sh | sh
fi
log_success "code-server installed ($(code-server --version))"
# -------------------------------------------------------------------------
# Step 5: Create hermes user
# -------------------------------------------------------------------------
log_info "Creating hermes user..."
if ! id hermes &>/dev/null; then
useradd -m -s /usr/bin/zsh hermes
else
chsh -s /usr/bin/zsh hermes 2>/dev/null || true
fi
# Create ttyd group and add hermes to it
groupadd ttyd 2>/dev/null || true
usermod -aG ttyd hermes
usermod -aG docker hermes
# Allow hermes user's systemd services to run without an active session
loginctl enable-linger hermes 2>/dev/null || true
log_success "Hermes user created (uid=$(id -u hermes))"
# -------------------------------------------------------------------------
# Step 6: Install Oh My Zsh (as hermes user)
# -------------------------------------------------------------------------
log_info "Installing Oh My Zsh..."
if [ ! -d /home/hermes/.oh-my-zsh ]; then
su - hermes -c 'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended'
fi
log_success "Oh My Zsh installed"
# -------------------------------------------------------------------------
# Step 7: Install Homebrew (as hermes user)
# -------------------------------------------------------------------------
log_info "Installing Homebrew..."
if [ ! -d /home/linuxbrew/.linuxbrew ]; then
# Allow hermes to sudo without password during brew install
echo 'hermes ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/hermes
su - hermes -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
# Persist Homebrew in all shells
cat >> /home/hermes/.bashrc <<'BREWEOF'
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWEOF
cat >> /home/hermes/.profile <<'BREWEOF'
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWEOF
cat >> /home/hermes/.zshrc <<'BREWEOF'
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWEOF
# Turn off analytics
su - hermes -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew analytics off'
fi
log_success "Homebrew installed"
# -------------------------------------------------------------------------
# Step 8: Clone and install Hermes Agent
# -------------------------------------------------------------------------
log_info "Installing Hermes Agent..."
HERMES_HOME="/home/hermes/.hermes"
HERMES_LOCAL="/home/hermes/.local"
# Ensure directories exist with correct ownership
mkdir -p "$HERMES_LOCAL/bin"
chown -R hermes:hermes /home/hermes
if [ ! -d "$HERMES_HOME/hermes-agent" ]; then
su - hermes -c "git clone https://github.com/NousResearch/hermes-agent.git $HERMES_HOME/hermes-agent"
fi
log_success "Hermes Agent source cloned"
# -------------------------------------------------------------------------
# Step 9: Set up Python venv for hermes user
# -------------------------------------------------------------------------
log_info "Setting up Python venv for hermes user..."
# Remove old venv if exists
rm -rf "$HERMES_HOME/hermes-agent/venv"
# Create venv and install as hermes user
su - hermes -c "cd $HERMES_HOME/hermes-agent && python3 -m venv venv && ./venv/bin/pip install --upgrade pip --quiet && ./venv/bin/pip install -e '.[all]' --quiet"
# Create symlink
su - hermes -c "ln -sf $HERMES_HOME/hermes-agent/venv/bin/hermes $HERMES_LOCAL/bin/hermes"
# Add to hermes user's PATH
grep -q 'export PATH.*.local/bin' /home/hermes/.bashrc 2>/dev/null || \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/hermes/.bashrc
grep -q 'export PATH.*.local/bin' /home/hermes/.zshrc 2>/dev/null || \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/hermes/.zshrc
log_success "Hermes venv configured"
# -------------------------------------------------------------------------
# Step 10: Configure Vultr Inference provider
# -------------------------------------------------------------------------
log_info "Configuring Vultr Inference provider..."
mkdir -p "$HERMES_HOME"/{cron,sessions,logs,pairing,hooks,image_cache,audio_cache,memories,skills}
# Create .env with Vultr API key
cat > "$HERMES_HOME/.env" << ENV_EOF
# =============================================================================
# VULTR INFERENCE PROVIDER (Pre-configured)
# =============================================================================
VULTR_API_KEY=$VULTR_API_KEY
VULTR_BASE_URL=$VULTR_BASE_URL
# For OpenAI-compatible custom endpoint mode:
OPENAI_API_KEY=$VULTR_API_KEY
OPENAI_BASE_URL=$VULTR_BASE_URL
ENV_EOF
# Create config.yaml with Vultr model configured
cat > "$HERMES_HOME/config.yaml" << 'CONFIG_EOF'
# Hermes Agent Configuration - Vultr Inference
# =============================================================================
# Pre-configured for Vultr Inference API (OpenAI-compatible)
model:
# Default model (GLM-5-FP8 - 200K context, reasoning-capable)
default: "zai-org/GLM-5-FP8"
# Use custom provider for Vultr Inference (OpenAI-compatible)
provider: "custom"
base_url: "https://api.vultrinference.com/v1"
api_key: "55AIOTBABUBU7PAZRZEKORCG4T2H2VVZQ3XQ"
# Terminal backend (local execution)
terminal:
backend: "local"
cwd: "."
timeout: 180
lifetime_seconds: 300
# Enable persistent memory
memory:
memory_enabled: true
user_profile_enabled: true
memory_char_limit: 2200
user_char_limit: 1375
nudge_interval: 10
# Context compression for long conversations
compression:
enabled: true
threshold: 0.50
target_ratio: 0.20
protect_last_n: 20
summary_model: "zai-org/GLM-5-FP8"
# Agent settings
agent:
max_turns: 60
verbose: false
reasoning_effort: "medium"
# Toolsets for CLI
platform_toolsets:
cli: [hermes-cli]
# Display settings
display:
compact: false
tool_progress: all
streaming: true
CONFIG_EOF
# Set ownership
chown -R hermes:hermes "$HERMES_HOME" "$HERMES_LOCAL"
log_success "Vultr Inference configured"
# -------------------------------------------------------------------------
# Step 11: Set up ttyd systemd service
# -------------------------------------------------------------------------
log_info "Setting up ttyd service..."
HERMES_UID=$(id -u hermes)
HERMES_GID=$(id -g hermes)
# Disable default ttyd if enabled
systemctl disable ttyd 2>/dev/null || true
systemctl stop ttyd 2>/dev/null || true
# Create systemd service with Homebrew in PATH
cat > /etc/systemd/system/ttyd-hermes.service << SERVICE
[Unit]
Description=ttyd terminal for Hermes Agent
After=network.target
[Service]
Type=simple
Environment="HOME=/home/hermes"
Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/home/hermes/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/usr/bin/ttyd -p $TTYD_PORT -u $HERMES_UID -g $HERMES_GID -W -t fontSize=14 -t theme='{"background":"#1a1a2e","foreground":"#eee"}' /home/hermes/.local/bin/hermes
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
SERVICE
systemctl daemon-reload
systemctl enable ttyd-hermes
systemctl start ttyd-hermes
log_success "ttyd service started on port $TTYD_PORT"
# -------------------------------------------------------------------------
# Step 12: Set up code-server for hermes user
# -------------------------------------------------------------------------
log_info "Setting up code-server..."
mkdir -p /home/hermes/.config/code-server
# Create code-server config
cat > /home/hermes/.config/code-server/config.yaml << CODESERVER_EOF
bind-addr: 0.0.0.0:$CODE_SERVER_PORT
auth: password
password: hermes-dev
cert: false
CODESERVER_EOF
chown -R hermes:hermes /home/hermes/.config
# Create systemd service for code-server
cat > /etc/systemd/system/code-server-hermes.service << CODESERVICE
[Unit]
Description=code-server for Hermes
After=network.target
[Service]
Type=simple
User=hermes
Environment="HOME=/home/hermes"
Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/home/hermes/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
WorkingDirectory=/home/hermes
ExecStart=/usr/bin/code-server --config /home/hermes/.config/code-server/config.yaml
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
CODESERVICE
systemctl daemon-reload
systemctl enable code-server-hermes
systemctl start code-server-hermes
log_success "code-server started on port $CODE_SERVER_PORT"
# -------------------------------------------------------------------------
# Step 13: Configure Caddy (if domain provided)
# -------------------------------------------------------------------------
if [ -n "$DOMAIN" ]; then
log_info "Configuring Caddy for $DOMAIN..."
cat > /etc/caddy/Caddyfile << CADDYFILE
# Hermes Dev Environment - $DOMAIN
# Main domain - reverse proxy to services
$DOMAIN {
# Hermes terminal (ttyd)
handle /terminal/* {
reverse_proxy localhost:$TTYD_PORT
}
# code-server
handle /code/* {
reverse_proxy localhost:$CODE_SERVER_PORT
}
# Default: serve a landing page
handle / {
respond "Hermes Dev Environment
Routes:
/terminal/ - Hermes Agent (ttyd)
/code/ - VS Code (code-server)
" 200
}
}
# Also handle www redirect
www.$DOMAIN {
redir https://$DOMAIN{uri}
}
CADDYFILE
systemctl enable caddy
systemctl restart caddy
log_success "Caddy configured for https://$DOMAIN"
else
log_info "No domain provided, skipping Caddy HTTPS setup"
systemctl disable caddy 2>/dev/null || true
systemctl stop caddy 2>/dev/null || true
fi
# -------------------------------------------------------------------------
# Step 14: Add Homebrew to system PATH
# -------------------------------------------------------------------------
log_info "Adding Homebrew to system PATH..."
cat > /etc/profile.d/homebrew.sh << 'BREWPROFILE'
# Homebrew
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWPROFILE
chmod +x /etc/profile.d/homebrew.sh
log_success "Homebrew added to system PATH"
# -------------------------------------------------------------------------
# Step 15: Configure UFW firewall
# -------------------------------------------------------------------------
log_info "Configuring UFW firewall..."
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw allow "$TTYD_PORT/tcp" comment "ttyd"
ufw allow "$CODE_SERVER_PORT/tcp" comment "code-server"
ufw --force enable
log_success "UFW configured"
# -------------------------------------------------------------------------
# Step 15: Cleanup
# -------------------------------------------------------------------------
log_info "Cleaning up..."
apt-get autoremove -y -qq 2>/dev/null || true
apt-get clean
rm -rf /var/lib/apt/lists/*
# -------------------------------------------------------------------------
# Done!
# -------------------------------------------------------------------------
# Get server IP
SERVER_IP=$(curl -s ifconfig.me 2>/dev/null || curl -s icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP")
echo ""
echo -e "${GREEN}${BOLD}"
echo "┌─────────────────────────────────────────────────────────┐"
echo "│ ✓ Installation Complete! │"
echo "└─────────────────────────────────────────────────────────┘"
echo -e "${NC}"
echo ""
echo -e "${CYAN}${BOLD}📁 Hermes Configuration:${NC}"
echo ""
echo " Config: ~/.hermes/config.yaml"
echo " API Keys: ~/.hermes/.env"
echo " Data: ~/.hermes/cron/, sessions/, logs/"
echo ""
echo -e "${CYAN}${BOLD}🌐 Access URLs:${NC}"
echo ""
if [ -n "$DOMAIN" ]; then
echo " Hermes: https://$DOMAIN/terminal/"
echo " VS Code: https://$DOMAIN/code/"
else
echo " Hermes: http://$SERVER_IP:$TTYD_PORT"
echo " VS Code: http://$SERVER_IP:$CODE_SERVER_PORT"
fi
echo ""
echo -e "${CYAN}${BOLD}🔧 Services:${NC}"
echo ""
echo " ttyd-hermes - Hermes terminal (port $TTYD_PORT)"
echo " code-server-hermes - VS Code (port $CODE_SERVER_PORT)"
echo " caddy - Reverse proxy (if domain set)"
echo " docker - Container runtime"
echo ""
echo -e "${CYAN}${BOLD}🛠️ Tools Installed:${NC}"
echo ""
echo " Docker: $(docker --version | cut -d' ' -f3 | tr -d ',')"
echo " Caddy: $(caddy version 2>/dev/null | head -1 | cut -d' ' -f1 || echo 'installed')"
echo " code-server: $(code-server --version 2>/dev/null | head -1 || echo 'installed')"
echo " Homebrew: /home/linuxbrew/.linuxbrew/bin/brew"
echo " Oh My Zsh: ~/.oh-my-zsh"
echo ""
echo -e "${CYAN}${BOLD}🤖 Available Models (Vultr Inference):${NC}"
echo ""
echo " zai-org/GLM-5-FP8 (200K context, reasoning)"
echo " deepseek-ai/DeepSeek-V3.2 (128K context, reasoning)"
echo " Qwen/Qwen2.5-Coder-32B-Instruct (131K context, code)"
echo ""
if [ -n "$DOMAIN" ]; then
echo -e "${YELLOW}⚡ Open https://$DOMAIN/terminal/ to start chatting with Hermes!${NC}"
else
echo -e "${YELLOW}⚡ Open http://$SERVER_IP:$TTYD_PORT to start chatting with Hermes!${NC}"
fi
echo ""
}
main "$@"

167
scripts/cloud-init.yaml Normal file
View File

@@ -0,0 +1,167 @@
#cloud-config
# ------------------------------------------------------------------
# Deploy-time cloud-init for Vultr Marketplace
#
# The snapshot already has Hermes Agent, Docker, Caddy, ttyd, code-server
# installed. This script:
# 1. Fetches per-instance metadata from the Vultr metadata service
# 2. Configures Hermes Agent with Vultr Inference provider
# 3. Configures code-server with password
# 4. Starts ttyd and code-server services
# 5. Configures Caddy as a TLS reverse proxy
# - / → ttyd (Hermes terminal)
# - /code/* → code-server
# ------------------------------------------------------------------
runcmd:
# ── Fetch Vultr metadata and persist ──
- |
APP_PASSWORD=$(curl -sf -H "METADATA-TOKEN: vultr" http://169.254.169.254/v1/internal/app-password)
APP_INF_API_KEY=$(curl -sf -H "METADATA-TOKEN: vultr" http://169.254.169.254/v1/internal/app-inf_api_key)
APP_DOMAIN=$(curl -sf -H "METADATA-TOKEN: vultr" http://169.254.169.254/v1/internal/app-domain)
printf 'APP_PASSWORD="%s"\nAPP_INF_API_KEY="%s"\nAPP_DOMAIN="%s"\n' \
"$APP_PASSWORD" "$APP_INF_API_KEY" "$APP_DOMAIN" \
> /etc/hermes-deploy.env
chmod 600 /etc/hermes-deploy.env
# ── Configure Hermes Agent with Vultr Inference provider ──
- |
. /etc/hermes-deploy.env
# Create .env with API keys
cat > /home/hermes/.hermes/.env << ENV_EOF
# =============================================================================
# VULTR INFERENCE PROVIDER (Pre-configured)
# =============================================================================
VULTR_API_KEY=${APP_INF_API_KEY}
VULTR_BASE_URL=https://api.vultrinference.com/v1
# For OpenAI-compatible custom endpoint mode:
OPENAI_API_KEY=${APP_INF_API_KEY}
OPENAI_BASE_URL=https://api.vultrinference.com/v1
ENV_EOF
chown hermes:hermes /home/hermes/.hermes/.env
chmod 600 /home/hermes/.hermes/.env
# Create config.yaml
cat > /home/hermes/.hermes/config.yaml << 'CONFIG_EOF'
# Hermes Agent Configuration - Vultr Inference
# =============================================================================
# Pre-configured for Vultr Inference API (OpenAI-compatible)
model:
# Default model (GLM-5-FP8 - 200K context, reasoning-capable)
default: "zai-org/GLM-5-FP8"
# Use custom provider for Vultr Inference (OpenAI-compatible)
provider: "custom"
base_url: "https://api.vultrinference.com/v1"
# api_key will be substituted from .env
# Terminal backend (local execution)
terminal:
backend: "local"
cwd: "."
timeout: 180
lifetime_seconds: 300
# Enable persistent memory
memory:
memory_enabled: true
user_profile_enabled: true
memory_char_limit: 2200
user_char_limit: 1375
nudge_interval: 10
# Context compression for long conversations
compression:
enabled: true
threshold: 0.50
target_ratio: 0.20
protect_last_n: 20
summary_model: "zai-org/GLM-5-FP8"
# Agent settings
agent:
max_turns: 60
verbose: false
reasoning_effort: "medium"
# Toolsets for CLI
platform_toolsets:
cli: [hermes-cli]
# Display settings
display:
compact: false
tool_progress: all
streaming: true
CONFIG_EOF
chown hermes:hermes /home/hermes/.hermes/config.yaml
chmod 600 /home/hermes/.hermes/config.yaml
# ── Configure and start code-server ──
- |
. /etc/hermes-deploy.env
cat > /home/hermes/.config/code-server/config.yaml << CSCFG
bind-addr: 127.0.0.1:8080
auth: password
password: ${APP_PASSWORD}
cert: false
CSCFG
chown hermes:hermes /home/hermes/.config/code-server/config.yaml
chmod 600 /home/hermes/.config/code-server/config.yaml
# ── Start ttyd and code-server services ──
- systemctl enable ttyd-hermes code-server-hermes
- systemctl start ttyd-hermes code-server-hermes
# ── Write Caddyfile ──
# ttyd at root, code-server at /code/
# ZeroSSL primary (avoids LE rate limits), Let's Encrypt fallback
- |
. /etc/hermes-deploy.env
HASHED=$(caddy hash-password --plaintext "$APP_PASSWORD")
cat > /etc/caddy/Caddyfile << CADDY
${APP_DOMAIN} {
tls {
issuer acme {
dir https://acme.zerossl.com/v2/DV90
eab ur6CkPWFhPtp8af1SASQEA dRKaIDk9u4tn7MYrHNczzl_tnKSJ8Hipb2IFVui3oxEHCj_3wkGBA-zG93X7NXaROh-pzDJOugrtn1nxxGasxw
}
issuer acme {
dir https://acme-v02.api.letsencrypt.org/directory
}
}
basic_auth {
hermes ${HASHED}
}
# code-server at /code/
handle_path /code/* {
reverse_proxy 127.0.0.1:8080
}
# Default: Hermes terminal (ttyd) at root
handle {
reverse_proxy 127.0.0.1:7681
}
}
CADDY
# ── Start Caddy and wait for cert issuance ──
# ZeroSSL can take 30-60s; wait up to 90s for cert before proceeding
- systemctl enable caddy
- systemctl restart caddy
- |
echo "Waiting for TLS certificate issuance (up to 90s)..."
for i in $(seq 1 90); do
if find /var/lib/caddy -name '*.crt' 2>/dev/null | grep -q .; then
echo "Certificate obtained!"
break
fi
sleep 1
done
# ── Clean up secrets from disk ──
- rm -f /etc/hermes-deploy.env

194
scripts/provision.sh Executable file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------
# provision.sh — Packer bake-time provisioner for Hermes Agent
#
# Runs as root on a fresh Ubuntu 24.04 instance.
# Installs everything that does NOT depend on per-instance secrets:
# • system deps (incl. zsh, python3-venv)
# • Hermes Agent (from GitHub)
# • Docker (hermes user added to docker group)
# • Oh My Zsh + Homebrew (as hermes user)
# • code-server
# • ttyd
# • Caddy (stable)
# • ufw defaults
# ------------------------------------------------------------------
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
HERMES_VERSION="${HERMES_VERSION:-v0.7.0}"
echo "▶ System packages"
apt-get update
apt-get upgrade -y
apt-get install -y --no-install-recommends \
build-essential procps curl file git ca-certificates \
gnupg software-properties-common ufw jq python3 python3-venv \
zsh systemd-container ttyd ri ffmpeg
# ── Docker ──
echo "▶ Docker"
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker
systemctl start docker
# ── Caddy (stable) ──
echo "▶ Caddy"
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-stable.list
chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
chmod o+r /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
systemctl disable caddy # don't start until cloud-init configures it
# ── Firewall defaults ──
echo "▶ UFW"
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw allow 7681/tcp comment "ttyd"
ufw allow 8080/tcp comment "code-server"
ufw --force enable
# ── Create hermes user (regular user, zsh as default shell) ──
echo "▶ hermes user"
if ! id hermes &>/dev/null; then
useradd -m -s /usr/bin/zsh hermes
else
chsh -s /usr/bin/zsh hermes
fi
# Create ttyd group and add hermes to it
groupadd ttyd 2>/dev/null || true
usermod -aG ttyd hermes
usermod -aG docker hermes
# Allow hermes user's systemd services to run without an active session
loginctl enable-linger hermes
# Pre-create directories
mkdir -p /home/hermes/.hermes/{cron,sessions,logs,pairing,hooks,image_cache,audio_cache,memories,skills}
mkdir -p /home/hermes/.local/bin
mkdir -p /home/hermes/.config/code-server
chown -R hermes:hermes /home/hermes
# ── Oh My Zsh (as hermes user) ──
echo "▶ Oh My Zsh"
su - hermes -c 'sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended'
# ── Homebrew (installed as hermes user) ──
echo "▶ Homebrew"
echo 'hermes ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/hermes
su - hermes -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
# Persist Homebrew in all shells
cat >> /home/hermes/.bashrc <<'BREWEOF'
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWEOF
cat >> /home/hermes/.profile <<'BREWEOF'
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWEOF
cat >> /home/hermes/.zshrc <<'BREWEOF'
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWEOF
# Add Homebrew to system-wide PATH
cat > /etc/profile.d/homebrew.sh <<'BREWPROFILE'
# Homebrew
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
BREWPROFILE
chmod +x /etc/profile.d/homebrew.sh
chown hermes:hermes /home/hermes/.zshrc /home/hermes/.bashrc /home/hermes/.profile
su - hermes -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew --version && brew analytics off'
# ── code-server ──
echo "▶ code-server"
# Wait for any background apt/dpkg processes to finish
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
echo " Waiting for dpkg lock..."
sleep 5
done
curl -fsSL https://code-server.dev/install.sh | sh
chown -R hermes:hermes /home/hermes/.config
# ── Install Hermes Agent ──
echo "▶ Hermes Agent ${HERMES_VERSION}"
su - hermes -c "git clone --branch ${HERMES_VERSION} --depth 1 https://github.com/NousResearch/hermes-agent.git /home/hermes/.hermes/hermes-agent"
su - hermes -c "cd /home/hermes/.hermes/hermes-agent && python3 -m venv venv && ./venv/bin/pip install --upgrade pip --quiet && ./venv/bin/pip install -e '.[all]' --quiet"
su - hermes -c "ln -sf /home/hermes/.hermes/hermes-agent/venv/bin/hermes /home/hermes/.local/bin/hermes"
# Add hermes binary to PATH in shells
grep -q 'export PATH.*.local/bin' /home/hermes/.bashrc 2>/dev/null || \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/hermes/.bashrc
grep -q 'export PATH.*.local/bin' /home/hermes/.zshrc 2>/dev/null || \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> /home/hermes/.zshrc
chown -R hermes:hermes /home/hermes
# ── Create ttyd systemd service ──
echo "▶ ttyd service"
HERMES_UID=$(id -u hermes)
HERMES_GID=$(id -g hermes)
cat > /etc/systemd/system/ttyd-hermes.service << EOF
[Unit]
Description=ttyd terminal for Hermes Agent
After=network.target
[Service]
Type=simple
Environment="HOME=/home/hermes"
Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/home/hermes/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/usr/bin/ttyd -p 7681 -u $HERMES_UID -g $HERMES_GID -W -t fontSize=14 -t theme='{"background":"#1a1a2e","foreground":"#eee"}' /home/hermes/.local/bin/hermes
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
# ── Create code-server systemd service ──
echo "▶ code-server service"
cat > /etc/systemd/system/code-server-hermes.service << 'EOF'
[Unit]
Description=code-server for Hermes
After=network.target
[Service]
Type=simple
User=hermes
Environment="HOME=/home/hermes"
Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/home/hermes/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
WorkingDirectory=/home/hermes
ExecStart=/usr/bin/code-server --config /home/hermes/.config/code-server/config.yaml
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
# ── Cleanup ──
echo "▶ Cleanup"
apt-get autoremove -y
apt-get clean
rm -rf /var/lib/apt/lists/*
echo "✅ Packer provisioning complete"