~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
94 lines · 4.8 KB Raw History
# @pdsjs/git-host

A git forge served from a PDS reached over the wire. The PDS stays the data
store and the identity provider: users log in with their atproto account,
repositories live in the account's PDS as `dev.pdsjs.git.repo` records with
bundle blobs, pushes go to the PDS exactly as the [git helper](../git/README.md)
makes them. What moves out is the hosting: this service reads the account's
records and blobs through the public XRPC surface and serves the forge from
its own origin.

```
git CLI ──────────────────► PDS (uploadBlob + putRecord, branch protection)
git-ui ◄──► git-host ─┬─ /git/ smart HTTP, raw files, the file browser
                      ├─ dev.pdsjs.git.* reads, answered from the wire
                      ├─ discovery: Jetstream → SQLite, no Constellation
                      └─ owner login: atproto OAuth, session cookie
PDS ── the data store; git hosting there becomes optional
```

## Running

```sh
git-host --pds https://pds.example.com --account alice.example.com \
  --db ./forge.sqlite --port 8046 --origin https://git.alice.example.com \
  --ui ./git-ui-dist
```

| Option | Purpose | Default |
| --- | --- | --- |
| `--pds` | the account's PDS base URL | required |
| `--account` | the hosted account's handle or DID | required |
| `--db` | the index database (SQLite) | `./git-appview.sqlite` |
| `--port` | the port to serve on | `8046` |
| `--origin` | the origin requests are read as carrying; required for login | host header |
| `--ui` | the git-ui build to serve at `/` | none |
| `--plc-url` | the directory DIDs resolve against | `https://plc.directory` |
| `--jetstream` | the Jetstream to index from (a v2 instance) | `wss://jetstream.us-east.bsky.network` |
| `--jetstream-api-key` | the archive's bearer token, for replay on a metered instance; also read from `JETSTREAM_API_KEY` | none |
| `--replay` | catch up from the Jetstream archive on boot instead of walking the relay; implies `--no-backfill` | off |
| `--list-service` | the relay the backfill lists repos from | `https://bsky.network` |
| `--no-watch` | skip the Jetstream tail | |
| `--no-backfill` | skip the first-boot historical walk | |

## What it serves

- `/git/<account>/<repo>` — the read-only smart HTTP endpoint and raw
  files, exactly the surface the embedded extension serves. Stock git
  clones through it with no helper installed.
- `/xrpc/dev.pdsjs.git.*` — the same XRPC methods the embedded extension
  serves, answered from records and blobs fetched on demand. Record reads
  are anonymous; the owner's writes carry the owner's OAuth token, which
  the login flow below put in the session.
- `/.well-known/atproto-did` and `/.blobs/<cid>` — the two routes the
  browser app reads from its own origin, so the app works served from the
  forge without CORS negotiation.
- `/` — the [git-ui](../git-ui) build, when `--ui` names one.

## The index

Discovery on the PDS asks Constellation, a hosted reverse index, who wrote
about a repository. The forge asks its own index instead. Two ways to fill
it, one fold:

- **The archive, replayed.** Jetstream v2 keeps the whole network in
  sealed segment files. With `--replay` the forge pages `planSnapshot`
  for the `dev.pdsjs.git.*` collections, downloads the matching blocks
  from the archive, folds them into SQLite, and cuts over to the live
  tail at the sealed tip. A refused live cursor sends the tail back to
  the archive for the gap. On Bluesky's public v2 instances the archive
  HTTP is metered and needs `--jetstream-api-key`.
- **The relay walk, backfilled.** Without `--replay`, a first boot walks
  `com.atproto.sync.listReposByCollection` and lists each account's
  collections from their own PDS, so strangers' pull requests and issues
  appear without waiting for the next live write.

The index is at-least-once by design: a record upserts by uri, a delete
removes, and replaying an event already indexed changes nothing.

## Login

`GET /login` starts an atproto OAuth authorization-code flow for the forge
owner: PKCE, a DPoP-bound token pair, and a session cookie. The client
metadata document lives at `/oauth/client-metadata.json`, the callback at
`/oauth/callback`. A session whose DID is not the hosted account's grants
nothing, because every owner-only procedure compares against the hosted
account. The default grant is the `atproto` scope, which covers the record
and blob writes the forge makes.

Collaborators keep pushing from their own accounts to their own PDSes; the
forge reads their copies from there, exactly as the [collaboration
model](../../docs/plans/2026-08-21-git-collaboration.md) describes.
Repositories held in spaces are not served by the forge yet: the space
credential chain needs a consented `space:` grant per member, which the
CLI helper holds and the forge does not yet ask for.