An easy-to-host ATProto Personal Data Server (PDS) — designed to be simple to operate and approachable for end users.
ezpds is a self-hosted PDS implementation for the AT Protocol (the protocol behind Bluesky and the ATmosphere). It provides:
- A PDS server (
crates/pds) — an Axum-based HTTP server that implements the ATProto provisioning API, XRPC endpoints, and OAuth 2.0 flows. It stores accounts, DIDs, handles, and signing keys in SQLite. - A crypto library (
crates/crypto) — P-256 key generation,did:keyderivation, AES-256-GCM encryption, and fulldid:plclifecycle support (genesis ops, rotation ops, audit log verification, CID computation). - Obsign, an iOS identity wallet (
apps/identity-wallet) — a Tauri v2 app (SvelteKit 2 + Svelte 5 frontend, Rust backend) that walks users through account creation, DID ceremony, handle registration, and identity recovery. Keys are backed by the device Secure Enclave.
┌─────────────────────────────────────────────────────────┐
│ Obsign (iOS) │
│ Tauri v2 · SvelteKit · Secure Enclave keys │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Account │ │ DID Ceremony │ │ OAuth Client │ │
│ │ Creation │ │ (did:plc) │ │ (DPoP) │ │
│ └──────┬──────┘ └──────┬───────┘ └───────┬───────┘ │
└─────────┼────────────────┼──────────────────┼───────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ PDS Server (crates/pds) │
│ Axum · SQLite · sqlx │
│ │
│ Provisioning API XRPC Endpoints OAuth 2.0 │
│ /v1/accounts com.atproto.* /oauth/* │
│ /v1/dids (catch-all) DPoP + JWKS │
│ /v1/handles │
│ /v1/devices Auth (JWT + Argon2id) │
│ Handle resolution (DB + DNS + HTTP)│
└─────────────────────────────────────────────────────────┘
| Crate | Purpose | Status |
|---|---|---|
crates/pds |
ATProto PDS server — provisioning API, XRPC, OAuth, auth | Active — primary development focus |
crates/crypto |
P-256 keys, did:key, did:plc genesis/rotation, Shamir secret sharing, AES-256-GCM | Complete — well-tested |
crates/common |
Shared config, error types, serde utilities | Complete |
crates/repo-engine |
MST construction, CAR file storage, commit construction | Stub — not yet implemented |
apps/identity-wallet |
Tauri v2 iOS app (SvelteKit 2 + Svelte 5) | Active — account creation, DID ceremony, handle registration, OAuth, PLC monitoring, recovery |
Provisioning API (/v1/...):
POST /v1/accounts— Create accountPOST /v1/accounts/mobile— Create mobile account (with device key)POST /v1/accounts/sessions— Create provisioning sessionPOST /v1/accounts/claim-codes— Issue claim codesPOST /v1/dids— Create DID (submit signed genesis op)GET /v1/dids/:did— Get DID documentPOST /v1/handles— Register handleDELETE /v1/handles/:handle— Delete handlePOST /v1/devices— Register deviceGET /v1/devices/:id/pds— Get device PDSGET/POST /v1/pds/keys— Manage PDS signing keys
XRPC (ATProto standard):
com.atproto.server.createSession/getSession/refreshSession/deleteSessioncom.atproto.server.describeServercom.atproto.server.requestPasswordReset/resetPasswordcom.atproto.identity.resolveHandle- Catch-all
/:method— returnsMethodNotImplementedfor unimplemented NSIDs
OAuth 2.0 (with DPoP):
GET /.well-known/oauth-authorization-serverGET/POST /oauth/authorizePOST /oauth/par(Pushed Authorization Request)POST /oauth/tokenGET /oauth/client-metadata.jsonGET /oauth/jwks
Well-known:
GET /.well-known/atproto-did— DID document
The mobile app guides users through:
- PDS configuration — Connect to an ezpds PDS instance
- Account creation — Claim code → email + handle → device key registration
- DID ceremony — Build and sign a
did:plcgenesis op using the device Secure Enclave, submit to PDS, receive Shamir recovery shares - Handle registration — Register a handle on the PDS's domain
- OAuth login — Authenticate with the PDS via OAuth 2.0 + DPoP
- Identity management — Multi-identity home, DID document display, rotation key status
- PLC monitoring — Periodic audit log checks for unauthorized DID changes
- Recovery — Shamir secret sharing (3 shares, 2-of-3 threshold) with iCloud Keychain backup
This project uses a Nix flake + devenv development environment. All tools (Rust toolchain, SQLite, Node.js, pnpm, just, etc.) are managed by Nix — do not install them globally.
# Enter the dev shell (--impure for devenv CWD detection, --accept-flake-config for Cachix binary cache)
nix develop --impure --accept-flake-config
# Or use direnv (auto-activates on cd)
direnv allowOn first shell entry, rustup toolchain install runs automatically.
cargo build # Build all crates
cargo build -p pds # Build just the PDS binary
nix build .#pds --accept-flake-config # Build via Nix (output: ./result/bin/pds)# Create a config file (see pds.dev.toml for an example)
cargo run -p pds -- --config pds.tomljust ci # Full CI pipeline: fmt-check + clippy + test + cargo audit
just test # Run all tests
just clippy # Lint (warnings as errors)
just fmt-check # Check formatting
just nix-check # Validate NixOS module / flake structurenix build .#docker-image --accept-flake-config
docker load < resultOn macOS, use a remote Linux builder or CI.
The PDS is configured via a TOML file (default: pds.toml). See pds.dev.toml for a full example.
Key settings:
bind_address/port— Listen addressdatabase_url— SQLite path (default:./relay.db)public_url— The PDS's public-facing URLavailable_user_domains— Handle domains users can register (e.g.["ezpds.com"])invite_code_required— Whether claim codes are required for account creationadmin_token— Token for management endpointsoauth— OAuth 2.0 configurationtelemetry— OpenTelemetry trace export
ezpds is under active development. The PDS server and crypto library are functional; the iOS identity wallet is in development. Key capabilities:
Working now:
- Account creation (desktop + mobile flows)
- DID creation (
did:plc) with device-backed keys - Handle registration and resolution
- Session management (JWT + refresh tokens)
- OAuth 2.0 with DPoP
- Password reset flow
- PDS signing key management
- Shamir secret sharing for recovery
- OpenTelemetry observability
In progress / planned (see Linear project):
- Wave 4: Blob upload/download, blob garbage collection
- Wave 5: Federation — firehose, subscribeRepos, getRepo, requestCrawl
- Wave 6: App proxy — catch-all to appview, preferences, chat proxy
- Wave 7: Hardening — interop tests, cargo-audit, provisioning transfer endpoints, Tauri IPC lockdown
See LICENSE for details.