A GitHub ↔ Forgejo translation proxy

ANVIL

Your tools speak GitHub. Your forge speaks Forgejo. Anvil stands between them and translates every blow — so gh, Renovate, and CI scripts work against your own Forgejo, unmodified.

Click anywhere — or press space — to strike

▼ STRIKE LEDGER

The strike ledger

Every request gets struck once, on the way through.

These are real pairs from Anvil's mappings.toml — not a mockup. Strike again to forge the next one. Watch the method change on the merge.

mappings.toml — live translation 0 strikes

What rings true

The tools can't tell the difference.

86% of the GitHub endpoints these tools actually hit are mapped — field renames, param renames, path reshuffles, even method changes — all verified against a real Forgejo instance.

Repos & branches

List, read, patch, delete. Fields like homepagewebsite come back renamed and in GitHub's shape.

Issues & PRs

Create, comment, label, milestone — and merge. GitHub says PUT …/merge; Forgejo says POST. Anvil absorbs the difference.

Commit statuses

The CI loop works end to end: report a status from your pipeline, read it from another.

Pagination

per_page becomes limit, and Link headers are rewritten so clients keep following pages unaware.

Actions & artifacts

Workflow runs, artifacts, secrets, variables — the automation surface, translated.

Search

GET /search/repositories is re-routed to Forgejo's /repos/search — the path reshuffles, the client doesn't notice.

Contents, readme & releases

Read and write files — including GET …/readme, which Forgejo doesn't have and Anvil synthesizes from the contents API — plus releases, labels, milestones, webhooks, and collaborators.

Users & orgs

The identity surface gh checks on startup — /user, user repos, org membership — answers in GitHub's dialect.

Straight talk

And when it can't, it says no in GitHub's voice.

Anvil doesn't improvise. Anything Forgejo genuinely can't do comes back as the same 501 GitHub would send — so your tooling fails fast, loudly, and in a shape it understands. No silent wrong answers.

Under the anvil

The whole trick is one TOML file you can read in a diff.

Anvil is Shotgun — an OpenAPI-to-OpenAPI translation proxy — wearing a curated, hand-checked GitHub↔Forgejo configuration. The value is the mapping file, the one-command experience, and the testing against a real forge.

gh · Renovate · CI
speaks GitHub
ANVIL
strikes once
Forgejo
your own forge
# from mappings.toml — the merge, with its method change absorbed
[[endpoints]]
source = "PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"
target = "POST /repos/{owner}/{repo}/pulls/{index}/merge"

[endpoints.path_params]
pull_number = "index"  # same idea, different name — renamed on the way through

The heat gauge

86% forged, against a real forge.

75 of 87 endpoints mapped and verified live against Codeberg — not against fixtures. The rest have no Forgejo equivalent, and Anvil says so rather than guessing.

75 endpoints forged of 87 — every one exercised against Codeberg

Quickstart

Three commands to your first strike.

1

Set the forge

Clone both repos — Anvil builds its Shotgun engine on first run (needs Rust) — then point it at your instance.

git clone https://github.com/ThatXliner/shotgun shotgun
git clone https://github.com/ThatXliner/anvil anvil
cd anvil
./anvil.sh --forgejo-url https://git.example.com
2

Strike

Point any GitHub-speaking tool at the proxy. That's the whole integration.

# gh, against your own forge:
GH_HOST=127.0.0.1:3000 \
  GH_TOKEN=<forgejo-token> \
  gh repo list
3

Or run the container

The Dockerfile builds an image with the proxy and mappings baked in.

docker build -t anvil ..
docker run -p 3000:3000 \
  -e FORGEJO_URL=https://git.example.com \
  anvil

A note on gh: it insists on HTTPS, and Anvil speaks plain HTTP on localhost. Put any reverse proxy in front (Caddy, nginx, a Cloudflare Tunnel) — or use scripts/dev-tls-proxy.py for a self-signed cert locally. Tools that don't hardcode HTTPS, like curl, Renovate, and most CI scripts, work directly.