Skip to content

gh-to-gitea

Tired of GitHub going down? Automatically mirror your code on a server you own! A simple server built with Elysia.js and Bun that receives a GitHub organization webhook and, whenever a repository is created, creates a matching pull mirror in a Gitea / Forgejo instance automatically.

  1. Install packages

    Terminal window
    bun install
  2. Copy the example env file and fill it in

    Terminal window
    cp .env.example .env
  3. Run it

    Terminal window
    bun run dev # watch mode
    bun run start # once

GET /health reports configuration health: 200 with { "ok": true, "configuration": { "valid": true, "errors": [], "warnings": [] } } when the environment is valid, or 503 with the offending variables listed under configuration.errors. Invalid config is logged at startup but does not crash the server, so this endpoint stays reachable to diagnose it.

  • Deploy once, and leave it to do its job!
  • Click the button below to configure and deploy to Vercel:

Deploy with Vercel

The webhook only mirrors repos created after it is installed. To back-fill every repo that already exists in the org(s) named by GITHUB_ORG, run:

Terminal window
bun run seed

It reads the same .env, lists all repositories in each org via the GitHub API, and mirrors each one with createMirror (identical settings to the webhook). Already-mirrored repos are reported as exists and skipped, so it is safe to re-run. GITHUB_TOKEN is required to include private repositories. This may also be useful if the webhook fails to be sent for whatever reason, and a desync occurs.

Var Required Default Description
PORT no 3000 Local listen port (Vercel injects its own).
GITHUB_ORG no Owner allowlist. Empty accepts any owner the webhook delivers; comma-separated for several.
GITHUB_TOKEN for private repos PAT (repo scope). Sent to Gitea as the clone auth_token so it can pull private sources.
GITHUB_WEBHOOK_SECRET yes HMAC secret verifying X-Hub-Signature-256.
GITEA_URL yes Base URL, e.g. https://gitea.example.com (no /api/v1).
GITEA_TOKEN yes Gitea/Forgejo API token with repo create rights in GITEA_ORG.
GITEA_ORG yes Destination owner for every mirror. May be an org or a user.
MIRROR_INTERVAL no 8h0m0s Pull-mirror sync cadence.
MIRROR_LFS no false Mirror Git LFS objects.
MIRROR_PRIVATE no auto auto matches the source visibility; true/false forces it.
LOG_LEVEL no info Log verbosity.

Add the webhook on the organization (Settings → Webhooks):

  • Payload URL: your server URL + /webhook (e.g. https://host/webhook)
  • Content type: application/json
  • Secret: the same value as GITHUB_WEBHOOK_SECRET
  • Events: select individual events → Repositories

Create the token under a user that can create repositories in GITEA_ORG (repository read/write scope). The mirror is created with mirror: true, so Gitea keeps it in sync with GitHub on the MIRROR_INTERVAL schedule.