zds#
an at protocol personal data server.
name credit: jim (
calabro.io) suggestedzds.
zds stores atproto accounts, repos, records, blobs, sessions, oauth state,
account-security state, and identity state. it uses zat for
protocol primitives: syntax, tids, did resolution, jwt helpers, dag-cbor, car,
mst, repo verification, and key encoding. WebAuthn support comes from
webauthn.
dependency posture#
ZDS is an application. Dependencies are chosen around that boundary:
zatis the sibling library we maintain for reusable AT Protocol primitives. If ZDS grows a generally useful atproto parser, codec, verifier, resolver, or client helper, expect it to move upstream intozat.httpzis the HTTP/1.1 server boundary. ZDS should consume it directly from Tangled, not vendor it. HTTP server behavior should stay local to ZDS unless it is a focused compatibility fix or improvement forhttpzitself.zatandhttpzboth rely on the canonical first-partywebsocket.zig. Keep the graph on one websocket implementation; fix the upstream package we control rather than copying it into this repo.- Vendoring is a last resort for short-lived debugging only. Do not leave
vendor/as the dependency strategy.
See development for the longer decision rules.
docs#
- architecture
- account management
- comail
- development
- getRepo notes
- operator guide
- invite codes
- passkeys
- permissioned data
- references
- benchmarks
run locally
zig build test
zig build run -- --port 2583 --db dev/zds.sqlite3
caddy run --config dev/Caddyfile
before committing:
just test
just smoke
zig zen
configuration
run behind a public https origin:
ZDS_MAIL_PROVIDER=comail \
ZDS_COMAIL_API_KEY=... \
ZDS_COMAIL_DID='did:plc:mailIdentity' \
ZDS_EMAIL_FROM='noreply@example.com' \
ZDS_HOST=0.0.0.0 \
ZDS_PORT=2583 \
ZDS_DB=/var/lib/zds/zds.sqlite3 \
ZDS_BLOB_UPLOAD_LIMIT=100000000 \
ZDS_BLOBSTORE_PATH=/var/lib/zds/blobs \
ZDS_HANDLE_DOMAINS='.example.com,example.com' \
ZDS_CRAWLERS='https://bsky.network,https://vsky.network' \
ZDS_MAX_CONCURRENT_REPO_EXPORTS=4 \
ZDS_PLC_ROTATION_KEY='64-hex-secp256k1-secret-or-private-multikey' \
ZDS_RECOVERY_DID_KEY='did:key:optionalRecoveryKey' \
ZDS_JWT_SECRET='at-least-32-random-bytes-here' \
ZDS_DPOP_SECRET='32-plus-random-bytes-for-dpop-nonces' \
ZDS_ADMIN_TOKEN='another-random-secret' \
ZDS_INVITE_REQUIRED=true \
zig build run -- \
--public-url https://pds.example.com \
--server-did did:web:pds.example.com
See the operator guide for deployment, configuration, invite-code, migration, and release notes.
container image
published to atcr.io:
docker pull atcr.io/zat.dev/zds:latest
docker run -p 2583:2583 \
-v zds-data:/data \
-e ZDS_PUBLIC_URL=https://pds.example.com \
-e ZDS_SERVER_DID=did:web:pds.example.com \
-e ZDS_JWT_SECRET='at-least-32-random-bytes-here' \
-e ZDS_DB=/data/zds.sqlite3 \
atcr.io/zat.dev/zds:latest
to publish:
just docker-publish
just docker-publish v0.1.1
just docker-publish-current
just docker-publish-release v0.1.1
behavior#
- Appview XRPCs are reached through
atproto-proxy. - Known record schemas are validated before writes are accepted.
- Unknown record schemas are accepted with
validationStatus: "unknown", matching the official PDS behavior when validation is not explicitly required. - Uploaded blob bytes are stored in the configured disk blobstore.
- Invite codes use the official PDS table shape: code metadata plus recorded
uses. When invites are required,
createAccountrejects missing, disabled, or exhausted codes. - App passwords use durable account-owned rows and can be listed or revoked. Revocation also invalidates active sessions created with that app password.
- Passkeys are optional account credentials for the OAuth login page. ZDS stores credential IDs, public keys, counters, names, and last-use timestamps.
/accountis the resident account hub. It includes passkey and app-password management, app access/session views, email/account status controls, and the experimental permissioned-space record browser.- Permissioned-data routes are experimental and operator gated with
ZDS_PERMISSIONED_DATA. ZDS keeps protocol routes undercom.atproto.space.*and baseline PDS-managed space management undercom.atproto.simplespace.*, but the upstream proposal is still moving and this surface is not a stable compatibility contract.