A simple Bash-based backup engine that mirrors GitHub repositories to local storage using the GitHub API and Git over SSH.
It discovers all repositories accessible to the authenticated GitHub account, including private repositories, and maintains local bare Git mirrors.
- 🔐 GitHub API authentication with a Personal Access Token
- 🔑 SSH-based Git transport
- 📦 Mirrors private and public repositories
- 👥 Includes repositories owned by the user, collaborated on, or accessible through organizations
- 🔄 Incremental updates with
git remote update --prune - 🗑️ Detects repositories that are no longer visible on GitHub without deleting the local backup
⚠️ Keeps broken or invalid existing targets instead of overwriting them- 🔒 Prevents concurrent backup runs
- 📋 Detailed terminal output and rotating log files
- 📊 Backup summary with repository counts, changes, failures, disk usage and duration
- ↩️ Returns meaningful exit codes for automation and scheduled jobs
- Bash 4.4+
- Git
- curl
- jq
- OpenSSH
- A GitHub Personal Access Token
- An SSH key registered with GitHub
The script was designed for Linux/NAS environments and works particularly well as a scheduled backup job.
Create the token file:
mkdir -p ~/.config/github
chmod 700 ~/.config/github
nano ~/.config/github/tokenPut the GitHub Personal Access Token into the file and secure it:
chmod 600 ~/.config/github/tokenMake sure SSH authentication works:
ssh -T git@github.comYou should receive GitHub's successful authentication message.
Then configure the backup destination in github-backup.sh:
BASE="/path/to/backup"Make the script executable:
chmod +x github-backup.shRun it:
./github-backup.shThe script uses two separate authentication methods:
GitHub API → Personal Access Token
The API is used to discover repositories available to the authenticated account.
Git → SSH
Repository data is transferred using each repository's SSH URL. The Personal Access Token is never embedded into Git URLs.
For a new repository, the script performs:
git clone --mirrorFor an existing mirror, it performs an incremental update:
git remote update --pruneThis keeps the local mirror synchronized with the repository's Git refs.
By default, each repository is stored as a bare mirror:
backup/
├── repository-one.git/
├── repository-two.git/
├── repository-three.git/
└── _logs/
├── github-backup-2026-09-28_08-00-00.log
└── ...
If two repositories have the same name under different owners, the owner is added to avoid collisions:
owner1__project.git
owner2__project.git
| Code | Meaning |
|---|---|
0 |
Backup completed successfully |
1 |
Backup completed, but one or more repositories failed |
2 |
Fatal error prevented the backup from completing |
This makes the script suitable for cron, Synology Task Scheduler, monitoring systems, or other automation.
The mirror contains the Git repository itself, including branches, tags and Git objects.
This script does not currently back up GitHub-specific metadata such as:
- Issues
- Pull request discussions
- Releases metadata
- Actions artifacts
- Packages
- Repository settings
- GitHub Wiki
- Git LFS objects unless separately fetched
Those are separate from the Git repository and require additional backup handling.
The GitHub token is read from a local file and passed to curl through stdin rather than being placed in the command line.
Do not commit the token file to Git.
Recommended permissions:
chmod 600 ~/.config/github/tokenMIT