#!/usr/bin/env bash # ============================================================================== # Hermes Agent Local (Bare-Metal) Deployment Script # ============================================================================== # Installs Hermes Agent directly on the host via uv + Python 3.11. # Runs as a gateway connecting to Telegram/Discord/Slack/WhatsApp. # No web UI — does NOT use Traefik. # # Source: https://github.com/NousResearch/hermes-agent # # Usage: deploy-local-hermes.sh # deploy-local-hermes.sh --remove [--purge] [--yes] # ============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/common.sh" # --- Parse arguments --- parse_args "$@" # --- Service config --- SERVICE_NAME="hermes" BASE_DIR="/opt/${SERVICE_NAME}" UNIT_NAME="${SERVICE_NAME}" HERMES_REPO="${HERMES_REPO:-https://github.com/NousResearch/hermes-agent.git}" # --- Handle --remove --- if [[ "$ARG_REMOVE" == "1" ]]; then require_root echo "" echo -e "${YELLOW}══════════════════════════════════════════════${NC}" echo -e "${YELLOW} Removing ${SERVICE_NAME} (local)${NC}" echo -e "${YELLOW}══════════════════════════════════════════════${NC}" if [[ "$ARG_PURGE" == "1" ]]; then echo -e " Mode: ${RED}Purge (delete all data)${NC}" else echo -e " Mode: Safe (keep data)" fi echo -e " Data: ${BASE_DIR}" echo -e " Systemd: ${UNIT_NAME}.service" echo -e "${YELLOW}══════════════════════════════════════════════${NC}" echo "" if [[ "$ARG_PURGE" == "1" ]] && [[ -d "$BASE_DIR" ]]; then backup_script="${SCRIPT_DIR}/backup-${SERVICE_NAME}.sh" [[ -f "$backup_script" ]] && warn "Back up first? ${backup_script}" echo "" fi unit_file="/etc/systemd/system/${UNIT_NAME}.service" if [[ -f "$unit_file" ]]; then systemctl stop "${UNIT_NAME}.service" > /dev/null 2>&1 || true systemctl disable "${UNIT_NAME}.service" > /dev/null 2>&1 || true rm -f "$unit_file" systemctl daemon-reload ok "Removed systemd unit: ${UNIT_NAME}.service" fi if [[ "$ARG_PURGE" == "1" ]]; then echo "" warn "This will permanently delete: $BASE_DIR" if [[ "$ARG_YES" != "1" ]]; then read -rp "Are you sure? [y/N] " confirm if [[ "${confirm,,}" != "y" ]]; then info "Purge cancelled." exit 0 fi fi rm -rf "$BASE_DIR" rm -rf "$HOME/.hermes" 2>/dev/null ok "Purged: $BASE_DIR" fi echo "" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo -e "${GREEN} ${SERVICE_NAME} removed${NC}" echo -e "${GREEN}══════════════════════════════════════════════${NC}" if [[ "$ARG_PURGE" != "1" ]]; then echo -e " Data preserved at ${BASE_DIR}" echo -e " To purge: $0 --remove --purge" fi echo -e " To redeploy: $0" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo "" exit 0 fi # --- Detect current state for banner --- install_mode="Fresh install" if [[ -f "${BASE_DIR}/.env" ]]; then install_mode="Re-run (preserving data)" fi service_status="Not running" if systemctl is-active --quiet "${UNIT_NAME}.service" 2>/dev/null; then service_status="Running" fi # --- Print deployment plan --- echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e "${CYAN} Deploying Hermes Agent (Local/Gateway)${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e " Data: ${BASE_DIR}/data/" echo -e " Traefik: Not used (no web UI)" echo -e " Service: ${service_status}" echo -e " Mode: ${install_mode}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" # --- Check if already deployed and running --- if [[ -f "${BASE_DIR}/.env" ]] && \ systemctl is-active --quiet "${UNIT_NAME}.service" 2>/dev/null; then echo -e "${GREEN} Hermes is already running — nothing to do.${NC}" echo "" echo -e " Health: hermes doctor" echo -e " Logs: journalctl -u ${UNIT_NAME}.service -f" echo -e " Backup: ${SCRIPT_DIR}/backup-hermes.sh" echo -e " Remove: $0 --remove [--purge]" echo "" exit 0 fi # --- Infrastructure --- require_root detect_os # --- Install prerequisites --- info "Installing prerequisites..." case "$DISTRO_FAMILY" in debian) export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq curl git openssl ca-certificates \ python3 python3-venv python3-pip > /dev/null 2>&1 ;; fedora) dnf install -y -q curl git openssl ca-certificates \ python3 python3-pip > /dev/null 2>&1 ;; esac ok "System prerequisites installed." # Install uv (Python package manager) if ! command -v uv &>/dev/null; then info "Installing uv..." curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1 export PATH="$HOME/.local/bin:$PATH" fi ok "uv $(uv --version 2>/dev/null || echo 'installed')." # --- Backup restore check --- if check_and_restore_backup "$SERVICE_NAME"; then ok "Backup restored." else # --- Fresh install --- if [[ -d "${BASE_DIR}/.git" ]]; then info "Updating Hermes source..." (cd "$BASE_DIR" && git pull) else info "Cloning Hermes from ${HERMES_REPO}..." git clone --recurse-submodules "$HERMES_REPO" "$BASE_DIR" fi info "Creating Python venv and installing dependencies..." (cd "$BASE_DIR" && uv venv venv --python 3.11 2>/dev/null || uv venv venv) export VIRTUAL_ENV="${BASE_DIR}/venv" (cd "$BASE_DIR" && uv pip install -e ".[all]" 2>/dev/null) || \ (cd "$BASE_DIR" && "${BASE_DIR}/venv/bin/pip" install -e ".[all]") ok "Hermes installed." # Symlink binary mkdir -p /usr/local/bin ln -sf "${BASE_DIR}/venv/bin/hermes" /usr/local/bin/hermes # Data directory — redirect ~/.hermes here mkdir -p "${BASE_DIR}/data" rm -rf "$HOME/.hermes" 2>/dev/null ln -sfn "${BASE_DIR}/data" "$HOME/.hermes" # Create data subdirs if fresh if [[ ! -d "${BASE_DIR}/data/sessions" ]]; then mkdir -p "${BASE_DIR}/data"/{cron,sessions,logs,memories,skills,pairing,hooks,image_cache,audio_cache} fi if [[ ! -f "${BASE_DIR}/.env" ]]; then cat > "${BASE_DIR}/.env" < "/etc/systemd/system/${UNIT_NAME}.service" < /dev/null 2>&1 systemctl restart "${UNIT_NAME}.service" ok "Systemd unit installed and started: ${UNIT_NAME}.service" # --- Health check --- sleep 3 if hermes doctor > /dev/null 2>&1; then ok "Hermes is healthy." else warn "hermes doctor failed — may need initial setup (API keys)." fi # --- Summary --- echo "" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo -e "${GREEN} Hermes Agent deployed successfully (local)${NC}" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo -e " Data: ${BASE_DIR}/data/" echo -e " Systemd: systemctl status ${UNIT_NAME}" echo -e " Logs: journalctl -u ${UNIT_NAME}.service -f" echo "" echo -e " ${YELLOW}First run? Set up API keys:${NC}" echo -e " hermes setup" echo -e " Then: systemctl restart ${UNIT_NAME}" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo ""