GitHub Backup Sync
github_backup_sync.py mirrors every GitHub repository your account can access 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
Requirements
uv(recommended; the shebang runs the script throughuv run)- GitHub CLI (
gh) authenticated withgh auth login gitand, optionally,git-lfsif you use the--lfsflag
The script also works inside a manual virtual environment if you install the same dependencies listed in the script header, but uv provides the fastest startup and dependency management.
Usage
./github_backup_sync.py --root /path/to/mirrors [--https] [--prune] [--skip-forks] [--workers 4]
--rootpoints at the backup directory.--httpsswitches from SSH to HTTPS remotes (still using theghtoken).--pruneremoves local mirrors that no longer exist upstream.--skip-forksmirrors only non-fork repositories.--workerscontrols how many repositories sync in parallel (default: CPU-based).--limitis handy for smoke-testing with only a few repos.
The script automatically fetches a GitHub token from gh auth token, so you only need to keep the CLI logged in.
CLI Help
github_backup_sync.py --help output
Usage: github_backup_sync.py [OPTIONS]
Coordinate the CLI workflow for mirroring repositories.
╭─ Options ──────────────────────────────────────────────────────────────────────────────╮
│ --root -r PATH Directory that will │
│ hold the mirror │
│ --https Use HTTPS remotes │
│ (token optional for │
│ public repos) │
│ --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 │
│ --sleep FLOAT RANGE Delay between │
│ [x>=0.0] repositories in │
│ seconds │
│ [default: 0.0] │
│ --limit INTEGER RANGE Process at most this │
│ [x>=1] many repositories │
│ --skip-forks Ignore forked │
│ repositories while │
│ mirroring │
│ --workers -w INTEGER RANGE Max concurrent │
│ [x>=1] 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
All repositories are bare mirrors, suitable for backup purposes.
Working with Mirrors
Repositories under source/ and forks/ are bare --mirror clones, so they do not contain a checked-out working tree. Clone from a mirror path when you want editable files, for example:
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, e.g. git --git-dir=/srv/github-backups/source/basnijholt/dotfiles.git log --oneline.
Automated Backups (cron)
You can schedule a daily sync via cron after installing the script somewhere on your $PATH:
0 3 * * * /usr/bin/env uv run /opt/github-backup-sync/github_backup_sync.py --root /srv/github-backups --https --prune --workers 4 >> /var/log/github-backup-sync.log 2>&1
This example runs every day at 03:00, performs a prune, and logs output. Adjust the path, flags, and worker count to suit your environment.
Metadata
Release files for github-backup-sync 0.1.0
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.1.0.tar.gz | 8.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| github_backup_sync-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.5 kB
Release files / github_backup_sync-0.1.0.tar.gz
| Download URL | github_backup_sync-0.1.0.tar.gz |
|---|---|
| Size | 8.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8603b0be43b9926d6b7023c9409496e9279bd7361d4091ec7f234249f42e39f7
|
|
BLAKE2b-256 checksum How to use checksums |
1ac56d674b4dc9f60c39a6da0ae394f1086c3083f51b4ba14bbfe4a534c6b90f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 24, 2025.
Transparency logRelease files / github_backup_sync-0.1.0-py3-none-any.whl
| Download URL | github_backup_sync-0.1.0-py3-none-any.whl |
|---|---|
| Size | 8.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a2ac1881cd407a75c783a95e58a859b3b3b5d06be68dd12e88003a723be8c47
|
|
BLAKE2b-256 checksum How to use checksums |
3b128676173346eb35cb2a814fd8144e67f674c359ecde23284fdc7f31cfb857
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 24, 2025.
Transparency log