zig pds
0

Configure Feed

Select the types of activity you want to include in your feed.

migration#

zds implements the pds endpoints used by browser-based account migration tools, including pds moover.

pds moover flow#

pds moover asks for:

  • current handle and password
  • new pds url
  • new email
  • new handle
  • invite code

the migration is not complete until the user enters a plc token delivered by email.

implemented endpoints#

implemented:

  • com.atproto.server.createAccount
  • com.atproto.server.checkAccountStatus
  • com.atproto.server.activateAccount
  • com.atproto.server.deactivateAccount
  • com.atproto.server.refreshSession
  • com.atproto.repo.importRepo
  • com.atproto.repo.listMissingBlobs
  • com.atproto.sync.getRepo
  • com.atproto.sync.listBlobs
  • com.atproto.sync.getRepoStatus
  • com.atproto.identity.getRecommendedDidCredentials
  • com.atproto.identity.requestPlcOperationSignature
  • com.atproto.identity.signPlcOperation
  • com.atproto.identity.submitPlcOperation
  • app.bsky.actor.getPreferences
  • app.bsky.actor.putPreferences

implementation details:

  • createAccount verifies migration service jwts against the migrating did
  • session tokens are signed with the configured ZDS_JWT_SECRET
  • stored passwords use salted pbkdf2-sha256 hashes
  • repo writes produce signed commit blocks and mst updates
  • imported repo cars are verified against the account did and signing key
  • each account has its own signing key for repo commits and plc credentials
  • uploadBlob defaults to 100,000,000 bytes and is configurable with ZDS_BLOB_UPLOAD_LIMIT or --blob-upload-limit
  • uploaded blob bytes are stored in the disk blobstore rooted at ZDS_BLOBSTORE_PATH or --blobstore-path
  • the server serves /.well-known/did.json for the pds service did and /.well-known/atproto-did for hosted handles

not yet implemented#

  • real sequenced account/identity events
  • password reset and account recovery paths
  • object-store blob backend with backup/restore tooling

deployment#

run behind a public https origin:

ZDS_RESEND_API_KEY=... \
ZDS_EMAIL_FROM='ZDS <pds@example.com>' \
ZDS_BLOB_UPLOAD_LIMIT=100000000 \
ZDS_BLOBSTORE_PATH=/var/lib/zds/blobs \
ZDS_HANDLE_DOMAINS='.example.com,example.com' \
ZDS_JWT_SECRET='at-least-32-random-bytes-here' \
zig build run -- \
  --port 2583 \
  --db /var/lib/zds/zds.sqlite3 \
  --public-url https://pds.example.com \
  --server-did did:web:pds.example.com

the public url is used for did credentials and blob views. ZDS_RESEND_API_KEY and ZDS_EMAIL_FROM enable real email delivery; without them, token emails are logged to stdout for local development. ZDS_BLOB_UPLOAD_LIMIT controls the generic com.atproto.repo.uploadBlob request body ceiling. ZDS_BLOBSTORE_PATH controls where uploaded blob bytes are stored. ZDS_HANDLE_DOMAINS is what describeServer advertises to clients. ZDS_JWT_SECRET signs app-session access and refresh jwts; keep it stable across deploys or existing sessions will be invalidated.

the at protocol uploadBlob lexicon does not define one universal byte limit; it says blob restrictions are enforced when a record references the uploaded blob. the official pds exposes a configurable blob upload limit, while the bluesky video embed lexicon allows 100,000,000-byte mp4 blobs. zds uses that as the default generic upload ceiling so migration tests do not fail below bluesky's current video shape.

deployment also needs sqlite data-directory backups and operational access to inspect account status, repo import, missing blobs, and plc submission failures.

core atproto account, repo, sync, and identity code stays separate from app-view services. app-view XRPCs are reached through the generic atproto-proxy path; PDS-owned app state such as app.bsky.actor.getPreferences is stored locally because that is how the reference PDS and Tranquil treat it.