# gitfor A git forge built entirely on Cloudflare: repositories live in [Artifacts](https://developers.cloudflare.com/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 (`gfd runner`, a container per job) - 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](https://bun.sh/), Docker (for building the runner image), a Cloudflare account on the Workers Paid plan with Artifacts enabled. ```sh bun install ``` Create resources and put their IDs in `wrangler.jsonc` (also set `account_id` if your login has several accounts): ```sh 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 is referenced by the binding in `wrangler.jsonc`; check it with `bunx wrangler artifacts namespaces get `. Use one namespace per deployment and never share it: `vite dev` (and `bun run preview`) rebind every Artifacts namespace to `-dev` (see `vite.config.ts`), so local development never touches the deployed repositories, and `bun run deploy` uses the configured names. Set `PUBLIC_URL` in `wrangler.jsonc` to your deployment URL. Optional: `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` secrets enable GitHub login. Set `USERCONTENT_ORIGIN` to a separate registrable domain routed to the same Worker (for example `https://gitforusercontent.dev`), the way GitHub serves user files from `githubusercontent.com`. Raw files, archives, release assets, attachments, avatars and proxied markdown images are then served only from that origin, which never receives the forge's `__Host-` session cookie; private content gets a short-lived signed grant on redirect. The value must not be the forge host, the editor origin, or under `SITES_DOMAIN` or `PAGES_DOMAIN`. Local development maps `localhost` to `usercontent.localhost` automatically. ### Develop ```sh 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 the real Artifacts service, in the `-dev` namespace. Local data is disposable: rows in the local D1 point at repositories in that namespace, so after switching namespaces (or whenever local state is broken) reset it with `rm -rf .wrangler/state/v3/d1 && bun run db:migrate`. Repositories created by local development before the `-dev` split live in the deployed namespace; the orphan sweep reports them there. Review that report before ever enabling deletion. #### Artifacts namespaces Artifacts rate-limits control-plane calls per namespace (2,000 per 10 seconds), so a large instance can spread repositories over several namespaces. Each extra namespace needs its own binding, named `ARTIFACTS_1`, `ARTIFACTS_2`, … (the first stays `ARTIFACTS`), and its own `cf.artifacts.repo.pushed` and `cf.artifacts.repo.forked` trigger entries in `wrangler.jsonc`; without them, pushes and forks in that namespace never reach the push and provisioning Workflows. Then set `ARTIFACTS_SHARDS` to the total count. Only new repositories are spread out; the shard is part of each repository's Artifacts name, so never remove a binding that still holds repositories. A cron finds Artifacts repositories with no matching row, which keep incurring storage charges. `ARTIFACTS_ORPHAN_SWEEP` controls it: `report` (default) only logs them, `off` disables it, and `delete` removes them. `delete` only takes effect with `ARTIFACTS_NAMESPACE_EXCLUSIVE=true` and a non-localhost `PUBLIC_URL`; otherwise it logs an error and keeps reporting. Set that flag only when no other deployment, local development included, uses the namespace. CI jobs fetch just the commit they run on (depth 1). Set `GITFOR_FETCH_DEPTH` in a workflow's `env` to fetch more history, or to `0` for a full fetch. ### Deploy ```sh 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: ```sh git clone https://gitfor.dev/alice/project.git ``` ## Workflows ```yaml # .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), the built-in `checkout`, `cache` and `upload-artifact`, `setup-node`/`setup-bun` (built in unless a version is requested), and JavaScript actions from public github.com repositories or the repository itself (`uses: ./path`). Third-party actions: - A tag or branch is resolved to its commit when the run is scheduled; that SHA is stored with the run, shown in the log ("Download action repository … (SHA: …)") and used for every retry. - A full commit SHA is accepted only if a branch or tag of the named repository reaches it, so a commit that only exists in a fork can't pose as the repository. Short SHAs are refused. - Code is fetched over git and every object is checked against its SHA; the archive is stored once by commit and its sha256 is verified by the runner before extracting. Actions run only inside the job's runner, never in the Worker. - **Repository settings → Allowed actions** chooses between any public action, only SHA-pinned actions, or only built-in ones. - `${{ github.token }}` written in a step's `with:` reads the `GITHUB_COM_TOKEN` secret (a github.com token, for actions that call the GitHub API). An action's own `${{ github.token }}` defaults get an empty value, so no action receives that secret unless the workflow passes it. Fork pull request runs get no secrets. - Composite and Docker actions are not supported yet. A pin covers the action's code, not what it downloads while it runs. Job status is published as commit statuses named ` / `, which branch protection can require. ### Self-hosted runners Create a registration token under **Repository settings → Runners** (or `/orgs//runners` for owner-wide runners), choosing the labels and the isolation, then on the host (needs Docker or Podman): ```sh gfd runner register --url https://gitfor.dev --token gitfor_reg_... gfd runner run ``` Each job runs in a fresh, non-root container with a read-only root filesystem and an egress firewall that blocks cloud metadata and private networks. Fork pull request jobs never reach self-hosted runners unless a repository admin opts in, and then only runners whose container isolation the admin declared on the token. See the gfd README for what is and isn't isolated. The old `runner/gitfor-runner.ts` is retired. ## Pages Static sites are served from `.//` (a repo named `.` is served at the root) and pull request previews from `/_pr//`. `PAGES_DOMAIN` must be a separate registrable domain from the forge so user content can never read forge cookies. - Add Worker routes for `*./*` (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/`), 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) license.