Github 2 Forgejo: This script migrates your GitHub repositories to a Forgejo instance using the Forgejo API. It fetches your repositories from GitHub and automatically creates matching repositories on Forgejo. Use it for backups, migrating, or maintaining a self-hosted mirror of your GitHub projects.
Shell
103
5 commits
updated Jun 8, 2026
[!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
mirrorstrategy reliably.
This script is inspired by and based on RGBCube's original version, rewritten 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.
bashcurljqdocker (optional, for development environment)direnv (optional, for automated configuration)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) |
If you want to test the script without setting up a real Forgejo instance, you can use the provided Docker environment.
bats test/validation.bats
Input validation, default values, and settings summary output. No credentials needed.
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:
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.
./setup_and_test.sh # validation only
GITHUB_USER=you GITHUB_TOKEN=ghp_xxx ./setup_and_test.sh # everything
GITHUB_TOKENYou 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.
Settings -> Developer settings -> Personal access tokens ->
Fine-grained tokens.Generate new token.All repositories (or select specific ones).Contents: Read-onlyMetadata: Read-onlyGenerate token.Settings -> Developer settings -> Personal access tokens ->
Tokens (classic).Generate new token (classic).repo.Generate token.FORGEJO_TOKENSettingsApplications on the leftrepository to
Read and Writeexport FORGEJO_TOKEN=<Your token here>GITHUB_IS_ORG).mirror or clone).clone strategy. Forgejo
mirrors cannot be manually archived via the API.MIRROR_DIRECTION=pull): Forgejo periodically fetches updates from GitHub. The GitHub repo is the source of truth.MIRROR_DIRECTION=push): Forgejo pushes local changes back to GitHub. The Forgejo repo is the source of truth.STRATEGY=clone): One-time copy. No ongoing sync.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.
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.
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/>.
Shell
100.0%
Github 2 Forgejo: This script migrates your GitHub repositories to a Forgejo instance using the Forgejo API. It fetches your repositories from GitHub and automatically creates matching repositories on Forgejo. Use it for backups, migrating, or maintaining a self-hosted mirror of your GitHub projects.
Shell
103
5 commits
updated Jun 8, 2026
[!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
mirrorstrategy reliably.
This script is inspired by and based on RGBCube's original version, rewritten 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.
bashcurljqdocker (optional, for development environment)direnv (optional, for automated configuration)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) |
If you want to test the script without setting up a real Forgejo instance, you can use the provided Docker environment.
bats test/validation.bats
Input validation, default values, and settings summary output. No credentials needed.
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:
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.
./setup_and_test.sh # validation only
GITHUB_USER=you GITHUB_TOKEN=ghp_xxx ./setup_and_test.sh # everything
GITHUB_TOKENYou 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.
Settings -> Developer settings -> Personal access tokens ->
Fine-grained tokens.Generate new token.All repositories (or select specific ones).Contents: Read-onlyMetadata: Read-onlyGenerate token.Settings -> Developer settings -> Personal access tokens ->
Tokens (classic).Generate new token (classic).repo.Generate token.FORGEJO_TOKENSettingsApplications on the leftrepository to
Read and Writeexport FORGEJO_TOKEN=<Your token here>GITHUB_IS_ORG).mirror or clone).clone strategy. Forgejo
mirrors cannot be manually archived via the API.MIRROR_DIRECTION=pull): Forgejo periodically fetches updates from GitHub. The GitHub repo is the source of truth.MIRROR_DIRECTION=push): Forgejo pushes local changes back to GitHub. The Forgejo repo is the source of truth.STRATEGY=clone): One-time copy. No ongoing sync.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.
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.
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/>.
Shell
100.0%