- TypeScript 96.1%
- Dockerfile 3.9%
| .github/workflows | ||
| .idea | ||
| scripts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| .oxfmtrc.json | ||
| bun.lock | ||
| Dockerfile | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vercel.json | ||
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
-
Install packages
bun install -
Copy the example env file and fill it in
cp .env.example .env -
Run it
bun run dev # watch mode bun run start # onceGET /healthreports configuration health:200with{ "ok": true, "configuration": { "valid": true, "errors": [], "warnings": [] } }when the environment is valid, or503with the offending variables listed underconfiguration.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:
Docker
- Docker images are available at cr.mikandev.com.
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.