Tired of GitHub going down? I was, so I made a (kind of) solution! https://docs.mikn.dev/solutions/developers/gh-to-gitea/
  • TypeScript 96.1%
  • Dockerfile 3.9%
Find a file
2026-08-21 01:19:15 +09:00
.github/workflows fix: MDCR login 2026-08-18 12:34:48 +09:00
.idea init 2026-08-18 12:10:07 +09:00
scripts init 2026-08-18 12:10:07 +09:00
src feat: Docs redirect on root 2026-08-18 12:57:05 +09:00
.env.example init 2026-08-18 12:10:07 +09:00
.gitignore init 2026-08-18 12:10:07 +09:00
.oxfmtrc.json init 2026-08-18 12:10:07 +09:00
bun.lock init 2026-08-18 12:10:07 +09:00
Dockerfile init 2026-08-18 12:10:07 +09:00
package.json init 2026-08-18 12:10:07 +09:00
README.md fix: Typo in Vercel env 2026-08-18 12:51:41 +09:00
tsconfig.json init 2026-08-18 12:10:07 +09:00
vercel.json feat: Bun v1.4 for Vercel 2026-08-21 01:19:15 +09:00

gh-to-gitea

Tired of GitHub going down? Automatically mirror your code on a server you own!

A simple Elysia/Bun server that receives a GitHub organization webhook and, whenever a repository is created, creates a matching pull mirror in a Gitea / Forgejo instance automatically.

Setup / Run locally

  1. Install packages

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

    cp .env.example .env
    
  3. Run it

    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

Vercel

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

Deploy with Vercel

Docker

Seeding existing repositories

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:

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.

Environment variables

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.

GitHub webhook configuration

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

Gitea token

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.