PDSharp#
A Personal Data Server (PDS) for the AT Protocol, written in F# with Giraffe.
Goal#
Build and deploy a single-user PDS that can host your AT Protocol repository, serve blobs, and federate with Bluesky.
Requirements#
.NET 9.0 SDK
Getting Started#
Restore & Build the project#
dotnet restore
dotnet build
Run the tests#
dotnet test
Run the Server#
dotnet run --project PDSharp/PDSharp.fsproj
The server will start at http://localhost:5000.
Configuration#
The application uses appsettings.json and supports Environment Variable overrides.
| Key | Env Var | Default | Description |
|---|---|---|---|
DidHost |
PDSHARP_DidHost |
did:web:localhost |
The DID of the PDS itself |
PublicUrl |
PDSHARP_PublicUrl |
http://localhost:5000 |
Publicly reachable URL |
Example appsettings.json:
{
"PublicUrl": "http://localhost:5000",
"DidHost": "did:web:localhost"
}
API Testing#
Server Info
curl http://localhost:5000/xrpc/com.atproto.server.describeServer
Record Operations#
Create a record
curl -X POST http://localhost:5000/xrpc/com.atproto.repo.createRecord \
-H "Content-Type: application/json" \
-d '{"repo":"did:web:test","collection":"app.bsky.feed.post","record":{"text":"Hello, ATProto!"}}'
Get a record
curl "http://localhost:5000/xrpc/com.atproto.repo.getRecord?repo=did:web:test&collection=app.bsky.feed.post&rkey=<RKEY>"
Put a record
curl -X POST http://localhost:5000/xrpc/com.atproto.repo.putRecord \
-H "Content-Type: application/json" \
-d '{"repo":"did:web:test","collection":"app.bsky.feed.post","rkey":"my-post","record":{"text":"Updated!"}}'
Sync & CAR Export#
Get entire repository as CAR
curl "http://localhost:5000/xrpc/com.atproto.sync.getRepo?did=did:web:test" -o repo.car
Get specific blocks
curl "http://localhost:5000/xrpc/com.atproto.sync.getBlocks?did=did:web:test&cids=<CID1>,<CID2>" -o blocks.car
Get a blob by CID
curl "http://localhost:5000/xrpc/com.atproto.sync.getBlob?did=did:web:test&cid=<BLOB_CID>"
Firehose (WebSocket)#
Subscribe to real-time commit events using websocat:
Open a WebSocket connection
websocat ws://localhost:5000/xrpc/com.atproto.sync.subscribeRepos
Then create/update records in another terminal to see CBOR-encoded commit events stream in real-time.
Open a WebSocket connection with cursor for resumption
websocat "ws://localhost:5000/xrpc/com.atproto.sync.subscribeRepos?cursor=5"
Architecture#
App (Giraffe)
XrpcRouter:/xrpc/<NSID>routingAuth: Session management (JWTs)RepoApi: Write/Read records (putRecord,getRecord)ServerApi: Server meta (describeServer)
Core (Pure F#)
DidResolver: Identity resolutionRepoEngine: MST, DAG-CBOR, CIDs, BlocksModels: Data types for XRPC/Database
Infra
- SQLite/Postgres for persistence
- S3/Disk for blob storage