diff --git a/plans/todo/endpoint-implementation.md b/plans/todo/endpoint-implementation.md index 9f84ffc0..729f5f99 100644 --- a/plans/todo/endpoint-implementation.md +++ b/plans/todo/endpoint-implementation.md @@ -70,80 +70,101 @@ This document tracks the implementation status of all AT Protocol XRPC endpoints ## TODO Endpoints (Grouped by Priority) -### Migration Support (P1 - Critical) +### Account Lifecycle (P1 - Critical for Migration) -**Account Lifecycle:** -- `com.atproto.server.createAccount` - Create deactivated account for migration -- `com.atproto.server.activateAccount` - Activate account after migration -- `com.atproto.server.deactivateAccount` - Deactivate old account post-migration -- `com.atproto.server.checkAccountStatus` - Verify migration progress +For the deactivated account pattern (see `migration-wizard.md`): -**Identity Management (PLC Operations):** -- `com.atproto.identity.getRecommendedDidCredentials` - Get DID credentials from new PDS -- `com.atproto.identity.requestPlcOperationSignature` - Request email challenge -- `com.atproto.identity.signPlcOperation` - Sign PLC operation with email token -- `com.atproto.identity.submitPlcOperation` - Submit to PLC directory +| Endpoint | Purpose | Notes | +|----------|---------|-------| +| `activateAccount` | Transition deactivated โ†’ active | Enables writes, firehose | +| `deactivateAccount` | Transition active โ†’ deactivated | Disables writes | +| Enhanced `getAccountStatus` | Return activation state | Add `activated`, `imported` fields | -**Data Migration:** -- `com.atproto.repo.listMissingBlobs` - Identify failed blob imports +**Deactivation guards needed:** +- Block writes (`createRecord`, `putRecord`, `deleteRecord`, `applyWrites`) when deactivated +- Allow reads, `importRepo`, `uploadBlob`, `activateAccount` -**Total: 9 endpoints** +**Total: 2 new endpoints + 1 enhancement** ### App Passwords (P2 - Important) -- `com.atproto.server.createAppPassword` - Create app-specific revocable passwords -- `com.atproto.server.listAppPasswords` - List all app passwords -- `com.atproto.server.revokeAppPassword` - Revoke specific app password +| Endpoint | Purpose | +|----------|---------| +| `createAppPassword` | Create app-specific revocable passwords | +| `listAppPasswords` | List all app passwords | +| `revokeAppPassword` | Revoke specific app password | **Total: 3 endpoints** ### Advanced Sync (P3 - Nice to Have) -- `com.atproto.sync.getBlocks` - Get specific blocks by CID -- `com.atproto.sync.getLatestCommit` - Get latest commit without full repo -- `com.atproto.sync.getRecord` - Get record with merkle proof +| Endpoint | Purpose | +|----------|---------| +| `getBlocks` | Get specific blocks by CID | +| `getLatestCommit` | Get latest commit without full repo | +| `getRecord` (sync) | Get record with merkle proof | **Total: 3 endpoints** -## Will NOT Support +## Not Implementing + +### createAccount + +**Reason:** Account creation happens at deploy time, not via API. + +For migration: DID set in env vars, data imported via `importRepo`. +For new accounts: Deploy script generates DID, publishes to PLC. + +May revisit if tools like Goat require it. + +### PLC Operation Endpoints + +| Endpoint | Reason | +|----------|--------| +| `getRecommendedDidCredentials` | Not needed - keys generated at deploy | +| `requestPlcOperationSignature` | Handled by old PDS during migration | +| `signPlcOperation` | Handled by old PDS during migration | +| `submitPlcOperation` | Handled by old PDS during migration | + +PLC operations for migration are performed against the **old** PDS, not the new one. ### Multi-User Administration (14 endpoints) + **Reason:** Single-user PDS has no admin/user separation All `com.atproto.admin.*` endpoints ### Moderation (1 endpoint) + **Reason:** Single-user PDS doesn't need moderation infrastructure - `com.atproto.moderation.createReport` ### Account Creation & Invites (5 endpoints) -**Reason:** Single-user PDS is pre-configured -- `com.atproto.server.createInviteCode` -- `com.atproto.server.createInviteCodes` -- `com.atproto.server.getAccountInviteCodes` -- `com.atproto.temp.checkSignupQueue` +**Reason:** Single-user PDS is pre-configured -*Exception:* `createAccount` will be implemented for migration only +- `createInviteCode` +- `createInviteCodes` +- `getAccountInviteCodes` +- `checkSignupQueue` ### Email Verification & Recovery (6 endpoints) + **Reason:** Single-user PDS has no email system -- `com.atproto.server.confirmEmail` -- `com.atproto.server.requestEmailConfirmation` -- `com.atproto.server.requestEmailUpdate` -- `com.atproto.server.updateEmail` -- `com.atproto.server.requestPasswordReset` -- `com.atproto.server.resetPassword` +- `confirmEmail` +- `requestEmailConfirmation` +- `requestEmailUpdate` +- `updateEmail` +- `requestPasswordReset` +- `resetPassword` ### Deprecated (2 endpoints) - `com.atproto.sync.deprecated.getCheckout` - `com.atproto.sync.deprecated.getHead` -**Will Not Support Total: 28 endpoints** - ## Proxy Strategy All unimplemented `app.bsky.*` endpoints are proxied to `api.bsky.app` with service auth. This includes: @@ -157,19 +178,25 @@ This is intentional - the edge PDS focuses on repository operations and federate ## Implementation Phases -### Phase 1: Migration Support (13 endpoints) -Enable full account migration to/from this PDS -- See `migration-wizard.md` for detailed specification +### Phase 1: Account Lifecycle (2 endpoints) + +Enable deactivated account pattern for migration: +- `activateAccount` +- `deactivateAccount` +- Deactivation guards on write operations ### Phase 2: OAuth Provider -Enable ecosystem compatibility with "Login with Bluesky" apps -- See `oauth-provider.md` for detailed specification -### Phase 3: Enhanced Features (3 endpoints) -Multi-device auth with app passwords +Enable ecosystem compatibility with "Login with Bluesky" apps. +See `oauth-provider.md` for detailed specification. + +### Phase 3: App Passwords (3 endpoints) + +Multi-device auth with revocable app passwords. ### Phase 4: Advanced Sync (3 endpoints) -Efficient partial sync and merkle proofs + +Efficient partial sync and merkle proofs. ## Endpoint Coverage by Namespace diff --git a/plans/todo/migration-wizard.md b/plans/todo/migration-wizard.md index ecdbf5d3..ef7d313a 100644 --- a/plans/todo/migration-wizard.md +++ b/plans/todo/migration-wizard.md @@ -1,688 +1,268 @@ -# Migration Wizard - Gold Standard UX +# Account Migration **Status:** ๐Ÿ“‹ Planning **Priority:** P0 (Critical feature for user adoption) ## Overview -A one-command migration experience that enables users to migrate their Bluesky accounts to a self-hosted edge PDS with zero downtime, full data preservation, and the ability to test before committing. +A deploy-first approach where accounts start deactivated and are activated after importing data and updating identity. -## Unique Advantages of Serverless +## Core Concept: Deactivated Accounts -**Traditional PDS Migration:** -- Downtime while switching servers -- Can't test before switching -- Expensive to run two servers in parallel -- Scary "point of no return" +Instead of a complex migration wizard, we use a simple two-state model: -**Edge PDS Migration:** -- โœ… Deploy new PDS in seconds (just a Worker) -- โœ… Run old + new simultaneously (pennies in cost) -- โœ… Test thoroughly before switching -- โœ… Instant rollback if issues -- โœ… Zero downtime cutover (just update PLC) +1. **Deactivated** - PDS is deployed, configured with DID, but not yet active +2. **Active** - Account is live, accepting writes, emitting firehose events -## User Experience Goal +The "migration" is just: deploy deactivated โ†’ import data โ†’ activate. -```bash -npx @ascorbic/pds migrate -``` - -One command. Everything automatic. Test mode before cutover. Zero risk. - -## Migration Flow Overview - -### Stage 1: Account Detection & Setup -- Auto-detect Bluesky account from local app data (macOS/Windows/Linux) -- Connect Cloudflare account (OAuth) -- Choose domain (owned domain or workers.dev) -- Show cost estimate (~$0.01/month) - -### Stage 2: Infrastructure Provisioning -- Create Worker + R2 bucket automatically -- Set up DNS records (if domain on Cloudflare) -- Generate signing keys and secrets -- Deploy to `-staging` subdomain - -### Stage 3: Data Migration -- Export CAR file from old PDS -- Download blobs (with progress bars, resumable) -- Import to staging PDS -- Validate all data present - -### Stage 4: Test Mode -- Staging PDS fully operational -- User can test extensively -- NO PLC update yet (safe to abandon) -- Automated validation suite -- Manual testing guide - -### Stage 5: Cutover -- Update PLC directory atomically -- Switch staging โ†’ production -- Update handle (if desired) -- Keep 24h rollback window - -## Detailed User Journeys - -### Journey A: Fresh Migration from Bluesky - -**User:** Alice (@alice.bsky.social) wants her own PDS - -``` -$ npx create-pds alice-pds -$ cd alice-pds -$ pnpm pds migrate - -๐Ÿ” Detecting your Bluesky account... - - Auto-detected: alice.bsky.social - DID: did:plc:abc123xyz - Current PDS: bsky.social - - ๐Ÿ“Š Your account: - 142 posts โ€ข 23 images (2.4 MB) โ€ข 89 followers - -โ˜๏ธ Connect Cloudflare account - โ†’ Opening browser for authentication... - โœ“ Connected: alice@example.com - -๐ŸŒ Choose your PDS domain - โ†’ alice.com (recommended) โญ - Can use as your handle: @alice.com - -๐Ÿ’ฐ Cost estimate: ~$0.01/month - -๐Ÿ” Setting up infrastructure... - โœ“ Worker: alice-pds-staging - โœ“ R2 bucket: alice-pds-blobs - โœ“ DNS: alice.com โ†’ Worker - โœ“ Signing keys generated - -๐Ÿ“ฆ Exporting from bsky.social... - Repository (1.2 MB) โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“ 100% - Media (23 files, 2.4 MB) โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“ 100% - -๐Ÿ“ฅ Importing to staging PDS... - โœ“ All records imported - โœ“ All media accessible - -๐Ÿงช TEST MODE: Your PDS is ready to test! - - Staging URL: https://alice-pds-staging.workers.dev - - Try it: - 1. API test: curl https://alice-pds-staging.workers.dev/xrpc/... - 2. Run tests: pnpm pds test - 3. Test with Bluesky app (debug mode) - - When ready: pnpm pds cutover - ---- - -$ pnpm pds cutover - -๐Ÿš€ Ready to go live? - - This will: - โœ“ Update PLC directory - โœ“ Point did:plc:abc123 โ†’ alice.com - โœ“ Update handle: @alice.bsky.social โ†’ @alice.com - - Continue? (y/N) y - -๐Ÿ”„ Updating identity... - โœ“ PLC operation submitted - โœ“ Verified propagation - โœ“ Activated alice.com - -๐ŸŽ‰ Migration complete! - Your PDS: https://alice.com - Your handle: @alice.com -``` - -### Journey B: Interrupted Migration +## Deployment Configuration -**User:** Charlie's network died during blob download +### Required Secrets/Environment Variables +**Infrastructure (always required):** ``` -$ pnpm pds migrate - -๐Ÿ”„ Found incomplete migration - - Progress: - โœ“ Cloudflare setup - โœ“ Repository export (1.2 MB) - โธ Media: 14/23 files downloaded - - Resume? (Y/n) y - -๐Ÿ“ฆ Resuming download... - Media files โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 61% (14/23) - - [Continues normally from where it left off] +PDS_HOSTNAME = "alice.example.com" +JWT_SECRET = "" +PASSWORD_HASH = "" ``` -### Journey C: Rollback After Issues - -**User:** Diana found an issue after going live - -``` -$ pnpm pds rollback - -โšก Rolling back to bsky.social - - โœ“ Reverted PLC directory (1.2s) - โœ“ Verified: you're back on bsky.social - - Your alice.com PDS still exists for debugging. -``` - -## Implementation Components - -### CLI Commands - +**Identity:** ``` -pds migrate # Main migration wizard (interactive) -pds migrate status # Show current progress -pds migrate resume # Resume from checkpoint -pds cutover # Go live after testing -pds rollback # Emergency rollback (24h window) -pds test # Run validation suite -pds cleanup # Remove old PDS data +DID = "did:plc:xyz..." # For migration: the existing DID + # For new account: generated by deploy script +SIGNING_KEY = "" # Generated by deploy script ``` -### State Management +### How Configuration Works -**Location:** `.pds/migration-state.json` +| Value | Set By | Notes | +|-------|--------|-------| +| `PDS_HOSTNAME` | User | Their domain | +| `JWT_SECRET` | Deploy script | Generated | +| `PASSWORD_HASH` | User/script | From chosen password | +| `DID` | User (migration) or script (new) | Existing or generated | +| `SIGNING_KEY` | Deploy script | Always generated fresh | -```json -{ - "version": "1.0.0", - "migrationId": "mig_2024-01-15_abc123", - "currentStep": "import", - "status": "in_progress", - - "account": { - "did": "did:plc:abc123", - "handle": "alice.bsky.social", - "oldPdsUrl": "https://bsky.social" - }, - - "cloudflare": { - "accountId": "cf-account-123", - "domain": "alice.com", - "zoneId": "zone-456" - }, - - "resources": { - "stagingWorker": { - "name": "alice-pds-staging", - "url": "https://alice-pds-staging.workers.dev", - "created": true - } - }, - - "export": { - "completed": true, - "repo": { - "file": "repo.car", - "size": 1234567, - "downloaded": true - }, - "blobs": { - "total": 23, - "downloaded": 14, - "manifest": [...] - } - }, - - "cutover": { - "completed": false, - "rollbackWindowUntil": null - } -} -``` - -**Features:** -- Atomic writes (write to temp, rename) -- Encrypted auth tokens -- Checkpoint after each major step -- Enables resume from any point - -### Account Detection +## Account States -**Auto-detection strategy:** +### Deactivated State -1. Check for Bluesky app session files: - - macOS: `~/Library/Application Support/xyz.blueskyweb.app/` - - Linux: `~/.config/xyz.blueskyweb.app/` - - Windows: `%APPDATA%\xyz.blueskyweb.app\` +When deployed with a DID but not yet activated: -2. Parse session JSON for DID and tokens +- โœ… Serves DID document (required for PLC operations) +- โœ… Accepts `importRepo` and `uploadBlob` +- โœ… Read operations work (getRepo, getRecord, etc.) +- โŒ Write operations rejected (createRecord, etc.) +- โŒ Firehose does not emit events +- โ„น๏ธ `getAccountStatus` returns `active: false` -3. Fallback to manual entry if not found +### Active State -4. Validate by fetching account info from current PDS +After activation: -### Cloudflare Authentication +- โœ… All operations work normally +- โœ… Firehose emits events +- โ„น๏ธ `getAccountStatus` returns `active: true` -**OAuth flow:** +## Migration Flow -1. Check for existing credentials (env, wrangler config, project) -2. If none, initiate OAuth: - - Generate PKCE challenge - - Open browser to Cloudflare OAuth endpoint - - Start local HTTP server for callback - - Exchange code for token -3. Verify permissions (Workers, R2, DNS) +### Step 1: Deploy -### Domain Selection - -**Detection:** - -``` -GET /zones โ†’ List domains in account -``` - -**Presentation:** - -``` -Choose your PDS domain: - -โ†’ alice.com (recommended) โญ - โ€ข Active on Cloudflare - โ€ข Can use as your handle: @alice.com - -โ†’ example.com - โ€ข Alternative option - -โ†’ Use Workers.dev subdomain - โ€ข Free: alice-pds.alice.workers.dev - โ€ข Cannot use as handle - -Which domain? (1) -``` - -**DNS automation:** -- If domain on Cloudflare: Create DNS records via API -- If external DNS: Provide instructions - -### Resource Provisioning - -**Worker:** -``` -POST /accounts/{account_id}/workers/scripts/{script_name} -``` - -**Naming:** -- Staging: `{project-name}-staging` -- Production: `{project-name}` - -**R2 Bucket:** -``` -POST /accounts/{account_id}/r2/buckets -{ "name": "{project-name}-blobs" } -``` - -**DNS (if Cloudflare domain):** -``` -POST /zones/{zone_id}/dns_records -[ - { type: "CNAME", name: domain, content: worker-url }, - { type: "TXT", name: "_atproto", content: "did=..." } -] -``` - -**Cost Estimation:** +```bash +# Clone/scaffold project +npx create-pds my-pds +cd my-pds -Before creating, show: +# Set secrets (DID from existing account) +wrangler secret put DID # did:plc:xyz... +wrangler secret put PASSWORD_HASH +wrangler secret put JWT_SECRET +wrangler secret put SIGNING_KEY +# Deploy +pnpm deploy ``` -Monthly cost estimate: - -Workers (unlimited requests) Free -R2 Storage (2.4 MB) $0.00 -R2 Operations (~10k/mo) $0.01 -โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -Total ~$0.01/month -99% of users stay on free tier! ๐ŸŽ‰ -``` +PDS is now live but deactivated. -### Data Export +### Step 2: Export from Old PDS -**Repository:** -``` -GET /xrpc/com.atproto.sync.getRepo?did={did} -โ†’ Save to .pds/cache/{did}/repo.car -``` +```bash +# Export repository +curl -o repo.car \ + "https://bsky.social/xrpc/com.atproto.sync.getRepo?did=did:plc:xyz" -**Blobs:** +# List blobs to export +curl "https://bsky.social/xrpc/com.atproto.sync.listBlobs?did=did:plc:xyz" ``` -GET /xrpc/com.atproto.sync.listBlobs?did={did} -โ†’ Get CID list -For each CID: - GET /xrpc/com.atproto.sync.getBlob?did={did}&cid={cid} - โ†’ Save to .pds/cache/{did}/blobs/{cid} -``` +### Step 3: Import to New PDS -**Parallel download:** -- Up to 5 blobs concurrently -- Resume support (track completed CIDs) -- Progress bars with speed and ETA +```bash +# Import repository +curl -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.ipld.car" \ + --data-binary @repo.car \ + "https://alice.example.com/xrpc/com.atproto.repo.importRepo" -**Cache manifest:** -```json -{ - "did": "did:plc:abc123", - "exportedAt": "2024-01-15T10:30:00Z", - "repo": { - "file": "repo.car", - "size": 1234567, - "sha256": "..." - }, - "blobs": [ - { - "cid": "bafyxxx", - "file": "blobs/bafyxxx", - "size": 124567, - "mimeType": "image/jpeg" - } - ] -} +# Import blobs (one by one, or use bulk endpoint if available) +curl -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: image/jpeg" \ + --data-binary @blob.jpg \ + "https://alice.example.com/xrpc/com.atproto.repo.uploadBlob" ``` -### Data Import +### Step 4: Verify Import -**Repository:** -``` -POST /xrpc/com.atproto.repo.importRepo -Content-Type: application/vnd.ipld.car -Authorization: Bearer {token} +```bash +# Check repo was imported correctly +curl "https://alice.example.com/xrpc/com.atproto.repo.describeRepo?repo=did:plc:xyz" -{CAR file bytes} +# Check account status +curl "https://alice.example.com/xrpc/com.atproto.server.getAccountStatus" +# Returns: { "active": false, "imported": true, ... } ``` -**Blobs:** -``` -POST /xrpc/com.atproto.repo.uploadBlob -Content-Type: {mime-type} -Authorization: Bearer {token} +### Step 5: Update PLC Directory -{blob bytes} -``` +This points your DID to the new PDS. Requires signing by old PDS via email challenge: -**Parallel upload:** -- Up to 3 blobs concurrently -- Track uploaded CIDs in state +```bash +# Request PLC operation signature from old PDS +# (triggers email with token) +curl -X POST \ + -H "Authorization: Bearer $OLD_PDS_TOKEN" \ + "https://bsky.social/xrpc/com.atproto.identity.requestPlcOperationSignature" -**Post-import validation:** +# Sign operation with email token +# ... (flow TBD based on old PDS implementation) ``` -1. describeRepo - verify collections -2. listRecords - count records per collection -3. getRecord - sample records -4. getBlob - sample blobs -5. Check firehose operational -``` - -### PLC Directory Operations -**Current limitation:** Requires rotation keys +### Step 6: Activate -**Email challenge flow (official):** -``` -1. GET /xrpc/com.atproto.identity.getRecommendedDidCredentials -2. POST /xrpc/com.atproto.identity.requestPlcOperationSignature - โ†’ Email sent with token -3. POST /xrpc/com.atproto.identity.signPlcOperation - โ†’ Sign with email token -4. POST /xrpc/com.atproto.identity.submitPlcOperation - โ†’ Submit to plc.directory +```bash +curl -X POST \ + -H "Authorization: Bearer $TOKEN" \ + "https://alice.example.com/xrpc/com.atproto.server.activateAccount" ``` -**Implementation:** -- Prompt user to check email for token -- Sign operation with token -- Submit to PLC directory -- Poll for propagation (up to 60s) +Account is now live. -### Automated Validation Suite +## New Account Flow -**`pds test` command:** - -``` -Running PDS Tests -โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +For creating a fresh identity (not migration): -Identity - โœ“ DID document served - โœ“ Handle resolves correctly - โœ“ Keys match expected values +### Step 1: Generate Identity -Repository - โœ“ describeRepo returns correct collections - โœ“ Sample records accessible (5/5) - โœ“ Record count matches export (142) - -Blobs - โœ“ Blob storage configured - โœ“ Sample blobs accessible (5/5) - โœ“ All blob CIDs present (23/23) - -Federation - โœ“ Firehose subscription works - โœ“ Can receive commit events +```bash +# Deploy script generates DID and signing key +npx create-pds my-pds --new-account -All tests passed! โœ“ +# This: +# 1. Generates did:plc and signing keypair +# 2. Publishes DID document to PLC directory +# 3. Sets secrets in Cloudflare +# 4. Deploys the Worker ``` -### Manual Testing Guide +### Step 2: Ready to Use -**Provided after staging deployment:** +Account starts active (no deactivated state needed for new accounts). +```bash +# Log in via Bluesky app or API +# Handle is your domain: @alice.example.com ``` -TEST MODE ACTIVE -Your staging PDS: https://alice-pds-staging.workers.dev +## Endpoints Required -Try it: +### Already Implemented -1. API test: - curl https://alice-pds-staging.workers.dev/xrpc/com.atproto.repo.describeRepo?repo=did:plc:abc123 +| Endpoint | Notes | +|----------|-------| +| `importRepo` | โœ… Complete | +| `uploadBlob` | โœ… Complete | +| `getAccountStatus` | โœ… Basic implementation | +| `describeRepo` | โœ… Complete | +| `getRepo` | โœ… Complete | -2. Test with Bluesky app (safe - won't affect main account): - โ€ข Open Bluesky settings - โ€ข Advanced โ†’ Custom PDS (debug mode) - โ€ข Enter: https://alice-pds-staging.workers.dev - โ€ข Browse posts, test posting - โ€ข Switch back when done +### To Implement -3. Run automated tests: - pnpm pds test +| Endpoint | Purpose | +|----------|---------| +| `activateAccount` | Transition deactivated โ†’ active | +| `deactivateAccount` | Transition active โ†’ deactivated | +| Enhanced `getAccountStatus` | Return activation state, import status | -Take your time. When ready: pnpm pds cutover -``` +### Not Needed -## Code Changes Needed +| Endpoint | Reason | +|----------|--------| +| `createAccount` | Account created at deploy time | +| PLC operation endpoints | Handled externally via old PDS | -### 1. Add Force Flag to importRepo +## Deactivation Guards -**Location:** `packages/pds/src/account-do.ts` +When account is deactivated, these operations should fail with error: ```typescript -async rpcImportRepo( - carBytes: Uint8Array, - force = false -): Promise<{ did: string; rev: string }> { - const existingRoot = await this.storage!.getRoot(); - - if (existingRoot && !force) { - throw new Error("Repository exists. Use force=true to overwrite."); - } - - if (force && existingRoot) { - // Wipe and reimport - await this.storage!.destroy(); - await this.ensureStorageInitialized(); - } - - // ... rest of import logic -} -``` - -### 2. Add Blob Migration Helper - -**Location:** `packages/pds/src/account-do.ts` - -```typescript -async rpcImportBlobs( - oldPdsUrl: string, - did: string -): Promise<{ imported: number; failed: string[] }> { - // List blobs from old PDS - const listUrl = `${oldPdsUrl}/xrpc/com.atproto.sync.listBlobs?did=${did}`; - const listRes = await fetch(listUrl); - const { cids } = await listRes.json(); - - const failed: string[] = []; - for (const cid of cids) { - try { - const blobUrl = `${oldPdsUrl}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`; - const blobRes = await fetch(blobUrl); - const bytes = new Uint8Array(await blobRes.arrayBuffer()); - const mimeType = blobRes.headers.get('content-type') || 'application/octet-stream'; - - await this.rpcUploadBlob(bytes, mimeType); - } catch (err) { - failed.push(cid); - } - } - - return { imported: cids.length - failed.length, failed }; +// Error response for writes when deactivated +{ + "error": "AccountDeactivated", + "message": "Account is deactivated. Call activateAccount to enable writes." } ``` -### 3. Add PLC Management Endpoints - -**New file:** `packages/pds/src/xrpc/identity.ts` - -Implement: -- `getRecommendedDidCredentials` -- `requestPlcOperationSignature` -- `signPlcOperation` -- `submitPlcOperation` - -Using `@atproto/identity` package - -### 4. Add Account Lifecycle Endpoints - -**Location:** `packages/pds/src/xrpc/server.ts` +**Blocked operations:** +- `createRecord` +- `putRecord` +- `deleteRecord` +- `applyWrites` -Implement: -- `createAccount` (deactivated state) +**Allowed operations:** +- All read operations +- `importRepo` +- `uploadBlob` - `activateAccount` -- `deactivateAccount` -- `checkAccountStatus` (enhanced) - -### 5. Add listMissingBlobs Endpoint - -**Location:** `packages/pds/src/xrpc/repo.ts` - -```typescript -export async function listMissingBlobs(c: Context) { - const repo = c.req.query('repo'); - // Get all blob CIDs from records - // Check which ones are missing from R2 - // Return missing list -} -``` - -## Package Structure - -``` -packages/pds-migrate/ -โ”œโ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ cli.ts # Main CLI entry -โ”‚ โ”œโ”€โ”€ commands/ -โ”‚ โ”‚ โ”œโ”€โ”€ migrate.ts # Main migration flow -โ”‚ โ”‚ โ”œโ”€โ”€ cutover.ts # Go live -โ”‚ โ”‚ โ”œโ”€โ”€ rollback.ts # Undo cutover -โ”‚ โ”‚ โ”œโ”€โ”€ test.ts # Validation suite -โ”‚ โ”‚ โ””โ”€โ”€ status.ts # Show progress -โ”‚ โ”œโ”€โ”€ steps/ -โ”‚ โ”‚ โ”œโ”€โ”€ detect-account.ts # Auto-detect Bluesky -โ”‚ โ”‚ โ”œโ”€โ”€ connect-cloudflare.ts # OAuth -โ”‚ โ”‚ โ”œโ”€โ”€ provision.ts # Create resources -โ”‚ โ”‚ โ”œโ”€โ”€ export.ts # Download from old PDS -โ”‚ โ”‚ โ”œโ”€โ”€ import.ts # Upload to new PDS -โ”‚ โ”‚ โ”œโ”€โ”€ validate.ts # Test everything -โ”‚ โ”‚ โ””โ”€โ”€ update-identity.ts # PLC operations -โ”‚ โ”œโ”€โ”€ lib/ -โ”‚ โ”‚ โ”œโ”€โ”€ cloudflare.ts # CF API wrapper -โ”‚ โ”‚ โ”œโ”€โ”€ atproto.ts # AT Protocol helpers -โ”‚ โ”‚ โ”œโ”€โ”€ plc.ts # PLC directory ops -โ”‚ โ”‚ โ”œโ”€โ”€ state.ts # Migration state -โ”‚ โ”‚ โ””โ”€โ”€ keys.ts # Crypto generation -โ”‚ โ””โ”€โ”€ ui/ -โ”‚ โ”œโ”€โ”€ prompts.ts # Interactive prompts -โ”‚ โ”œโ”€โ”€ progress.ts # Progress bars -โ”‚ โ””โ”€โ”€ errors.ts # Error formatting -โ””โ”€โ”€ package.json -``` - -## Non-Interactive Mode - -**For scripting:** - -```bash -pnpm pds migrate \ - --yes \ - --from alice.bsky.social \ - --to alice.com \ - --cf-account-id xxx \ - --cf-api-token yyy \ - --staging-only -``` - -**Exit codes:** -- 0 = Success -- 1 = User cancelled -- 2 = Validation failed -- 3 = Network error (retryable) -- 4 = Configuration error -## Success Criteria +## CLI Tooling (Future) -1. โœ… One command migration (`pnpm pds migrate`) -2. โœ… Auto-detects Bluesky account -3. โœ… Provisions all infrastructure automatically -4. โœ… Exports and imports all data -5. โœ… Test mode before committing -6. โœ… Automated validation -7. โœ… Zero downtime cutover -8. โœ… 24-hour rollback window -9. โœ… Clear progress indicators -10. โœ… Resumable from any point +The deploy script (`create-pds`) should handle: -## Timeline +1. **Interactive setup:** + - Prompt for domain + - Prompt for existing DID (migration) or generate new + - Generate secrets + - Deploy to Cloudflare -**Minimal (Fix Current Issues):** 1 day -- Force flag, blob migration, validation +2. **Migration helpers:** + - Export from old PDS + - Import to new PDS + - Validate import + - Guide through PLC update -**Good (Smooth but Manual):** 3-4 days -- + PLC endpoints, detailed guide +3. **Commands:** + ``` + npx create-pds my-pds # New account + npx create-pds my-pds --migrate # Migration mode + pnpm pds status # Check account status + pnpm pds activate # Activate account + pnpm pds validate # Run validation suite + ``` -**Great (Turnkey Solution):** 2 weeks -- + CLI wizard, automation +## Advantages Over Complex Wizard -**Amazing (Best Migration UX):** 3-4 weeks -- + Everything above + polish +1. **Simpler state model** - Just active/deactivated, not a migration state machine +2. **Decoupled concerns** - Deploy is separate from migration +3. **Standard AT Protocol** - Uses existing endpoints, no custom flows +4. **Resumable** - Can stop/restart at any point +5. **Testable** - Read operations work while deactivated +6. **No staging/production split** - One deployment, two states ## References - [Account Migration - AT Protocol](https://atproto.com/guides/account-migration) -- [Account Migration Details](https://github.com/bluesky-social/atproto/discussions/3176) -- [Bluesky PDS Migration Docs](https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md) -- [Enabling Migration Back to Bluesky](https://docs.bsky.app/blog/incoming-migration) +- [PDS Migration Docs](https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md)