Archive of https://git.etawen.dev/earthonion/github-backup — One-script GitHub account snapshot — repos, wikis, gists, issues/PRs/releases as JSON. Uses gh CLI, no embedded tokens. https://git.etawen.dev/earthonion/github-backup
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
earthonion d07fe5cbfa
README: spell out the three-step push-to-forgejo flow
Was vague about 'create a token'; explicit now: which scope, where to
find the menu (Forgejo + Gitea), where the repos and gists land, and
what to do when a run dies halfway (re-run; mirror-push is
idempotent).
2026-06-02 13:02:52 -04:00
github-backup.sh github-backup.sh: one-script snapshot of a GitHub account 2026-06-02 11:31:08 -04:00
push-to-forgejo.sh add push-to-forgejo.sh — publish the backup to a Forgejo/Gitea instance 2026-06-02 13:01:39 -04:00
README.md README: spell out the three-step push-to-forgejo flow 2026-06-02 13:02:52 -04:00

github-backup

A small bash script that snapshots everything in your GitHub account to a local directory: repos (mirror clones), wikis, gists, issues/PRs/releases/labels/milestones as JSON, your profile, your stars, your followers, your SSH and GPG keys.

Useful for:

  • Periodic offline archival
  • Migrating to a self-hosted forge (Forgejo, Gitea, GitLab — push the mirror clones)
  • Knowing that whatever GitHub decides tomorrow, you have a byte-exact copy today

What it produces

~/github-backup/
├── account/
│   ├── profile.json
│   ├── starred.json          ← repos you've starred (outbound)
│   ├── followers.json
│   ├── following.json
│   ├── watching.json
│   ├── gpg_keys.json
│   ├── ssh_keys.json
│   └── repos.json            ← the list this script iterates over
├── repos/
│   └── <name>.git/           ← bare mirror clone, fetch-on-rerun via `remote update`
├── wikis/
│   └── <name>.wiki.git/
├── gists/
│   └── <gist_id>/
├── meta/
│   └── <name>/
│       ├── issues.json
│       ├── pulls.json
│       ├── releases.json
│       ├── labels.json
│       └── milestones.json
└── backup.log

Requirements

  • gh (GitHub CLI), logged in via gh auth login with the repo and read:user scopes
  • git
  • jq
  • bash, coreutils

That's it. The script reads your username from gh api user --jq .login and uses gh's stored token for both API calls and git clone (via gh's credential helper) — there is no token to paste into this script.

Usage

./github-backup.sh

Re-runs are incremental: existing mirror clones get git remote update --prune, metadata JSONs get overwritten with current state. Safe to put on a cron.

Bandwidth on a re-run is roughly proportional to what's changed since last time, plus a constant ~few MB of JSON.

What it does NOT capture

  • Private notes (only stored client-side in GitHub's UI)
  • Discussions API content (could be added — PRs welcome)
  • Codespaces / Pages build artifacts (out of scope; the source is in the repo mirror)
  • Two-factor recovery codes (regenerate those separately)
  • Outbound social graph at the org level (only your personal account)

Publishing the backup to a Forgejo / Gitea instance

The companion push-to-forgejo.sh script mirror-pushes every repos/*.git/ and gists/*.git/ from the backup to a self-hosted Forgejo (or Gitea) instance.

Three steps

  1. Sign up on the target instance and create a personal access token with the write:repository scope (plus write:organization if you want the gist namespace auto-created). For Forgejo: Settings → Applications → Generate New Token. For Gitea: same path.

  2. Get this repo:

    git clone https://git.example.com/somebody/github-backup.git
    cd github-backup
    
  3. Point the script at your backup + your forge and run it:

    export FORGEJO_HOST=https://git.example.com
    export FORGEJO_USER=yourname            # the namespace repos land under
    export FORGEJO_TOKEN=<the token from step 1>
    ./push-to-forgejo.sh
    

That's it. Every repo lands under git.example.com/yourname/<repo> and gists land under git.example.com/gists/<id> (override the gist org with GIST_ORG=somethingelse).

Idempotency

  • Repos that already exist on the target return 409 from the create call — the script just skips that step and still mirror-pushes, so any commits added since the previous run land.
  • Forgejo's git push --mirror overwrites refs cleanly, so this is safe to cron right after github-backup.sh.

What if it dies halfway

Just re-run. ~/github-backup/push.log records what was attempted; the next run picks up exactly where this one stopped, modulo the few seconds of overlap.

Sister scripts (not yet published)

  • scrub-and-deploy.sh — strip secrets from meta/ JSONs before re-publishing them anywhere
  • upload-and-adopt.sh — register the mirrors with a content-tracking tool

Open an issue if you want either of those.

License

Public domain / CC0. Do whatever you want with it.