Automatically mirror all your Forgejo repositories to GitHub or any Forgejo instance
  • Python 77.7%
  • Nix 21.3%
  • Dockerfile 1%
Find a file
2026-02-22 03:48:10 +01:00
src/forgesync add --purge 2026-02-21 12:43:49 +01:00
.envrc init 2025-05-01 21:30:27 +02:00
.gitignore gitignore 2025-11-30 21:50:04 +01:00
.python-version init 2025-05-01 21:30:27 +02:00
Containerfile improvements 2025-08-21 03:34:16 +02:00
flake.lock debloat flake 2026-02-21 13:05:43 +01:00
flake.nix debloat flake 2026-02-21 13:05:43 +01:00
LICENSE init 2025-05-01 21:30:27 +02:00
module.nix fix eval warning 2026-01-29 22:52:59 +01:00
pyproject.toml formatting 2026-02-21 13:06:05 +01:00
README.md fix readme 2026-02-22 03:48:10 +01:00
treefmt.toml debloat flake 2026-02-21 13:05:43 +01:00
uv.lock deps 2026-02-21 13:12:16 +01:00

Forgesync

Forgesync automatically synchronizes all your Forgejo repositories to GitHub or any Forgejo instance (e.g. Codeberg).

While Forgejo supports periodic Git mirroring out of the box, it doesn't support syncing repository metadata, and setting these mirrors up manually can be a lot of work. Forgesync resolves this by:

  • Automatically creating target repositories
  • Syncing repository metadata (descriptions, topics, etc.)
  • Enabling or disabling features like issues or pull requests on the destination
  • Setting up mirrors directly within the source Forgejo instance
  • Filtering out forks, mirrors, and private repositories

⬇️ Getting Forgesync

Forgesync is currently available as:

💻 CLI usage

Here's how you would synchronize your Codeberg repositories to GitHub:

# Token for gathering source repository metadata and setting up mirrors.
export SOURCE_TOKEN=my_codeberg_token

# Token for creating and synchronizing repositories at the destination of your choosing.
export TARGET_TOKEN=my_github_token

# Token used within Forgejo for Git mirroring.
export MIRROR_TOKEN=my_github_mirror_token

# Run the sync:
forgesync https://codeberg.org/api/v1 github \
  --remirror \
  --feature issues \
  --feature pull-requests \
  --on-commit \
  --mirror-interval 8h0m0s \
  --exclude myrepo

Run forgesync --help to see what the example options do, and which ones you can add on top.

Check required token scopes to find out what you need to specify when creating tokens.

⚠️ Back up your destination repositories!

Warning

Before running Forgesync, make sure you have backed up your repositories from the destination if you have any and plan to keep them. Forgesync will overwrite any repositories at the destination that share the same names as those on the source Forgejo instance. For more information, see syncing by name.

Usage via Nix

To use Forgesync in an ephemeral shell, run this:

nix shell git+https://hack.moontide.ink/m64/forgesync.git

NixOS module

First, add the flake input:

{
  inputs = {
    # ...

    forgesync = {
      url = "git+https://hack.moontide.ink/m64/forgesync.git";
      # If you already use any of the inputs listed here, you might want to de-duplicate them.
      # inputs = {
      #   treefmt.follows = "treefmt";
      #   hooks.follows = "hooks";
      #   flake-parts.follows = "flake-parts";
      # };
    };
  };

  # ...
}

Then, configure Forgesync via the module:

{ inputs, ... }:
{
  # Either pass inputs via specialArgs and import the module here, or import the module via lib.nixosSystem.
  imports = [
    inputs.forgesync.nixosModules.default
  ];

  services.forgesync = {
    enable = true;
    jobs.github = {
      source = "https://codeberg.org/api/v1";
      target = "github";

      settings = {
        remirror = true;
        feature = [
          "issues"
          "pull-requests"
        ];
        sync-on-push = true;
        mirror-interval = "0h0m0s";
      };

      # Use whichever secret management you prefer, e.g. agenix.
      secretFile = "/path/to/secrets";

      timerConfig = {
        OnCalendar = "daily";
        Persistent = true;
      };
    };
  };
}

Take a look at the module source for more details.

Container usage

Alternatively, Forgesync can also be run in a container with Podman, Docker, Kubernetes, etc.

Building the container

You can build the container with your favorite image building tool (Podman, Buildah, Docker, etc.).

Example with Podman:

podman build -t localhost/forgesync .

Running the container

The SOURCE_TOKEN, TARGET_TOKEN, and MIRROR_TOKEN tokens must be passed to the container at runtime (-e for Podman/Docker, or as a Kubernetes secret).

Example with Podman:

podman run --rm -it \
  -e SOURCE_TOKEN=my_forgejo_token \
  -e TARGET_TOKEN=my_github_token \
  -e MIRROR_TOKEN=my_github_mirror_token \
  localhost/forgesync https://codeberg.org/api/v1 github \
    --remirror \
    --feature issues \
    --feature pull-requests \
    --on-commit \
    --mirror-interval 8h0m0s \
    --exclude myrepo

Required token scopes

Source token (Forgejo)

Your SOURCE_TOKEN must support:

  • Repository: read + write
  • User data: read + write

Destination token (GitHub or Forgejo)

Your TARGET_TOKEN must support:

  • Repository: read + write
  • User data: read + write

Mirror token

The MIRROR_TOKEN only needs to support:

  • Repository: read + write

For GitHub fine-grained personal access tokens, this means that you will need to check "all repositories" under repository access and enable read and write permissions on repository contents.

🪞 Mirror management

Re-mirroring

Forgejo stores a few bits of information as part of a push mirror, including:

  • The mirror token
  • The mirror interval
  • The "on commit" toggle

There is currently no way to diff these fields via the Forgejo API, so if you want to change any of them, you need to use re-mirroring to recreate the push mirror with the desired configuration.

Syncing by name

Forgesync synchronizes repositories by their names, so a typical setup would look like this:

  • forgejo-user/repo-a → github-user/repo-a
  • forgejo-user/repo-b → github-user/repo-b
  • forgejo-user/repo-c → github-user/repo-c

If you rename repo-a to repo-a-ng, the old push mirror will remain in Forgejo, and it will keep mirroring to github-user/repo-a as well as github-user/repo-a-ng. Forgesync does not track renames or maintain any state about repository history, so it won't detect that the destination no longer matches the source. As a workaround, you can pass --purge to wipe all existing push mirrors from the source repository before creating any new ones.