an atproto pds written in F# (.NET 9) 馃
pds
fsharp
giraffe
dotnet
atproto
1# PDSharp
2
3> A Personal Data Server (PDS) for the AT Protocol, written in F# with Giraffe.
4
5## Goal
6
7Build and deploy a single-user PDS that can host your AT Protocol repository, serve blobs, and federate with Bluesky.
8
9## Requirements
10
11- .NET 9.0 SDK
12- [Just](https://github.com/casey/just) (optional, for potential future task running)
13
14## Getting Started
15
16### Restore & Build the project
17
18```bash
19dotnet restore
20dotnet build
21```
22
23### Run the tests
24
25```bash
26dotnet test
27```
28
29### Run the Server
30
31```bash
32dotnet run --project PDSharp/PDSharp.fsproj
33```
34
35The server will start at `http://localhost:5000`.
36
37## API Testing
38
39### Server Info
40
41```bash
42curl http://localhost:5000/xrpc/com.atproto.server.describeServer
43```
44
45### Record Operations
46
47**Create a record:**
48
49```bash
50curl -X POST http://localhost:5000/xrpc/com.atproto.repo.createRecord \
51 -H "Content-Type: application/json" \
52 -d '{"repo":"did:web:test","collection":"app.bsky.feed.post","record":{"text":"Hello, ATProto!"}}'
53```
54
55**Get a record** (use the rkey from createRecord response):
56
57```bash
58curl "http://localhost:5000/xrpc/com.atproto.repo.getRecord?repo=did:web:test&collection=app.bsky.feed.post&rkey=<RKEY>"
59```
60
61**Put a record** (upsert with explicit rkey):
62
63```bash
64curl -X POST http://localhost:5000/xrpc/com.atproto.repo.putRecord \
65 -H "Content-Type: application/json" \
66 -d '{"repo":"did:web:test","collection":"app.bsky.feed.post","rkey":"my-post","record":{"text":"Updated!"}}'
67```
68
69### Sync & CAR Export
70
71**Get entire repository as CAR:**
72
73```bash
74curl "http://localhost:5000/xrpc/com.atproto.sync.getRepo?did=did:web:test" -o repo.car
75```
76
77**Get specific blocks** (comma-separated CIDs):
78
79```bash
80curl "http://localhost:5000/xrpc/com.atproto.sync.getBlocks?did=did:web:test&cids=<CID1>,<CID2>" -o blocks.car
81```
82
83**Get a blob by CID:**
84
85```bash
86curl "http://localhost:5000/xrpc/com.atproto.sync.getBlob?did=did:web:test&cid=<BLOB_CID>"
87```
88
89### Firehose (WebSocket)
90
91Subscribe to real-time commit events using [websocat](https://github.com/vi/websocat):
92
93```bash
94# Install websocat (macOS)
95brew install websocat
96
97# Connect to firehose
98websocat ws://localhost:5000/xrpc/com.atproto.sync.subscribeRepos
99```
100
101Then create/update records in another terminal to see CBOR-encoded commit events stream in real-time.
102
103**With cursor for resumption:**
104
105```bash
106websocat "ws://localhost:5000/xrpc/com.atproto.sync.subscribeRepos?cursor=5"
107```
108
109## Configuration
110
111The application uses `appsettings.json` and supports Environment Variable overrides.
112
113| Key | Env Var | Default | Description |
114| ----------- | ------------------- | ----------------------- | ------------------------- |
115| `DidHost` | `PDSHARP_DidHost` | `did:web:localhost` | The DID of the PDS itself |
116| `PublicUrl` | `PDSHARP_PublicUrl` | `http://localhost:5000` | Publicly reachable URL |
117
118Example `appsettings.json`:
119
120```json
121{
122 "PublicUrl": "http://localhost:5000",
123 "DidHost": "did:web:localhost"
124}
125```
126
127## Architecture
128
129### App (Giraffe)
130
131- `XrpcRouter`: `/xrpc/<NSID>` routing
132- `Auth`: Session management (JWTs)
133- `RepoApi`: Write/Read records (`putRecord`, `getRecord`)
134- `ServerApi`: Server meta (`describeServer`)
135
136### Core (Pure F#)
137
138- `DidResolver`: Identity resolution
139- `RepoEngine`: MST, DAG-CBOR, CIDs, Blocks
140- `Models`: Data types for XRPC/Database
141
142### Infra
143
144- SQLite/Postgres for persistence
145- S3/Disk for blob storage