An atproto PDS written in Go
1# Cocoon 2 3> [!WARNING] 4I migrated and have been running my main account on this PDS for months now without issue, however, I am still not responsible if things go awry, particularly during account migration. Please use caution. 5 6Cocoon is a PDS implementation in Go. It is highly experimental, and is not ready for any production use. 7 8## Quick Start with Docker Compose 9 10### Prerequisites 11 12- Docker and Docker Compose installed 13- A domain name pointing to your server (for automatic HTTPS) 14- Ports 80 and 443 open in i.e. UFW 15 16### Installation 17 181. **Clone the repository** 19 ```bash 20 git clone https://github.com/haileyok/cocoon.git 21 cd cocoon 22 ``` 23 242. **Create your configuration file** 25 ```bash 26 cp .env.example .env 27 ``` 28 293. **Edit `.env` with your settings** 30 31 Required settings: 32 ```bash 33 COCOON_DID="did:web:your-domain.com" 34 COCOON_HOSTNAME="your-domain.com" 35 COCOON_CONTACT_EMAIL="you@example.com" 36 COCOON_RELAYS="https://bsky.network" 37 38 # Generate with: openssl rand -hex 16 39 COCOON_ADMIN_PASSWORD="your-secure-password" 40 41 # Generate with: openssl rand -hex 32 42 COCOON_SESSION_SECRET="your-session-secret" 43 ``` 44 454. **Start the services** 46 ```bash 47 # Pull pre-built image from GitHub Container Registry 48 docker-compose pull 49 docker-compose up -d 50 ``` 51 52 Or build locally: 53 ```bash 54 docker-compose build 55 docker-compose up -d 56 ``` 57 58 **For PostgreSQL deployment:** 59 ```bash 60 # Add POSTGRES_PASSWORD to your .env file first! 61 docker-compose -f docker-compose.postgres.yaml up -d 62 ``` 63 645. **Get your invite code** 65 66 On first run, an invite code is automatically created. View it with: 67 ```bash 68 docker-compose logs create-invite 69 ``` 70 71 Or check the saved file: 72 ```bash 73 cat keys/initial-invite-code.txt 74 ``` 75 76 **IMPORTANT**: Save this invite code! You'll need it to create your first account. 77 786. **Monitor the services** 79 ```bash 80 docker-compose logs -f 81 ``` 82 83### What Gets Set Up 84 85The Docker Compose setup includes: 86 87- **init-keys**: Automatically generates cryptographic keys (rotation key and JWK) on first run 88- **cocoon**: The main PDS service running on port 8080 89- **create-invite**: Automatically creates an initial invite code after Cocoon starts (first run only) 90- **caddy**: Reverse proxy with automatic HTTPS via Let's Encrypt 91 92### Data Persistence 93 94The following directories will be created automatically: 95 96- `./keys/` - Cryptographic keys (generated automatically) 97 - `rotation.key` - PDS rotation key 98 - `jwk.key` - JWK private key 99 - `initial-invite-code.txt` - Your first invite code (first run only) 100- `./data/` - SQLite database and blockstore 101- Docker volumes for Caddy configuration and certificates 102 103### Optional Configuration 104 105#### Database Configuration 106 107By default, Cocoon uses SQLite which requires no additional setup. For production deployments with higher traffic, you can use PostgreSQL: 108 109```bash 110# Database type: sqlite (default) or postgres 111COCOON_DB_TYPE="postgres" 112 113# PostgreSQL connection string (required if db-type is postgres) 114# Format: postgres://user:password@host:port/database?sslmode=disable 115COCOON_DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable" 116 117# Or use the standard DATABASE_URL environment variable 118DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable" 119``` 120 121For SQLite (default): 122```bash 123COCOON_DB_TYPE="sqlite" 124COCOON_DB_NAME="/data/cocoon/cocoon.db" 125``` 126 127> **Note**: When using PostgreSQL, database backups to S3 are not handled by Cocoon. Use `pg_dump` or your database provider's backup solution instead. 128 129#### SMTP Email Settings 130```bash 131COCOON_SMTP_USER="your-smtp-username" 132COCOON_SMTP_PASS="your-smtp-password" 133COCOON_SMTP_HOST="smtp.example.com" 134COCOON_SMTP_PORT="587" 135COCOON_SMTP_EMAIL="noreply@example.com" 136COCOON_SMTP_NAME="Cocoon PDS" 137``` 138 139#### S3 Storage 140 141Cocoon supports S3-compatible storage for both database backups (SQLite only) and blob storage (images, videos, etc.): 142 143```bash 144# Enable S3 backups (SQLite databases only - hourly backups) 145COCOON_S3_BACKUPS_ENABLED=true 146 147# Enable S3 for blob storage (images, videos, etc.) 148# When enabled, blobs are stored in S3 instead of the database 149COCOON_S3_BLOBSTORE_ENABLED=true 150 151# S3 configuration (works with AWS S3, MinIO, Cloudflare R2, etc.) 152COCOON_S3_REGION="us-east-1" 153COCOON_S3_BUCKET="your-bucket" 154COCOON_S3_ENDPOINT="https://s3.amazonaws.com" 155COCOON_S3_ACCESS_KEY="your-access-key" 156COCOON_S3_SECRET_KEY="your-secret-key" 157 158# Optional: CDN/public URL for blob redirects 159# When set, com.atproto.sync.getBlob redirects to this URL instead of proxying 160COCOON_S3_CDN_URL="https://cdn.example.com" 161``` 162 163**Blob Storage Options:** 164- `COCOON_S3_BLOBSTORE_ENABLED=false` (default): Blobs stored in the database 165- `COCOON_S3_BLOBSTORE_ENABLED=true`: Blobs stored in S3 bucket under `blobs/{did}/{cid}` 166 167**Blob Serving Options:** 168- Without `COCOON_S3_CDN_URL`: Blobs are proxied through the PDS server 169- With `COCOON_S3_CDN_URL`: `getBlob` returns a 302 redirect to `{CDN_URL}/blobs/{did}/{cid}` 170 171> **Tip**: For Cloudflare R2, you can use the public bucket URL as the CDN URL. For AWS S3, you can use CloudFront or the S3 bucket URL directly if public access is enabled. 172 173### Management Commands 174 175Create an invite code: 176```bash 177docker exec cocoon-pds /cocoon create-invite-code --uses 1 178``` 179 180Reset a user's password: 181```bash 182docker exec cocoon-pds /cocoon reset-password --did "did:plc:xxx" 183``` 184 185### Updating 186 187```bash 188docker-compose pull 189docker-compose up -d 190``` 191 192## Implemented Endpoints 193 194> [!NOTE] 195Just because something is implemented doesn't mean it is finished. Tons of these are returning bad errors, don't do validation properly, etc. I'll make a "second pass" checklist at some point to do all of that. 196 197### Identity 198 199- [x] `com.atproto.identity.getRecommendedDidCredentials` 200- [x] `com.atproto.identity.requestPlcOperationSignature` 201- [x] `com.atproto.identity.resolveHandle` 202- [x] `com.atproto.identity.signPlcOperation` 203- [x] `com.atproto.identity.submitPlcOperation` 204- [x] `com.atproto.identity.updateHandle` 205 206### Repo 207 208- [x] `com.atproto.repo.applyWrites` 209- [x] `com.atproto.repo.createRecord` 210- [x] `com.atproto.repo.putRecord` 211- [x] `com.atproto.repo.deleteRecord` 212- [x] `com.atproto.repo.describeRepo` 213- [x] `com.atproto.repo.getRecord` 214- [x] `com.atproto.repo.importRepo` (Works "okay". Use with extreme caution.) 215- [x] `com.atproto.repo.listRecords` 216- [x] `com.atproto.repo.listMissingBlobs` 217 218### Server 219 220- [x] `com.atproto.server.activateAccount` 221- [x] `com.atproto.server.checkAccountStatus` 222- [x] `com.atproto.server.confirmEmail` 223- [x] `com.atproto.server.createAccount` 224- [x] `com.atproto.server.createInviteCode` 225- [x] `com.atproto.server.createInviteCodes` 226- [x] `com.atproto.server.deactivateAccount` 227- [x] `com.atproto.server.deleteAccount` 228- [x] `com.atproto.server.deleteSession` 229- [x] `com.atproto.server.describeServer` 230- [ ] `com.atproto.server.getAccountInviteCodes` 231- [x] `com.atproto.server.getServiceAuth` 232- ~~[ ] `com.atproto.server.listAppPasswords`~~ - not going to add app passwords 233- [x] `com.atproto.server.refreshSession` 234- [x] `com.atproto.server.requestAccountDelete` 235- [x] `com.atproto.server.requestEmailConfirmation` 236- [x] `com.atproto.server.requestEmailUpdate` 237- [x] `com.atproto.server.requestPasswordReset` 238- [x] `com.atproto.server.reserveSigningKey` 239- [x] `com.atproto.server.resetPassword` 240- ~~[] `com.atproto.server.revokeAppPassword`~~ - not going to add app passwords 241- [x] `com.atproto.server.updateEmail` 242 243### Sync 244 245- [x] `com.atproto.sync.getBlob` 246- [x] `com.atproto.sync.getBlocks` 247- [x] `com.atproto.sync.getLatestCommit` 248- [x] `com.atproto.sync.getRecord` 249- [x] `com.atproto.sync.getRepoStatus` 250- [x] `com.atproto.sync.getRepo` 251- [x] `com.atproto.sync.listBlobs` 252- [x] `com.atproto.sync.listRepos` 253- ~~[ ] `com.atproto.sync.notifyOfUpdate`~~ - BGS doesn't even have this implemented lol 254- [x] `com.atproto.sync.requestCrawl` 255- [x] `com.atproto.sync.subscribeRepos` 256 257### Other 258 259- [x] `com.atproto.label.queryLabels` 260- [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS) 261- [x] `app.bsky.actor.getPreferences` 262- [x] `app.bsky.actor.putPreferences` 263 264## License 265 266This project is licensed under MIT license. `server/static/pico.css` is also licensed under MIT license, available at [https://github.com/picocss/pico/](https://github.com/picocss/pico/).