Elyndor

Backup every GitHub repo, encrypted,
off-site.

A Go CLI that backs up every repo you can see on GitHub, wraps it in an age-encrypted archive, and uploads it to S3 — or anything S3-compatible. Tiny daily incrementals, a fresh full once a week. Signed and notarized on macOS; named configs for multiple profiles; runs on a schedule via launchd or systemd.

Elyndor
$ curl -fsSL https://dl.velaintelligence.com/elyndor/latest/install.sh | sh

Then: elyndor install · elyndor new · elyndor backup · elyndor service install --schedule @daily

Mirrors everything you can see

User repos and any number of organizations. Public and private. Branches, tags, and full history. Glob include/exclude when you want a subset.

Age-encrypted archives

Opt-in age (scrypt passphrase) wraps either tar.gz or zip. The passphrase must be a ${NAME} reference resolved from env or the macOS Keychain — literals on disk are rejected.

S3, anywhere

AWS, Hetzner, MinIO, Wasabi, DigitalOcean Spaces, Cloudflare R2 — anything S3-compatible. Multipart upload, configurable storage class.

Concurrent & resilient

Worker pool clones in parallel. Exponential backoff with jitter on transient network errors. Atomic temp-then-rename — no half-cloned repos left behind.

Full + daily incrementals

Opt-in incremental: true: a fresh full archive once a week, daily diffs in between that ship only the repos whose HEAD changed. A 67-repo backup goes from ~1.2 GB nightly to a few hundred bytes when nothing changed. Chain-aware retention never deletes a full while its incrementals reference it.

Pure Go, signed binary

One static binary, no external git required at runtime. The macOS build is signed with a Developer ID Application certificate and notarized by Apple — Gatekeeper accepts it on first run, no xattr workaround.

Named configs

elyndor new work drops a new profile next to the default. Each config gets its own archive prefix, its own credentials, its own schedule — keep work, personal, and org backups isolated in one install.

Runs on a schedule

elyndor service install --schedule @daily registers a launchd plist on macOS or a systemd user timer on Linux. Per-config. Logs land alongside each archive so a glance at the directory tells you what happened.

Restore a chain in one command

elyndor restore --name <config> reassembles the full plus every dependent incremental, layering files in order. For one-off use, elyndor decrypt still unwraps a single archive; or reach for age -d when you don't have Elyndor handy.

Any S3-compatible bucket

Custom endpoint + path-style addressing means anything that speaks S3 just works.

AWS S3 Hetzner Object Storage MinIO Wasabi DigitalOcean Spaces Cloudflare R2