forked from
willdot.net/cocoon
A fork of the Cocoon PDS but being made more distributed.
1# Cocoon
2
3## Distributed version
4
5This is a fork of the Cocoon PDS with the intention of making it a distributed PDS. You can read my thoughts on why I'm trying this POC out [here](https://willdot.leaflet.pub/3miiirzf7jc2j) and follow along the series for updates.
6
7> [!WARNING]
8I 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.
9
10Cocoon is a PDS implementation in Go. It is highly experimental, and is not ready for any production use.
11
12## Quick Start with Docker Compose
13
14### Prerequisites
15
16- Docker and Docker Compose installed
17- A domain name pointing to your server (for automatic HTTPS)
18- Ports 80 and 443 open in i.e. UFW
19
20### Installation
21
221. **Clone the repository**
23 ```bash
24 git clone https://github.com/haileyok/cocoon.git
25 cd cocoon
26 ```
27
282. **Create your configuration file**
29 ```bash
30 cp .env.example .env
31 ```
32
333. **Edit `.env` with your settings**
34
35 Required settings:
36 ```bash
37 COCOON_DID="did:web:your-domain.com"
38 COCOON_HOSTNAME="your-domain.com"
39 COCOON_CONTACT_EMAIL="you@example.com"
40 COCOON_RELAYS="https://bsky.network"
41
42 # Generate with: openssl rand -hex 16
43 COCOON_ADMIN_PASSWORD="your-secure-password"
44
45 # Generate with: openssl rand -hex 32
46 COCOON_SESSION_SECRET="your-session-secret"
47 ```
48
494. **Start the services**
50 ```bash
51 # Pull pre-built image from GitHub Container Registry
52 docker-compose pull
53 docker-compose up -d
54 ```
55
56 Or build locally:
57 ```bash
58 docker-compose build
59 docker-compose up -d
60 ```
61
62 **For PostgreSQL deployment:**
63 ```bash
64 # Add POSTGRES_PASSWORD to your .env file first!
65 docker-compose -f docker-compose.postgres.yaml up -d
66 ```
67
685. **Get your invite code**
69
70 On first run, an invite code is automatically created. View it with:
71 ```bash
72 docker-compose logs create-invite
73 ```
74
75 Or check the saved file:
76 ```bash
77 cat keys/initial-invite-code.txt
78 ```
79
80 **IMPORTANT**: Save this invite code! You'll need it to create your first account.
81
826. **Monitor the services**
83 ```bash
84 docker-compose logs -f
85 ```
86
87### What Gets Set Up
88
89The Docker Compose setup includes:
90
91- **init-keys**: Automatically generates cryptographic keys (rotation key and JWK) on first run
92- **cocoon**: The main PDS service running on port 8080
93- **create-invite**: Automatically creates an initial invite code after Cocoon starts (first run only)
94- **caddy**: Reverse proxy with automatic HTTPS via Let's Encrypt
95
96### Data Persistence
97
98The following directories will be created automatically:
99
100- `./keys/` - Cryptographic keys (generated automatically)
101 - `rotation.key` - PDS rotation key
102 - `jwk.key` - JWK private key
103 - `initial-invite-code.txt` - Your first invite code (first run only)
104- `./data/` - SQLite database and blockstore
105- Docker volumes for Caddy configuration and certificates
106
107### Optional Configuration
108
109#### Database Configuration
110
111By default, Cocoon uses SQLite which requires no additional setup. For production deployments with higher traffic, you can use PostgreSQL:
112
113```bash
114# Database type: sqlite (default) or postgres
115COCOON_DB_TYPE="postgres"
116
117# PostgreSQL connection string (required if db-type is postgres)
118# Format: postgres://user:password@host:port/database?sslmode=disable
119COCOON_DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable"
120
121# Or use the standard DATABASE_URL environment variable
122DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable"
123```
124
125For SQLite (default):
126```bash
127COCOON_DB_TYPE="sqlite"
128COCOON_DB_NAME="/data/cocoon/cocoon.db"
129```
130
131> **Note**: When using PostgreSQL, database backups to S3 are not handled by Cocoon. Use `pg_dump` or your database provider's backup solution instead.
132
133#### SMTP Email Settings
134```bash
135COCOON_SMTP_USER="your-smtp-username"
136COCOON_SMTP_PASS="your-smtp-password"
137COCOON_SMTP_HOST="smtp.example.com"
138COCOON_SMTP_PORT="587"
139COCOON_SMTP_EMAIL="noreply@example.com"
140COCOON_SMTP_NAME="Cocoon PDS"
141```
142
143#### S3 Storage
144
145Cocoon supports S3-compatible storage for both database backups (SQLite only) and blob storage (images, videos, etc.):
146
147```bash
148# Enable S3 backups (SQLite databases only - hourly backups)
149COCOON_S3_BACKUPS_ENABLED=true
150
151# Enable S3 for blob storage (images, videos, etc.)
152# When enabled, blobs are stored in S3 instead of the database
153COCOON_S3_BLOBSTORE_ENABLED=true
154
155# S3 configuration (works with AWS S3, MinIO, Cloudflare R2, etc.)
156COCOON_S3_REGION="us-east-1"
157COCOON_S3_BUCKET="your-bucket"
158COCOON_S3_ENDPOINT="https://s3.amazonaws.com"
159COCOON_S3_ACCESS_KEY="your-access-key"
160COCOON_S3_SECRET_KEY="your-secret-key"
161
162# Optional: CDN/public URL for blob redirects
163# When set, com.atproto.sync.getBlob redirects to this URL instead of proxying
164COCOON_S3_CDN_URL="https://cdn.example.com"
165```
166
167**Blob Storage Options:**
168- `COCOON_S3_BLOBSTORE_ENABLED=false` (default): Blobs stored in the database
169- `COCOON_S3_BLOBSTORE_ENABLED=true`: Blobs stored in S3 bucket under `blobs/{did}/{cid}`
170
171**Blob Serving Options:**
172- Without `COCOON_S3_CDN_URL`: Blobs are proxied through the PDS server
173- With `COCOON_S3_CDN_URL`: `getBlob` returns a 302 redirect to `{CDN_URL}/blobs/{did}/{cid}`
174
175> **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.
176
177### Management Commands
178
179Create an invite code:
180```bash
181docker exec cocoon-pds /cocoon create-invite-code --uses 1
182```
183
184Reset a user's password:
185```bash
186docker exec cocoon-pds /cocoon reset-password --did "did:plc:xxx"
187```
188
189### Updating
190
191```bash
192docker-compose pull
193docker-compose up -d
194```
195
196## Implemented Endpoints
197
198> [!NOTE]
199Just 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.
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- [ ] `com.atproto.server.getAccountInviteCodes`
235- [x] `com.atproto.server.getServiceAuth`
236- ~~[ ] `com.atproto.server.listAppPasswords`~~ - not going to add app passwords
237- [x] `com.atproto.server.refreshSession`
238- [x] `com.atproto.server.requestAccountDelete`
239- [x] `com.atproto.server.requestEmailConfirmation`
240- [x] `com.atproto.server.requestEmailUpdate`
241- [x] `com.atproto.server.requestPasswordReset`
242- [x] `com.atproto.server.reserveSigningKey`
243- [x] `com.atproto.server.resetPassword`
244- ~~[] `com.atproto.server.revokeAppPassword`~~ - not going to add app passwords
245- [x] `com.atproto.server.updateEmail`
246
247### Sync
248
249- [x] `com.atproto.sync.getBlob`
250- [x] `com.atproto.sync.getBlocks`
251- [x] `com.atproto.sync.getLatestCommit`
252- [x] `com.atproto.sync.getRecord`
253- [x] `com.atproto.sync.getRepoStatus`
254- [x] `com.atproto.sync.getRepo`
255- [x] `com.atproto.sync.listBlobs`
256- [x] `com.atproto.sync.listRepos`
257- ~~[ ] `com.atproto.sync.notifyOfUpdate`~~ - BGS doesn't even have this implemented lol
258- [x] `com.atproto.sync.requestCrawl`
259- [x] `com.atproto.sync.subscribeRepos`
260
261### Other
262
263- [x] `com.atproto.label.queryLabels`
264- [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS)
265- [x] `app.bsky.actor.getPreferences`
266- [x] `app.bsky.actor.putPreferences`
267
268## License
269
270This 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/).