diff --git a/README.md b/README.md index 96ee77cb..cb151da8 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,66 @@ -# @ascorbic/pds +# AT Protocol PDS on Cloudflare Workers -A single-user [AT Protocol](https://atproto.com) Personal Data Server (PDS) that runs on Cloudflare Workers. Host your own Bluesky identity with minimal infrastructure. +A single-user [AT Protocol](https://atproto.com) Personal Data Server (PDS) that runs entirely on Cloudflare's edge infrastructure. Host your Bluesky identity on your own domain with minimal operational overhead. -> **⚠️ Experimental Software** -> -> This is an early-stage project under active development. **You cannot migrate your main Bluesky account to this PDS yet.** Use a test account or create a new identity for experimentation. Data loss, breaking changes, and missing features are expected. +## Why run your own PDS? -## What is this? +A PDS is where Bluesky data lives – posts, follows, profile, and media. Running a personal PDS provides: -A PDS is where your Bluesky data lives – your posts, follows, profile, and media. This package lets you run your own PDS on Cloudflare Workers, giving you control over your data and identity. +- **Independence from platform changes** – If Bluesky's ownership or policies change, the account remains under full control. No billionaire can take it away. +- **Network resilience** – A diverse ecosystem of PDS providers makes the AT Protocol network stronger. More independent servers mean no single point of failure. +- **Data sovereignty** – The repository lives on infrastructure under direct control +- **Portability** – Move between hosting providers without losing followers or identity -## Quick Start +## Architecture + +This implementation uses Cloudflare Workers with Durable Objects and R2: + +- **Worker** – Stateless edge handler for routing, authentication, and DID document serving +- **Durable Object** – Single-instance SQLite storage for your AT Protocol repository +- **R2** – Object storage for blobs (images, videos) + +The result is a PDS that runs at the edge with no servers to manage, automatic scaling, and pay-per-use pricing. -The fastest way to get started: +## Quick Start ```bash npm create pds ``` +This scaffolds a new project, installs dependencies, and runs the setup wizard. See the [PDS package documentation](./packages/pds/) for detailed setup and configuration. + ## Packages -- [`@ascorbic/pds`](./packages/pds/) - The main PDS library -- [`create-pds`](./packages/create-pds/) - A CLI tool to scaffold a new PDS project +| Package | Description | +|---------|-------------| +| [`@ascorbic/pds`](./packages/pds/) | The PDS implementation – handles repository operations, federation, OAuth, and the CLI | +| [`@ascorbic/atproto-oauth-provider`](./packages/oauth-provider/) | OAuth 2.1 provider for "Login with Bluesky" | +| [`create-pds`](./packages/create-pds/) | Scaffolding CLI to create new PDS projects | + +## Status + +This is beta software under active development. Core functionality is complete and tested: + +- ✅ Repository operations (create, read, update, delete records) +- ✅ Federation (sync, firehose, blob storage) +- ✅ OAuth 2.1 provider (PKCE, DPoP, PAR) +- ✅ Account migration from existing PDS +- ✅ 140+ tests passing + +See the [PDS documentation](./packages/pds/) for current limitations and roadmap. + +## Requirements + +- Cloudflare account with Workers, Durable Objects, and R2 enabled +- A domain you control (for your handle and DID) +- Node.js 18+ for local development + +## Resources + +- [AT Protocol Documentation](https://atproto.com) +- [Bluesky](https://bsky.app) +- [Cloudflare Workers](https://developers.cloudflare.com/workers/) + +## License + +MIT diff --git a/packages/create-pds/templates/pds-worker/README.md b/packages/create-pds/templates/pds-worker/README.md index 2771ae6c..a5342e41 100644 --- a/packages/create-pds/templates/pds-worker/README.md +++ b/packages/create-pds/templates/pds-worker/README.md @@ -1,4 +1,4 @@ -# Personal PDS on Cloudflare Workers +# Your Personal PDS A single-user AT Protocol Personal Data Server running on Cloudflare Workers. @@ -6,26 +6,29 @@ A single-user AT Protocol Personal Data Server running on Cloudflare Workers. > > This is an early-stage project under active development. **Do not migrate your main Bluesky account to this PDS yet.** Use a test account or create a new identity for experimentation. -## Setup +## Getting Started ### 1. Install dependencies ```bash pnpm install +# or: npm install / yarn install ``` -### 2. Configure environment +### 2. Configure the PDS -If you haven't already, run the setup wizard: +Run the setup wizard if not already done: ```bash pnpm pds init -# or -npm run pds init -yarn pds init ``` -This prompts for your hostname, handle, and password, then writes configuration to `.dev.vars`. +This prompts for: +- **PDS hostname** – The deployment domain (e.g., `pds.example.com`) +- **Handle** – The Bluesky username (e.g., `alice.example.com`) +- **Password** – For logging in from Bluesky apps + +The wizard generates cryptographic keys and writes configuration to `.dev.vars` and `wrangler.jsonc`. ### 3. Run locally @@ -33,60 +36,153 @@ This prompts for your hostname, handle, and password, then writes configuration pnpm dev ``` -This starts a local development server at http://localhost:5173. +The PDS is now running at http://localhost:5173. Test it with: + +```bash +curl http://localhost:5173/health +curl http://localhost:5173/.well-known/did.json +``` ### 4. Deploy to production -First configure for production: +First, push secrets to Cloudflare: ```bash pnpm pds init --production +``` + +Then deploy: -# or -npm run pds init --production -yarn pds init --production +```bash +pnpm run deploy ``` -This sets vars in `wrangler.jsonc` and secrets via `wrangler secret put`. +Finally, configure DNS to point your domain to the worker. -Then deploy: +## Migrating an Existing Account + +To move an existing Bluesky account from bsky.social or another PDS: + +### 1. Configure for migration + +```bash +pnpm pds init +# Answer "Yes" when asked about migrating an existing account +``` + +### 2. Deploy and transfer the data ```bash pnpm run deploy +pnpm pds migrate +``` + +This downloads the repository (posts, follows, likes) and all images/videos from the current PDS. + +### 3. Update the identity + +Follow the [AT Protocol account migration guide](https://atproto.com/guides/account-migration) to update the DID document. This requires email verification from the current PDS. + +### 4. Go live + +```bash +pnpm pds activate ``` +The account is now live on the new PDS. + +## CLI Commands + +| Command | Description | +|---------|-------------| +| `pnpm pds init` | Interactive setup wizard | +| `pnpm pds init --production` | Deploy secrets to Cloudflare | +| `pnpm pds migrate` | Transfer account from source PDS | +| `pnpm pds migrate --clean` | Reset and re-import data | +| `pnpm pds activate` | Enable writes (go live) | +| `pnpm pds deactivate` | Disable writes (for re-import) | +| `pnpm pds secret key` | Generate new signing keypair | +| `pnpm pds secret jwt` | Generate new JWT secret | +| `pnpm pds secret password` | Set account password | + +Add `--dev` to target your local development server instead of production. + ## Configuration -Configuration uses environment variables: vars in `wrangler.jsonc` and secrets. +### Public Variables (wrangler.jsonc) + +| Variable | Description | +|----------|-------------| +| `PDS_HOSTNAME` | Public hostname (e.g., pds.example.com) | +| `DID` | Account DID | +| `HANDLE` | Account handle | +| `SIGNING_KEY_PUBLIC` | Public key for DID document | + +### Secrets (.dev.vars or Cloudflare) + +| Variable | Description | +|----------|-------------| +| `AUTH_TOKEN` | Bearer token for API write operations | +| `SIGNING_KEY` | Private signing key | +| `JWT_SECRET` | Secret for session tokens | +| `PASSWORD_HASH` | Bcrypt hash of the account password | + +## Handle Verification + +Bluesky verifies control of the handle domain. + +**If the handle matches the PDS hostname** (for example, both are `pds.example.com`): +- No extra setup needed. The PDS handles verification automatically. + +**If the handle is on a different domain** (for example, handle `alice.example.com`, PDS at `pds.example.com`): + +Add a DNS TXT record: + +``` +_atproto.alice.example.com TXT "did=did:web:pds.example.com" +``` + +Verify with: + +```bash +dig TXT _atproto.alice.example.com +``` -**Vars (in wrangler.jsonc):** +## Project Structure -- `PDS_HOSTNAME` - Public hostname of the PDS -- `DID` - Account DID (e.g., did:web:pds.example.com) -- `HANDLE` - Account handle (e.g., alice.example.com) -- `SIGNING_KEY_PUBLIC` - Public key for DID document (multibase) +``` +├── src/ +│ └── index.ts # Worker entry point (re-exports PDS) +├── wrangler.jsonc # Cloudflare Worker configuration +├── .dev.vars # Local secrets (not committed) +└── package.json +``` + +## Troubleshooting -**Secrets (via wrangler):** +### "PDS not responding" -- `AUTH_TOKEN` - Bearer token for API write operations -- `SIGNING_KEY` - Private signing key (secp256k1 JWK) -- `JWT_SECRET` - Secret for signing session JWTs -- `PASSWORD_HASH` - Bcrypt hash of account password (for Bluesky app login) +Ensure the worker is deployed (`pnpm run deploy`) or the dev server is running (`pnpm dev`). -## Endpoints +### "Failed to resolve handle" -Once deployed, your PDS serves: +Check the handle configuration: +- For DNS verification: ensure the TXT record has propagated (`dig TXT _atproto.yourhandle.com`) +- For same-domain handles: ensure the PDS is accessible at `https://yourdomain.com/.well-known/atproto-did` -- `GET /.well-known/did.json` - DID document -- `GET /health` - Health check -- `GET /xrpc/com.atproto.sync.getRepo` - Export repository as CAR -- `GET /xrpc/com.atproto.sync.subscribeRepos` - WebSocket firehose -- `POST /xrpc/com.atproto.repo.createRecord` - Create a record (authenticated) -- `POST /xrpc/com.atproto.repo.uploadBlob` - Upload a blob (authenticated) -- And more... +### Migration issues + +If migration fails partway through: +- Run `pnpm pds migrate` again to resume from where you left off +- Use `pnpm pds migrate --clean` to start fresh (only on deactivated accounts) ## Resources -- [AT Protocol Docs](https://atproto.com) +- [AT Protocol Documentation](https://atproto.com) - [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/) -- [@ascorbic/pds on GitHub](https://github.com/ascorbic/atproto-worker) +- [@ascorbic/pds Documentation](https://github.com/ascorbic/atproto-worker/tree/main/packages/pds) +- [Account Migration Guide](https://atproto.com/guides/account-migration) + +## License + +MIT diff --git a/packages/pds/README.md b/packages/pds/README.md index cdb39b96..82fe1ecc 100644 --- a/packages/pds/README.md +++ b/packages/pds/README.md @@ -6,21 +6,25 @@ A single-user [AT Protocol](https://atproto.com) Personal Data Server (PDS) that > > This is an early-stage project under active development. **Do not migrate your main Bluesky account to this PDS yet.** Use a test account or create a new identity for experimentation. Data loss, breaking changes, and missing features are expected. -## What is this? +## What is a PDS? -A PDS is where your Bluesky data lives – your posts, follows, profile, and media. This package lets you run your own PDS on Cloudflare Workers, giving you control over your data and identity. +A Personal Data Server is where your Bluesky data lives – your posts, follows, profile, and media. This package lets you run your own PDS on Cloudflare Workers, giving you control over your data and identity. -## Quick Start +Key benefits: + +- **Independence from platform changes** – If Bluesky's ownership or policies change, the account remains under full control +- **Network resilience** – More independent PDS providers make the AT Protocol network stronger +- **Data sovereignty** – The repository lives on infrastructure under direct control +- **Portability** – Move between hosting providers without losing followers or identity +- **Edge performance** – Runs globally on Cloudflare's edge network -The fastest way to get started: +## Quick Start ```bash npm create pds ``` -This scaffolds a new project, installs dependencies, and runs the setup wizard to configure your PDS. - -Then start the dev server: +This scaffolds a new project, installs dependencies, and runs the setup wizard. Start the dev server: ```bash cd pds-worker @@ -29,8 +33,6 @@ npm run dev ## Manual Installation -If you prefer to set things up yourself: - ### 1. Install the package ```bash @@ -48,97 +50,213 @@ export { default, AccountDurableObject } from "@ascorbic/pds"; ```jsonc { - "name": "my-pds", - "main": "src/index.ts", - "compatibility_date": "2024-12-01", - "compatibility_flags": ["nodejs_compat"], - "durable_objects": { - "bindings": [{ "name": "ACCOUNT", "class_name": "AccountDurableObject" }], - }, - "migrations": [ - { "tag": "v1", "new_sqlite_classes": ["AccountDurableObject"] }, - ], - "r2_buckets": [{ "binding": "BLOBS", "bucket_name": "pds-blobs" }], + "name": "my-pds", + "main": "src/index.ts", + "compatibility_date": "2024-12-01", + "compatibility_flags": ["nodejs_compat"], + "durable_objects": { + "bindings": [{ "name": "ACCOUNT", "class_name": "AccountDurableObject" }] + }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["AccountDurableObject"] }], + "r2_buckets": [{ "binding": "BLOBS", "bucket_name": "pds-blobs" }] } ``` ### 4. Run the setup wizard ```bash -pnpm pds init +npx pds init ``` This prompts for your hostname, handle, and password, then generates signing keys and writes configuration. -## CLI +## CLI Reference + +The package includes a CLI for setup, migration, and secret management. + +### `pds init` -The package includes a CLI for setup and configuration: +Interactive setup wizard for configuring the PDS. ```bash -pds init # Interactive setup (writes to .dev.vars) +pds init # Configure for local development pds init --production # Deploy secrets to Cloudflare -pds secret key # Generate new signing keypair -pds secret jwt # Generate new JWT secret -pds secret password # Set account password ``` -## Deploying to Production +**What it does:** + +- Prompts for PDS hostname, handle, and account password +- Generates cryptographic signing keys (secp256k1) +- Creates authentication token and JWT secret +- Writes public configuration to `wrangler.jsonc` +- Saves secrets to `.dev.vars` (local) or Cloudflare (production) + +For migrations, it detects existing accounts and configures the PDS in deactivated mode, ready for data import. -1. [Enable R2 in your Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/r2/overview) (the bucket will be created automatically on first deploy). +### `pds migrate` -2. Run the production setup to deploy secrets: +Transfers account data from an existing PDS to a new one. ```bash -npx pds init --production +pds migrate # Migrate to production PDS +pds migrate --dev # Migrate to local development server +pds migrate --clean # Reset and start fresh migration ``` -3. Deploy your worker: +**What it does:** + +1. Resolves the DID to find the current PDS +2. Authenticates with the source PDS +3. Downloads the repository (posts, follows, likes, etc.) +4. Imports the repository to the new PDS +5. Transfers all blobs (images, videos) +6. Copies user preferences + +The migration is resumable. If interrupted, run `pds migrate` again to continue. + +**Flags:** + +- `--dev` – Target the local development server instead of production +- `--clean` – Delete any existing imported data and start fresh (only works on deactivated accounts) + +### `pds activate` + +Enables writes on the account after migration. ```bash -wrangler deploy +pds activate # Activate production account +pds activate --dev # Activate local development account +``` + +Run this after migrating data and updating the DID document to point to the new PDS. The account will start accepting new posts, follows, and other writes. + +### `pds deactivate` + +Disables writes on the account. + +```bash +pds deactivate # Deactivate production account +pds deactivate --dev # Deactivate local development account +``` + +Use this before re-importing data (for example, to recover from issues). Deactivating prevents new writes during the reset and re-migration. + +After deactivating: + +```bash +pds migrate --clean # Reset and re-import +pds activate # Go live again +``` + +### `pds secret` + +Manage individual secrets. + +```bash +pds secret key # Generate new signing keypair +pds secret jwt # Generate new JWT secret +pds secret password # Set account password +``` + +All secret commands support: + +- `--local` – Write to `.dev.vars` instead of Cloudflare + +#### `pds secret key` + +Generates a new secp256k1 signing keypair. Updates both the private key secret and the public key in your configuration. + +#### `pds secret jwt` + +Generates a new JWT signing secret for session tokens. + +#### `pds secret password` + +Prompts for a new password and stores the bcrypt hash. + +## Architecture + +The PDS runs as a Cloudflare Worker with a Durable Object for state: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Cloudflare Worker │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Hono Router │ │ +│ │ • Authentication middleware │ │ +│ │ • CORS handling │ │ +│ │ • DID document serving │ │ +│ │ • XRPC endpoint routing │ │ +│ │ • OAuth 2.1 provider │ │ +│ │ • Proxy to AppView for read endpoints │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ AccountDurableObject │ │ +│ │ • SQLite repository storage │ │ +│ │ • Merkle tree for commits │ │ +│ │ • Record indexing │ │ +│ │ • WebSocket firehose │ │ +│ │ • OAuth token storage │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ R2 Bucket │ │ +│ │ • Blob storage (images, videos) │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ ``` -4. Configure DNS to point your domain to the worker. +### Components + +- **Worker** – Stateless edge handler for routing, authentication, and DID document serving +- **AccountDurableObject** – Single-instance SQLite storage for your AT Protocol repository. Handles all write coordination and maintains the commit history. +- **R2** – Object storage for blobs (images, videos). Blobs are content-addressed by CID. + +### XRPC Proxy + +For endpoints this PDS doesn't implement directly (like feed generation or notifications), requests are proxied to the Bluesky AppView. The PDS signs these requests with service authentication, so you get full Bluesky functionality without implementing every endpoint. ## Identity: DIDs and Handles AT Protocol uses two types of identifiers: -- **DID** (Decentralized Identifier): Your permanent, cryptographic identity (e.g., `did:web:pds.example.com`). This never changes and is tied to your signing key. -- **Handle**: Your human-readable username (e.g., `alice.example.com`). This can be any domain you control. +- **DID** (Decentralized Identifier): A permanent, cryptographic identity (for example, `did:web:pds.example.com` or `did:plc:abc123`). This never changes and is tied to a signing key. +- **Handle**: A human-readable username (for example, `alice.example.com`). This can be any domain under the owner's control. + +The DID document (served at `/.well-known/did.json`) contains the public key and tells the network where the PDS is. The `alsoKnownAs` field links the DID to the handle. -The DID document (served at `/.well-known/did.json`) contains your public key and tells the network where your PDS is. The `alsoKnownAs` field links your DID to your handle. +### Supported DID Methods + +- **did:web** – Domain-based DIDs. The DID document is served by the PDS at `/.well-known/did.json` +- **did:plc** – PLC directory DIDs. Used when migrating from an existing Bluesky account ### Handle Verification -Bluesky verifies that you control your handle domain. There are two methods: +Bluesky verifies control of the handle domain. Two methods are available: #### Option A: Handle matches PDS hostname -If your handle is the same as your PDS hostname (e.g., both are `pds.example.com`): - -- The PDS automatically serves `/.well-known/atproto-did` returning your DID -- No additional DNS setup needed -- This is the simplest option +When the handle matches the PDS hostname (for example, both are `pds.example.com`), the PDS automatically serves `/.well-known/atproto-did` with the DID. No additional DNS setup required. #### Option B: Handle on a different domain -If you want a handle on a different domain (e.g., handle `alice.example.com` while PDS is at `pds.example.com`): +For a handle on a different domain (for example, handle `alice.example.com` while PDS is at `pds.example.com`): -1. Add a DNS TXT record to your handle domain: +1. Add a DNS TXT record to the handle domain: ``` _atproto.alice.example.com TXT "did=did:web:pds.example.com" ``` -2. Verify it's working: +2. Verify the record: ```bash dig TXT _atproto.alice.example.com ``` -This lets you use any domain you own as your Bluesky handle, even your personal website. - ## Configuration The PDS uses environment variables for configuration. Public values go in `wrangler.jsonc`, secrets are stored via Wrangler or in `.dev.vars` for local development. @@ -151,6 +269,7 @@ The PDS uses environment variables for configuration. Public values go in `wrang | `DID` | Account DID (did:web:... or did:plc:...) | | `HANDLE` | Account handle | | `SIGNING_KEY_PUBLIC` | Public key for DID document (multibase) | +| `INITIAL_ACTIVE` | Whether account starts active (true/false) | ### Secrets @@ -163,46 +282,152 @@ The PDS uses environment variables for configuration. Public values go in `wrang ## API Endpoints -### Public - -| Endpoint | Description | -| ------------------------------------------- | ---------------------------- | -| `GET /.well-known/did.json` | DID document | -| `GET /.well-known/atproto-did` | Handle verification | -| `GET /xrpc/com.atproto.sync.getRepo` | Export repository as CAR | -| `GET /xrpc/com.atproto.sync.subscribeRepos` | WebSocket firehose | -| `GET /xrpc/com.atproto.repo.describeRepo` | Repository metadata | -| `GET /xrpc/com.atproto.repo.getRecord` | Get a single record | -| `GET /xrpc/com.atproto.repo.listRecords` | List records in a collection | - -### Authenticated - -| Endpoint | Description | -| ---------------------------------------------- | -------------------------- | -| `POST /xrpc/com.atproto.server.createSession` | Login (returns JWT) | -| `POST /xrpc/com.atproto.server.refreshSession` | Refresh JWT | -| `POST /xrpc/com.atproto.repo.createRecord` | Create a record | -| `POST /xrpc/com.atproto.repo.deleteRecord` | Delete a record | -| `POST /xrpc/com.atproto.repo.putRecord` | Create or update a record | -| `POST /xrpc/com.atproto.repo.uploadBlob` | Upload a blob | -| `POST /xrpc/com.atproto.repo.importRepo` | Import repository from CAR | +### Identity + +| Endpoint | Description | +| ------------------------------ | ---------------------------------------------------- | +| `GET /.well-known/did.json` | DID document for did:web resolution | +| `GET /.well-known/atproto-did` | Handle verification (only if handle matches hostname) | +| `GET /health` | Health check with version info | + +### Federation (Sync) + +| Endpoint | Description | +| ------------------------------------------- | ------------------------------------------- | +| `GET /xrpc/com.atproto.sync.getRepo` | Export repository as CAR file | +| `GET /xrpc/com.atproto.sync.getRepoStatus` | Repository status (commit, rev) | +| `GET /xrpc/com.atproto.sync.getBlocks` | Get specific blocks from repository | +| `GET /xrpc/com.atproto.sync.getBlob` | Download a blob by CID | +| `GET /xrpc/com.atproto.sync.listRepos` | List repositories (single-user: just yours) | +| `GET /xrpc/com.atproto.sync.listBlobs` | List all blobs in repository | +| `GET /xrpc/com.atproto.sync.subscribeRepos` | WebSocket firehose for real-time updates | + +### Repository Operations + +| Endpoint | Auth | Description | +| --------------------------------------------- | ---- | ------------------------------------------ | +| `GET /xrpc/com.atproto.repo.describeRepo` | No | Repository metadata | +| `GET /xrpc/com.atproto.repo.getRecord` | No | Get a single record | +| `GET /xrpc/com.atproto.repo.listRecords` | No | List records in a collection | +| `POST /xrpc/com.atproto.repo.createRecord` | Yes | Create a new record | +| `POST /xrpc/com.atproto.repo.putRecord` | Yes | Create or update a record | +| `POST /xrpc/com.atproto.repo.deleteRecord` | Yes | Delete a record | +| `POST /xrpc/com.atproto.repo.applyWrites` | Yes | Batch create/update/delete operations | +| `POST /xrpc/com.atproto.repo.uploadBlob` | Yes | Upload an image or video | +| `POST /xrpc/com.atproto.repo.importRepo` | Yes | Import repository from CAR file | +| `GET /xrpc/com.atproto.repo.listMissingBlobs` | Yes | List blobs referenced but not yet uploaded | + +### Server & Session + +| Endpoint | Auth | Description | +| ----------------------------------------------- | ---- | --------------------------------- | +| `GET /xrpc/com.atproto.server.describeServer` | No | Server capabilities and info | +| `POST /xrpc/com.atproto.server.createSession` | No | Login with password, get JWT | +| `POST /xrpc/com.atproto.server.refreshSession` | Yes | Refresh JWT tokens | +| `GET /xrpc/com.atproto.server.getSession` | Yes | Get current session info | +| `POST /xrpc/com.atproto.server.deleteSession` | Yes | Logout | +| `GET /xrpc/com.atproto.server.getServiceAuth` | Yes | Get JWT for external services | +| `GET /xrpc/com.atproto.server.getAccountStatus` | Yes | Account status (active/deactivated) | +| `POST /xrpc/com.atproto.server.activateAccount` | Yes | Enable writes | +| `POST /xrpc/com.atproto.server.deactivateAccount` | Yes | Disable writes | + +### Handle Resolution + +| Endpoint | Description | +| --------------------------------------------- | ----------------------------------------- | +| `GET /xrpc/com.atproto.identity.resolveHandle` | Resolve handle to DID (local or proxied) | + +### Actor Preferences + +| Endpoint | Auth | Description | +| ----------------------------------------- | ---- | -------------------- | +| `GET /xrpc/app.bsky.actor.getPreferences` | Yes | Get user preferences | +| `POST /xrpc/app.bsky.actor.putPreferences` | Yes | Set user preferences | + +### OAuth 2.1 + +The PDS includes a complete OAuth 2.1 provider for "Login with Bluesky": + +| Endpoint | Description | +| --------------------------------------------- | ------------------------------------ | +| `GET /.well-known/oauth-authorization-server` | OAuth server metadata | +| `POST /oauth/par` | Pushed Authorization Request | +| `GET /oauth/authorize` | Authorization endpoint | +| `POST /oauth/authorize` | Process authorization decision | +| `POST /oauth/token` | Token exchange | +| `POST /oauth/revoke` | Token revocation | + +See the [@ascorbic/atproto-oauth-provider](../oauth-provider/) package for implementation details. -## Architecture +## Deploying to Production -The PDS runs as a Cloudflare Worker with a Durable Object for state: +1. **Enable R2** in your [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/r2/overview). The bucket will be created automatically on first deploy. + +2. **Run the production setup** to deploy secrets: + +```bash +npx pds init --production +``` + +3. **Deploy your worker:** -- **Worker**: Handles routing, authentication, and DID document serving -- **AccountDurableObject**: Stores repository data in SQLite, manages the Merkle tree -- **R2**: Stores blobs (images, videos) +```bash +wrangler deploy +``` + +4. **Configure DNS** to point your domain to the worker. In Cloudflare DNS, add a CNAME record pointing to your workers.dev subdomain, or use a custom domain in your Worker settings. + +## Migration Guide + +Moving an existing Bluesky account to your own PDS: + +### 1. Configure for migration + +```bash +npx pds init +# Answer "Yes" when asked about migrating an existing account +``` + +### 2. Deploy and migrate data + +```bash +wrangler deploy +npx pds migrate +``` + +### 3. Update your identity + +Follow the [AT Protocol account migration guide](https://atproto.com/guides/account-migration) to update your DID document. This typically requires email verification from your current PDS. + +### 4. Go live + +```bash +npx pds activate +``` + +## Validation + +Records are validated against AT Protocol lexicon schemas before being stored. The PDS uses optimistic validation: + +- If a schema exists for the collection, the record must pass validation +- If no schema is loaded, the record is accepted (fail-open) + +This allows the PDS to accept records for new or custom collection types while still enforcing validation for known types like `app.bsky.feed.post`. ## Limitations -- **Single-user only**: One account per deployment -- **No account creation**: The owner is configured at deploy time -- **did:web only**: Uses domain-based DIDs (did:plc support planned) +- **Single-user only** – One account per deployment +- **No account creation** – The owner is configured at deploy time +- **No email** – Password reset and email verification are not supported +- **No moderation** – No reporting or content moderation features ## Resources - [AT Protocol Documentation](https://atproto.com) - [Bluesky](https://bsky.app) - [Cloudflare Workers](https://developers.cloudflare.com/workers/) +- [Account Migration Guide](https://atproto.com/guides/account-migration) + +## License + +MIT