Skip to content

ADR-0008 · VPS Agnosticism + Disaster Recovery

We chose to architect the Agent Host so that Hermes can be migrated to any Ubuntu VPS provider within hours, with at most one day of Hermes state loss.

The Agent Host runs on Hostinger VPS KVM2. If Hostinger has an outage, a breach, a pricing change, or goes bankrupt, we need to be back online quickly without losing Hermes’s memory, skills, sessions, or configuration.

All Hermes state is backed up daily as encrypted archives to a private personal GitHub repository, plus weekly to the External HD. A fresh VPS can be provisioned and restored in under 2 hours without storing readable Hermes memory or secrets in GitHub.

What lives ONLY on the VPS (and must be backed up)

Section titled “What lives ONLY on the VPS (and must be backed up)”
DataPathCriticalityRecovery without backup
Sessions DB (conversation history)~/.hermes/sessions.dbHigh — irreplaceable memories❌ Lost forever
Skills (learned procedures)~/.hermes/skills/High — weeks of learning❌ Must re-learn from scratch
Memory (cross-session knowledge)~/.hermes/memory.dbHigh — agent’s long-term knowledge❌ Lost forever
Config~/.hermes/config.yamlMedium — can be recreated from docs✅ Recreate from ADRs + SOUL.md
Cron jobs~/.hermes/cron/Medium — recreatable✅ Recreate from docs
SOUL.md + CONTEXT.md~/.hermes/SOUL.md, ~/.hermes/CONTEXT.mdLow — source is GitHub repo✅ Copy from GitHub
.env (secrets)~/.hermes/.envLow — source is Bitwarden✅ Re-copy from Bitwarden
DataWhereWhy it’s safe
Architecture docs (ADRs, diagrams, glossary)GitHub repoGit history, redundant
Knowledge Vault (PARA notes, docs)Google DriveGoogle’s infrastructure
Code ProjectsGitHub reposGit history, redundant
API keys & passwordsBitwardenCloud-synced, encrypted
Static sitesCloudflare PagesBuilt from GitHub, stateless

Run this with an OS-level systemd timer, not Hermes cron. Backups must continue even if the Hermes Agent or its scheduler is broken.

Terminal window
# Run daily from the Agent Host — backs up critical Hermes state, excluding secrets
BACKUP_DATE="$(date +%Y%m%d)"
BACKUP_DIR="/tmp/hermes-backup-$BACKUP_DATE"
ARCHIVE="/tmp/hermes-backup-$BACKUP_DATE.tar.gz"
ENCRYPTED="/tmp/hermes-backup-$BACKUP_DATE.tar.gz.age"
mkdir -p "$BACKUP_DIR"
# Sessions, memory, skills (the irreplaceable data)
cp ~/.hermes/sessions.db "$BACKUP_DIR/"
cp ~/.hermes/memory.db "$BACKUP_DIR/"
cp -r ~/.hermes/skills/ "$BACKUP_DIR/skills/"
cp -r ~/.hermes/cron/ "$BACKUP_DIR/cron/"
cp ~/.hermes/config.yaml "$BACKUP_DIR/"
# Compress, encrypt, and push only ciphertext
tar -czf "$ARCHIVE" -C "$BACKUP_DIR" .
age -r "<AGE_PUBLIC_RECIPIENT>" -o "$ENCRYPTED" "$ARCHIVE"
cp "$ENCRYPTED" ~/hermes-backups/
cd ~/hermes-backups
git add "$(basename "$ENCRYPTED")"
git commit -m "backup: Hermes state $BACKUP_DATE"
git push
rm -rf "$BACKUP_DIR" "$ARCHIVE" "$ENCRYPTED"

The private GitHub backup repo uses a fine-grained PAT scoped only to that single repo with contents write permission. The repo must contain only encrypted .age archives — never .env, plaintext SQLite databases, OAuth tokens, API keys, SSH keys, or unencrypted tarballs.

Once per week, copy the latest encrypted archive from the private GitHub backup repo to the External HD. Keep the External HD as the offline recovery path if GitHub, the PAT, or the account is unavailable.

The age private identity is stored in the Credential Vault, not on GitHub. To decrypt a backup on a fresh machine:

Terminal window
# 1. Retrieve age private identity from Bitwarden into a local file
chmod 600 hermes-backup-age-key.txt
# 2. Decrypt the selected archive
age -d -i hermes-backup-age-key.txt \
-o hermes-backup-YYYYMMDD.tar.gz \
hermes-backup-YYYYMMDD.tar.gz.age
# 3. Extract for inspection or restore
mkdir -p /tmp/hermes-restore
tar -xzf hermes-backup-YYYYMMDD.tar.gz -C /tmp/hermes-restore
# 4. Restore after Hermes is installed and stopped
systemctl --user stop hermes-gateway
cp /tmp/hermes-restore/sessions.db ~/.hermes/sessions.db
cp /tmp/hermes-restore/memory.db ~/.hermes/memory.db
rsync -a /tmp/hermes-restore/skills/ ~/.hermes/skills/
rsync -a /tmp/hermes-restore/cron/ ~/.hermes/cron/
cp /tmp/hermes-restore/config.yaml ~/.hermes/config.yaml
systemctl --user start hermes-gateway

Restore .env separately from Bitwarden. Never restore secrets from the backup repo because secrets should not be there.

Scenario: Hostinger VPS is gone. Rebuild on a new provider.

StepActionTimeSource
1Provision new Ubuntu VPS (any provider: Hetzner, DigitalOcean, Scaleway, OVH…)10 minAny KVM provider, €5-10/mo
2Run Hermes quickstart installer8 minFlipZ3ro/hermes-quickstart or pipx install hermes-agent
3Copy .env secrets from Bitwarden5 minBitwarden vault
4Decrypt and restore sessions.db, memory.db, skills/ from latest backup10 minPrivate GitHub backup repo or External HD
5Copy SOUL.md + CONTEXT.md from GitHub repo2 mingit clone fducat18/docs.ducatillon.net
6Configure rclone (Google Drive OAuth2)15 minGoogle Cloud Console
7Install cloudflared, update Cloudflare Tunnel to point to new IP10 minCloudflare dashboard
8Update Telegram webhook URL (via Cloudflare Tunnel — same domain, no change needed)0 minAutomatic — tunnel handles routing
9Verify: send Telegram message, check dashboard5 min
Total~60 min
  1. No vendor-specific features — Hermes runs on any Linux with Python 3.11. No Hostinger-specific APIs or tools.
  2. Cloudflare Tunnel abstracts the IP — hermes.ducatillon.net points to a tunnel, not an IP. Changing VPS = updating tunnel config, not DNS.
  3. Secrets in Bitwarden, not only on VPS — .env can be reconstructed from the vault.
  4. Architecture docs in GitHub — SOUL.md and CONTEXT.md are versioned and recoverable.
  5. The only truly VPS-bound data is the SQLite DBs — and those get daily encrypted backups.
  • RTO (Recovery Time Objective): ~1 hour (provision + restore + verify)
  • RPO (Recovery Point Objective): 1 day for Hermes state (daily encrypted GitHub backup), 1 week for offline recovery (External HD). Acceptable for personal use — worst case, you lose one day of conversations and any new skills learned that day.
TriggerImprovement
Hermes becomes business-critical (Djuly’s venture depends on it)Add a second encrypted cloud backup target (e.g., Backblaze B2, ~€0.50/mo)
Multiple agents or high-availability neededDocker Compose + volume mounts → portable container image
Regulatory requirementFull audit trail + geo-redundant backup