Skip to main content

hermes-backup-sync

Back up configurable paths to a private GitHub repo with LFS and age encryption. Supports dry run, cron triggers, and a manifest-verify round trip.

Quellinformationen

Repository
neoalienson/skills
Letzte Quellaktivität
28. September 2026 um 16:29
Erkannte Sprache von SKILL.md
Englisch
Sterne
0
Forks
4

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
5 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
hermes-backup-sync
description
Back up configurable paths to a private GitHub repo with LFS and age encryption. Supports dry run, cron triggers, and a manifest-verify round trip.
version
0.1.0
author
Neoalienson, Hermes Agent
license
MIT
platforms
["linux","macos"]
metadata
{"hermes":{"tags":["backup","github","lfs","encryption","cron","sync","devops"],"related_skills":[]}}
# Backup Sync Pushes a configurable subset of a local data tree to a **private** GitHub repo. Sensitive files are age-encrypted in-transit; large binaries ride on Git LFS; every run emits a `MANIFEST.json` you can diff on restore. Pure bash + small Python helpers, safe for `cronjob` (no LLM tokens burned). ## When to Use - Nightly/hourly snapshot of `/opt/data` (or any tree) into a private GitHub repo you control. - You want diff-able, browse-able config backups — `config.yaml` etc. are plain text in the repo so you can grep history. - You need secrets encrypted before leaving the box but still restorable. - A `cronjob` skill invocation needs to be safe to run unattended. ## Don't Use For - Database-only backups (use `pg_dump` / `sqlite3 .backup` instead — this skill snapshots files). - Pushing to a **public** repo. Refused at runtime. - Backing up private keys. Encrypt first with a separate age identity; do not commit that identity to the backup repo. ## Prerequisites Install once on the host: ```bash # Debian/Ubuntu sudo apt-get install -y git-lfs age jq rsync python3 git lfs install # macOS brew install git-lfs age jq rsync python3 git lfs install ``` Create an age keypair (or reuse one). The script auto-generates one if `AGE_RECIPIENT` is unset, but reusing an existing key is recommended: ```bash mkdir -p ~/.config/hermes-backup-sync age-keygen -o ~/.config/hermes-backup-sync/key.txt # public key printed to stdout export AGE_RECIPIENT="age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Optionally also keep the private key for restore: # cp ~/.config/hermes-backup-sync/key.txt somewhere safe (NOT the backup repo) ``` GitHub side: 1. Create a **private** repo (e.g. `hermes-backup-sync-store`). 2. Create a fine-grained PAT scoped to that one repo with **Contents: Read & Write** + **LFS: Read & Write** (or classic `repo` if you must). 3. Export: ```bash export GITHUB_TOKEN="ghp_..." export GITHUB_USER="your-handle" ``` ## Configuration All behaviour is driven by `config.yml` next to the script (path overridable via `HERMES_BACKUP_SYNC_CONFIG`). A complete example: ```yaml # config.yml — hermes-backup-sync data_path: /opt/data # root tree to back up workdir: /var/tmp/hermes-backup-sync # local clone + staging area remote: https://github.com/<user>/hermes-backup-sync-store.git branch: main # What to copy (relative to data_path). Each entry is either a directory # (copied recursively) or a file (copied verbatim). include: - config.yaml - SOUL.md - skills - memories - cron - home # Patterns to exclude (rsync globs, matched against any path component). exclude: - ".cache/**" - ".npm/**" - "node_modules/**" - "*.lock" - "__pycache__/**" - "*.pyc" - ".venv/**" - "audio_cache/**" - "image_cache/**" - "cache/**" - ".curator_backups/**" # Files to encrypt with age before pushing. The plaintext NEVER lands in # the repo. Only `*.age` files do. Keys listed here must match files that # would otherwise be copied verbatim. encrypt: - .env - auth.json - install_id # File globs routed through git-lfs. Keep this narrow; LFS is not free. lfs: patterns: - "*.bin" - "*.sqlite" - "*.db" - "*.tar" - "*.tgz" - "*.zip" # Public-repo safety check (uses GITHUB_TOKEN). Disable only if you # truly know what you're doing. verify_remote_private: true # Cron / retention: keep the last N local snapshots, prune older. retention: keep_local: 7 # Optional notifications — webhook URL or a script to invoke. notify: on_success: false on_failure: true telegram: bot_token_env: TELEGRAM_BOT_TOKEN chat_id_env: TELEGRAM_HOME_CHAT_ID script: null # e.g. /opt/data/scripts/notify.sh # Dry run does no writes, no push, no encryption — just prints the plan. dry_run: false ``` ## How to Run ```bash # From a Hermes terminal: terminal(command="bash {baseDir}/scripts/hermes-backup-sync.sh", timeout=300) # Dry run (no writes, no push): terminal(command="bash {baseDir}/scripts/hermes-backup-sync.sh --dry-run", timeout=60) # Verify a remote snapshot against local source: terminal(command="bash {baseDir}/scripts/hermes-backup-sync.sh verify", timeout=120) # Prune old local snapshots: terminal(command="bash {baseDir}/scripts/hermes-backup-sync.sh prune --keep 7", timeout=30) # Restore (decrypts into a target dir): terminal(command="bash {baseDir}/scripts/hermes-backup-sync.sh restore --to /restore/path", timeout=300) ``` > **Recovery flow** — see [`README.md` → Restoring From a Backup](./README.md#restoring-from-a-backup) for the full step-by-step. SKILL.md is for the agent; README.md is for you. ## Quick Reference | Command | Effect | |---|---| | `hermes-backup-sync.sh` | Sync per config | | `hermes-backup-sync.sh --dry-run` | Plan only, no writes | | `hermes-backup-sync.sh verify` | Compare local vs remote manifest | | `hermes-backup-sync.sh prune --keep N` | Keep N most recent snapshots | | `hermes-backup-sync.sh restore --to DIR` | Clone + decrypt into DIR | | `hermes-backup-sync.sh init` | One-time setup: LFS track, age recipient | ### Cron wiring ```bash # Place the script where your Hermes cron can find it: cp scripts/hermes-backup-sync.sh /opt/data/cron/scripts/ cronjob action=create \ name=hermes-backup-sync \ schedule="0 16 * * *" \ script="hermes-backup-sync.sh" \ no_agent=true \ deliver=local ``` ## Procedure 1. **Pre-flight**: load config; check `git-lfs`, `age`, `jq`, `rsync`, `python3`; ensure `GITHUB_TOKEN` and `AGE_RECIPIENT` resolve. Hard-fail otherwise. 2. **Visibility check**: GET `https://api.github.com/repos/<owner>/<repo>`; abort if `private != true` (unless `verify_remote_private: false`). 3. **Clone or pull** the remote into `workdir`, branch checked out, `chmod 700`. 4. **Stage**: rsync each `include` entry from `data_path` into the workdir, applying `exclude` and `lfs.patterns`. Tag LFS paths so the next `git add` uploads via LFS. 5. **Encrypt**: for each path in `encrypt:`, age-encrypt into `<path>.age`; remove the plaintext from the staging area. 6. **Manifest**: write `MANIFEST.json` with `{path: {sha256, size, mode, lfs, encrypted}}`. 7. **Commit + push**: `git add -A && git commit -m "hermes-backup-sync: <UTC ts>" && git push origin HEAD`. If `--dry-run`, stop here. 8. **Notify**: on success/failure per `notify:` policy. 9. **Retention**: prune `workdir/_snapshots/` (one timestamped copy kept per run) to `keep_local` entries. Completion criterion: each numbered step has a single side effect you can grep for (`[OK] visibility=private`, `[OK] staged N paths`, `[OK] committed <sha>`, `[OK] pushed to <remote>`, `[OK] notify=...`). `--dry-run` prints the same lines with `[DRY]` prefix. ## Pitfalls - **Public-repo push is refused.** If you accidentally point at a public repo, the script exits non-zero before staging. Set `verify_remote_private: false` only on private networks where you've already verified by hand. - **PAT in `~/.git/config`.** The remote URL embeds the token. `workdir` is `chmod 700`; don't move it to `/tmp` on shared hosts — use `workdir:` under your home. - **LFS quota.** GitHub gives 1 GB free LFS bandwidth/month; large binary backups will burn it. Keep `lfs.patterns` narrow, or use S3 + rclone instead. - **SQLite WAL files.** `.db-shm` / `.db-wal` are silently excluded; on restore the database may need a `sqlite3 foo.db ".recover"` if it was mid-transaction. Prefer `sqlite3 .backup` for critical DBs. - **Age key loss = data loss.** The repo holds `*.age` files, useless without the private key. Store `~/.config/hermes-backup-sync/key.txt` somewhere *outside* the backup repo (password manager, USB, etc.). - **Idempotency.** Each run creates a timestamped commit; the remote grows ~1 commit per run. Use `prune` for local snapshots; remote history needs a separate GC if you care. - **`--dry-run` skips encryption.** If your config has typos in `encrypt:`, you won't notice until a real run. Read the `[DRY] would encrypt` line. ## Verification - **After every run:** `git log -1 --oneline` in `workdir` shows the new commit; `[OK] pushed` line in the log; `MANIFEST.json` updated. - **Round trip:** `verify` subcommand downloads the latest remote manifest and prints per-file `OK`/`MISSING`/`MODIFIED` against local. - **Cron proof:** the `cronjob` entry below invokes the script with `no_agent: true`; the next scheduled fire produces a commit within `timeout`.
Auf GitHub ansehen