A shell script that allows you to download every repository on your GitHub Account in one giant swoop. Can be used for regular backups or for (finally) moving away from GitHub.
Find a file
Florian Schuttkowski 9115a5c921
Publish readme
2026-05-19 14:11:55 +02:00
github-backup.sh Initial commit 2026-05-19 14:09:59 +02:00
README.md Publish readme 2026-05-19 14:11:55 +02:00

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 .git directories 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 list is best for repos owned by one specific user or organization
  • gh api /user/repos is better for account-wide backup because it can enumerate everything the authenticated account can access
  • gh already knows how to use saved login state, GH_TOKEN, GITHUB_TOKEN, and GitHub Enterprise settings

Requirements

You need:

  • bash
  • git
  • gh (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:

  1. verify gh and git are installed
  2. verify that GitHub authentication is available
  3. query GitHub for repositories the account can access
  4. clone missing repositories as normal working copies
  5. update existing repositories with git fetch --all --prune
  6. attempt a fast-forward merge on the currently checked-out branch when possible
  7. 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 .git directories
  • 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

  • --self Use the authenticated account. This is the default.
  • --user USERNAME Back up repositories owned by a specific user.
  • --org ORG Back up repositories owned by a specific organization.

Access filters

  • --owned-only In --self mode, include only repositories owned by the authenticated account.
  • --all-accessible In --self mode, include owned, collaborator, and organization-member repositories.
  • --visibility all|public|private Filter by repository visibility.
  • --include-archived Include archived repositories.
  • --exclude-archived Skip archived repositories.

Clone mode and updates

  • --working-copy Create normal checked-out repositories. This is the default.
  • --mirror Create bare mirror clones ending in .git.
  • --update-existing Update repositories that already exist locally. This is the default.
  • --skip-existing Skip repositories that already exist locally.

Execution behavior

  • --dest DIR Destination directory.
  • --parallel N Number of repositories to process in parallel.
  • --protocol https|ssh Clone via HTTPS or SSH.
  • --token TOKEN Provide a token directly for this run.
  • --host HOST GitHub host, for example github.com or a GitHub Enterprise host.
  • --no-clean-archive-prompt Skip the final source-only archive prompt.
  • --quiet Reduce wrapper-level output.
  • --help Show usage.
  • --version Show 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

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.