[!IMPORTANT]
Mirror mode requires Forgejo v15.0.0+. A bug
where authentication credentials were not properly saved when creating mirrors has been
fixed in Forgejo v15.0.0 LTS.
Please update to v15+ to use the mirror strategy reliably.
This script is inspired by and based on
RGBCube’s original version,
rewritten in Bash.
GitHub ➡️ Forgejo Migration Script in Bash
This is a Bash script for migrating repositories from a GitHub user or
organization account to a specified Forgejo instance. By default, it migrates
all repositories, but you can filter them by using a fine-grained GitHub
token with specific repository access. It supports mirroring or one-time
cloning and includes a cleanup feature for removing repositories on Forgejo
that no longer exist on GitHub.
Features
- Migrates all (or selected) repositories for a GitHub user or organization.
- User or Org Detection: Automatically detects if the account is a User or
Organization.
- Supports selecting public, private, or both repository visibilities.
- Mirror mode: repositories stay in sync with GitHub.
- Clone mode: one-time copy without ongoing sync.
- Archive Transfer: Optionally transfers the archived status so archived
repos remain read-only on Forgejo.
- Skip Forks: Option to ignore forked repositories during migration.
- Visibility Filter: Migrate only private, only public, or all repositories.
- Dry Run Mode: Preview what would happen without making changes.
- Optional cleanup of outdated mirrors on Forgejo.
- Fully terminal-interactive or configurable via environment variables.
Requirements
bash
curl
jq
docker (optional, for development environment)
direnv (optional, for automated configuration)
Usage
1. Manual Approach
You can run the script directly:
./github-forgejo-migrate.sh
You will be prompted for required values unless you provide them via environment
variables:
| Variable |
Description |
GITHUB_USER |
GitHub username or organization name |
GITHUB_IS_ORG |
(Optional) Force account type (Yes/No). Auto-detected if omitted. |
GITHUB_TOKEN |
GitHub access token. Required for private repos or Organizations. |
FORGEJO_URL |
Full URL to your Forgejo instance (e.g., https://forgejo.example.com) |
FORGEJO_USER |
Forgejo username or organization to own the migrated repos |
FORGEJO_TOKEN |
Forgejo personal access token |
STRATEGY |
Either mirror (default) or clone |
MIRROR_DIRECTION |
When mirror: pull (GitHub→Forgejo, default) or push (Forgejo→GitHub) |
PUSH_MIRROR_INTERVAL |
Push mirror sync interval, e.g. 8h, 1h (default: 8h). Push mirrors only |
PUSH_MIRROR_SYNC_ON_COMMIT |
Sync push mirror on every commit (default: Yes). Push mirrors only |
FORCE_SYNC |
Set to Yes to delete Forgejo repos that no longer exist on GitHub |
MIGRATE_ARCHIVE_STATUS |
Set to Yes (default) to transfer the archived status of repositories |
MIGRATE_FORKS |
Set to No to skip fork repositories during migration (default: Yes) |
VISIBILITY |
Filter by visibility: private, public, or both (default: both) |
SORT |
Sort repos by: created, updated, pushed, or full_name (default: pushed) |
SORT_DIRECTION |
Sort direction: asc or desc (default: desc) |
DRY_RUN |
Set to Yes to preview actions without executing (dry run mode, default: No) |
2. Automated Development & Testing Environment
If you want to test the script without setting up a real Forgejo instance, you
can use the provided Docker environment.
Validation Tests (no Docker required)
bats test/validation.bats
Input validation, default values, and settings summary output.
No credentials needed.
E2E Tests (requires Docker + GitHub credentials)
The E2E tests migrate repos from your GitHub account into a throwaway Forgejo
container. They assume your test account has a mix of repo types:
- At least one public and one private repo
- At least one forked repo
- At least one archived repo
GITHUB_USER=youruser GITHUB_TOKEN=ghp_xxx bats test/
Or run individual suites:
bats test/strategy.bats # clone, mirror pull, mirror push
bats test/visibility.bats # public, private, both
bats test/options.bats # dry-run, forks, archive, sort, force-sync
Each file spins up its own Forgejo container and tears it down after.
Full suite
./setup_and_test.sh # validation only
GITHUB_USER=you GITHUB_TOKEN=ghp_xxx ./setup_and_test.sh # everything
Generate GITHUB_TOKEN
You can use either a Fine-grained token (recommended) or a Classic
token.
[!IMPORTANT]
For Organizations: To migrate private repositories belonging to an
organization, your token must have sufficient permissions. For Fine-grained
tokens, ensure the Resource owner is set to the specific organization if
your personal token doesn’t grant access.
Fine-grained Token (Recommended)
- Go to
Settings -> Developer settings -> Personal access tokens ->
Fine-grained tokens.
- Click
Generate new token.
- Set Resource owner to your account.
- Set Repository access to
All repositories (or select specific ones).
- Set Permissions:
Contents: Read-only
Metadata: Read-only
- Click
Generate token.
Classic Token
- Go to
Settings -> Developer settings -> Personal access tokens ->
Tokens (classic).
- Click
Generate new token (classic).
- Select scope:
repo.
- Click
Generate token.
Generate FORGEJO_TOKEN
- Navigate to Forgejo
- Click your profile at the top right
- Click
Settings
- Click
Applications on the left
- Generate a token 5a. Expand the select permissions 5b. Set
repository to
Read and Write
- Either enter when prompted or save to FORGEJO_TOKEN w/
export FORGEJO_TOKEN=<Your token here>
What It Does
- Account Auto-Detection: Checks if the specified GitHub account is a User
or an Organization (can be overridden via
GITHUB_IS_ORG).
- Repository Discovery: Fetches all repositories (or specific ones if using
a restricted token) belonging to the target account.
- Cleanup (Optional): Deletes any Forgejo mirrored repositories that no
longer have a source on GitHub.
- Migration: Migrates each repository to Forgejo using the selected
strategy (
mirror or clone).
- Archive Status (Optional): If enabled, ensures repositories archived on
GitHub are also archived (read-only) on Forgejo after migration.
- Note: This currently only applies to the
clone strategy. Forgejo
mirrors cannot be manually archived via the API.
FAQ
❓ What is the difference between mirroring and cloning?
- Mirror pull (
MIRROR_DIRECTION=pull): Forgejo periodically fetches updates from GitHub. The GitHub repo is the source of truth.
- Mirror push (
MIRROR_DIRECTION=push): Forgejo pushes local changes back to GitHub. The Forgejo repo is the source of truth.
- Cloning (
STRATEGY=clone): One-time copy. No ongoing sync.
❓ Can I migrate specific repositories?
Yes! While the script defaults to migrating all accessible repositories, you can
limit the scope by using a GitHub Fine-grained Personal Access Token. When
creating the token, select “Only select repositories” instead of “All
repositories”. The script will then only see and migrate the repositories you
explicitly selected.
❓ What about the sort order?
The sort order returned by the GitHub API is preserved through the entire
migration pipeline — repos are migrated in the exact order requested. Note that
full_name uses case-sensitive ASCII ordering (e.g. Z sorts before a),
which is a GitHub API limitation.
License
GPL-3.0
Copyright (C) 2024-present
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
评论