README.md

gitfor

A git forge built entirely on Cloudflare: repositories live in Artifacts, and everything else runs on Workers, D1, KV, R2, Queues, Workflows, Durable Objects and Containers.

Features

  • Repositories: create, import from HTTPS remotes, fork, archive, delete, public/private visibility
  • Browsing: trees, syntax-highlighted files, raw files, README rendering, commit history, commit diffs, branches, tags, .tar.gz archives
  • Git over HTTPS: clone/fetch/push through a Worker proxy authenticated with personal access tokens
  • Web editing: create, edit, rename and delete files, optionally on a new branch
  • Branch protection: required pull requests, approvals and status checks, deletion protection, admin bypass
  • Issues: labels, assignees, comments, locking, closing keywords (fixes #12), full-text search
  • Pull requests: cross-fork PRs, inline review threads, approvals and change requests, review requests, drafts, merge/squash/rebase, mergeability checks
  • Actions: GitHub-Actions-style workflows in .gitfor/workflows/*.yml with needs, matrices, if: expressions, secrets, artifacts and live logs
  • Runners: Cloudflare Containers by default, plus self-hosted runners (runner/gitfor-runner.ts)
  • Releases with R2-backed assets, webhooks with HMAC signatures and redelivery, notifications, activity feeds, organizations

Architecture

Concern Cloudflare primitive
Git objects and refs Artifacts (Workers binding + smart HTTP)
Users, repos, issues, PRs, CI state D1 (with FTS5 search)
Sessions, refs/object/diff caches, token cache KV
Logs, CI artifacts, release assets R2
Webhook fan-out and delivery, notifications Queues (with retries and a DLQ)
Post-push pipeline, CI run orchestration Workflows (PushWorkflow, CiRunWorkflow)
Job execution, log streaming, merges Durable Object Runner with a Container
Push events from Artifacts triggers.events (cf.artifacts.repo.pushed)

Key code paths:

  • src/server.ts — Worker entry: git proxy, runner API, raw/archive/asset routes, queue and cron handlers
  • src/server/git/ — pkt-line, refs, packfile writer, receive-pack client, tree diff, merge-base
  • src/server/ci/ — workflow schema, expressions, compiler, bash job script, runner DO, scheduler
  • src/fns/ — TanStack Start server functions used by the UI
  • migrations/ — D1 schema

Setup

Requirements: Bun, Docker (for building the runner image), a Cloudflare account on the Workers Paid plan with Artifacts enabled.

bun install

Create resources and put their IDs in wrangler.jsonc (also set account_id if your login has several accounts):

bunx wrangler d1 create gitfor
bunx wrangler kv namespace create gitfor
bunx wrangler r2 bucket create gitfor
bunx wrangler queues create gitfor-events
bunx wrangler queues create gitfor-events-dlq
bunx wrangler secret put SECRETS_KEY

The Artifacts namespace (gitfor) is referenced by the binding in wrangler.jsonc; check it with bunx wrangler artifacts namespaces get gitfor. Set PUBLIC_URL in wrangler.jsonc to your deployment URL. Optional: GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET secrets enable GitHub login.

Develop

echo "SECRETS_KEY=dev-secret" > .dev.vars
bun run db:migrate
bun run dev

The Artifacts binding uses remote: true, so local development talks to your real Artifacts namespace.

Deploy

bun run deploy

The first account to sign up becomes the instance administrator. Set SIGNUPS_ENABLED to "false" to close registration afterwards.

Using git

Create a token under Settings → Access tokens, then use your username and the token as the password:

git clone https://gitfor.dev/alice/project.git

Workflows

# .gitfor/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
jobs:
  test:
    runs-on: cloudflare
    strategy:
      matrix:
        node: [22, 24]
    steps:
      - uses: actions/checkout@v4
      - run: bun install && bun test
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-${{ matrix.node }}
          path: coverage
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: [self-hosted, linux]
    steps:
      - run: ./deploy.sh
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Supported triggers: push (branches, tags, paths filters), pull_request (types, branches, paths), release, workflow_dispatch (with inputs). Steps are run scripts (bash, sh, python, node, bun) plus checkout, upload-artifact and setup actions treated as no-ops; third-party actions are not supported. Job status is published as commit statuses named <workflow> / <job>, which branch protection can require.

Self-hosted runners

Register a runner under Repository settings → Runners, then on the host (needs bash, git, tar):

GITFOR_URL=https://gitfor.dev GITFOR_RUNNER_TOKEN=gitfor_runner_... GITFOR_RUNNER_LABELS=linux bun runner/gitfor-runner.ts

Pages

Static sites are served from <owner>.<PAGES_DOMAIN>/<repo>/ (a repo named <owner>.<PAGES_DOMAIN> is served at the root) and pull request previews from /_pr/<number>/. PAGES_DOMAIN must be a separate registrable domain from the forge so user content can never read forge cookies.

  • Add Worker routes for *.<PAGES_DOMAIN>/* (with a wildcard DNS record and certificate).
  • Custom domains use Cloudflare for SaaS: set PAGES_ZONE_ID to the SaaS zone (with a fallback origin and a */* Worker route) and add a CF_API_TOKEN secret with "SSL and Certificates: Edit" on that zone.
  • Deploys copy files into content-addressed R2 objects (pages/objects/<git blob sha>), publish a manifest to KV and flip a KV pointer, so rollbacks are instant.

Scripts

Command Purpose
bun run dev Local dev server
bun run build Production build and type-check
bun run deploy Apply D1 migrations and deploy
bun run cf-typegen Regenerate worker-configuration.d.ts
bun run lint / bun run format oxlint / oxfmt

License

Released under the 0BSD license.