···11-# Cocoon
11+# Vow
2233> [!WARNING]
44-I 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.
44+> This is highly experimental software. Use with caution, especially during account migration.
5566-Cocoon is a PDS implementation in Go. It is highly experimental, and is not ready for any production use.
66+Vow is a PDS (Personal Data Server) implementation in Go for the AT Protocol.
7788## Quick Start with Docker Compose
991010### Prerequisites
11111212- Docker and Docker Compose installed
1313-- A domain name pointing to your server (for automatic HTTPS)
1414-- Ports 80 and 443 open in i.e. UFW
1313+- A domain name pointing to your server
1414+- Ports 80 and 443 open
15151616### Installation
171718181. **Clone the repository**
1919+1920 ```bash
2020- git clone https://github.com/haileyok/cocoon.git
2121- cd cocoon
2121+ git clone https://pkg.rbrt.fr/vow.git
2222+ cd vow
2223 ```
232424252. **Create your configuration file**
2626+2527 ```bash
2628 cp .env.example .env
2729 ```
283029313. **Edit `.env` with your settings**
30323131- Required settings:
3233 ```bash
3333- COCOON_DID="did:web:your-domain.com"
3434- COCOON_HOSTNAME="your-domain.com"
3535- COCOON_CONTACT_EMAIL="you@example.com"
3636- COCOON_RELAYS="https://bsky.network"
3434+ VOW_DID="did:web:your-domain.com"
3535+ VOW_HOSTNAME="your-domain.com"
3636+ VOW_CONTACT_EMAIL="you@example.com"
3737+ VOW_RELAYS="https://bsky.network"
37383839 # Generate with: openssl rand -hex 16
3939- COCOON_ADMIN_PASSWORD="your-secure-password"
4040+ VOW_ADMIN_PASSWORD="your-secure-password"
40414142 # Generate with: openssl rand -hex 32
4242- COCOON_SESSION_SECRET="your-session-secret"
4343+ VOW_SESSION_SECRET="your-session-secret"
4344 ```
444545464. **Start the services**
4646- ```bash
4747- # Pull pre-built image from GitHub Container Registry
4848- docker-compose pull
4949- docker-compose up -d
5050- ```
51475252- Or build locally:
5348 ```bash
5454- docker-compose build
4949+ docker-compose pull
5550 docker-compose up -d
5651 ```
57525858- **For PostgreSQL deployment:**
5959- ```bash
6060- # Add POSTGRES_PASSWORD to your .env file first!
6161- docker-compose -f docker-compose.postgres.yaml up -d
6262- ```
6363-64535. **Get your invite code**
65546655 On first run, an invite code is automatically created. View it with:
5656+6757 ```bash
6858 docker-compose logs create-invite
6959 ```
70607161 Or check the saved file:
6262+7263 ```bash
7364 cat keys/initial-invite-code.txt
7465 ```
75667676- **IMPORTANT**: Save this invite code! You'll need it to create your first account.
7777-78676. **Monitor the services**
7968 ```bash
8069 docker-compose logs -f
···82718372### What Gets Set Up
84738585-The Docker Compose setup includes:
8686-8787-- **init-keys**: Automatically generates cryptographic keys (rotation key and JWK) on first run
8888-- **cocoon**: The main PDS service running on port 8080
8989-- **create-invite**: Automatically creates an initial invite code after Cocoon starts (first run only)
9090-- **caddy**: Reverse proxy with automatic HTTPS via Let's Encrypt
7474+- **init-keys**: Generates cryptographic keys (rotation key and JWK) on first run
7575+- **vow**: The main PDS service running on port 8080
7676+- **create-invite**: Creates an initial invite code on first run
91779278### Data Persistence
93799494-The following directories will be created automatically:
9595-9696-- `./keys/` - Cryptographic keys (generated automatically)
9797- - `rotation.key` - PDS rotation key
9898- - `jwk.key` - JWK private key
9999- - `initial-invite-code.txt` - Your first invite code (first run only)
100100-- `./data/` - SQLite database and blockstore
101101-- Docker volumes for Caddy configuration and certificates
8080+- `./keys/` — Cryptographic keys (generated automatically)
8181+ - `rotation.key` — PDS rotation key
8282+ - `jwk.key` — JWK private key
8383+ - `initial-invite-code.txt` — Your first invite code (first run only)
8484+- `./data/` — SQLite database and blockstore
10285103103-### Optional Configuration
8686+## Configuration
10487105105-#### Database Configuration
8888+### Database
10689107107-By default, Cocoon uses SQLite which requires no additional setup. For production deployments with higher traffic, you can use PostgreSQL:
9090+Vow uses SQLite by default. No additional setup required.
1089110992```bash
110110-# Database type: sqlite (default) or postgres
111111-COCOON_DB_TYPE="postgres"
112112-113113-# PostgreSQL connection string (required if db-type is postgres)
114114-# Format: postgres://user:password@host:port/database?sslmode=disable
115115-COCOON_DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable"
116116-117117-# Or use the standard DATABASE_URL environment variable
118118-DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable"
119119-```
120120-121121-For SQLite (default):
122122-```bash
123123-COCOON_DB_TYPE="sqlite"
124124-COCOON_DB_NAME="/data/cocoon/cocoon.db"
9393+VOW_DB_NAME="/data/vow/vow.db"
12594```
12695127127-> **Note**: When using PostgreSQL, database backups to S3 are not handled by Cocoon. Use `pg_dump` or your database provider's backup solution instead.
9696+### SMTP Email
12897129129-#### SMTP Email Settings
13098```bash
131131-COCOON_SMTP_USER="your-smtp-username"
132132-COCOON_SMTP_PASS="your-smtp-password"
133133-COCOON_SMTP_HOST="smtp.example.com"
134134-COCOON_SMTP_PORT="587"
135135-COCOON_SMTP_EMAIL="noreply@example.com"
136136-COCOON_SMTP_NAME="Cocoon PDS"
9999+VOW_SMTP_USER="your-smtp-username"
100100+VOW_SMTP_PASS="your-smtp-password"
101101+VOW_SMTP_HOST="smtp.example.com"
102102+VOW_SMTP_PORT="587"
103103+VOW_SMTP_EMAIL="noreply@example.com"
104104+VOW_SMTP_NAME="Vow PDS"
137105```
138106139139-#### S3 Storage
107107+### IPFS Blob Storage
140108141141-Cocoon supports S3-compatible storage for both database backups (SQLite only) and blob storage (images, videos, etc.):
109109+By default blobs are stored in SQLite. Optionally, blobs can be stored on IPFS via a local [Kubo](https://github.com/ipfs/kubo) node:
142110143111```bash
144144-# Enable S3 backups (SQLite databases only - hourly backups)
145145-COCOON_S3_BACKUPS_ENABLED=true
112112+VOW_IPFS_BLOBSTORE_ENABLED=true
146113147147-# Enable S3 for blob storage (images, videos, etc.)
148148-# When enabled, blobs are stored in S3 instead of the database
149149-COCOON_S3_BLOBSTORE_ENABLED=true
114114+# URL of the local Kubo RPC API (default: http://127.0.0.1:5001)
115115+VOW_IPFS_NODE_URL="http://127.0.0.1:5001"
150116151151-# S3 configuration (works with AWS S3, MinIO, Cloudflare R2, etc.)
152152-COCOON_S3_REGION="us-east-1"
153153-COCOON_S3_BUCKET="your-bucket"
154154-COCOON_S3_ENDPOINT="https://s3.amazonaws.com"
155155-COCOON_S3_ACCESS_KEY="your-access-key"
156156-COCOON_S3_SECRET_KEY="your-secret-key"
117117+# Optional: redirect getBlob to a public gateway instead of proxying
118118+VOW_IPFS_GATEWAY_URL="https://ipfs.io"
157119158158-# Optional: CDN/public URL for blob redirects
159159-# When set, com.atproto.sync.getBlob redirects to this URL instead of proxying
160160-COCOON_S3_CDN_URL="https://cdn.example.com"
120120+# Optional: remote pinning service
121121+VOW_IPFS_PINNING_SERVICE_URL="https://api.pinata.cloud/psa"
122122+VOW_IPFS_PINNING_SERVICE_TOKEN="your-token"
161123```
162124163163-**Blob Storage Options:**
164164-- `COCOON_S3_BLOBSTORE_ENABLED=false` (default): Blobs stored in the database
165165-- `COCOON_S3_BLOBSTORE_ENABLED=true`: Blobs stored in S3 bucket under `blobs/{did}/{cid}`
166166-167167-**Blob Serving Options:**
168168-- Without `COCOON_S3_CDN_URL`: Blobs are proxied through the PDS server
169169-- With `COCOON_S3_CDN_URL`: `getBlob` returns a 302 redirect to `{CDN_URL}/blobs/{did}/{cid}`
170170-171171-> **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.
125125+## Management Commands
172126173173-### Management Commands
127127+Create an invite code:
174128175175-Create an invite code:
176129```bash
177177-docker exec cocoon-pds /cocoon create-invite-code --uses 1
130130+docker exec vow-pds /vow create-invite-code --uses 1
178131```
179132180133Reset a user's password:
134134+181135```bash
182182-docker exec cocoon-pds /cocoon reset-password --did "did:plc:xxx"
136136+docker exec vow-pds /vow reset-password --did "did:plc:xxx"
183137```
184138185185-### Updating
139139+## Updating
186140187141```bash
188142docker-compose pull
···192146## Implemented Endpoints
193147194148> [!NOTE]
195195-Just 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.
149149+> Just because something is implemented doesn't mean it is finished. Many endpoints still have rough edges around validation and error handling.
196150197151### Identity
198152···229183- [x] `com.atproto.server.describeServer`
230184- [ ] `com.atproto.server.getAccountInviteCodes`
231185- [x] `com.atproto.server.getServiceAuth`
232232-- ~~[ ] `com.atproto.server.listAppPasswords`~~ - not going to add app passwords
233186- [x] `com.atproto.server.refreshSession`
234187- [x] `com.atproto.server.requestAccountDelete`
235188- [x] `com.atproto.server.requestEmailConfirmation`
···237190- [x] `com.atproto.server.requestPasswordReset`
238191- [x] `com.atproto.server.reserveSigningKey`
239192- [x] `com.atproto.server.resetPassword`
240240-- ~~[] `com.atproto.server.revokeAppPassword`~~ - not going to add app passwords
241193- [x] `com.atproto.server.updateEmail`
242194243195### Sync
···250202- [x] `com.atproto.sync.getRepo`
251203- [x] `com.atproto.sync.listBlobs`
252204- [x] `com.atproto.sync.listRepos`
253253-- ~~[ ] `com.atproto.sync.notifyOfUpdate`~~ - BGS doesn't even have this implemented lol
254205- [x] `com.atproto.sync.requestCrawl`
255206- [x] `com.atproto.sync.subscribeRepos`
256207257208### Other
258209259210- [x] `com.atproto.label.queryLabels`
260260-- [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS)
211211+- [x] `com.atproto.moderation.createReport`
261212- [x] `app.bsky.actor.getPreferences`
262213- [x] `app.bsky.actor.putPreferences`
263214264215## License
265216266266-This 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/).
217217+MIT. `server/static/pico.css` is also MIT licensed, available at [https://github.com/picocss/pico/](https://github.com/picocss/pico/).
···44 "encoding/json"
55 "net/http"
6677- "github.com/haileyok/cocoon/models"
77+ "pkg.rbrt.fr/vow/models"
88)
991010// This is kinda lame. Not great to implement app.bsky in the pds, but alas
+1-1
server/handle_actor_put_preferences.go
···44 "encoding/json"
55 "net/http"
6677- "github.com/haileyok/cocoon/models"
77+ "pkg.rbrt.fr/vow/models"
88)
991010// This is kinda lame. Not great to implement app.bsky in the pds, but alas
+1-1
server/handle_age_assurance.go
···55 "time"
6677 "github.com/bluesky-social/indigo/util"
88- "github.com/haileyok/cocoon/models"
88+ "pkg.rbrt.fr/vow/models"
99)
10101111func (s *Server) handleAgeAssurance(w http.ResponseWriter, r *http.Request) {
···39394040This is an AT Protocol Personal Data Server (aka, an atproto PDS)
41414242-Code: https://github.com/haileyok/cocoon
4242+Code: https://pkg.rbrt.fr/vow
4343Version: `+s.config.Version+"\n")
4444}