#!/bin/bash # hermes-backup.sh — Full Hermes backup to Wasabi S3 # Backs up everything needed to restore Hermes on a new server: # - Config, env, auth, state, sessions, profiles, skills, scripts # - SSH keys, AWS creds, mail passwords # - Systemd service files # Also creates a restore script in the backup. set -euo pipefail # Activate AWS CLI from venv (DR FIX 2026-07-11 — backup was failing with "aws: command not found" in cron) if [ -f /opt/awscli-venv/bin/activate ]; then source /opt/awscli-venv/bin/activate fi # Use root disk, not tmpfs — /tmp is 958M tmpfs that fills up BACKUP_DIR="/root/.hermes/.backups/hermes-backup-$(date +%F)" DEST_DIR="hermes-full-backup" DATE_TAG=$(date +%F) TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # --- Wasabi config --- ENDPOINT="https://s3.us-east-1.wasabisys.com" BUCKET="hermes-vps-backups" # S3 bucket for Hermes config backups # Credentials from ~/.aws/credentials HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" # Activate AWS CLI venv for cron runs (cron has minimal PATH) if [ -f /opt/awscli-venv/bin/activate ]; then source /opt/awscli-venv/bin/activate fi echo "[+] Creating backup at $BACKUP_DIR" rm -rf "$BACKUP_DIR" mkdir -p "$BACKUP_DIR/config" mkdir -p "$BACKUP_DIR/ssh" mkdir -p "$BACKUP_DIR/himalaya" mkdir -p "$BACKUP_DIR/aws" mkdir -p "$BACKUP_DIR/systemd" # --- Core Hermes config --- cp "$HERMES_HOME/config.yaml" "$BACKUP_DIR/config/" cp "$HERMES_HOME/.env" "$BACKUP_DIR/config/" cp "$HERMES_HOME/auth.json" "$BACKUP_DIR/config/" 2>/dev/null || true # --- State database + sessions --- echo "[+] Backing up state.db (${s:-large})..." cp "$HERMES_HOME/state.db" "$BACKUP_DIR/" 2>/dev/null || echo "[!] No state.db" cp -r "$HERMES_HOME/sessions" "$BACKUP_DIR/sessions" 2>/dev/null || echo "[!] No sessions dir" # --- Skills --- echo "[+] Backing up skills..." cp -r "$HERMES_HOME/skills" "$BACKUP_DIR/skills" 2>/dev/null || echo "[!] No skills" # --- Scripts --- echo "[+] Backing up scripts..." cp -r "$HERMES_HOME/scripts" "$BACKUP_DIR/scripts" 2>/dev/null || echo "[!] No scripts" # --- Profiles (Anita etc.) --- if [ -d "$HERMES_HOME/profiles" ]; then echo "[+] Backing up profiles..." # Exclude bulky cache/sandbox dirs rsync -a --exclude 'audio_cache' --exclude 'image_cache' --exclude 'cache' --exclude 'sandboxes' --exclude 'bin' "$HERMES_HOME/profiles" "$BACKUP_DIR/profiles" 2>/dev/null || true fi # --- Cron jobs --- cp -r "$HERMES_HOME/cron" "$BACKUP_DIR/cron" 2>/dev/null || echo "[!] No cron dir" # --- Other JSON state files --- for f in channel_directory.json gateway_state.json processes.json; do [ -f "$HERMES_HOME/$f" ] && cp "$HERMES_HOME/$f" "$BACKUP_DIR/config/" done # --- SSH keys --- for f in "$HOME/.ssh/wisp_rsa" "$HOME/.ssh/wisp_rsa.pub"; do [ -f "$f" ] && cp "$f" "$BACKUP_DIR/ssh/" done # --- Mail passwords --- if [ -d "$HOME/.config/himalaya" ]; then cp -r "$HOME/.config/himalaya" "$BACKUP_DIR/himalaya/" fi # --- AWS/Wasabi credentials --- if [ -f "$HOME/.aws/credentials" ]; then cp "$HOME/.aws/credentials" "$BACKUP_DIR/aws/" fi # --- Systemd service files --- # DR FIX: corrected path from ~/.config/systemd/user to /etc/systemd/system (2026-07-08) if [ -d "/etc/systemd/system" ]; then mkdir -p "$BACKUP_DIR/systemd" cp /etc/systemd/system/hermes*.service "$BACKUP_DIR/systemd/" 2>/dev/null || true cp /etc/systemd/system/mysql-tunnel.service "$BACKUP_DIR/systemd/" 2>/dev/null || true cp /etc/systemd/system/ollama.service "$BACKUP_DIR/systemd/" 2>/dev/null || true cp /etc/systemd/system/shark-game.service "$BACKUP_DIR/systemd/" 2>/dev/null || true fi # --- Caddyfile --- # DR FIX: added /etc/caddy/Caddyfile backup (2026-07-08) if [ -f "/etc/caddy/Caddyfile" ]; then mkdir -p "$BACKUP_DIR/caddy" cp /etc/caddy/Caddyfile "$BACKUP_DIR/caddy/" fi # --- Migration credentials --- # DR FIX: added migration-creds.txt backup (2026-07-08) if [ -f "$HERMES_HOME/migration-creds.txt" ]; then mkdir -p "$BACKUP_DIR/config" cp "$HERMES_HOME/migration-creds.txt" "$BACKUP_DIR/config/migration-creds.txt" fi # --- Generate restore script --- cat > "$BACKUP_DIR/restore.sh" << 'RESTOREEOF' #!/bin/bash # restore.sh — Restore Hermes from this backup # Run from the backup directory on the target server set -euo pipefail HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" echo "[+] Creating Hermes home..." mkdir -p "$HERMES_HOME" echo "[+] Restoring config..." cp config.yaml "$HERMES_HOME/" cp .env "$HERMES_HOME/" 2>/dev/null || true cp auth.json "$HERMES_HOME/" 2>/dev/null || true echo "[+] Restoring state.db..." cp state.db "$HERMES_HOME/" 2>/dev/null || echo "[!] No state.db in backup" echo "[+] Restoring sessions..." cp -r sessions "$HERMES_HOME/" 2>/dev/null || true echo "[+] Restoring skills..." cp -r skills "$HERMES_HOME/" 2>/dev/null || true echo "[+] Restoring scripts..." cp -r scripts "$HERMES_HOME/" 2>/dev/null || true echo "[+] Restoring cron..." cp -r cron "$HERMES_HOME/" 2>/dev/null || true echo "[+] Restoring profiles..." cp -r profiles "$HERMES_HOME/" 2>/dev/null || true echo "[+] Restoring JSON state..." for f in channel_directory.json gateway_state.json processes.json; do [ -f "config/$f" ] && cp "config/$f" "$HERMES_HOME/$f" done echo "[+] Restoring SSH keys..." mkdir -p "$HOME/.ssh" cp ssh/* "$HOME/.ssh/" 2>/dev/null || true echo "[+] Restoring mail config..." cp -r himalaya "$HOME/.config/himalaya" 2>/dev/null || true echo "[+] Restoring AWS/Wasabi creds..." mkdir -p "$HOME/.aws" cp aws/credentials "$HOME/.aws/" 2>/dev/null || true echo "[+] Restoring systemd services..." # DR FIX: corrected path from ~/.config/systemd/user to /etc/systemd/system (2026-07-08) if [ -d systemd ]; then sudo mkdir -p /etc/systemd/system sudo cp systemd/* /etc/systemd/system/ 2>/dev/null || true sudo systemctl daemon-reload 2>/dev/null || true fi echo "[+] Restoring Caddyfile..." # DR FIX: added Caddyfile restore (2026-07-08) if [ -f caddy/Caddyfile ]; then sudo mkdir -p /etc/caddy sudo cp caddy/Caddyfile /etc/caddy/ fi echo "[+] Restoring migration credentials..." # DR FIX: added migration-creds.txt restore (2026-07-08) if [ -f config/migration-creds.txt ]; then cp config/migration-creds.txt "$HERMES_HOME/migration-creds.txt" chmod 600 "$HERMES_HOME/migration-creds.txt" fi echo "" echo "=== Restore complete! ===" echo "Next steps:" echo " 1. Run: hermes gateway restart" echo " 2. Verify: hermes gateway status" echo " 3. Check sessions: hermes sessions list" echo " 4. Verify Anita's gateway if applicable" RESTOREEOF chmod +x "$BACKUP_DIR/restore.sh" # --- Write a manifest --- cat > "$BACKUP_DIR/MANIFEST.txt" << EOF Hermes Full Backup Date: $TIMESTAMP Hermes home: $HERMES_HOME Contents: - config.yaml, .env, auth.json (core config) - migration-creds.txt (migration credentials) - state.db + sessions/ (full session history) - skills/ (all installed skills) - scripts/ (custom scripts) - profiles/ (Anita and any other profiles) - cron/ (scheduled job definitions) - ssh/ (WISP SSH keys) - himalaya/ (mail passwords) - aws/ (Wasabi/AWS credentials) - systemd/ (service files from /etc/systemd/system/) - caddy/ (Caddyfile from /etc/caddy/) - restore.sh (self-contained restore script) EOF echo "[+] Creating archive..." ARCHIVE="/root/.hermes/.backups/hermes-full-backup-$DATE_TAG.tar.gz" cd "$(dirname "$BACKUP_DIR")" && tar czf "$ARCHIVE" "$(basename "$BACKUP_DIR")" echo "[+] Uploading to Wasabi bucket..." aws s3 cp "$ARCHIVE" "s3://$BUCKET/$DEST_DIR/hermes-full-backup-$DATE_TAG.tar.gz" \ --endpoint-url "$ENDPOINT" echo "[+] Uploading manifest..." aws s3 cp "$BACKUP_DIR/MANIFEST.txt" "s3://$BUCKET/$DEST_DIR/hermes-full-backup-$DATE_TAG-manifest.txt" \ --endpoint-url "$ENDPOINT" --content-type text/plain echo "[+] Cleaning up..." rm -rf "$BACKUP_DIR" "$ARCHIVE" echo "" echo "=== Backup complete! ===" echo "S3: s3://$BUCKET/$DEST_DIR/hermes-full-backup-$DATE_TAG.tar.gz" echo "Size: $(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1 || echo 'N/A')"