GitHub Backup Sync
Mirror every GitHub repository you can access into local git backups.
Authenticated through the GitHub CLI, so there are no tokens to juggle.
github-backup-sync mirrors every GitHub repository your account can access (owned, collaborator, and organization member) into bare --mirror clones, grouping sources and forks separately.
The project was inspired after watching ThePrimeTime's reminder about GitHub bans, highlighting why local backups matter. Plenty of alternative backup tools exist, but I wanted something simple that relies on the GitHub CLI for authentication so I never have to juggle API tokens directly.
Table of Contents
Features
- Complete mirrors: bare
git clone --mirrorcopies keep every branch, tag, and ref; re-runs update them in place. - Sources and forks apart: forks land in their own directory, or skip them entirely with
--skip-forks. - No token management: the token comes from
gh auth token, so staying logged in to the GitHub CLI is all it takes. - Fast: repositories sync in parallel (
--workers). - Safe pruning: mirrors of repositories that disappeared upstream are listed on every run and only deleted with
--prune. - Flexible: optional working-tree checkouts, Git LFS objects, and archived-repository filtering.
- Single file: one script with a
uv run --scriptshebang, so you can also vendor it anywhere.
Quick Start
gh auth login # once
uvx github-backup-sync --root ~/github-backups # mirror everything
Run the same command again whenever you want to pull in new commits, branches, and repositories.
Requirements
uv(recommended) or any Python 3.12+ environment- GitHub CLI (
gh) authenticated withgh auth login git, plusgit-lfsif you use--lfs- SSH access to GitHub for the default SSH remotes, or pass
--https
Installation
| Method | Command |
|---|---|
| Run without installing | uvx github-backup-sync --help |
| Install as a tool | uv tool install github-backup-sync |
| Install with pip | pip install github-backup-sync |
| Vendor the script | Copy github_backup_sync.py and run ./github_backup_sync.py |
uvx fetches the latest release on each run, while uv tool install puts a github-backup-sync executable on your PATH.
The vendored script installs its own dependencies through the uv run --script shebang.
Usage
# If installed from PyPI
github-backup-sync --root /path/to/mirrors [--https] [--prune] [--skip-forks] [--workers 4]
# From a local checkout
./github_backup_sync.py --root /path/to/mirrors [--https] [--prune] [--skip-forks] [--workers 4]
Options
| Option | Description |
|---|---|
-r, --root PATH |
Backup directory (required, created if missing) |
--https |
Use HTTPS remotes instead of SSH, authenticated through the GitHub CLI |
--prune |
Delete local mirrors that no longer exist upstream; without it they are only listed |
--working-tree / --bare |
Clone with files checked out instead of bare mirrors (default: --bare) |
--include-archived / --exclude-archived |
Whether to back up archived repositories (default: include) |
--skip-forks |
Mirror only non-fork repositories |
--lfs |
Fetch Git LFS objects after mirroring |
-w, --workers N |
Parallel sync operations (default: CPU count, capped at 8) |
--sleep SECONDS |
Delay between repositories |
--limit N |
Process at most N repositories, handy for smoke tests |
CLI Help
github_backup_sync.py --help output
Usage: github_backup_sync.py [OPTIONS]
Mirror every GitHub repository you can access into local git mirrors.
╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --root -r <path> Directory that will hold the mirror │
│ --https Use HTTPS remotes, authenticated │
│ through the GitHub CLI │
│ --include-archived --exclude-archived Include archived repositories │
│ [default: include-archived] │
│ --prune Remove local mirrors that no longer │
│ exist upstream │
│ --lfs Fetch Git LFS objects after │
│ mirroring │
│ --working-tree --bare Clone repositories as working trees │
│ instead of bare mirrors │
│ [default: bare] │
│ --sleep <float range> [x>=0.0] Delay between repositories in │
│ seconds │
│ [default: 0.0] │
│ --limit <int range> [x>=1] Process at most this many │
│ repositories │
│ --skip-forks Ignore forked repositories while │
│ mirroring │
│ --workers -w <int range> [x>=1] Max concurrent mirror operations │
│ (default: based on CPU count) │
│ --help -h Show this message and exit. │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Layout
<root>/source/<owner>/<repo>.git
<root>/forks/<owner>/<repo>.git
This is the default layout when backing up as bare mirrors.
When --working-tree is used, the .git suffix is omitted and each directory contains a checked-out working tree instead.
Working with Mirrors
Repositories under source/ and forks/ are bare --mirror clones, so they do not contain a checked-out working tree unless you opted into --working-tree.
Clone from a mirror path when you want editable files:
git clone /srv/github-backups/source/basnijholt/dotfiles.git ~/dotfiles
You can also inspect a mirror directly without cloning by pointing Git commands at it:
git --git-dir=/srv/github-backups/source/basnijholt/dotfiles.git log --oneline
Automated Backups (cron)
Schedule a daily sync with cron:
0 3 * * * /usr/bin/env uvx github-backup-sync --root /srv/github-backups --https --prune --workers 4 >> /var/log/github-backup-sync.log 2>&1
This example runs every day at 03:00, prunes mirrors of deleted repositories, and logs the output.
The cron user needs uv on its PATH and a logged-in gh.
Adjust the path, flags, and worker count to suit your environment.
License
Metadata
Release files for github-backup-sync 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| github_backup_sync-0.2.1.tar.gz | 12.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| github_backup_sync-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 24.1 kB
Release files / github_backup_sync-0.2.1.tar.gz
| Download URL | github_backup_sync-0.2.1.tar.gz |
|---|---|
| Size | 12.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b6cbaaac78649edc805cce7984851ea6d6d2489fa2baefed455f35193261e3c3
|
|
BLAKE2b-256 checksum How to use checksums |
26411d30ddc4fe49d1130bfb5613987185fdbe55ae17bf87b182283c6c68a304
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / github_backup_sync-0.2.1-py3-none-any.whl
| Download URL | github_backup_sync-0.2.1-py3-none-any.whl |
|---|---|
| Size | 12.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0fd20b35f829d18f58fbc323a595d233c735b2fb830d158d9f4eb7f7f73d1639
|
|
BLAKE2b-256 checksum How to use checksums |
8f6584c0c40833a15d11ce832db26acf3d01f50e0663d198bc57f04ac2591104
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log