···11# PDSharp
2233-Personal Data Server written in F#
33+> A Personal Data Server (PDS) for the AT Protocol, written in F# with Giraffe.
44+55+## Goal
66+77+Build and deploy a single-user PDS that can host your AT Protocol repository, serve blobs, and federate with Bluesky.
88+99+## Requirements
1010+1111+- .NET 9.0 SDK
1212+- [Just](https://github.com/casey/just) (optional, for potential future task running)
1313+1414+## Getting Started
1515+1616+### Restore & Build the project
1717+1818+```bash
1919+dotnet restore
2020+dotnet build
2121+```
2222+2323+### Run the tests
2424+2525+```bash
2626+dotnet test
2727+```
2828+2929+### Run the Server
3030+3131+```bash
3232+dotnet run --project PDSharp/PDSharp.fsproj
3333+```
3434+3535+The server will start at `http://localhost:5000`.
3636+3737+### Verify
3838+3939+Check the `describeServer` endpoint:
4040+4141+```bash
4242+curl http://localhost:5000/xrpc/com.atproto.server.describeServer
4343+```
4444+4545+## Configuration
4646+4747+The application uses `appsettings.json` and supports Environment Variable overrides.
4848+4949+| Key | Env Var | Default | Description |
5050+| ----------- | ------------------- | ----------------------- | ------------------------- |
5151+| `DidHost` | `PDSHARP_DidHost` | `did:web:localhost` | The DID of the PDS itself |
5252+| `PublicUrl` | `PDSHARP_PublicUrl` | `http://localhost:5000` | Publicly reachable URL |
5353+5454+Example `appsettings.json`:
5555+5656+```json
5757+{
5858+ "PublicUrl": "http://localhost:5000",
5959+ "DidHost": "did:web:localhost"
6060+}
6161+```
6262+6363+## Architecture
6464+6565+### App (Giraffe)
6666+6767+- `XrpcRouter`: `/xrpc/<NSID>` routing
6868+- `Auth`: Session management (JWTs)
6969+- `RepoApi`: Write/Read records (`putRecord`, `getRecord`)
7070+- `ServerApi`: Server meta (`describeServer`)
7171+7272+### Core (Pure F#)
7373+7474+- `DidResolver`: Identity resolution
7575+- `RepoEngine`: MST, DAG-CBOR, CIDs, Blocks
7676+- `Models`: Data types for XRPC/Database
7777+7878+### Infra
7979+8080+- SQLite/Postgres for persistence
8181+- S3/Disk for blob storage
+142
roadmap.txt
···11+================================================================================
22+*roadmap.txt* PDSharp: F#/Giraffe PDS Implementation Roadmap
33+================================================================================
44+PHASE 1: IMPLEMENTATION (Build)
55+================================================================================
66+Milestone A: Giraffe XRPC Shell
77+--------------------------------------------------------------------------------
88+- [x] Implement /xrpc/com.atproto.server.describeServer (GET)
99+- [x] Implement NSID-based routing with structured error responses
1010+DoD: describeServer responds with stable JSON
1111+--------------------------------------------------------------------------------
1212+Milestone B: Identity + Crypto Primitives
1313+--------------------------------------------------------------------------------
1414+- DID document fetch/parse for signing key and PDS endpoint
1515+- SHA-256 hashing, ECDSA sign/verify (p256 + k256), low-S enforcement
1616+DoD: Sign and verify atproto commit hash with low-S
1717+--------------------------------------------------------------------------------
1818+Milestone C: DAG-CBOR + CID
1919+--------------------------------------------------------------------------------
2020+- Canonical DAG-CBOR encode/decode with IPLD link tagging
2121+- CID creation/parsing (multicodec dag-cbor, sha2-256)
2222+DoD: Record JSON → stable DAG-CBOR bytes → deterministic CID
2323+--------------------------------------------------------------------------------
2424+Milestone D: MST Implementation
2525+--------------------------------------------------------------------------------
2626+- Merkle Search Tree per repository spec
2727+- Key depth = leading zero bits in SHA-256(key) counted in 2-bit chunks
2828+- Node encoding: (l, e[p,k,v,t]) with key prefix compression
2929+DoD: Insert/update/delete yields reproducible root CID
3030+--------------------------------------------------------------------------------
3131+Milestone E: Commit + BlockStore + putRecord
3232+--------------------------------------------------------------------------------
3333+- BlockStore: cid → bytes, indexed by DID/rev/head
3434+- Commit signing: UnsignedCommit → DAG-CBOR → sha256 → ECDSA sign
3535+- Implement com.atproto.repo.putRecord/createRecord
3636+DoD: Write and read records by path/AT-URI
3737+--------------------------------------------------------------------------------
3838+Milestone F: CAR Export + Sync Endpoints
3939+--------------------------------------------------------------------------------
4040+- CARv1 writer (roots = commit CID, blocks stream)
4141+- Implement: sync.getRepo, sync.getBlocks, sync.getBlob
4242+DoD: External services can fetch repo snapshot + blocks
4343+--------------------------------------------------------------------------------
4444+Milestone G: subscribeRepos Firehose
4545+--------------------------------------------------------------------------------
4646+- Monotonic sequence number + commit event generation
4747+- WebSocket streaming for subscribeRepos
4848+DoD: Relay/client receives commit events after writes
4949+--------------------------------------------------------------------------------
5050+Milestone H: Account + Sessions
5151+--------------------------------------------------------------------------------
5252+- Implement: server.createAccount, server.createSession, refreshSession
5353+- Password/app-password hashing + JWT issuance
5454+DoD: Authenticate and write records with accessJwt
5555+--------------------------------------------------------------------------------
5656+Milestone I: Lexicon Validation + Conformance
5757+--------------------------------------------------------------------------------
5858+- Lexicon validation for writes (app.bsky.* records)
5959+- Conformance testing: diff CIDs/CARs/signatures vs reference PDS
6060+DoD: Same inputs → same outputs for repo/sync surfaces
6161+================================================================================
6262+PHASE 2: DEPLOYMENT (Self-Host)
6363+================================================================================
6464+Milestone J: Topology + Domain Planning
6565+--------------------------------------------------------------------------------
6666+- Choose PDS hostname (pds.example.com) vs handle domain (example.com)
6767+- Obtain domain, DNS access, VPS with static IP, reverse proxy
6868+DoD: Clear plan for PDS location, handle, and DID resolution
6969+--------------------------------------------------------------------------------
7070+Milestone K: DNS + TLS + Reverse Proxy
7171+--------------------------------------------------------------------------------
7272+- DNS A/AAAA records for PDS hostname
7373+- TLS certs (ACME) via Caddy/Nginx/Traefik
7474+DoD: https://<pds-hostname> responds with valid cert
7575+--------------------------------------------------------------------------------
7676+Milestone L: Deploy PDSharp
7777+--------------------------------------------------------------------------------
7878+- Deploy built PDS with persistence (SQLite/Postgres + blob storage)
7979+- Verify /xrpc/com.atproto.server.describeServer
8080+DoD: describeServer returns capabilities payload
8181+--------------------------------------------------------------------------------
8282+Milestone M: Account Creation
8383+--------------------------------------------------------------------------------
8484+- Create account using admin tooling
8585+- Verify authentication: createSession
8686+DoD: Obtain session and perform authenticated write
8787+--------------------------------------------------------------------------------
8888+Milestone N: Smoke Test Repo + Blobs
8989+--------------------------------------------------------------------------------
9090+- Write record via putRecord
9191+- Upload blob, verify retrieval via sync.getBlob
9292+DoD: Posts appear in clients, media loads reliably
9393+--------------------------------------------------------------------------------
9494+Milestone O: Account Migration (Optional)
9595+--------------------------------------------------------------------------------
9696+- Export/import from bsky.social
9797+- Update DID service endpoint
9898+- Verify handle/DID resolution
9999+DoD: Handle unchanged, DID points to your PDS
100100+--------------------------------------------------------------------------------
101101+Milestone P: Reliability
102102+--------------------------------------------------------------------------------
103103+- Backups: repo storage + database + blobs
104104+- Restore drill on fresh instance
105105+- Monitoring: uptime checks for describeServer + getBlob
106106+DoD: Restore from backup passes smoke tests
107107+--------------------------------------------------------------------------------
108108+Milestone Q: Updates + Security
109109+--------------------------------------------------------------------------------
110110+- Update cadence with rollback plan
111111+- Rate limits and access controls at proxy
112112+- Log retention and disk growth alerts
113113+DoD: Update smoothly, maintain stable federation
114114+================================================================================
115115+QUICK CHECKLIST
116116+================================================================================
117117+[ ] describeServer endpoint working
118118+[ ] Crypto primitives (sha256, ECDSA p256/k256, low-S)
119119+[ ] DAG-CBOR + CID generation correct
120120+[ ] MST producing deterministic root CIDs
121121+[ ] putRecord + blockstore operational
122122+[ ] CAR export + sync endpoints
123123+[ ] subscribeRepos firehose
124124+[ ] Authentication (createAccount, createSession)
125125+[ ] Lexicon validation
126126+[ ] Domain + TLS configured
127127+[ ] PDS deployed and reachable
128128+[ ] Account created, session works
129129+[ ] Writes + blobs verified
130130+[ ] Backups + monitoring in place
131131+================================================================================
132132+REFERENCES
133133+================================================================================
134134+https://atproto.com/guides/self-hosting
135135+https://github.com/bluesky-social/pds
136136+https://atproto.com/specs/repository
137137+https://atproto.com/specs/sync
138138+https://atproto.com/specs/blob
139139+https://docs.bsky.app/docs/api/com-atproto-server-describe-server
140140+https://docs.bsky.app/docs/api/com-atproto-server-create-session
141141+https://docs.bsky.app/docs/api/com-atproto-repo-put-record
142142+https://docs.bsky.app/docs/api/com-atproto-sync-get-blob