#!/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"