shove into git
This commit is contained in:
77
Jenkinsfile
vendored
Normal file
77
Jenkinsfile
vendored
Normal 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
132
README.md
Normal 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
91
hermes.pkr.hcl
Normal 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
567
install-hermes.sh
Executable 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
167
scripts/cloud-init.yaml
Normal 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
194
scripts/provision.sh
Executable 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"
|
||||
Reference in New Issue
Block a user