- Shell 100%
| github-backup.sh | ||
| README.md | ||
GitHub Backup Script
github-backup.sh backs up repositories from GitHub using the official GitHub CLI (gh) for authentication and repository discovery.
It is designed for account-wide backups with noisy, human-readable output:
- Unicode status symbols and progress bars
- Verbose logging for discovery, cloning, and updates
- Support for GitHub.com and GitHub Enterprise
- Full working-tree checkouts by default
- Optional source-only archive output with all
.gitdirectories removed
What It Does
The script can:
- back up repositories owned by the authenticated account
- back up all repositories the authenticated account can access
- back up repositories owned by a specific user or organization
- update existing local checkouts instead of cloning them again
- optionally create a clean archive that contains only source files
By default, it creates normal checked-out repositories, not bare mirrors.
Why It Uses gh api
The script uses gh api to list repositories instead of relying on gh repo list.
That is intentional:
gh repo listis best for repos owned by one specific user or organizationgh api /user/reposis better for account-wide backup because it can enumerate everything the authenticated account can accessghalready knows how to use saved login state,GH_TOKEN,GITHUB_TOKEN, and GitHub Enterprise settings
Requirements
You need:
bashgitgh(GitHub CLI)tar
Check that the basics exist:
gh --version
git --version
bash --version
tar --version
Authentication
The script uses GitHub CLI authentication.
It supports any of these:
1. Logged-in GitHub CLI session
gh auth login
Then run the script normally.
2. Token via environment variable
export GH_TOKEN=your_token_here
./github-backup.sh --self --all-accessible --dest ./github-backup
GITHUB_TOKEN may also work through gh, but GH_TOKEN is the most explicit choice.
3. Token passed directly to the script
./github-backup.sh --self --all-accessible --token "$GH_TOKEN" --dest ./github-backup
For private repositories, your token needs enough scope to list and clone the repositories you want to back up. For classic personal access tokens on GitHub.com, that usually means repo for private repository access.
Basic Usage
Make the script executable if needed:
chmod +x ./github-backup.sh
Show help:
./github-backup.sh --help
Most Common Command
Back up everything your authenticated account can access:
./github-backup.sh --self --all-accessible --dest ./github-backup
That includes:
- repositories you own
- repositories where you are a collaborator
- repositories available through organizations you belong to
Common Examples
Back up everything your account can access, using four parallel jobs:
./github-backup.sh --self --all-accessible --parallel 4 --dest ./github-backup
Back up only repositories owned by your authenticated account:
./github-backup.sh --self --owned-only --dest ./my-owned-repos
Back up repositories owned by a public user account:
./github-backup.sh --user octocat --dest ./octocat-backup
Back up repositories owned by an organization:
./github-backup.sh --org my-company --dest ./my-company-backup
Back up only public repositories:
./github-backup.sh --self --all-accessible --visibility public --dest ./public-only
Skip archived repositories:
./github-backup.sh --self --all-accessible --exclude-archived --dest ./active-repos
Pass the token directly for a single run:
./github-backup.sh --self --all-accessible --token "$GH_TOKEN" --dest ./github-backup
Output Layout
For working-copy backups, repositories are stored by owner/repo under the destination directory:
github-backup/
├── owner-one/
│ ├── repo-a/
│ └── repo-b/
└── owner-two/
└── repo-c/
This keeps the original GitHub namespace intact and avoids collisions between repositories with the same name owned by different accounts.
Default Behavior
If you run:
./github-backup.sh --self --all-accessible --dest ./github-backup
the script will:
- verify
ghandgitare installed - verify that GitHub authentication is available
- query GitHub for repositories the account can access
- clone missing repositories as normal working copies
- update existing repositories with
git fetch --all --prune - attempt a fast-forward merge on the currently checked-out branch when possible
- ask whether you want a clean source-only archive after the backup succeeds
Clean Source-Only Archive
At the end of a successful working-copy backup, the script prompts:
Create a clean source-only archive without .git directories? [y/N]:
If you answer y, it creates a compressed archive next to the destination folder, for example:
./github-backup-source-only-20260519-143000.tar.gz
The archive:
- preserves the repository folder layout
- excludes all
.gitdirectories - is useful when you want a portable code snapshot without git history
If you do not want the prompt:
./github-backup.sh --self --all-accessible --no-clean-archive-prompt --dest ./github-backup
Updating Existing Repositories
If a repository already exists locally, the script updates it by default.
For normal working copies, it does:
git fetch --all --prune- fast-forward merge of the checked-out branch if it has an upstream configured
If the checked-out branch has no upstream, the script fetches remote changes but does not guess what to merge.
If the repository is in detached HEAD, the script fetches remote changes but leaves the checkout as-is.
If you want to leave existing repositories untouched:
./github-backup.sh --self --all-accessible --skip-existing --dest ./github-backup
Optional Mirror Mode
The script supports bare mirror clones, but that is no longer the default.
Use mirror mode only if you want a git-centric archival backup rather than a checked-out source tree:
./github-backup.sh --self --all-accessible --mirror --dest ./github-mirrors
In mirror mode:
- local folders end in
.git - the script updates them with
git remote update --prune - the clean source-only archive prompt is skipped
Options Reference
Repository scope
--selfUse the authenticated account. This is the default.--user USERNAMEBack up repositories owned by a specific user.--org ORGBack up repositories owned by a specific organization.
Access filters
--owned-onlyIn--selfmode, include only repositories owned by the authenticated account.--all-accessibleIn--selfmode, include owned, collaborator, and organization-member repositories.--visibility all|public|privateFilter by repository visibility.--include-archivedInclude archived repositories.--exclude-archivedSkip archived repositories.
Clone mode and updates
--working-copyCreate normal checked-out repositories. This is the default.--mirrorCreate bare mirror clones ending in.git.--update-existingUpdate repositories that already exist locally. This is the default.--skip-existingSkip repositories that already exist locally.
Execution behavior
--dest DIRDestination directory.--parallel NNumber of repositories to process in parallel.--protocol https|sshClone via HTTPS or SSH.--token TOKENProvide a token directly for this run.--host HOSTGitHub host, for examplegithub.comor a GitHub Enterprise host.--no-clean-archive-promptSkip the final source-only archive prompt.--quietReduce wrapper-level output.--helpShow usage.--versionShow the script version.
HTTPS vs SSH
HTTPS is the default and is usually the easiest choice because it works cleanly with GitHub CLI authentication.
Use SSH if you already have SSH keys configured:
./github-backup.sh --self --all-accessible --protocol ssh --dest ./github-backup
If you use SSH, make sure your keys already work for the target GitHub host.
GitHub Enterprise
You can target GitHub Enterprise by changing the host:
./github-backup.sh --self --all-accessible --host github.example.com --dest ./ghe-backup
If needed, combine that with a token:
./github-backup.sh --self --all-accessible --host github.example.com --token "$GH_ENTERPRISE_TOKEN" --dest ./ghe-backup
Troubleshooting
No usable GitHub authentication found
Fix authentication first:
gh auth login
or:
export GH_TOKEN=your_token_here
Private repositories fail to clone over HTTPS
Try:
gh auth setup-git
Then run the script again.
Existing repositories do not move to the newest commit
That can happen if:
- the current branch has no upstream configured
- the checkout is in detached
HEAD - the local branch cannot be fast-forwarded cleanly
In those cases, the script fetches updates but avoids making destructive guesses.
The script is very noisy
That is intentional. If you want less wrapper output:
./github-backup.sh --self --all-accessible --quiet --dest ./github-backup
Git and GitHub CLI may still print their own progress information.
Safety Notes
- The script is intended for backup/update workflows, not destructive synchronization.
- It does not delete local repositories that disappear from GitHub.
- For working copies, it only fast-forwards the currently checked-out branch when safe.
- It preserves existing repository layout under the destination directory.
Files
- Script: github-backup.sh
- README: README.md
Quick Start
If you just want the shortest path:
gh auth login
./github-backup.sh --self --all-accessible --parallel 4 --dest ./github-backup
Then answer y at the end if you also want a clean source-only archive without .git directories.