diff --git a/README.md b/README.md index b30d15e..3e817cf 100644 --- a/README.md +++ b/README.md @@ -1,118 +1,222 @@ # pds.js -An AT Protocol Personal Data Server written in JavaScript. It hosts one account, -and it runs on Node.js, Deno, or Cloudflare Workers. +A single-account [AT Protocol](https://atproto.com) Personal Data Server written in JavaScript. Runs on Node.js, Deno, or Cloudflare Workers. Federates with the live network. -> **Work in progress.** This is experimental. You probably should not use it yet. +> **Work in progress.** Experimental. Probably not production-ready yet. -![The account home page, showing stored record counts and the apps that wrote them](docs/images/account-home.png) +--- -## Why pds.js +## What makes this different from the official PDS -The [official atproto PDS](https://github.com/bluesky-social/atproto/tree/main/packages/pds) -is the reference server. It is the right choice for most people. Pick pds.js when -one of these matters to you: +The [official atproto PDS](https://github.com/bluesky-social/atproto/tree/main/packages/pds) is a multi-tenant server built for running thousands of accounts. pds.js is built around a different premise: **one server, one identity, yours**. -- **It runs on the edge.** The Cloudflare Workers build keeps repos in Durable - Objects and blobs in R2. -- **It serves one account.** The server answers `RepoNotFound` for every other - DID. Nothing in it models tenants, invite codes, or moderation of other people. -- **It includes an account interface.** Sign in at `/account` to see what each - app stored, manage passkeys and app passwords, take backups, and change the - handle. -- **It implements permissioned data.** Private per-user repos, shared through - spaces. The proposal and its reference implementation are both drafts, so the - wire formats may change. See [permissioned data](docs/permissioned-data.md). -- **It hosts git repositories.** Push over a `git-remote-atproto` helper into - `dev.pdsjs.git.repo` records and chunked bundle blobs; anyone clones with - stock git over read-only smart HTTP. See [@pdsjs/git](packages/git/README.md). +That constraint unlocks some things the reference implementation doesn't have: -pds.js federates with the live network. It signs commits with a did:plc identity, -serves the firehose over `subscribeRepos`, and proxies `app.bsky.*` to an AppView -with service auth. +**It runs on the edge.** The Cloudflare Workers build keeps repos in Durable Objects and blobs in R2. No VM, no persistent disk. -## Try it +**It hosts an account interface.** Sign in at `/account` to see what each app has written to your repo, manage passkeys and app passwords, take a CAR backup, or rename your handle — without touching a command line. -```bash -git clone https://tangled.org/chadtmiller.com/pds.js -cd pds.js && pnpm install -just dev -``` +**It has a site platform.†** Lexicon records stored in your repo can power a public website served from your PDS. No separate hosting required. -The workspace uses the `workspace:*` protocol, so install with pnpm. `npm install` -fails with `EUNSUPPORTEDPROTOCOL`. +**It hosts git repositories.†** Push to your PDS over `git-remote-atproto`, clone with stock `git` over read-only HTTP. Repos live as `dev.pdsjs.git.repo` records and chunked blobs in your repo. -`just dev` needs [`just`](https://github.com/casey/just) and Docker. It starts a -local PLC, relay, and Caddy, runs the PDS, and registers a mock account with a few -records. Open http://localhost:2471/account and sign in with the printed -credentials. Run `just dev-down` to stop the PDS and remove the docker infra. +**It includes a private file store.** Drive† is a blob-backed file manager built into the account interface. Upload files, get public `/.blobs/` links, stream video with seeking. Large files upload across multiple requests and land as a single blob. -To put a PDS on the public network instead, see [Deploy](#deploy). +**It implements permissioned data.†** Private per-user repos shared through *spaces* — a draft proposal on top of atproto. Wire formats may change. See [docs/permissioned-data.md](docs/permissioned-data.md). -[CONTRIBUTING.md](CONTRIBUTING.md) covers the setup without `just`, and the -live-reload workflow for account interface work. +> † Labs feature — experimental, may change. -## The account interface +For everything it covers and everything it doesn't, see the [endpoint comparison](docs/endpoint-comparison.md). -Sign in at `/account`. Every page is served by the PDS itself. +--- -**Your apps** groups the repo by the application that wrote it. Each row carries -its sessions and its write history. +## Quick start -![The apps page, listing each application with its sessions and a sparkline of its writes](docs/images/account-apps.png) - -**Sign-in and security** holds passkeys, active logins, app passwords, the -recovery address, and the switch that pauses the account. - -![The sign-in and security page, showing passkeys, active logins, and app passwords](docs/images/account-security.png) +```sh +git clone https://tangled.org/chadtmiller.com/pds.js +cd pds.js +pnpm install # must be pnpm — npm fails on workspace:* protocol +just dev +``` -## What it does +`just dev` requires [`just`](https://github.com/casey/just) and Docker. It starts a local PLC directory, relay, and Caddy reverse proxy, then runs the PDS and registers a mock account with a few seed records. Open `http://localhost:2471/account` and sign in with the printed credentials. -pds.js implements the repo, sync, server, and identity namespaces of -`com.atproto.*`. That covers record writes, the firehose, blob upload and -download, handle resolution and rename, and account migration in both -directions. Sessions come from passwords, app passwords, passkeys, or OAuth 2.0 -with PKCE and DPoP-bound tokens. +Tear it all down with `just dev-down`. -It implements no `com.atproto.admin.*` endpoint, and no invite codes. A -single-account server has nobody to administer. +For a manual setup without `just`, or for working on the account UI with live reload, see [CONTRIBUTING.md](CONTRIBUTING.md). -The [endpoint comparison](docs/endpoint-comparison.md) lists every endpoint on -both sides, including the ones pds.js does not have. +--- ## Deploy -[start.pdsjs.dev](https://start.pdsjs.dev) deploys pds.js from the browser. Sign -in with Cloudflare and the wizard creates a Worker and an R2 bucket in your own -account, with a workers.dev hostname as the handle. The signing key and the -secrets are generated in your browser tab and go only to your worker. +The fastest path is **[start.pdsjs.dev](https://start.pdsjs.dev)** — a browser wizard that deploys to your own Cloudflare account. It generates your signing key and secrets client-side; nothing sensitive leaves your tab. -To deploy by hand, pick a guide: +For manual deployments: | Target | Guide | -|--------|-------| +|---|---| | Docker | [docs/deploy-docker.md](docs/deploy-docker.md) | | Node.js | [docs/deploy-node.md](docs/deploy-node.md) | | Cloudflare Workers | [docs/deploy-cloudflare.md](docs/deploy-cloudflare.md) | | Deno | [docs/deploy-deno.md](docs/deploy-deno.md) | -These deployments need TLS in front of them, a public hostname, and one run of -`npm run setup` to register the DID with the PLC directory. +All targets need TLS, a public hostname, and one run of `npm run setup` to register your DID with the PLC directory. + +--- + +## Architecture + +pds.js uses hexagonal architecture. `@pdsjs/core` holds all business logic and XRPC handlers. It never touches storage directly — it talks through ports. Each platform supplies its own adapters. + +``` + ┌────────────────┐ space route table ┌───────────────────────────────────┐ + │ @pdsjs/spaces │ ────────────────► │ @pdsjs/core │ + │ (optional) │ │ (business logic, XRPC handlers) │ + └───────┬────────┘ └────────────────┬──────────────────┘ + │ │ + ▼ ┌───────────────────────┼─────────────────┐ +┌────────────────┐ ▼ ▼ ▼ +│SpaceStoragePort│ ┌────────────────┐ ┌─────────────────┐ ┌──────────┐ +│ (space repos) │ │ActorStoragePort│ │SharedStoragePort│ │ BlobPort │ +└──────┬─────────┘ │ (per-user data)│ │ (global data) │ │ (binary) │ + │ └───────┬────────┘ └────────┬────────┘ └────┬─────┘ + ┌───┴──┬──────┐ ┌───┴─────┐ ┌────┴─────┐ ┌──┴────┐ + ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ +┌──────┐┌────┐┌──────┐ ┌──────┐ ┌───────┐ ┌──────┐ ┌──────┐ ┌────┐ ┌────┐ +│SQLite││ DO ││Memory│ │SQLite│ │Durable│ │SQLite│ │ DO │ │ FS │ │ R2 │ +│ ││ ││(test)│ │ │ │Objects│ │ │ │SQLite│ │ │ │ │ +└──────┘└────┘└──────┘ └──────┘ └───────┘ └──────┘ └──────┘ └────┘ └────┘ +``` + +### Packages + +| Package | What it does | +|---|---| +| `@pdsjs/core` | Platform-agnostic business logic and XRPC handlers | +| `@pdsjs/node` | Node.js HTTP server with WebSocket support | +| `@pdsjs/deno` | Deno HTTP server with WebSocket support | +| `@pdsjs/cloudflare` | Cloudflare Workers entrypoint with Durable Objects | +| `@pdsjs/storage-sqlite` | SQLite storage adapter (better-sqlite3 or node:sqlite) | +| `@pdsjs/blobs-fs` | Filesystem blob storage for Node.js | +| `@pdsjs/blobs-deno` | Filesystem blob storage for Deno | +| `@pdsjs/blobs-s3` | S3-compatible blob storage | +| `@pdsjs/spaces` | Permissioned space repos, credentials, routes (optional) | +| `@pdsjs/sites` | Site platform: lexicon-driven public pages served from your repo | +| `@pdsjs/git` | Git hosting: push via `git-remote-atproto`, clone over HTTP | +| `@pdsjs/readonly` | Read-only server that serves repositories from CAR files | +| `@pdsjs/lexicon-resolver` | Lexicon schema resolution and record validation | +| `@pdsjs/account-ui` | The account interface (bundled, not a public import) | + +### Library usage + +**Node.js** + +```js +import { createServer } from '@pdsjs/node' + +const { listen } = await createServer({ + dbPath: './pds.db', + blobsDir: './blobs', + jwtSecret: process.env.JWT_SECRET, + port: 3000, +}) + +await listen() +``` -The packages publish to npm under `@pdsjs/*`. The -[architecture guide](docs/architecture.md) covers using them as a library. +**Deno** + +```js +import { createServer } from '@pdsjs/deno' + +const { listen } = await createServer({ + dbPath: './pds.db', + blobsDir: './blobs', + jwtSecret: Deno.env.get('JWT_SECRET'), + port: 3000, +}) + +await listen() +``` + +**Cloudflare Workers** + +```js +// wrangler.toml points here, or re-export from your own entry +export { default, PDSDurableObject } from '@pdsjs/cloudflare' +``` + +--- + +## The account interface + +Sign in at `/account`. The PDS serves every page itself — no external dashboard. + +**Your apps** groups the repo by the application that wrote it. Each row shows its sessions and a write history. + +**Sign-in and security** holds passkeys, active logins, app passwords, the recovery address, and a kill switch that pauses the account. + +**Drive†** is a private file store backed by blobs. Upload anything from the browser; large files chunk across multiple requests and land as a single blob. Public files get a `/.blobs/` link you can use anywhere an image or video URL works. Drop a video in Drive and it streams with seeking in the example video gallery app. + +**Sites†** manages installable web apps served from your repo. Each app is a single `.mjs` file that deploys a manifest record and a set of lexicon records; anyone who visits your PDS origin sees the site. + +> † Labs feature — experimental, may change. + +--- + +## Example apps + +Four working apps live in `examples/apps/`. Each one discovers whose repo it serves from its own origin, so any of them runs on any pds.js account that installs it. + +| App | What it does | +|---|---| +| `photos.mjs` | Photo galleries from `social.grain.*` records — justified layouts, lightbox, map, terrain headers, network favourite counts | +| `roasts.mjs` | Coffee roast log — live roast timer, weight-loss and development metrics against a Sweet Maria's roast card, notes and photos | +| `drop.mjs` | Image host — drag, paste, or pick a file and the public `/.blobs/` URL copies itself | +| `videos.mjs` | Video gallery from Drive† — plays same-origin with seeking, read-only; uploads happen in Drive | + +Deploy any of them: + +```sh +PDS_URL=https://pds.example.com \ +PDS_DID=did:plc:… \ +PDS_APP_PASSWORD=… \ +node photos.mjs +``` + +Then install from the account page's Sites section, or with: + +```sh +pdsjs-site install at://you.example.com/dev.pdsjs.app.manifest/photos +``` + +--- + +## What it implements + +pds.js covers the `com.atproto.repo.*`, `com.atproto.sync.*`, `com.atproto.server.*`, and `com.atproto.identity.*` namespaces. That includes record writes, the firehose (`subscribeRepos`), streaming blob upload and ranged download, handle resolution and rename, and account migration in and out. + +Sessions come from passwords, app passwords, passkeys, or OAuth 2.0 with PKCE and DPoP-bound tokens. + +There are no `com.atproto.admin.*` endpoints and no invite codes. A single-account server has nobody to administer. + +The [endpoint comparison](docs/endpoint-comparison.md) lists every endpoint on both sides. + +--- ## Documentation | Document | Contents | -|----------|----------| +|---|---| | [Configuration](docs/configuration.md) | Every environment variable | -| [Building an app](docs/apps.md) | Sites, manifests, installs, and how to update one | | [Architecture](docs/architecture.md) | Ports, adapters, packages, library usage | -| [Permissioned data](docs/permissioned-data.md) | Proposal 0016, spaces, the run club example | +| [Building an app](docs/apps.md) | Sites, manifests, installs, how to update | +| [Permissioned data](docs/permissioned-data.md) | Spaces proposal, the run club example | | [Endpoint comparison](docs/endpoint-comparison.md) | Coverage against the official atproto PDS | | [Scope comparison](docs/scope-comparison.md) | OAuth scopes against the reference implementation | -| [Contributing](CONTRIBUTING.md) | Local development, tests, the commit gate | +| [Contributing](CONTRIBUTING.md) | Local dev setup, tests, commit gate | + +--- ## License