| name | nightly-backup |
| description | Run `hermes backup`, encrypt, upload to remote storage, prune old backups |
| when_to_use | ["Scheduled nightly via cron","User requests an explicit backup","Before a risky config change"] |
| toolsets | ["terminal","file"] |
| parameters | {"remote":{"type":"string","description":"Remote target. Supports s3://bucket/prefix, b2://bucket/prefix, ssh://user@host:/path, or \"local\"","default":"local"},"retain_days":{"type":"integer","default":30}} |
| security | {"trust":"trusted","notes":"No untrusted input. Handles the backup archive and encryption key\nmaterial — never logs key contents; the age identity/passphrase must\nlive outside this host's .env (see Security notes below).\n"} |
| model_hint | google/gemini-3.7-flash |
nightly-backup — Hermes Backup Automation
Thin wrapper around hermes backup + encryption + optional remote upload + retention.
Procedure
-
Snapshot. Run:
STAMP=$(date +%Y%m%d-%H%M%S)
hermes backup -o /tmp/hermes-backup-$STAMP.zip
hermes backup writes a zip of the Hermes home (config, skills,
sessions, memory, cron jobs, logs) per Part 16.
-q/--quick takes a fast snapshot of the critical state files instead.
-
Encrypt. Non-interactively — this runs from cron, so nothing may prompt.
Route A — age with a keyfile (preferred):
age-keygen -o ~/.age-backup-key && chmod 600 ~/.age-backup-key
age -r "$(age-keygen -y ~/.age-backup-key)" \
-o /tmp/hermes-backup-$STAMP.tar.age /tmp/hermes-backup-$STAMP.tar
(age -p is interactive passphrase mode — it cannot run from cron.)
Route B — gpg symmetric with a passphrase:
BACKUP_PASSPHRASE="${BACKUP_PASSPHRASE:-}"
gpg --batch --yes --symmetric --cipher-algo AES256 \
--passphrase "$BACKUP_PASSPHRASE" \
/tmp/hermes-backup-$STAMP.zip
(There is no hermes secrets get for plain env vars — hermes secrets
only manages Bitwarden/1Password vault sources. Read the passphrase from
the environment instead, or pull it from a vault at runtime.)
Then either way:
shred -u /tmp/hermes-backup-$STAMP.zip
Cron wiring
Jobs live in ~/.hermes/cron/jobs.json and are managed via hermes cron create — the old cron.yaml list format was removed upstream:
hermes cron create "0 3 * * *" \
"Run the nightly-backup skill with remote=s3://my-backups/hermes/ retain_days=30" \
--skill nightly-backup --name nightly-backup --deliver telegram
Security notes
hermes backup archives the Hermes home — as of v0.20 the --quick
snapshot explicitly includes .env and auth.json. Do not assume the
archive excludes secrets: inspect what ships (unzip -l backup.zip),
and treat the encryption step above as the real protection at rest.
- The decryption secret must live outside this host's
.env — Route A: keep an offline copy of ~/.age-backup-key (that file is the only way back into your archives); Route B: keep BACKUP_PASSPHRASE in a separate secret store. Otherwise a stolen Hermes host gets both the backups and the key to them.
- Rotate the backup key/passphrase yearly with
skills/security/rotate-secrets, then re-encrypt (or at least re-verify) the archives you still need.