BYOK Personal Data Server (PDS) written in Go
ipfs vow atproto pds go
1
fork

Configure Feed

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

at main 288 lines 8.7 kB view raw view rendered
1# Vow 2 3> [!WARNING] 4> This is highly experimental software. Use with caution, especially during account migration. 5 6Vow is a Bring-Your-Own-Key (BYOK) PDS (Personal Data Server) for AT Protocol. 7The server never stores a private signing key; all repository transactions are signed by a user passkey. 8 9## Quick Start 10 11> [!NOTE] 12> Experiment with Vow using the following `LFPJ3REG-BFICVMGE` invite code on the test server [vowpds.srv.rbrt.fr](https://vowpds.srv.rbrt.fr). 13> The PDS accounts are cleared regularly. 14 15[![Container Image](https://img.shields.io/badge/image-atcr.io%2Fjulien.rbrt.fr%2Fvow-blue)](https://atcr.io/r/julien.rbrt.fr/vow) 16 17### Prerequisites 18 19- Docker and Docker Compose installed 20- A domain name pointing to your server 21 22### Installation 23 241. **Clone repository** 25 26```bash 27git clone https://pkg.rbrt.fr/vow.git 28cd vow 29``` 30 312. **Create your configuration file** 32 33```bash 34cp .env.example .env 35``` 36 373. **Edit `.env` with your settings** 38 39```bash 40VOW_DID="did:web:your-domain.com" 41VOW_HOSTNAME="your-domain.com" 42VOW_CONTACT_EMAIL="you@example.com" 43VOW_RELAYS="https://bsky.network" 44 45# Generate with: openssl rand -hex 16 46VOW_ADMIN_PASSWORD="your-secure-password" 47 48# Generate with: openssl rand -hex 32 49VOW_SESSION_SECRET="your-session-secret" 50``` 51 524. **Start services** 53 54```bash 55docker compose pull 56docker compose up -d 57``` 58 59This starts three services: 60 61- **ipfs** — a Kubo node for repo blocks and blobs 62- **vow** — the PDS 63- **create-invite** — creates an initial invite code on first run 64 655. **Get your invite code** 66 67On first run, an invite code is automatically created: 68 69```bash 70docker compose logs create-invite 71``` 72 73Or check saved file: 74 75```bash 76cat keys/initial-invite-code.txt 77``` 78 796. **Monitor services** 80 81```bash 82docker compose logs -f 83``` 84 85### What Gets Set Up 86 87- **init-keys**: Generates rotation key and JWK on first run 88- **ipfs**: A Kubo node for repo blocks and blobs. The RPC API (port 5001) stays internal; gateway (port 8080) is exposed on `127.0.0.1:8081` for your reverse proxy. 89- **vow**: The main PDS service on port 8080 90- **create-invite**: Creates an initial invite code on first run 91 92### Data Persistence 93 94- `./keys/` — generated keys 95 - `rotation.key` — PDS rotation key 96 - `jwk.key` — JWK private key 97 - `initial-invite-code.txt` — first invite code (first run only) 98- `./data/` — SQLite metadata database 99- `/opt/ipfs` Docker volume — IPFS blocks and blobs 100 101### Reverse Proxy 102 103You need a reverse proxy (nginx, Caddy, etc.) in front of the PDS: 104 105| Service | Internal address | Purpose | 106| ------- | ---------------- | ----------------------------- | 107| vow | `127.0.0.1:8080` | AT Protocol PDS | 108| ipfs | `127.0.0.1:8081` | IPFS gateway for blob serving | 109 110Set `VOW_IPFS_GATEWAY_URL` to your public gateway URL so `sync.getBlob` redirects clients there instead of proxying through vow. 111 112## Configuration 113 114### Database 115 116Vow uses SQLite for relational metadata such as accounts, sessions, record indexes, and tokens. 117 118```bash 119VOW_DB_NAME="/data/vow/vow.db" 120``` 121 122### IPFS Node 123 124```bash 125# URL of Kubo RPC API 126VOW_IPFS_NODE_URL="http://127.0.0.1:5001" 127 128# Optional: redirect sync.getBlob to a public gateway 129VOW_IPFS_GATEWAY_URL="https://ipfs.example.com" 130``` 131 132### SMTP Email 133 134```bash 135VOW_SMTP_USER="your-smtp-username" 136VOW_SMTP_PASS="your-smtp-password" 137VOW_SMTP_HOST="smtp.example.com" 138VOW_SMTP_PORT="587" 139VOW_SMTP_EMAIL="noreply@example.com" 140VOW_SMTP_NAME="Vow PDS" 141``` 142 143### BYOK (Bring Your Own Key) 144 145The PDS holds two keys: 146 147- **Rotation key** (`rotation.key`) — used for DID genesis operations and for signing the PLC operation that transfers control to the user's passkey during passkey registration. 148- **JWK key** (`jwk.key`) — a P-256 ECDSA key used exclusively to sign ATProto session JWTs (access and refresh tokens) and OAuth tokens. It has no role in repo writes or identity operations. 149 150Neither key is ever used to sign repo commits or service-auth JWTs. 151 152## How It Works 153 154### Browser-Based Signer 155 156The account page (`/account`) connects over WebSocket and runs entirely in the browser. No browser extension or extra software is needed — the user just keeps the tab open and signs commits automatically when prompted. 157 158### Key Flow 159 1601. **Before passkey registration** — PDS controls DID with its rotation key 1612. **After passkey registration** — User's passkey becomes the rotation key, derived via WebAuthn PRF extension. Only the user can modify their DID. 1623. **Signing commits** — Passkey authenticates user and provides PRF output. A deterministic signing key is derived from PRF output and used to sign commits. 163 164### Two-Key Model 165 166Vow implements a two-key model: 167 168| Property | PDS Server Key | Passkey-Derived Key | 169| ---------------------- | ------------------ | --------------------------- | 170| **DID slot** | `#atproto_service` | `#atproto` | 171| **Purpose** | Service-auth JWTs | Repo commits | 172| **Passkey required** | No | Yes (for repo writes) | 173| **Private key stored** | Yes (in `jwk.key`) | **No** (derived on-the-fly) | 174 175## Management Commands 176 177Create an invite code: 178 179```bash 180docker exec vow-pds /vow create-invite-code --uses 1 181``` 182 183Reset a user's password: 184 185```bash 186docker exec vow-pds /vow reset-password --did "did:plc:xxx" 187``` 188 189## Updating 190 191```bash 192docker compose build 193docker compose up -d 194``` 195 196## Implemented Endpoints 197 198> [!NOTE] 199> Just because something is implemented doesn't mean it is finished. Many endpoints still have rough edges around validation and error handling. 200 201### Identity 202 203- [x] `com.atproto.identity.getRecommendedDidCredentials` 204- [x] `com.atproto.identity.requestPlcOperationSignature` 205- [x] `com.atproto.identity.resolveHandle` 206- [x] `com.atproto.identity.signPlcOperation` 207- [x] `com.atproto.identity.submitPlcOperation` 208- [x] `com.atproto.identity.updateHandle` 209 210### Repo 211 212- [x] `com.atproto.repo.applyWrites` 213- [x] `com.atproto.repo.createRecord` 214- [x] `com.atproto.repo.putRecord` 215- [x] `com.atproto.repo.deleteRecord` 216- [x] `com.atproto.repo.describeRepo` 217- [x] `com.atproto.repo.getRecord` 218- [x] `com.atproto.repo.importRepo` (Works "okay". Use with extreme caution.) 219- [x] `com.atproto.repo.listRecords` 220- [x] `com.atproto.repo.listMissingBlobs` 221 222### Server 223 224- [x] `com.atproto.server.activateAccount` 225- [x] `com.atproto.server.checkAccountStatus` 226- [x] `com.atproto.server.confirmEmail` 227- [x] `com.atproto.server.createAccount` 228- [x] `com.atproto.server.createInviteCode` 229- [x] `com.atproto.server.createInviteCodes` 230- [x] `com.atproto.server.deactivateAccount` 231- [x] `com.atproto.server.deleteAccount` 232- [x] `com.atproto.server.deleteSession` 233- [x] `com.atproto.server.describeServer` 234- [x] `com.atproto.server.getAccountInviteCodes` 235- [x] `com.atproto.server.getServiceAuth` 236- [x] `com.atproto.server.refreshSession` 237- [x] `com.atproto.server.requestAccountDelete` 238- [x] `com.atproto.server.requestEmailConfirmation` 239- [x] `com.atproto.server.requestEmailUpdate` 240- [x] `com.atproto.server.requestPasswordReset` 241- [x] `com.atproto.server.resetPassword` 242- [x] `com.atproto.server.updateEmail` 243 244### Sync 245 246- [x] `com.atproto.sync.getBlob` 247- [x] `com.atproto.sync.getBlocks` 248- [x] `com.atproto.sync.getLatestCommit` 249- [x] `com.atproto.sync.getRecord` 250- [x] `com.atproto.sync.getRepoStatus` 251- [x] `com.atproto.sync.getRepo` 252- [x] `com.atproto.sync.listBlobs` 253- [x] `com.atproto.sync.requestCrawl` 254- [x] `com.atproto.sync.subscribeRepos` 255 256### Other 257 258- [x] `com.atproto.label.queryLabels` 259- [x] `com.atproto.moderation.createReport` 260- [x] `app.bsky.actor.getPreferences` 261- [x] `app.bsky.actor.putPreferences` 262 263## License 264 265[MIT](license). `server/static/pico.css` is also MIT licensed, available at [https://github.com/picocss/pico](https://github.com/picocss/pico). 266 267## Thanks 268 269Vow is based on [Cocoon](https://tangled.org/hailey.at/cocoon). Many thanks for the solid foundation. 270 271### Vow vs Cocoon 272 273| Feature | Vow | Cocoon | 274| ----------------------- | ---------- | ------ | 275| Language | Go | Go | 276| SQLite (metadata) | ✅ | ✅ | 277| SQLite blockstore | ❌ removed | ✅ | 278| PostgreSQL support | ❌ removed | ✅ | 279| S3 blob storage | ❌ removed | ✅ | 280| IPFS repo block storage | ✅ (Kubo) | ❌ | 281| IPFS blob storage | ✅ (Kubo) | ❌ | 282| Email 2FA | ❌ removed | ✅ | 283| BYOK (keyless PDS) | ✅ | ❌ | 284| Passkey signer | ✅ | ❌ | 285 286## Technical Details 287 288For in-depth specifications, flows, trade-offs, and maintenance considerations, see [specs.md](specs.md).