+23
README.md
+23
README.md
···
32
just lint # clippy + fmt
33
```
34
35
+
## Production Deployment
36
+
37
+
### Quick Deploy (Docker/Podman Compose)
38
+
39
+
```bash
40
+
cp .env.prod.example .env.prod
41
+
# Edit .env.prod with your values (generate secrets with: openssl rand -base64 48)
42
+
podman-compose -f docker-compose.prod.yml up -d
43
+
```
44
+
45
+
### Full Installation Guides
46
+
47
+
| Guide | Best For |
48
+
|-------|----------|
49
+
| **Native Installation** | Maximum performance, full control |
50
+
| [Debian](docs/install-debian.md) | Debian 13+ with systemd |
51
+
| [Alpine](docs/install-alpine.md) | Alpine 3.23+ with OpenRC |
52
+
| [OpenBSD](docs/install-openbsd.md) | OpenBSD 7.8+ with rc.d |
53
+
| **Containerized** | Easier updates, isolation |
54
+
| [Containers](docs/install-containers.md) | Podman with quadlets (Debian) or OpenRC (Alpine) |
55
+
| **Orchestrated** | High availability, auto-scaling |
56
+
| [Kubernetes](docs/install-kubernetes.md) | Multi-node k8s cluster deployment |
57
+
58
## License
59
60
TBD
+61
deploy/nginx/nginx-quadlet.conf
+61
deploy/nginx/nginx-quadlet.conf
···
···
1
+
worker_processes auto;
2
+
error_log /var/log/nginx/error.log warn;
3
+
4
+
events {
5
+
worker_connections 4096;
6
+
}
7
+
8
+
http {
9
+
include /etc/nginx/mime.types;
10
+
default_type application/octet-stream;
11
+
access_log /var/log/nginx/access.log;
12
+
sendfile on;
13
+
keepalive_timeout 65;
14
+
15
+
gzip on;
16
+
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
17
+
18
+
ssl_protocols TLSv1.2 TLSv1.3;
19
+
ssl_prefer_server_ciphers off;
20
+
ssl_session_cache shared:SSL:10m;
21
+
ssl_stapling on;
22
+
ssl_stapling_verify on;
23
+
24
+
server {
25
+
listen 80;
26
+
listen [::]:80;
27
+
server_name _;
28
+
29
+
location /.well-known/acme-challenge/ {
30
+
root /var/www/acme;
31
+
}
32
+
33
+
location / {
34
+
return 301 https://$host$request_uri;
35
+
}
36
+
}
37
+
38
+
server {
39
+
listen 443 ssl http2;
40
+
listen [::]:443 ssl http2;
41
+
server_name _;
42
+
43
+
ssl_certificate /etc/nginx/certs/fullchain.pem;
44
+
ssl_certificate_key /etc/nginx/certs/privkey.pem;
45
+
client_max_body_size 100M;
46
+
47
+
location / {
48
+
proxy_pass http://127.0.0.1:3000;
49
+
proxy_http_version 1.1;
50
+
proxy_set_header Upgrade $http_upgrade;
51
+
proxy_set_header Connection "upgrade";
52
+
proxy_set_header Host $host;
53
+
proxy_set_header X-Real-IP $remote_addr;
54
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
55
+
proxy_set_header X-Forwarded-Proto $scheme;
56
+
proxy_read_timeout 86400;
57
+
proxy_send_timeout 86400;
58
+
proxy_buffering off;
59
+
}
60
+
}
61
+
}
+28
deploy/quadlets/bspds-app.container
+28
deploy/quadlets/bspds-app.container
···
···
1
+
[Unit]
2
+
Description=BSPDS AT Protocol PDS
3
+
After=bspds-db.service bspds-minio.service bspds-valkey.service
4
+
5
+
[Container]
6
+
ContainerName=bspds-app
7
+
Image=localhost/bspds:latest
8
+
Pod=bspds.pod
9
+
EnvironmentFile=/srv/bspds/config/bspds.env
10
+
Environment=SERVER_HOST=0.0.0.0
11
+
Environment=SERVER_PORT=3000
12
+
Environment=S3_ENDPOINT=http://localhost:9000
13
+
Environment=AWS_REGION=us-east-1
14
+
Environment=S3_BUCKET=pds-blobs
15
+
Environment=VALKEY_URL=redis://localhost:6379
16
+
Environment=FRONTEND_DIR=/app/frontend/dist
17
+
HealthCmd=wget -q --spider http://localhost:3000/xrpc/_health
18
+
HealthInterval=30s
19
+
HealthTimeout=10s
20
+
HealthRetries=3
21
+
HealthStartPeriod=15s
22
+
23
+
[Service]
24
+
Restart=always
25
+
RestartSec=10
26
+
27
+
[Install]
28
+
WantedBy=default.target
+23
deploy/quadlets/bspds-db.container
+23
deploy/quadlets/bspds-db.container
···
···
1
+
[Unit]
2
+
Description=BSPDS postgres database
3
+
4
+
[Container]
5
+
ContainerName=bspds-db
6
+
Image=docker.io/library/postgres:18-alpine
7
+
Pod=bspds.pod
8
+
Environment=POSTGRES_USER=bspds
9
+
Environment=POSTGRES_DB=pds
10
+
Secret=bspds-db-password,type=env,target=POSTGRES_PASSWORD
11
+
Volume=/srv/bspds/postgres:/var/lib/postgresql/data:Z
12
+
HealthCmd=pg_isready -U bspds -d pds
13
+
HealthInterval=10s
14
+
HealthTimeout=5s
15
+
HealthRetries=5
16
+
HealthStartPeriod=10s
17
+
18
+
[Service]
19
+
Restart=always
20
+
RestartSec=10
21
+
22
+
[Install]
23
+
WantedBy=default.target
+23
deploy/quadlets/bspds-minio.container
+23
deploy/quadlets/bspds-minio.container
···
···
1
+
[Unit]
2
+
Description=BSPDS minio object storage
3
+
4
+
[Container]
5
+
ContainerName=bspds-minio
6
+
Image=docker.io/minio/minio:RELEASE.2025-10-15T17-29-55Z
7
+
Pod=bspds.pod
8
+
Environment=MINIO_ROOT_USER=minioadmin
9
+
Secret=bspds-minio-password,type=env,target=MINIO_ROOT_PASSWORD
10
+
Volume=/srv/bspds/minio:/data:Z
11
+
Exec=server /data --console-address :9001
12
+
HealthCmd=curl -f http://localhost:9000/minio/health/live || exit 1
13
+
HealthInterval=30s
14
+
HealthTimeout=10s
15
+
HealthRetries=3
16
+
HealthStartPeriod=10s
17
+
18
+
[Service]
19
+
Restart=always
20
+
RestartSec=10
21
+
22
+
[Install]
23
+
WantedBy=default.target
+18
deploy/quadlets/bspds-nginx.container
+18
deploy/quadlets/bspds-nginx.container
···
···
1
+
[Unit]
2
+
Description=BSPDS nginx reverse proxy
3
+
After=bspds-app.service
4
+
5
+
[Container]
6
+
ContainerName=bspds-nginx
7
+
Image=docker.io/library/nginx:1.28-alpine
8
+
Pod=bspds.pod
9
+
Volume=/srv/bspds/config/nginx.conf:/etc/nginx/nginx.conf:ro,Z
10
+
Volume=/srv/bspds/certs:/etc/nginx/certs:ro,Z
11
+
Volume=/srv/bspds/acme:/var/www/acme:ro,Z
12
+
13
+
[Service]
14
+
Restart=always
15
+
RestartSec=10
16
+
17
+
[Install]
18
+
WantedBy=default.target
+21
deploy/quadlets/bspds-valkey.container
+21
deploy/quadlets/bspds-valkey.container
···
···
1
+
[Unit]
2
+
Description=BSPDS valkey cache
3
+
4
+
[Container]
5
+
ContainerName=bspds-valkey
6
+
Image=docker.io/valkey/valkey:9-alpine
7
+
Pod=bspds.pod
8
+
Volume=/srv/bspds/valkey:/data:Z
9
+
Exec=valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
10
+
HealthCmd=valkey-cli ping
11
+
HealthInterval=10s
12
+
HealthTimeout=5s
13
+
HealthRetries=3
14
+
HealthStartPeriod=5s
15
+
16
+
[Service]
17
+
Restart=always
18
+
RestartSec=10
19
+
20
+
[Install]
21
+
WantedBy=default.target
+7
deploy/quadlets/bspds.pod
+7
deploy/quadlets/bspds.pod
+173
docker-compose.prod.yml
+173
docker-compose.prod.yml
···
···
1
+
services:
2
+
bspds:
3
+
build:
4
+
context: .
5
+
dockerfile: Dockerfile
6
+
image: bspds:latest
7
+
restart: unless-stopped
8
+
ports:
9
+
- "127.0.0.1:3000:3000"
10
+
environment:
11
+
SERVER_HOST: "0.0.0.0"
12
+
SERVER_PORT: "3000"
13
+
PDS_HOSTNAME: "${PDS_HOSTNAME:?PDS_HOSTNAME is required}"
14
+
DATABASE_URL: "postgres://bspds:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/pds"
15
+
S3_ENDPOINT: "http://minio:9000"
16
+
AWS_REGION: "us-east-1"
17
+
S3_BUCKET: "pds-blobs"
18
+
AWS_ACCESS_KEY_ID: "${MINIO_ROOT_USER:-minioadmin}"
19
+
AWS_SECRET_ACCESS_KEY: "${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}"
20
+
VALKEY_URL: "redis://valkey:6379"
21
+
JWT_SECRET: "${JWT_SECRET:?JWT_SECRET is required (min 32 chars)}"
22
+
DPOP_SECRET: "${DPOP_SECRET:?DPOP_SECRET is required (min 32 chars)}"
23
+
MASTER_KEY: "${MASTER_KEY:?MASTER_KEY is required (min 32 chars)}"
24
+
APPVIEW_URL: "${APPVIEW_URL:-https://api.bsky.app}"
25
+
CRAWLERS: "${CRAWLERS:-https://bsky.network}"
26
+
FRONTEND_DIR: "/app/frontend/dist"
27
+
depends_on:
28
+
db:
29
+
condition: service_healthy
30
+
minio:
31
+
condition: service_healthy
32
+
valkey:
33
+
condition: service_healthy
34
+
healthcheck:
35
+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/xrpc/_health"]
36
+
interval: 30s
37
+
timeout: 10s
38
+
retries: 3
39
+
start_period: 10s
40
+
deploy:
41
+
resources:
42
+
limits:
43
+
memory: 1G
44
+
reservations:
45
+
memory: 256M
46
+
47
+
db:
48
+
image: postgres:18-alpine
49
+
restart: unless-stopped
50
+
environment:
51
+
POSTGRES_USER: bspds
52
+
POSTGRES_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}"
53
+
POSTGRES_DB: pds
54
+
volumes:
55
+
- postgres_data:/var/lib/postgresql/data
56
+
healthcheck:
57
+
test: ["CMD-SHELL", "pg_isready -U bspds -d pds"]
58
+
interval: 10s
59
+
timeout: 5s
60
+
retries: 5
61
+
start_period: 10s
62
+
deploy:
63
+
resources:
64
+
limits:
65
+
memory: 512M
66
+
reservations:
67
+
memory: 128M
68
+
69
+
minio:
70
+
image: minio/minio:RELEASE.2025-10-15T17-29-55Z
71
+
restart: unless-stopped
72
+
command: server /data --console-address ":9001"
73
+
environment:
74
+
MINIO_ROOT_USER: "${MINIO_ROOT_USER:-minioadmin}"
75
+
MINIO_ROOT_PASSWORD: "${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}"
76
+
volumes:
77
+
- minio_data:/data
78
+
healthcheck:
79
+
test: ["CMD", "mc", "ready", "local"]
80
+
interval: 30s
81
+
timeout: 10s
82
+
retries: 3
83
+
start_period: 10s
84
+
deploy:
85
+
resources:
86
+
limits:
87
+
memory: 512M
88
+
reservations:
89
+
memory: 128M
90
+
91
+
minio-init:
92
+
image: minio/mc:RELEASE.2025-07-16T15-35-03Z
93
+
depends_on:
94
+
minio:
95
+
condition: service_healthy
96
+
entrypoint: >
97
+
/bin/sh -c "
98
+
mc alias set local http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD};
99
+
mc mb --ignore-existing local/pds-blobs;
100
+
mc anonymous set none local/pds-blobs;
101
+
exit 0;
102
+
"
103
+
environment:
104
+
MINIO_ROOT_USER: "${MINIO_ROOT_USER:-minioadmin}"
105
+
MINIO_ROOT_PASSWORD: "${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}"
106
+
107
+
valkey:
108
+
image: valkey/valkey:9-alpine
109
+
restart: unless-stopped
110
+
command: valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
111
+
volumes:
112
+
- valkey_data:/data
113
+
healthcheck:
114
+
test: ["CMD", "valkey-cli", "ping"]
115
+
interval: 10s
116
+
timeout: 5s
117
+
retries: 3
118
+
start_period: 5s
119
+
deploy:
120
+
resources:
121
+
limits:
122
+
memory: 300M
123
+
reservations:
124
+
memory: 64M
125
+
126
+
nginx:
127
+
image: nginx:1.28-alpine
128
+
restart: unless-stopped
129
+
ports:
130
+
- "80:80"
131
+
- "443:443"
132
+
volumes:
133
+
- ./nginx.prod.conf:/etc/nginx/nginx.conf:ro
134
+
- ./certs:/etc/nginx/certs:ro
135
+
- acme_challenge:/var/www/acme:ro
136
+
depends_on:
137
+
- bspds
138
+
healthcheck:
139
+
test: ["CMD", "nginx", "-t"]
140
+
interval: 30s
141
+
timeout: 10s
142
+
retries: 3
143
+
144
+
certbot:
145
+
image: certbot/certbot:v5.2.2
146
+
volumes:
147
+
- ./certs:/etc/letsencrypt
148
+
- acme_challenge:/var/www/acme
149
+
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/acme; sleep 12h & wait $${!}; done'"
150
+
151
+
prometheus:
152
+
image: prom/prometheus:v3.8.0
153
+
restart: unless-stopped
154
+
ports:
155
+
- "127.0.0.1:9090:9090"
156
+
volumes:
157
+
- ./observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro
158
+
- prometheus_data:/prometheus
159
+
command:
160
+
- '--config.file=/etc/prometheus/prometheus.yml'
161
+
- '--storage.tsdb.path=/prometheus'
162
+
- '--storage.tsdb.retention.time=30d'
163
+
deploy:
164
+
resources:
165
+
limits:
166
+
memory: 256M
167
+
168
+
volumes:
169
+
postgres_data:
170
+
minio_data:
171
+
valkey_data:
172
+
prometheus_data:
173
+
acme_challenge:
+2
-2
docker-compose.yaml
+2
-2
docker-compose.yaml
+313
docs/install-alpine.md
+313
docs/install-alpine.md
···
···
1
+
# BSPDS Production Installation on Alpine Linux
2
+
3
+
> **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified.
4
+
5
+
This guide covers installing BSPDS on Alpine Linux 3.23 (current stable as of December 2025).
6
+
7
+
## Choose Your Installation Method
8
+
9
+
| Method | Best For |
10
+
|--------|----------|
11
+
| **Native (this guide)** | Maximum performance, minimal footprint, full control |
12
+
| **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments |
13
+
| **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling |
14
+
15
+
This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md).
16
+
17
+
---
18
+
19
+
## Prerequisites
20
+
21
+
- A VPS with at least 2GB RAM and 20GB disk
22
+
- A domain name pointing to your server's IP
23
+
- Root access
24
+
25
+
## 1. System Setup
26
+
27
+
```sh
28
+
apk update && apk upgrade
29
+
apk add curl git build-base openssl-dev pkgconf
30
+
```
31
+
32
+
## 2. Install Rust
33
+
34
+
```sh
35
+
apk add rustup
36
+
rustup-init -y
37
+
source ~/.cargo/env
38
+
rustup default stable
39
+
```
40
+
41
+
This installs the latest stable Rust (1.92+ as of December 2025). Alpine 3.23 also ships Rust 1.91 via `apk add rust cargo` if you prefer system packages.
42
+
43
+
## 3. Install postgres
44
+
45
+
Alpine 3.23 includes PostgreSQL 18:
46
+
47
+
```sh
48
+
apk add postgresql postgresql-contrib
49
+
50
+
rc-update add postgresql
51
+
/etc/init.d/postgresql setup
52
+
rc-service postgresql start
53
+
54
+
psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';"
55
+
psql -U postgres -c "CREATE DATABASE pds OWNER bspds;"
56
+
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
57
+
```
58
+
59
+
## 4. Install minio
60
+
61
+
```sh
62
+
curl -O https://dl.min.io/server/minio/release/linux-amd64/minio
63
+
chmod +x minio
64
+
mv minio /usr/local/bin/
65
+
66
+
mkdir -p /var/lib/minio/data
67
+
adduser -D -H -s /sbin/nologin minio-user
68
+
chown -R minio-user:minio-user /var/lib/minio
69
+
70
+
cat > /etc/conf.d/minio << 'EOF'
71
+
MINIO_ROOT_USER="minioadmin"
72
+
MINIO_ROOT_PASSWORD="your-minio-password"
73
+
MINIO_VOLUMES="/var/lib/minio/data"
74
+
MINIO_OPTS="--console-address :9001"
75
+
EOF
76
+
77
+
cat > /etc/init.d/minio << 'EOF'
78
+
#!/sbin/openrc-run
79
+
80
+
name="minio"
81
+
description="MinIO Object Storage"
82
+
83
+
command="/usr/local/bin/minio"
84
+
command_args="server ${MINIO_VOLUMES} ${MINIO_OPTS}"
85
+
command_user="minio-user"
86
+
command_background=true
87
+
pidfile="/run/${RC_SVCNAME}.pid"
88
+
output_log="/var/log/minio.log"
89
+
error_log="/var/log/minio.log"
90
+
91
+
depend() {
92
+
need net
93
+
}
94
+
95
+
start_pre() {
96
+
. /etc/conf.d/minio
97
+
export MINIO_ROOT_USER MINIO_ROOT_PASSWORD
98
+
}
99
+
EOF
100
+
101
+
chmod +x /etc/init.d/minio
102
+
rc-update add minio
103
+
rc-service minio start
104
+
```
105
+
106
+
Create the blob bucket (wait a few seconds for minio to start):
107
+
108
+
```sh
109
+
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
110
+
chmod +x mc
111
+
mv mc /usr/local/bin/
112
+
113
+
mc alias set local http://localhost:9000 minioadmin your-minio-password
114
+
mc mb local/pds-blobs
115
+
```
116
+
117
+
## 5. Install valkey
118
+
119
+
Alpine 3.23 includes Valkey 9:
120
+
121
+
```sh
122
+
apk add valkey
123
+
124
+
rc-update add valkey
125
+
rc-service valkey start
126
+
```
127
+
128
+
## 6. Install deno (for frontend build)
129
+
130
+
```sh
131
+
curl -fsSL https://deno.land/install.sh | sh
132
+
export PATH="$HOME/.deno/bin:$PATH"
133
+
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile
134
+
```
135
+
136
+
## 7. Clone and Build BSPDS
137
+
138
+
```sh
139
+
mkdir -p /opt && cd /opt
140
+
git clone https://tangled.org/lewis.moe/bspds.git
141
+
cd bspds
142
+
143
+
cd frontend
144
+
deno task build
145
+
cd ..
146
+
147
+
cargo build --release
148
+
```
149
+
150
+
## 8. Install sqlx-cli and Run Migrations
151
+
152
+
```sh
153
+
cargo install sqlx-cli --no-default-features --features postgres
154
+
155
+
export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds"
156
+
sqlx migrate run
157
+
```
158
+
159
+
## 9. Configure BSPDS
160
+
161
+
```sh
162
+
mkdir -p /etc/bspds
163
+
cp /opt/bspds/.env.example /etc/bspds/bspds.env
164
+
chmod 600 /etc/bspds/bspds.env
165
+
```
166
+
167
+
Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with:
168
+
169
+
```sh
170
+
openssl rand -base64 48
171
+
```
172
+
173
+
## 10. Create OpenRC Service
174
+
175
+
```sh
176
+
adduser -D -H -s /sbin/nologin bspds
177
+
178
+
cp /opt/bspds/target/release/bspds /usr/local/bin/
179
+
mkdir -p /var/lib/bspds
180
+
cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend
181
+
chown -R bspds:bspds /var/lib/bspds
182
+
183
+
cat > /etc/init.d/bspds << 'EOF'
184
+
#!/sbin/openrc-run
185
+
186
+
name="bspds"
187
+
description="BSPDS - AT Protocol PDS"
188
+
189
+
command="/usr/local/bin/bspds"
190
+
command_user="bspds"
191
+
command_background=true
192
+
pidfile="/run/${RC_SVCNAME}.pid"
193
+
output_log="/var/log/bspds.log"
194
+
error_log="/var/log/bspds.log"
195
+
196
+
depend() {
197
+
need net postgresql minio
198
+
}
199
+
200
+
start_pre() {
201
+
export FRONTEND_DIR=/var/lib/bspds/frontend
202
+
. /etc/bspds/bspds.env
203
+
export SERVER_HOST SERVER_PORT PDS_HOSTNAME DATABASE_URL
204
+
export S3_ENDPOINT AWS_REGION S3_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
205
+
export VALKEY_URL JWT_SECRET DPOP_SECRET MASTER_KEY APPVIEW_URL CRAWLERS
206
+
}
207
+
EOF
208
+
209
+
chmod +x /etc/init.d/bspds
210
+
rc-update add bspds
211
+
rc-service bspds start
212
+
```
213
+
214
+
## 11. Install and Configure nginx
215
+
216
+
Alpine 3.23 includes nginx 1.28:
217
+
218
+
```sh
219
+
apk add nginx certbot certbot-nginx
220
+
221
+
cat > /etc/nginx/http.d/bspds.conf << 'EOF'
222
+
server {
223
+
listen 80;
224
+
listen [::]:80;
225
+
server_name pds.example.com;
226
+
227
+
location / {
228
+
proxy_pass http://127.0.0.1:3000;
229
+
proxy_http_version 1.1;
230
+
proxy_set_header Upgrade $http_upgrade;
231
+
proxy_set_header Connection "upgrade";
232
+
proxy_set_header Host $host;
233
+
proxy_set_header X-Real-IP $remote_addr;
234
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
235
+
proxy_set_header X-Forwarded-Proto $scheme;
236
+
proxy_read_timeout 86400;
237
+
}
238
+
}
239
+
EOF
240
+
241
+
rc-update add nginx
242
+
rc-service nginx start
243
+
```
244
+
245
+
## 12. Obtain SSL Certificate
246
+
247
+
```sh
248
+
certbot --nginx -d pds.example.com
249
+
```
250
+
251
+
Set up auto-renewal:
252
+
253
+
```sh
254
+
echo "0 0 * * * certbot renew --quiet" | crontab -
255
+
```
256
+
257
+
## 13. Configure Firewall
258
+
259
+
```sh
260
+
apk add iptables ip6tables
261
+
262
+
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
263
+
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
264
+
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
265
+
iptables -A INPUT -i lo -j ACCEPT
266
+
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
267
+
iptables -P INPUT DROP
268
+
269
+
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
270
+
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
271
+
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
272
+
ip6tables -A INPUT -i lo -j ACCEPT
273
+
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
274
+
ip6tables -P INPUT DROP
275
+
276
+
rc-update add iptables
277
+
rc-update add ip6tables
278
+
/etc/init.d/iptables save
279
+
/etc/init.d/ip6tables save
280
+
```
281
+
282
+
## 14. Verify Installation
283
+
284
+
```sh
285
+
rc-service bspds status
286
+
curl -s https://pds.example.com/xrpc/_health
287
+
curl -s https://pds.example.com/.well-known/atproto-did
288
+
```
289
+
290
+
## Maintenance
291
+
292
+
View logs:
293
+
```sh
294
+
tail -f /var/log/bspds.log
295
+
```
296
+
297
+
Update BSPDS:
298
+
```sh
299
+
cd /opt/bspds
300
+
git pull
301
+
cd frontend && deno task build && cd ..
302
+
cargo build --release
303
+
rc-service bspds stop
304
+
cp target/release/bspds /usr/local/bin/
305
+
cp -r frontend/dist /var/lib/bspds/frontend
306
+
DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run
307
+
rc-service bspds start
308
+
```
309
+
310
+
Backup database:
311
+
```sh
312
+
pg_dump -U postgres pds > /var/backups/pds-$(date +%Y%m%d).sql
313
+
```
+428
docs/install-containers.md
+428
docs/install-containers.md
···
···
1
+
# BSPDS Containerized Production Deployment
2
+
3
+
> **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified.
4
+
5
+
This guide covers deploying BSPDS using containers with podman.
6
+
7
+
- **Debian 13+**: Uses systemd quadlets (modern, declarative container management)
8
+
- **Alpine 3.23+**: Uses OpenRC service script with podman-compose
9
+
10
+
## Prerequisites
11
+
12
+
- A VPS with at least 2GB RAM and 20GB disk
13
+
- A domain name pointing to your server's IP
14
+
- Root or sudo access
15
+
16
+
## Quick Start (Docker/Podman Compose)
17
+
18
+
If you just want to get running quickly:
19
+
20
+
```sh
21
+
cp .env.example .env
22
+
23
+
# Edit .env with your values
24
+
# Generate secrets: openssl rand -base64 48
25
+
26
+
# Build and start
27
+
podman-compose -f docker-compose.prod.yml up -d
28
+
29
+
# Get initial certificate (after DNS is configured)
30
+
podman-compose -f docker-compose.prod.yml run --rm certbot certonly \
31
+
--webroot -w /var/www/acme -d pds.example.com
32
+
33
+
# Restart nginx to load certificate
34
+
podman-compose -f docker-compose.prod.yml restart nginx
35
+
```
36
+
37
+
For production setups with proper service management, continue to either the Debian or Alpine section below.
38
+
39
+
---
40
+
41
+
# Debian 13+ with Systemd Quadlets
42
+
43
+
Quadlets are the modern way to run podman containers under systemd.
44
+
45
+
## 1. Install Podman
46
+
47
+
```bash
48
+
apt update
49
+
apt install -y podman
50
+
```
51
+
52
+
## 2. Create Directory Structure
53
+
54
+
```bash
55
+
mkdir -p /etc/containers/systemd
56
+
mkdir -p /srv/bspds/{postgres,minio,valkey,certs,acme,config}
57
+
```
58
+
59
+
## 3. Create Environment File
60
+
61
+
```bash
62
+
cp /opt/bspds/.env.example /srv/bspds/config/bspds.env
63
+
chmod 600 /srv/bspds/config/bspds.env
64
+
```
65
+
66
+
Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with:
67
+
68
+
```bash
69
+
openssl rand -base64 48
70
+
```
71
+
72
+
For quadlets, also add `DATABASE_URL` with the full connection string (systemd doesn't support variable expansion).
73
+
74
+
## 4. Install Quadlet Definitions
75
+
76
+
Copy the quadlet files from the repository:
77
+
78
+
```bash
79
+
cp /opt/bspds/deploy/quadlets/*.pod /etc/containers/systemd/
80
+
cp /opt/bspds/deploy/quadlets/*.container /etc/containers/systemd/
81
+
```
82
+
83
+
Note: Systemd doesn't support shell-style variable expansion in `Environment=` lines. The quadlet files expect DATABASE_URL to be set in the environment file.
84
+
85
+
## 5. Create nginx Configuration
86
+
87
+
```bash
88
+
cp /opt/bspds/deploy/nginx/nginx-quadlet.conf /srv/bspds/config/nginx.conf
89
+
```
90
+
91
+
## 6. Build BSPDS Image
92
+
93
+
```bash
94
+
cd /opt
95
+
git clone https://tangled.org/lewis.moe/bspds.git
96
+
cd bspds
97
+
podman build -t bspds:latest .
98
+
```
99
+
100
+
## 7. Create Podman Secrets
101
+
102
+
```bash
103
+
source /srv/bspds/config/bspds.env
104
+
echo "$DB_PASSWORD" | podman secret create bspds-db-password -
105
+
echo "$MINIO_ROOT_PASSWORD" | podman secret create bspds-minio-password -
106
+
```
107
+
108
+
## 8. Start Services and Initialize
109
+
110
+
```bash
111
+
systemctl daemon-reload
112
+
systemctl start bspds-db bspds-minio bspds-valkey
113
+
114
+
sleep 10
115
+
116
+
# Create MinIO bucket
117
+
podman run --rm --pod bspds \
118
+
-e MINIO_ROOT_USER=minioadmin \
119
+
-e MINIO_ROOT_PASSWORD=your-minio-password \
120
+
docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \
121
+
sh -c "mc alias set local http://localhost:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs"
122
+
123
+
# Run migrations
124
+
cargo install sqlx-cli --no-default-features --features postgres
125
+
DATABASE_URL="postgres://bspds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/bspds/migrations
126
+
```
127
+
128
+
## 9. Obtain SSL Certificate
129
+
130
+
Create temporary self-signed cert:
131
+
132
+
```bash
133
+
openssl req -x509 -nodes -days 1 -newkey rsa:2048 \
134
+
-keyout /srv/bspds/certs/privkey.pem \
135
+
-out /srv/bspds/certs/fullchain.pem \
136
+
-subj "/CN=pds.example.com"
137
+
138
+
systemctl start bspds-app bspds-nginx
139
+
140
+
# Get real certificate
141
+
podman run --rm \
142
+
-v /srv/bspds/certs:/etc/letsencrypt:Z \
143
+
-v /srv/bspds/acme:/var/www/acme:Z \
144
+
docker.io/certbot/certbot:v5.2.2 certonly \
145
+
--webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com
146
+
147
+
# Link certificates
148
+
ln -sf /srv/bspds/certs/live/pds.example.com/fullchain.pem /srv/bspds/certs/fullchain.pem
149
+
ln -sf /srv/bspds/certs/live/pds.example.com/privkey.pem /srv/bspds/certs/privkey.pem
150
+
151
+
systemctl restart bspds-nginx
152
+
```
153
+
154
+
## 10. Enable All Services
155
+
156
+
```bash
157
+
systemctl enable bspds-db bspds-minio bspds-valkey bspds-app bspds-nginx
158
+
```
159
+
160
+
## 11. Configure Firewall
161
+
162
+
```bash
163
+
apt install -y ufw
164
+
ufw allow ssh
165
+
ufw allow 80/tcp
166
+
ufw allow 443/tcp
167
+
ufw enable
168
+
```
169
+
170
+
## 12. Certificate Renewal
171
+
172
+
Add to root's crontab (`crontab -e`):
173
+
174
+
```
175
+
0 0 * * * podman run --rm -v /srv/bspds/certs:/etc/letsencrypt:Z -v /srv/bspds/acme:/var/www/acme:Z docker.io/certbot/certbot:v5.2.2 renew --quiet && systemctl reload bspds-nginx
176
+
```
177
+
178
+
---
179
+
180
+
# Alpine 3.23+ with OpenRC
181
+
182
+
Alpine uses OpenRC, not systemd. We'll use podman-compose with an OpenRC service wrapper.
183
+
184
+
## 1. Install Podman
185
+
186
+
```sh
187
+
apk update
188
+
apk add podman podman-compose fuse-overlayfs cni-plugins
189
+
rc-update add cgroups
190
+
rc-service cgroups start
191
+
```
192
+
193
+
Enable podman socket for compose:
194
+
195
+
```sh
196
+
rc-update add podman
197
+
rc-service podman start
198
+
```
199
+
200
+
## 2. Create Directory Structure
201
+
202
+
```sh
203
+
mkdir -p /srv/bspds/{data,config}
204
+
mkdir -p /srv/bspds/data/{postgres,minio,valkey,certs,acme}
205
+
```
206
+
207
+
## 3. Clone Repository and Build
208
+
209
+
```sh
210
+
cd /opt
211
+
git clone https://tangled.org/lewis.moe/bspds.git
212
+
cd bspds
213
+
podman build -t bspds:latest .
214
+
```
215
+
216
+
## 4. Create Environment File
217
+
218
+
```sh
219
+
cp /opt/bspds/.env.example /srv/bspds/config/bspds.env
220
+
chmod 600 /srv/bspds/config/bspds.env
221
+
```
222
+
223
+
Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with:
224
+
225
+
```sh
226
+
openssl rand -base64 48
227
+
```
228
+
229
+
## 5. Set Up Compose and nginx
230
+
231
+
Copy the production compose and nginx configs:
232
+
233
+
```sh
234
+
cp /opt/bspds/docker-compose.prod.yml /srv/bspds/docker-compose.yml
235
+
cp /opt/bspds/nginx.prod.conf /srv/bspds/config/nginx.conf
236
+
```
237
+
238
+
Edit `/srv/bspds/docker-compose.yml` to adjust paths if needed:
239
+
- Update volume mounts to use `/srv/bspds/data/` paths
240
+
- Update nginx cert paths to match `/srv/bspds/data/certs/`
241
+
242
+
Edit `/srv/bspds/config/nginx.conf` to update cert paths:
243
+
- Change `/etc/nginx/certs/live/${PDS_HOSTNAME}/` to `/etc/nginx/certs/`
244
+
245
+
## 6. Create OpenRC Service
246
+
247
+
```sh
248
+
cat > /etc/init.d/bspds << 'EOF'
249
+
#!/sbin/openrc-run
250
+
251
+
name="bspds"
252
+
description="BSPDS AT Protocol PDS (containerized)"
253
+
254
+
command="/usr/bin/podman-compose"
255
+
command_args="-f /srv/bspds/docker-compose.yml up"
256
+
command_background=true
257
+
pidfile="/run/${RC_SVCNAME}.pid"
258
+
259
+
directory="/srv/bspds"
260
+
261
+
depend() {
262
+
need net podman
263
+
after firewall
264
+
}
265
+
266
+
start_pre() {
267
+
set -a
268
+
. /srv/bspds/config/bspds.env
269
+
set +a
270
+
}
271
+
272
+
stop() {
273
+
ebegin "Stopping ${name}"
274
+
cd /srv/bspds
275
+
set -a
276
+
. /srv/bspds/config/bspds.env
277
+
set +a
278
+
podman-compose -f /srv/bspds/docker-compose.yml down
279
+
eend $?
280
+
}
281
+
EOF
282
+
283
+
chmod +x /etc/init.d/bspds
284
+
```
285
+
286
+
## 7. Initialize Services
287
+
288
+
```sh
289
+
# Start services
290
+
rc-service bspds start
291
+
292
+
sleep 15
293
+
294
+
# Create MinIO bucket
295
+
source /srv/bspds/config/bspds.env
296
+
podman run --rm --network bspds_default \
297
+
-e MINIO_ROOT_USER="$MINIO_ROOT_USER" \
298
+
-e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \
299
+
docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \
300
+
sh -c 'mc alias set local http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs'
301
+
302
+
# Run migrations
303
+
apk add rustup
304
+
rustup-init -y
305
+
source ~/.cargo/env
306
+
cargo install sqlx-cli --no-default-features --features postgres
307
+
308
+
# Get database container IP
309
+
DB_IP=$(podman inspect bspds-db-1 --format '{{.NetworkSettings.Networks.bspds_default.IPAddress}}')
310
+
DATABASE_URL="postgres://bspds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/bspds/migrations
311
+
```
312
+
313
+
## 8. Obtain SSL Certificate
314
+
315
+
Create temporary self-signed cert:
316
+
317
+
```sh
318
+
openssl req -x509 -nodes -days 1 -newkey rsa:2048 \
319
+
-keyout /srv/bspds/data/certs/privkey.pem \
320
+
-out /srv/bspds/data/certs/fullchain.pem \
321
+
-subj "/CN=pds.example.com"
322
+
323
+
rc-service bspds restart
324
+
325
+
# Get real certificate
326
+
podman run --rm \
327
+
-v /srv/bspds/data/certs:/etc/letsencrypt \
328
+
-v /srv/bspds/data/acme:/var/www/acme \
329
+
--network bspds_default \
330
+
docker.io/certbot/certbot:v5.2.2 certonly \
331
+
--webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com
332
+
333
+
# Link certificates
334
+
ln -sf /srv/bspds/data/certs/live/pds.example.com/fullchain.pem /srv/bspds/data/certs/fullchain.pem
335
+
ln -sf /srv/bspds/data/certs/live/pds.example.com/privkey.pem /srv/bspds/data/certs/privkey.pem
336
+
337
+
rc-service bspds restart
338
+
```
339
+
340
+
## 9. Enable Service at Boot
341
+
342
+
```sh
343
+
rc-update add bspds
344
+
```
345
+
346
+
## 10. Configure Firewall
347
+
348
+
```sh
349
+
apk add iptables ip6tables
350
+
351
+
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
352
+
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
353
+
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
354
+
iptables -A INPUT -i lo -j ACCEPT
355
+
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
356
+
iptables -P INPUT DROP
357
+
358
+
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
359
+
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
360
+
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
361
+
ip6tables -A INPUT -i lo -j ACCEPT
362
+
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
363
+
ip6tables -P INPUT DROP
364
+
365
+
rc-update add iptables
366
+
rc-update add ip6tables
367
+
/etc/init.d/iptables save
368
+
/etc/init.d/ip6tables save
369
+
```
370
+
371
+
## 11. Certificate Renewal
372
+
373
+
Add to root's crontab (`crontab -e`):
374
+
375
+
```
376
+
0 0 * * * podman run --rm -v /srv/bspds/data/certs:/etc/letsencrypt -v /srv/bspds/data/acme:/var/www/acme docker.io/certbot/certbot:v5.2.2 renew --quiet && rc-service bspds restart
377
+
```
378
+
379
+
---
380
+
381
+
# Verification and Maintenance
382
+
383
+
## Verify Installation
384
+
385
+
```sh
386
+
curl -s https://pds.example.com/xrpc/_health | jq
387
+
curl -s https://pds.example.com/.well-known/atproto-did
388
+
```
389
+
390
+
## View Logs
391
+
392
+
**Debian:**
393
+
```bash
394
+
journalctl -u bspds-app -f
395
+
podman logs -f bspds-app
396
+
```
397
+
398
+
**Alpine:**
399
+
```sh
400
+
podman-compose -f /srv/bspds/docker-compose.yml logs -f
401
+
podman logs -f bspds-bspds-1
402
+
```
403
+
404
+
## Update BSPDS
405
+
406
+
```sh
407
+
cd /opt/bspds
408
+
git pull
409
+
podman build -t bspds:latest .
410
+
411
+
# Debian:
412
+
systemctl restart bspds-app
413
+
414
+
# Alpine:
415
+
rc-service bspds restart
416
+
```
417
+
418
+
## Backup Database
419
+
420
+
**Debian:**
421
+
```bash
422
+
podman exec bspds-db pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql
423
+
```
424
+
425
+
**Alpine:**
426
+
```sh
427
+
podman exec bspds-db-1 pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql
428
+
```
+280
docs/install-debian.md
+280
docs/install-debian.md
···
···
1
+
# BSPDS Production Installation on Debian
2
+
3
+
> **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified.
4
+
5
+
This guide covers installing BSPDS on Debian 13 "Trixie" (current stable as of December 2025).
6
+
7
+
## Choose Your Installation Method
8
+
9
+
| Method | Best For |
10
+
|--------|----------|
11
+
| **Native (this guide)** | Maximum performance, full control, simpler debugging |
12
+
| **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments |
13
+
| **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling |
14
+
15
+
This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md).
16
+
17
+
---
18
+
19
+
## Prerequisites
20
+
21
+
- A VPS with at least 2GB RAM and 20GB disk
22
+
- A domain name pointing to your server's IP
23
+
- Root or sudo access
24
+
25
+
## 1. System Setup
26
+
27
+
```bash
28
+
apt update && apt upgrade -y
29
+
apt install -y curl git build-essential pkg-config libssl-dev
30
+
```
31
+
32
+
## 2. Install Rust
33
+
34
+
```bash
35
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
36
+
source ~/.cargo/env
37
+
rustup default stable
38
+
```
39
+
40
+
This installs the latest stable Rust (1.92+ as of December 2025).
41
+
42
+
## 3. Install postgres
43
+
44
+
Debian 13 includes PostgreSQL 17:
45
+
46
+
```bash
47
+
apt install -y postgresql postgresql-contrib
48
+
49
+
systemctl enable postgresql
50
+
systemctl start postgresql
51
+
52
+
sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';"
53
+
sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;"
54
+
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
55
+
```
56
+
57
+
## 4. Install minio
58
+
59
+
```bash
60
+
curl -O https://dl.min.io/server/minio/release/linux-amd64/minio
61
+
chmod +x minio
62
+
mv minio /usr/local/bin/
63
+
64
+
mkdir -p /var/lib/minio/data
65
+
useradd -r -s /sbin/nologin minio-user
66
+
chown -R minio-user:minio-user /var/lib/minio
67
+
68
+
cat > /etc/default/minio << 'EOF'
69
+
MINIO_ROOT_USER=minioadmin
70
+
MINIO_ROOT_PASSWORD=your-minio-password
71
+
MINIO_VOLUMES="/var/lib/minio/data"
72
+
MINIO_OPTS="--console-address :9001"
73
+
EOF
74
+
75
+
cat > /etc/systemd/system/minio.service << 'EOF'
76
+
[Unit]
77
+
Description=MinIO Object Storage
78
+
After=network.target
79
+
80
+
[Service]
81
+
User=minio-user
82
+
Group=minio-user
83
+
EnvironmentFile=/etc/default/minio
84
+
ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS
85
+
Restart=always
86
+
LimitNOFILE=65536
87
+
88
+
[Install]
89
+
WantedBy=multi-user.target
90
+
EOF
91
+
92
+
systemctl daemon-reload
93
+
systemctl enable minio
94
+
systemctl start minio
95
+
```
96
+
97
+
Create the blob bucket (wait a few seconds for minio to start):
98
+
99
+
```bash
100
+
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
101
+
chmod +x mc
102
+
mv mc /usr/local/bin/
103
+
104
+
mc alias set local http://localhost:9000 minioadmin your-minio-password
105
+
mc mb local/pds-blobs
106
+
```
107
+
108
+
## 5. Install valkey
109
+
110
+
Debian 13 includes Valkey 8:
111
+
112
+
```bash
113
+
apt install -y valkey
114
+
115
+
systemctl enable valkey-server
116
+
systemctl start valkey-server
117
+
```
118
+
119
+
## 6. Install deno (for frontend build)
120
+
121
+
```bash
122
+
curl -fsSL https://deno.land/install.sh | sh
123
+
export PATH="$HOME/.deno/bin:$PATH"
124
+
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
125
+
```
126
+
127
+
## 7. Clone and Build BSPDS
128
+
129
+
```bash
130
+
cd /opt
131
+
git clone https://tangled.org/lewis.moe/bspds.git
132
+
cd bspds
133
+
134
+
cd frontend
135
+
deno task build
136
+
cd ..
137
+
138
+
cargo build --release
139
+
```
140
+
141
+
## 8. Install sqlx-cli and Run Migrations
142
+
143
+
```bash
144
+
cargo install sqlx-cli --no-default-features --features postgres
145
+
146
+
export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds"
147
+
sqlx migrate run
148
+
```
149
+
150
+
## 9. Configure BSPDS
151
+
152
+
```bash
153
+
mkdir -p /etc/bspds
154
+
cp /opt/bspds/.env.example /etc/bspds/bspds.env
155
+
chmod 600 /etc/bspds/bspds.env
156
+
```
157
+
158
+
Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with:
159
+
160
+
```bash
161
+
openssl rand -base64 48
162
+
```
163
+
164
+
## 10. Create Systemd Service
165
+
166
+
```bash
167
+
useradd -r -s /sbin/nologin bspds
168
+
169
+
cp /opt/bspds/target/release/bspds /usr/local/bin/
170
+
mkdir -p /var/lib/bspds
171
+
cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend
172
+
chown -R bspds:bspds /var/lib/bspds
173
+
174
+
cat > /etc/systemd/system/bspds.service << 'EOF'
175
+
[Unit]
176
+
Description=BSPDS - AT Protocol PDS
177
+
After=network.target postgresql.service minio.service
178
+
179
+
[Service]
180
+
Type=simple
181
+
User=bspds
182
+
Group=bspds
183
+
EnvironmentFile=/etc/bspds/bspds.env
184
+
Environment=FRONTEND_DIR=/var/lib/bspds/frontend
185
+
ExecStart=/usr/local/bin/bspds
186
+
Restart=always
187
+
RestartSec=5
188
+
189
+
[Install]
190
+
WantedBy=multi-user.target
191
+
EOF
192
+
193
+
systemctl daemon-reload
194
+
systemctl enable bspds
195
+
systemctl start bspds
196
+
```
197
+
198
+
## 11. Install and Configure nginx
199
+
200
+
Debian 13 includes nginx 1.26:
201
+
202
+
```bash
203
+
apt install -y nginx certbot python3-certbot-nginx
204
+
205
+
cat > /etc/nginx/sites-available/bspds << 'EOF'
206
+
server {
207
+
listen 80;
208
+
listen [::]:80;
209
+
server_name pds.example.com;
210
+
211
+
location / {
212
+
proxy_pass http://127.0.0.1:3000;
213
+
proxy_http_version 1.1;
214
+
proxy_set_header Upgrade $http_upgrade;
215
+
proxy_set_header Connection "upgrade";
216
+
proxy_set_header Host $host;
217
+
proxy_set_header X-Real-IP $remote_addr;
218
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
219
+
proxy_set_header X-Forwarded-Proto $scheme;
220
+
proxy_read_timeout 86400;
221
+
}
222
+
}
223
+
EOF
224
+
225
+
ln -s /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/
226
+
rm -f /etc/nginx/sites-enabled/default
227
+
nginx -t
228
+
systemctl reload nginx
229
+
```
230
+
231
+
## 12. Obtain SSL Certificate
232
+
233
+
```bash
234
+
certbot --nginx -d pds.example.com
235
+
```
236
+
237
+
Certbot automatically configures nginx for HTTP/2 and sets up auto-renewal.
238
+
239
+
## 13. Configure Firewall
240
+
241
+
```bash
242
+
apt install -y ufw
243
+
ufw allow ssh
244
+
ufw allow 80/tcp
245
+
ufw allow 443/tcp
246
+
ufw enable
247
+
```
248
+
249
+
## 14. Verify Installation
250
+
251
+
```bash
252
+
systemctl status bspds
253
+
curl -s https://pds.example.com/xrpc/_health | jq
254
+
curl -s https://pds.example.com/.well-known/atproto-did
255
+
```
256
+
257
+
## Maintenance
258
+
259
+
View logs:
260
+
```bash
261
+
journalctl -u bspds -f
262
+
```
263
+
264
+
Update BSPDS:
265
+
```bash
266
+
cd /opt/bspds
267
+
git pull
268
+
cd frontend && deno task build && cd ..
269
+
cargo build --release
270
+
systemctl stop bspds
271
+
cp target/release/bspds /usr/local/bin/
272
+
cp -r frontend/dist /var/lib/bspds/frontend
273
+
DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run
274
+
systemctl start bspds
275
+
```
276
+
277
+
Backup database:
278
+
```bash
279
+
sudo -u postgres pg_dump pds > /var/backups/pds-$(date +%Y%m%d).sql
280
+
```
+956
docs/install-kubernetes.md
+956
docs/install-kubernetes.md
···
···
1
+
# BSPDS Production Kubernetes Deployment
2
+
3
+
> **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified.
4
+
5
+
This guide covers deploying BSPDS on a production multi-node Kubernetes cluster with high availability, auto-scaling, and proper secrets management.
6
+
7
+
## Architecture Overview
8
+
9
+
```
10
+
┌─────────────────────────────────────────────────┐
11
+
│ Kubernetes Cluster │
12
+
│ │
13
+
Internet ──────►│ Ingress Controller (nginx/traefik) │
14
+
│ │ │
15
+
│ ▼ │
16
+
│ ┌─────────────┐ │
17
+
│ │ Service │◄── HPA (2-10 replicas) │
18
+
│ └──────┬──────┘ │
19
+
│ │ │
20
+
│ ┌────┴────┐ │
21
+
│ ▼ ▼ │
22
+
│ ┌─────┐ ┌─────┐ │
23
+
│ │BSPDS│ │BSPDS│ ... (pods) │
24
+
│ └──┬──┘ └──┬──┘ │
25
+
│ │ │ │
26
+
│ ▼ ▼ │
27
+
│ ┌──────────────────────────────────────┐ │
28
+
│ │ PostgreSQL │ MinIO │ Valkey │ │
29
+
│ │ (HA/Operator)│ (StatefulSet) │ (Sentinel) │
30
+
│ └──────────────────────────────────────┘ │
31
+
└─────────────────────────────────────────────────┘
32
+
```
33
+
34
+
## Prerequisites
35
+
36
+
- Kubernetes cluster (1.30+) with at least 3 nodes (1.34 is current stable)
37
+
- `kubectl` configured to access your cluster
38
+
- `helm` 3.x installed
39
+
- Storage class that supports `ReadWriteOnce` (for databases)
40
+
- Ingress controller installed (nginx-ingress or traefik)
41
+
- cert-manager installed for TLS certificates
42
+
43
+
### Quick Prerequisites Setup
44
+
45
+
If you need to install prerequisites:
46
+
47
+
```bash
48
+
# Install nginx-ingress (chart v4.14.1 - December 2025)
49
+
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
50
+
helm repo update
51
+
helm install ingress-nginx ingress-nginx/ingress-nginx \
52
+
--namespace ingress-nginx --create-namespace \
53
+
--version 4.14.1
54
+
55
+
# Install cert-manager (v1.19.2 - December 2025)
56
+
helm repo add jetstack https://charts.jetstack.io
57
+
helm repo update
58
+
helm install cert-manager jetstack/cert-manager \
59
+
--namespace cert-manager --create-namespace \
60
+
--version v1.19.2 \
61
+
--set installCRDs=true
62
+
```
63
+
64
+
---
65
+
66
+
## 1. Create Namespace
67
+
68
+
```bash
69
+
kubectl create namespace bspds
70
+
kubectl config set-context --current --namespace=bspds
71
+
```
72
+
73
+
## 2. Create Secrets
74
+
75
+
Generate secure passwords and secrets:
76
+
77
+
```bash
78
+
# Generate secrets
79
+
DB_PASSWORD=$(openssl rand -base64 32)
80
+
MINIO_PASSWORD=$(openssl rand -base64 32)
81
+
JWT_SECRET=$(openssl rand -base64 48)
82
+
DPOP_SECRET=$(openssl rand -base64 48)
83
+
MASTER_KEY=$(openssl rand -base64 48)
84
+
85
+
# Create Kubernetes secrets
86
+
kubectl create secret generic bspds-db-credentials \
87
+
--from-literal=username=bspds \
88
+
--from-literal=password="$DB_PASSWORD"
89
+
90
+
kubectl create secret generic bspds-minio-credentials \
91
+
--from-literal=root-user=minioadmin \
92
+
--from-literal=root-password="$MINIO_PASSWORD"
93
+
94
+
kubectl create secret generic bspds-secrets \
95
+
--from-literal=jwt-secret="$JWT_SECRET" \
96
+
--from-literal=dpop-secret="$DPOP_SECRET" \
97
+
--from-literal=master-key="$MASTER_KEY"
98
+
99
+
# Save secrets locally (KEEP SECURE!)
100
+
echo "DB_PASSWORD=$DB_PASSWORD" > secrets.txt
101
+
echo "MINIO_PASSWORD=$MINIO_PASSWORD" >> secrets.txt
102
+
echo "JWT_SECRET=$JWT_SECRET" >> secrets.txt
103
+
echo "DPOP_SECRET=$DPOP_SECRET" >> secrets.txt
104
+
echo "MASTER_KEY=$MASTER_KEY" >> secrets.txt
105
+
chmod 600 secrets.txt
106
+
```
107
+
108
+
## 3. Deploy PostgreSQL
109
+
110
+
### Option A: CloudNativePG Operator (Recommended for HA)
111
+
112
+
```bash
113
+
# Install CloudNativePG operator (v1.28.0 - December 2025)
114
+
kubectl apply --server-side -f \
115
+
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.0.yaml
116
+
117
+
# Wait for operator
118
+
kubectl wait --for=condition=available --timeout=120s \
119
+
deployment/cnpg-controller-manager -n cnpg-system
120
+
```
121
+
122
+
```bash
123
+
cat <<EOF | kubectl apply -f -
124
+
apiVersion: postgresql.cnpg.io/v1
125
+
kind: Cluster
126
+
metadata:
127
+
name: bspds-db
128
+
namespace: bspds
129
+
spec:
130
+
instances: 3
131
+
132
+
postgresql:
133
+
parameters:
134
+
max_connections: "200"
135
+
shared_buffers: "256MB"
136
+
137
+
bootstrap:
138
+
initdb:
139
+
database: pds
140
+
owner: bspds
141
+
secret:
142
+
name: bspds-db-credentials
143
+
144
+
storage:
145
+
size: 20Gi
146
+
storageClass: standard # adjust for your cluster
147
+
148
+
resources:
149
+
requests:
150
+
memory: "512Mi"
151
+
cpu: "250m"
152
+
limits:
153
+
memory: "1Gi"
154
+
cpu: "1000m"
155
+
156
+
affinity:
157
+
podAntiAffinityType: required
158
+
EOF
159
+
```
160
+
161
+
### Option B: Simple StatefulSet (Single Instance)
162
+
163
+
```bash
164
+
cat <<EOF | kubectl apply -f -
165
+
apiVersion: v1
166
+
kind: PersistentVolumeClaim
167
+
metadata:
168
+
name: bspds-db-pvc
169
+
namespace: bspds
170
+
spec:
171
+
accessModes:
172
+
- ReadWriteOnce
173
+
resources:
174
+
requests:
175
+
storage: 20Gi
176
+
---
177
+
apiVersion: apps/v1
178
+
kind: StatefulSet
179
+
metadata:
180
+
name: bspds-db
181
+
namespace: bspds
182
+
spec:
183
+
serviceName: bspds-db
184
+
replicas: 1
185
+
selector:
186
+
matchLabels:
187
+
app: bspds-db
188
+
template:
189
+
metadata:
190
+
labels:
191
+
app: bspds-db
192
+
spec:
193
+
containers:
194
+
- name: postgres
195
+
image: postgres:18-alpine
196
+
ports:
197
+
- containerPort: 5432
198
+
env:
199
+
- name: POSTGRES_DB
200
+
value: pds
201
+
- name: POSTGRES_USER
202
+
valueFrom:
203
+
secretKeyRef:
204
+
name: bspds-db-credentials
205
+
key: username
206
+
- name: POSTGRES_PASSWORD
207
+
valueFrom:
208
+
secretKeyRef:
209
+
name: bspds-db-credentials
210
+
key: password
211
+
- name: PGDATA
212
+
value: /var/lib/postgresql/data/pgdata
213
+
volumeMounts:
214
+
- name: data
215
+
mountPath: /var/lib/postgresql/data
216
+
resources:
217
+
requests:
218
+
memory: "256Mi"
219
+
cpu: "100m"
220
+
limits:
221
+
memory: "1Gi"
222
+
cpu: "500m"
223
+
livenessProbe:
224
+
exec:
225
+
command: ["pg_isready", "-U", "bspds", "-d", "pds"]
226
+
initialDelaySeconds: 30
227
+
periodSeconds: 10
228
+
readinessProbe:
229
+
exec:
230
+
command: ["pg_isready", "-U", "bspds", "-d", "pds"]
231
+
initialDelaySeconds: 5
232
+
periodSeconds: 5
233
+
volumes:
234
+
- name: data
235
+
persistentVolumeClaim:
236
+
claimName: bspds-db-pvc
237
+
---
238
+
apiVersion: v1
239
+
kind: Service
240
+
metadata:
241
+
name: bspds-db-rw
242
+
namespace: bspds
243
+
spec:
244
+
selector:
245
+
app: bspds-db
246
+
ports:
247
+
- port: 5432
248
+
targetPort: 5432
249
+
EOF
250
+
```
251
+
252
+
## 4. Deploy MinIO
253
+
254
+
```bash
255
+
cat <<EOF | kubectl apply -f -
256
+
apiVersion: v1
257
+
kind: PersistentVolumeClaim
258
+
metadata:
259
+
name: bspds-minio-pvc
260
+
namespace: bspds
261
+
spec:
262
+
accessModes:
263
+
- ReadWriteOnce
264
+
resources:
265
+
requests:
266
+
storage: 50Gi
267
+
---
268
+
apiVersion: apps/v1
269
+
kind: StatefulSet
270
+
metadata:
271
+
name: bspds-minio
272
+
namespace: bspds
273
+
spec:
274
+
serviceName: bspds-minio
275
+
replicas: 1
276
+
selector:
277
+
matchLabels:
278
+
app: bspds-minio
279
+
template:
280
+
metadata:
281
+
labels:
282
+
app: bspds-minio
283
+
spec:
284
+
containers:
285
+
- name: minio
286
+
image: minio/minio:RELEASE.2025-10-15T17-29-55Z
287
+
args:
288
+
- server
289
+
- /data
290
+
- --console-address
291
+
- ":9001"
292
+
ports:
293
+
- containerPort: 9000
294
+
name: api
295
+
- containerPort: 9001
296
+
name: console
297
+
env:
298
+
- name: MINIO_ROOT_USER
299
+
valueFrom:
300
+
secretKeyRef:
301
+
name: bspds-minio-credentials
302
+
key: root-user
303
+
- name: MINIO_ROOT_PASSWORD
304
+
valueFrom:
305
+
secretKeyRef:
306
+
name: bspds-minio-credentials
307
+
key: root-password
308
+
volumeMounts:
309
+
- name: data
310
+
mountPath: /data
311
+
resources:
312
+
requests:
313
+
memory: "256Mi"
314
+
cpu: "100m"
315
+
limits:
316
+
memory: "512Mi"
317
+
cpu: "500m"
318
+
livenessProbe:
319
+
httpGet:
320
+
path: /minio/health/live
321
+
port: 9000
322
+
initialDelaySeconds: 30
323
+
periodSeconds: 10
324
+
readinessProbe:
325
+
httpGet:
326
+
path: /minio/health/ready
327
+
port: 9000
328
+
initialDelaySeconds: 10
329
+
periodSeconds: 5
330
+
volumes:
331
+
- name: data
332
+
persistentVolumeClaim:
333
+
claimName: bspds-minio-pvc
334
+
---
335
+
apiVersion: v1
336
+
kind: Service
337
+
metadata:
338
+
name: bspds-minio
339
+
namespace: bspds
340
+
spec:
341
+
selector:
342
+
app: bspds-minio
343
+
ports:
344
+
- port: 9000
345
+
targetPort: 9000
346
+
name: api
347
+
- port: 9001
348
+
targetPort: 9001
349
+
name: console
350
+
EOF
351
+
```
352
+
353
+
### Initialize MinIO Bucket
354
+
355
+
```bash
356
+
kubectl run minio-init --rm -it --restart=Never \
357
+
--image=minio/mc:RELEASE.2025-07-16T15-35-03Z \
358
+
--env="MINIO_ROOT_USER=minioadmin" \
359
+
--env="MINIO_ROOT_PASSWORD=$(kubectl get secret bspds-minio-credentials -o jsonpath='{.data.root-password}' | base64 -d)" \
360
+
--command -- sh -c "
361
+
mc alias set local http://bspds-minio:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD &&
362
+
mc mb --ignore-existing local/pds-blobs
363
+
"
364
+
```
365
+
366
+
## 5. Deploy Valkey
367
+
368
+
```bash
369
+
cat <<EOF | kubectl apply -f -
370
+
apiVersion: v1
371
+
kind: PersistentVolumeClaim
372
+
metadata:
373
+
name: bspds-valkey-pvc
374
+
namespace: bspds
375
+
spec:
376
+
accessModes:
377
+
- ReadWriteOnce
378
+
resources:
379
+
requests:
380
+
storage: 5Gi
381
+
---
382
+
apiVersion: apps/v1
383
+
kind: StatefulSet
384
+
metadata:
385
+
name: bspds-valkey
386
+
namespace: bspds
387
+
spec:
388
+
serviceName: bspds-valkey
389
+
replicas: 1
390
+
selector:
391
+
matchLabels:
392
+
app: bspds-valkey
393
+
template:
394
+
metadata:
395
+
labels:
396
+
app: bspds-valkey
397
+
spec:
398
+
containers:
399
+
- name: valkey
400
+
image: valkey/valkey:9-alpine
401
+
args:
402
+
- valkey-server
403
+
- --appendonly
404
+
- "yes"
405
+
- --maxmemory
406
+
- "256mb"
407
+
- --maxmemory-policy
408
+
- allkeys-lru
409
+
ports:
410
+
- containerPort: 6379
411
+
volumeMounts:
412
+
- name: data
413
+
mountPath: /data
414
+
resources:
415
+
requests:
416
+
memory: "128Mi"
417
+
cpu: "50m"
418
+
limits:
419
+
memory: "300Mi"
420
+
cpu: "200m"
421
+
livenessProbe:
422
+
exec:
423
+
command: ["valkey-cli", "ping"]
424
+
initialDelaySeconds: 10
425
+
periodSeconds: 5
426
+
readinessProbe:
427
+
exec:
428
+
command: ["valkey-cli", "ping"]
429
+
initialDelaySeconds: 5
430
+
periodSeconds: 3
431
+
volumes:
432
+
- name: data
433
+
persistentVolumeClaim:
434
+
claimName: bspds-valkey-pvc
435
+
---
436
+
apiVersion: v1
437
+
kind: Service
438
+
metadata:
439
+
name: bspds-valkey
440
+
namespace: bspds
441
+
spec:
442
+
selector:
443
+
app: bspds-valkey
444
+
ports:
445
+
- port: 6379
446
+
targetPort: 6379
447
+
EOF
448
+
```
449
+
450
+
## 6. Build and Push BSPDS Image
451
+
452
+
```bash
453
+
# Build image
454
+
cd /path/to/bspds
455
+
docker build -t your-registry.com/bspds:latest .
456
+
docker push your-registry.com/bspds:latest
457
+
```
458
+
459
+
If using a private registry, create an image pull secret:
460
+
461
+
```bash
462
+
kubectl create secret docker-registry regcred \
463
+
--docker-server=your-registry.com \
464
+
--docker-username=your-username \
465
+
--docker-password=your-password \
466
+
--docker-email=your-email
467
+
```
468
+
469
+
## 7. Run Database Migrations
470
+
471
+
BSPDS runs migrations automatically on startup. However, if you want to run migrations separately (recommended for zero-downtime deployments), you can use a Job:
472
+
473
+
```bash
474
+
cat <<'EOF' | kubectl apply -f -
475
+
apiVersion: batch/v1
476
+
kind: Job
477
+
metadata:
478
+
name: bspds-migrate
479
+
namespace: bspds
480
+
spec:
481
+
ttlSecondsAfterFinished: 300
482
+
template:
483
+
spec:
484
+
restartPolicy: Never
485
+
containers:
486
+
- name: migrate
487
+
image: your-registry.com/bspds:latest
488
+
command: ["/usr/local/bin/bspds"]
489
+
args: ["--migrate-only"] # Add this flag to your app, or remove this Job
490
+
env:
491
+
- name: DB_PASSWORD
492
+
valueFrom:
493
+
secretKeyRef:
494
+
name: bspds-db-credentials
495
+
key: password
496
+
- name: DATABASE_URL
497
+
value: "postgres://bspds:$(DB_PASSWORD)@bspds-db-rw:5432/pds"
498
+
EOF
499
+
500
+
kubectl wait --for=condition=complete --timeout=120s job/bspds-migrate
501
+
```
502
+
503
+
> **Note**: If your BSPDS image doesn't have a `--migrate-only` flag, you can skip this step. The app will run migrations on first startup. Alternatively, build a separate migration image with `sqlx-cli` installed.
504
+
505
+
## 8. Deploy BSPDS Application
506
+
507
+
```bash
508
+
cat <<EOF | kubectl apply -f -
509
+
apiVersion: v1
510
+
kind: ConfigMap
511
+
metadata:
512
+
name: bspds-config
513
+
namespace: bspds
514
+
data:
515
+
PDS_HOSTNAME: "pds.example.com"
516
+
SERVER_HOST: "0.0.0.0"
517
+
SERVER_PORT: "3000"
518
+
S3_ENDPOINT: "http://bspds-minio:9000"
519
+
AWS_REGION: "us-east-1"
520
+
S3_BUCKET: "pds-blobs"
521
+
VALKEY_URL: "redis://bspds-valkey:6379"
522
+
APPVIEW_URL: "https://api.bsky.app"
523
+
CRAWLERS: "https://bsky.network"
524
+
FRONTEND_DIR: "/app/frontend/dist"
525
+
---
526
+
apiVersion: apps/v1
527
+
kind: Deployment
528
+
metadata:
529
+
name: bspds
530
+
namespace: bspds
531
+
spec:
532
+
replicas: 2
533
+
selector:
534
+
matchLabels:
535
+
app: bspds
536
+
template:
537
+
metadata:
538
+
labels:
539
+
app: bspds
540
+
spec:
541
+
imagePullSecrets:
542
+
- name: regcred # Remove if using public registry
543
+
affinity:
544
+
podAntiAffinity:
545
+
preferredDuringSchedulingIgnoredDuringExecution:
546
+
- weight: 100
547
+
podAffinityTerm:
548
+
labelSelector:
549
+
matchLabels:
550
+
app: bspds
551
+
topologyKey: kubernetes.io/hostname
552
+
containers:
553
+
- name: bspds
554
+
image: your-registry.com/bspds:latest
555
+
ports:
556
+
- containerPort: 3000
557
+
name: http
558
+
envFrom:
559
+
- configMapRef:
560
+
name: bspds-config
561
+
env:
562
+
- name: DB_PASSWORD
563
+
valueFrom:
564
+
secretKeyRef:
565
+
name: bspds-db-credentials
566
+
key: password
567
+
- name: DATABASE_URL
568
+
value: "postgres://bspds:$(DB_PASSWORD)@bspds-db-rw:5432/pds"
569
+
- name: AWS_ACCESS_KEY_ID
570
+
valueFrom:
571
+
secretKeyRef:
572
+
name: bspds-minio-credentials
573
+
key: root-user
574
+
- name: AWS_SECRET_ACCESS_KEY
575
+
valueFrom:
576
+
secretKeyRef:
577
+
name: bspds-minio-credentials
578
+
key: root-password
579
+
- name: JWT_SECRET
580
+
valueFrom:
581
+
secretKeyRef:
582
+
name: bspds-secrets
583
+
key: jwt-secret
584
+
- name: DPOP_SECRET
585
+
valueFrom:
586
+
secretKeyRef:
587
+
name: bspds-secrets
588
+
key: dpop-secret
589
+
- name: MASTER_KEY
590
+
valueFrom:
591
+
secretKeyRef:
592
+
name: bspds-secrets
593
+
key: master-key
594
+
resources:
595
+
requests:
596
+
memory: "256Mi"
597
+
cpu: "100m"
598
+
limits:
599
+
memory: "1Gi"
600
+
cpu: "1000m"
601
+
livenessProbe:
602
+
httpGet:
603
+
path: /xrpc/_health
604
+
port: 3000
605
+
initialDelaySeconds: 30
606
+
periodSeconds: 10
607
+
failureThreshold: 3
608
+
readinessProbe:
609
+
httpGet:
610
+
path: /xrpc/_health
611
+
port: 3000
612
+
initialDelaySeconds: 5
613
+
periodSeconds: 5
614
+
failureThreshold: 3
615
+
securityContext:
616
+
runAsNonRoot: true
617
+
runAsUser: 1000
618
+
allowPrivilegeEscalation: false
619
+
---
620
+
apiVersion: v1
621
+
kind: Service
622
+
metadata:
623
+
name: bspds
624
+
namespace: bspds
625
+
spec:
626
+
selector:
627
+
app: bspds
628
+
ports:
629
+
- port: 80
630
+
targetPort: 3000
631
+
name: http
632
+
EOF
633
+
```
634
+
635
+
## 9. Configure Horizontal Pod Autoscaler
636
+
637
+
```bash
638
+
cat <<EOF | kubectl apply -f -
639
+
apiVersion: autoscaling/v2
640
+
kind: HorizontalPodAutoscaler
641
+
metadata:
642
+
name: bspds
643
+
namespace: bspds
644
+
spec:
645
+
scaleTargetRef:
646
+
apiVersion: apps/v1
647
+
kind: Deployment
648
+
name: bspds
649
+
minReplicas: 2
650
+
maxReplicas: 10
651
+
metrics:
652
+
- type: Resource
653
+
resource:
654
+
name: cpu
655
+
target:
656
+
type: Utilization
657
+
averageUtilization: 70
658
+
- type: Resource
659
+
resource:
660
+
name: memory
661
+
target:
662
+
type: Utilization
663
+
averageUtilization: 80
664
+
behavior:
665
+
scaleDown:
666
+
stabilizationWindowSeconds: 300
667
+
policies:
668
+
- type: Pods
669
+
value: 1
670
+
periodSeconds: 60
671
+
scaleUp:
672
+
stabilizationWindowSeconds: 0
673
+
policies:
674
+
- type: Percent
675
+
value: 100
676
+
periodSeconds: 15
677
+
- type: Pods
678
+
value: 4
679
+
periodSeconds: 15
680
+
selectPolicy: Max
681
+
EOF
682
+
```
683
+
684
+
## 10. Configure Pod Disruption Budget
685
+
686
+
```bash
687
+
cat <<EOF | kubectl apply -f -
688
+
apiVersion: policy/v1
689
+
kind: PodDisruptionBudget
690
+
metadata:
691
+
name: bspds
692
+
namespace: bspds
693
+
spec:
694
+
minAvailable: 1
695
+
selector:
696
+
matchLabels:
697
+
app: bspds
698
+
EOF
699
+
```
700
+
701
+
## 11. Configure TLS with cert-manager
702
+
703
+
```bash
704
+
cat <<EOF | kubectl apply -f -
705
+
apiVersion: cert-manager.io/v1
706
+
kind: ClusterIssuer
707
+
metadata:
708
+
name: letsencrypt-prod
709
+
spec:
710
+
acme:
711
+
server: https://acme-v02.api.letsencrypt.org/directory
712
+
email: your-email@example.com
713
+
privateKeySecretRef:
714
+
name: letsencrypt-prod
715
+
solvers:
716
+
- http01:
717
+
ingress:
718
+
class: nginx
719
+
EOF
720
+
```
721
+
722
+
## 12. Configure Ingress
723
+
724
+
```bash
725
+
cat <<EOF | kubectl apply -f -
726
+
apiVersion: networking.k8s.io/v1
727
+
kind: Ingress
728
+
metadata:
729
+
name: bspds
730
+
namespace: bspds
731
+
annotations:
732
+
cert-manager.io/cluster-issuer: letsencrypt-prod
733
+
nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"
734
+
nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"
735
+
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
736
+
nginx.ingress.kubernetes.io/proxy-buffering: "off"
737
+
nginx.ingress.kubernetes.io/websocket-services: "bspds"
738
+
spec:
739
+
ingressClassName: nginx
740
+
tls:
741
+
- hosts:
742
+
- pds.example.com
743
+
secretName: bspds-tls
744
+
rules:
745
+
- host: pds.example.com
746
+
http:
747
+
paths:
748
+
- path: /
749
+
pathType: Prefix
750
+
backend:
751
+
service:
752
+
name: bspds
753
+
port:
754
+
number: 80
755
+
EOF
756
+
```
757
+
758
+
## 13. Configure Network Policies (Optional but Recommended)
759
+
760
+
```bash
761
+
cat <<EOF | kubectl apply -f -
762
+
apiVersion: networking.k8s.io/v1
763
+
kind: NetworkPolicy
764
+
metadata:
765
+
name: bspds-network-policy
766
+
namespace: bspds
767
+
spec:
768
+
podSelector:
769
+
matchLabels:
770
+
app: bspds
771
+
policyTypes:
772
+
- Ingress
773
+
- Egress
774
+
ingress:
775
+
- from:
776
+
- namespaceSelector:
777
+
matchLabels:
778
+
kubernetes.io/metadata.name: ingress-nginx
779
+
ports:
780
+
- protocol: TCP
781
+
port: 3000
782
+
egress:
783
+
- to:
784
+
- podSelector:
785
+
matchLabels:
786
+
app: bspds-db
787
+
ports:
788
+
- protocol: TCP
789
+
port: 5432
790
+
- to:
791
+
- podSelector:
792
+
matchLabels:
793
+
app: bspds-minio
794
+
ports:
795
+
- protocol: TCP
796
+
port: 9000
797
+
- to:
798
+
- podSelector:
799
+
matchLabels:
800
+
app: bspds-valkey
801
+
ports:
802
+
- protocol: TCP
803
+
port: 6379
804
+
- to: # Allow DNS
805
+
- namespaceSelector: {}
806
+
podSelector:
807
+
matchLabels:
808
+
k8s-app: kube-dns
809
+
ports:
810
+
- protocol: UDP
811
+
port: 53
812
+
- to: # Allow external HTTPS (for federation)
813
+
- ipBlock:
814
+
cidr: 0.0.0.0/0
815
+
ports:
816
+
- protocol: TCP
817
+
port: 443
818
+
EOF
819
+
```
820
+
821
+
## 14. Deploy Prometheus Monitoring (Optional)
822
+
823
+
```bash
824
+
cat <<EOF | kubectl apply -f -
825
+
apiVersion: monitoring.coreos.com/v1
826
+
kind: ServiceMonitor
827
+
metadata:
828
+
name: bspds
829
+
namespace: bspds
830
+
labels:
831
+
release: prometheus
832
+
spec:
833
+
selector:
834
+
matchLabels:
835
+
app: bspds
836
+
endpoints:
837
+
- port: http
838
+
path: /metrics
839
+
interval: 30s
840
+
EOF
841
+
```
842
+
843
+
---
844
+
845
+
## Verification
846
+
847
+
```bash
848
+
# Check all pods are running
849
+
kubectl get pods -n bspds
850
+
851
+
# Check services
852
+
kubectl get svc -n bspds
853
+
854
+
# Check ingress
855
+
kubectl get ingress -n bspds
856
+
857
+
# Check certificate
858
+
kubectl get certificate -n bspds
859
+
860
+
# Test health endpoint
861
+
curl -s https://pds.example.com/xrpc/_health | jq
862
+
863
+
# Test DID endpoint
864
+
curl -s https://pds.example.com/.well-known/atproto-did
865
+
```
866
+
867
+
---
868
+
869
+
## Maintenance
870
+
871
+
### View Logs
872
+
873
+
```bash
874
+
# All BSPDS pods
875
+
kubectl logs -l app=bspds -n bspds -f
876
+
877
+
# Specific pod
878
+
kubectl logs -f deployment/bspds -n bspds
879
+
```
880
+
881
+
### Scale Manually
882
+
883
+
```bash
884
+
kubectl scale deployment bspds --replicas=5 -n bspds
885
+
```
886
+
887
+
### Update BSPDS
888
+
889
+
```bash
890
+
# Build and push new image
891
+
docker build -t your-registry.com/bspds:v1.2.3 .
892
+
docker push your-registry.com/bspds:v1.2.3
893
+
894
+
# Update deployment
895
+
kubectl set image deployment/bspds bspds=your-registry.com/bspds:v1.2.3 -n bspds
896
+
897
+
# Watch rollout
898
+
kubectl rollout status deployment/bspds -n bspds
899
+
```
900
+
901
+
### Backup Database
902
+
903
+
```bash
904
+
# For CloudNativePG
905
+
kubectl cnpg backup bspds-db -n bspds
906
+
907
+
# For StatefulSet
908
+
kubectl exec -it bspds-db-0 -n bspds -- pg_dump -U bspds pds > backup-$(date +%Y%m%d).sql
909
+
```
910
+
911
+
### Run Migrations
912
+
913
+
If you have a migration Job defined, you can re-run it:
914
+
915
+
```bash
916
+
# Delete old job first (if exists)
917
+
kubectl delete job bspds-migrate -n bspds --ignore-not-found
918
+
919
+
# Re-apply the migration job from step 7
920
+
# Or simply restart the deployment - BSPDS runs migrations on startup
921
+
kubectl rollout restart deployment/bspds -n bspds
922
+
```
923
+
924
+
---
925
+
926
+
## Troubleshooting
927
+
928
+
### Pod Won't Start
929
+
930
+
```bash
931
+
kubectl describe pod -l app=bspds -n bspds
932
+
kubectl logs -l app=bspds -n bspds --previous
933
+
```
934
+
935
+
### Database Connection Issues
936
+
937
+
```bash
938
+
# Test connectivity from a debug pod
939
+
kubectl run debug --rm -it --restart=Never --image=postgres:18-alpine -- \
940
+
psql "postgres://bspds:PASSWORD@bspds-db-rw:5432/pds" -c "SELECT 1"
941
+
```
942
+
943
+
### Certificate Issues
944
+
945
+
```bash
946
+
kubectl describe certificate bspds-tls -n bspds
947
+
kubectl describe certificaterequest -n bspds
948
+
kubectl logs -l app.kubernetes.io/name=cert-manager -n cert-manager
949
+
```
950
+
951
+
### View Resource Usage
952
+
953
+
```bash
954
+
kubectl top pods -n bspds
955
+
kubectl top nodes
956
+
```
+354
docs/install-openbsd.md
+354
docs/install-openbsd.md
···
···
1
+
# BSPDS Production Installation on OpenBSD
2
+
3
+
> **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified.
4
+
5
+
This guide covers installing BSPDS on OpenBSD 7.8 (current release as of December 2025).
6
+
7
+
## Prerequisites
8
+
9
+
- A VPS with at least 2GB RAM and 20GB disk
10
+
- A domain name pointing to your server's IP
11
+
- Root access (or doas configured)
12
+
13
+
## Why nginx over relayd?
14
+
15
+
OpenBSD's native `relayd` supports WebSockets but does **not** support HTTP/2. For a modern PDS deployment, we recommend nginx which provides HTTP/2, WebSocket support, and automatic OCSP stapling.
16
+
17
+
## 1. System Setup
18
+
19
+
```sh
20
+
pkg_add curl git
21
+
```
22
+
23
+
## 2. Install Rust
24
+
25
+
```sh
26
+
pkg_add rust
27
+
```
28
+
29
+
OpenBSD 7.8 ships Rust 1.82+. For the latest stable (1.92+), use rustup:
30
+
31
+
```sh
32
+
pkg_add rustup
33
+
rustup-init -y
34
+
source ~/.cargo/env
35
+
rustup default stable
36
+
```
37
+
38
+
## 3. Install postgres
39
+
40
+
OpenBSD 7.8 includes PostgreSQL 17 (PostgreSQL 18 may not yet be in ports):
41
+
42
+
```sh
43
+
pkg_add postgresql-server postgresql-client
44
+
45
+
mkdir -p /var/postgresql/data
46
+
chown _postgresql:_postgresql /var/postgresql/data
47
+
su - _postgresql -c "initdb -D /var/postgresql/data -U postgres -A scram-sha-256"
48
+
49
+
rcctl enable postgresql
50
+
rcctl start postgresql
51
+
52
+
psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';"
53
+
psql -U postgres -c "CREATE DATABASE pds OWNER bspds;"
54
+
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;"
55
+
```
56
+
57
+
## 4. Install minio
58
+
59
+
OpenBSD doesn't have a minio package. Options:
60
+
61
+
**Option A: Use an external S3-compatible service (recommended for production)**
62
+
63
+
aws s3, backblaze b2, or upcloud managed object storage. Skip to step 5 and configure the S3 credentials in step 9.
64
+
65
+
**Option B: Build minio from source**
66
+
67
+
```sh
68
+
pkg_add go
69
+
70
+
mkdir -p /tmp/minio-build && cd /tmp/minio-build
71
+
ftp -o minio.tar.gz https://github.com/minio/minio/archive/refs/tags/RELEASE.2025-10-15T17-29-55Z.tar.gz
72
+
tar xzf minio.tar.gz
73
+
cd minio-*
74
+
go build -o minio .
75
+
cp minio /usr/local/bin/
76
+
77
+
mkdir -p /var/minio/data
78
+
useradd -d /var/minio -s /sbin/nologin _minio
79
+
chown -R _minio:_minio /var/minio
80
+
81
+
cat > /etc/minio.conf << 'EOF'
82
+
MINIO_ROOT_USER=minioadmin
83
+
MINIO_ROOT_PASSWORD=your-minio-password
84
+
EOF
85
+
chmod 600 /etc/minio.conf
86
+
87
+
cat > /etc/rc.d/minio << 'EOF'
88
+
#!/bin/ksh
89
+
90
+
daemon="/usr/local/bin/minio"
91
+
daemon_user="_minio"
92
+
daemon_flags="server /var/minio/data --console-address :9001"
93
+
94
+
. /etc/rc.d/rc.subr
95
+
96
+
rc_pre() {
97
+
. /etc/minio.conf
98
+
export MINIO_ROOT_USER MINIO_ROOT_PASSWORD
99
+
}
100
+
101
+
rc_cmd $1
102
+
EOF
103
+
104
+
chmod +x /etc/rc.d/minio
105
+
rcctl enable minio
106
+
rcctl start minio
107
+
```
108
+
109
+
Create the blob bucket:
110
+
111
+
```sh
112
+
ftp -o /usr/local/bin/mc https://dl.min.io/client/mc/release/openbsd-amd64/mc
113
+
chmod +x /usr/local/bin/mc
114
+
115
+
mc alias set local http://localhost:9000 minioadmin your-minio-password
116
+
mc mb local/pds-blobs
117
+
```
118
+
119
+
## 5. Install redis
120
+
121
+
OpenBSD has redis in ports (valkey may not be available yet):
122
+
123
+
```sh
124
+
pkg_add redis
125
+
126
+
rcctl enable redis
127
+
rcctl start redis
128
+
```
129
+
130
+
## 6. Install deno (for frontend build)
131
+
132
+
```sh
133
+
curl -fsSL https://deno.land/install.sh | sh
134
+
export PATH="$HOME/.deno/bin:$PATH"
135
+
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile
136
+
```
137
+
138
+
## 7. Clone and Build BSPDS
139
+
140
+
```sh
141
+
mkdir -p /opt && cd /opt
142
+
git clone https://tangled.org/lewis.moe/bspds.git
143
+
cd bspds
144
+
145
+
cd frontend
146
+
deno task build
147
+
cd ..
148
+
149
+
cargo build --release
150
+
```
151
+
152
+
## 8. Install sqlx-cli and Run Migrations
153
+
154
+
```sh
155
+
cargo install sqlx-cli --no-default-features --features postgres
156
+
157
+
export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds"
158
+
sqlx migrate run
159
+
```
160
+
161
+
## 9. Configure BSPDS
162
+
163
+
```sh
164
+
mkdir -p /etc/bspds
165
+
cp /opt/bspds/.env.example /etc/bspds/bspds.conf
166
+
chmod 600 /etc/bspds/bspds.conf
167
+
```
168
+
169
+
Edit `/etc/bspds/bspds.conf` and fill in your values. Generate secrets with:
170
+
171
+
```sh
172
+
openssl rand -base64 48
173
+
```
174
+
175
+
## 10. Create rc.d Service
176
+
177
+
```sh
178
+
useradd -d /var/empty -s /sbin/nologin _bspds
179
+
180
+
cp /opt/bspds/target/release/bspds /usr/local/bin/
181
+
mkdir -p /var/bspds
182
+
cp -r /opt/bspds/frontend/dist /var/bspds/frontend
183
+
chown -R _bspds:_bspds /var/bspds
184
+
185
+
cat > /etc/rc.d/bspds << 'EOF'
186
+
#!/bin/ksh
187
+
188
+
daemon="/usr/local/bin/bspds"
189
+
daemon_user="_bspds"
190
+
daemon_logger="daemon.info"
191
+
192
+
. /etc/rc.d/rc.subr
193
+
194
+
rc_pre() {
195
+
export FRONTEND_DIR=/var/bspds/frontend
196
+
while IFS='=' read -r key value; do
197
+
case "$key" in
198
+
\#*|"") continue ;;
199
+
esac
200
+
export "$key=$value"
201
+
done < /etc/bspds/bspds.conf
202
+
}
203
+
204
+
rc_cmd $1
205
+
EOF
206
+
207
+
chmod +x /etc/rc.d/bspds
208
+
rcctl enable bspds
209
+
rcctl start bspds
210
+
```
211
+
212
+
## 11. Install and Configure nginx
213
+
214
+
```sh
215
+
pkg_add nginx
216
+
217
+
cat > /etc/nginx/nginx.conf << 'EOF'
218
+
worker_processes 1;
219
+
220
+
events {
221
+
worker_connections 1024;
222
+
}
223
+
224
+
http {
225
+
include mime.types;
226
+
227
+
server {
228
+
listen 80;
229
+
listen [::]:80;
230
+
server_name pds.example.com;
231
+
232
+
location /.well-known/acme-challenge/ {
233
+
root /var/www/acme;
234
+
}
235
+
236
+
location / {
237
+
return 301 https://$host$request_uri;
238
+
}
239
+
}
240
+
241
+
server {
242
+
listen 443 ssl http2;
243
+
listen [::]:443 ssl http2;
244
+
server_name pds.example.com;
245
+
246
+
ssl_certificate /etc/ssl/pds.example.com.fullchain.pem;
247
+
ssl_certificate_key /etc/ssl/private/pds.example.com.key;
248
+
ssl_protocols TLSv1.2 TLSv1.3;
249
+
ssl_ciphers HIGH:!aNULL:!MD5;
250
+
ssl_prefer_server_ciphers on;
251
+
ssl_session_cache shared:SSL:10m;
252
+
253
+
location / {
254
+
proxy_pass http://127.0.0.1:3000;
255
+
proxy_http_version 1.1;
256
+
proxy_set_header Upgrade $http_upgrade;
257
+
proxy_set_header Connection "upgrade";
258
+
proxy_set_header Host $host;
259
+
proxy_set_header X-Real-IP $remote_addr;
260
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
261
+
proxy_set_header X-Forwarded-Proto $scheme;
262
+
proxy_read_timeout 86400;
263
+
}
264
+
}
265
+
}
266
+
EOF
267
+
268
+
mkdir -p /var/www/acme
269
+
rcctl enable nginx
270
+
```
271
+
272
+
## 12. Obtain SSL Certificate with acme-client
273
+
274
+
OpenBSD's native acme-client works well:
275
+
276
+
```sh
277
+
cat >> /etc/acme-client.conf << 'EOF'
278
+
279
+
authority letsencrypt {
280
+
api url "https://acme-v02.api.letsencrypt.org/directory"
281
+
account key "/etc/acme/letsencrypt-privkey.pem"
282
+
}
283
+
284
+
domain pds.example.com {
285
+
domain key "/etc/ssl/private/pds.example.com.key"
286
+
domain full chain certificate "/etc/ssl/pds.example.com.fullchain.pem"
287
+
sign with letsencrypt
288
+
}
289
+
EOF
290
+
291
+
mkdir -p /etc/acme
292
+
293
+
rcctl start nginx
294
+
295
+
acme-client -v pds.example.com
296
+
297
+
rcctl restart nginx
298
+
```
299
+
300
+
Set up auto-renewal in root's crontab:
301
+
302
+
```sh
303
+
crontab -e
304
+
```
305
+
306
+
Add:
307
+
```
308
+
0 0 * * * acme-client pds.example.com && rcctl reload nginx
309
+
```
310
+
311
+
## 13. Configure Packet Filter (pf)
312
+
313
+
```sh
314
+
cat >> /etc/pf.conf << 'EOF'
315
+
316
+
# BSPDS rules
317
+
pass in on egress proto tcp from any to any port { 22, 80, 443 }
318
+
EOF
319
+
320
+
pfctl -f /etc/pf.conf
321
+
```
322
+
323
+
## 14. Verify Installation
324
+
325
+
```sh
326
+
rcctl check bspds
327
+
ftp -o - https://pds.example.com/xrpc/_health
328
+
ftp -o - https://pds.example.com/.well-known/atproto-did
329
+
```
330
+
331
+
## Maintenance
332
+
333
+
View logs:
334
+
```sh
335
+
tail -f /var/log/daemon
336
+
```
337
+
338
+
Update BSPDS:
339
+
```sh
340
+
cd /opt/bspds
341
+
git pull
342
+
cd frontend && deno task build && cd ..
343
+
cargo build --release
344
+
rcctl stop bspds
345
+
cp target/release/bspds /usr/local/bin/
346
+
cp -r frontend/dist /var/bspds/frontend
347
+
DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run
348
+
rcctl start bspds
349
+
```
350
+
351
+
Backup database:
352
+
```sh
353
+
pg_dump -U postgres pds > /var/backups/pds-$(date +%Y%m%d).sql
354
+
```
+103
nginx.prod.conf
+103
nginx.prod.conf
···
···
1
+
worker_processes auto;
2
+
error_log /var/log/nginx/error.log warn;
3
+
pid /var/run/nginx.pid;
4
+
5
+
events {
6
+
worker_connections 4096;
7
+
use epoll;
8
+
multi_accept on;
9
+
}
10
+
11
+
http {
12
+
include /etc/nginx/mime.types;
13
+
default_type application/octet-stream;
14
+
15
+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16
+
'$status $body_bytes_sent "$http_referer" '
17
+
'"$http_user_agent" "$http_x_forwarded_for" '
18
+
'rt=$request_time uct="$upstream_connect_time" '
19
+
'uht="$upstream_header_time" urt="$upstream_response_time"';
20
+
21
+
access_log /var/log/nginx/access.log main;
22
+
23
+
sendfile on;
24
+
tcp_nopush on;
25
+
tcp_nodelay on;
26
+
keepalive_timeout 65;
27
+
types_hash_max_size 2048;
28
+
29
+
gzip on;
30
+
gzip_vary on;
31
+
gzip_proxied any;
32
+
gzip_comp_level 6;
33
+
gzip_types text/plain text/css text/xml application/json application/javascript
34
+
application/xml application/xml+rss text/javascript application/activity+json;
35
+
36
+
ssl_protocols TLSv1.2 TLSv1.3;
37
+
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
38
+
ssl_prefer_server_ciphers off;
39
+
ssl_session_cache shared:SSL:10m;
40
+
ssl_session_timeout 1d;
41
+
ssl_session_tickets off;
42
+
ssl_stapling on;
43
+
ssl_stapling_verify on;
44
+
45
+
upstream bspds {
46
+
server bspds:3000;
47
+
keepalive 32;
48
+
}
49
+
50
+
server {
51
+
listen 80;
52
+
listen [::]:80;
53
+
server_name _;
54
+
55
+
location /.well-known/acme-challenge/ {
56
+
root /var/www/acme;
57
+
}
58
+
59
+
location / {
60
+
return 301 https://$host$request_uri;
61
+
}
62
+
}
63
+
64
+
server {
65
+
listen 443 ssl http2;
66
+
listen [::]:443 ssl http2;
67
+
server_name _;
68
+
69
+
ssl_certificate /etc/nginx/certs/live/${PDS_HOSTNAME}/fullchain.pem;
70
+
ssl_certificate_key /etc/nginx/certs/live/${PDS_HOSTNAME}/privkey.pem;
71
+
72
+
client_max_body_size 100M;
73
+
74
+
location / {
75
+
proxy_pass http://bspds;
76
+
proxy_http_version 1.1;
77
+
proxy_set_header Upgrade $http_upgrade;
78
+
proxy_set_header Connection "upgrade";
79
+
proxy_set_header Host $host;
80
+
proxy_set_header X-Real-IP $remote_addr;
81
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
82
+
proxy_set_header X-Forwarded-Proto $scheme;
83
+
proxy_read_timeout 86400;
84
+
proxy_send_timeout 86400;
85
+
proxy_buffering off;
86
+
proxy_request_buffering off;
87
+
}
88
+
89
+
location /xrpc/com.atproto.sync.subscribeRepos {
90
+
proxy_pass http://bspds;
91
+
proxy_http_version 1.1;
92
+
proxy_set_header Upgrade $http_upgrade;
93
+
proxy_set_header Connection "upgrade";
94
+
proxy_set_header Host $host;
95
+
proxy_set_header X-Real-IP $remote_addr;
96
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
97
+
proxy_set_header X-Forwarded-Proto $scheme;
98
+
proxy_read_timeout 86400;
99
+
proxy_send_timeout 86400;
100
+
proxy_buffering off;
101
+
}
102
+
}
103
+
}
+2
-2
scripts/test-infra.sh
+2
-2
scripts/test-infra.sh
···
57
-e MINIO_ROOT_PASSWORD=minioadmin \
58
-P \
59
--label bspds_test=true \
60
-
minio/minio:latest server /data >/dev/null
61
62
echo "Starting Valkey..."
63
$CONTAINER_CMD run -d \
···
100
echo "Creating MinIO bucket..."
101
$CONTAINER_CMD run --rm --network host \
102
-e MC_HOST_minio="http://minioadmin:minioadmin@127.0.0.1:${MINIO_PORT}" \
103
-
minio/mc:latest mb minio/test-bucket --ignore-existing >/dev/null 2>&1 || true
104
105
cat > "$INFRA_FILE" << EOF
106
export DATABASE_URL="postgres://postgres:postgres@127.0.0.1:${PG_PORT}/postgres"
···
57
-e MINIO_ROOT_PASSWORD=minioadmin \
58
-P \
59
--label bspds_test=true \
60
+
minio/minio:RELEASE.2025-10-15T17-29-55Z server /data >/dev/null
61
62
echo "Starting Valkey..."
63
$CONTAINER_CMD run -d \
···
100
echo "Creating MinIO bucket..."
101
$CONTAINER_CMD run --rm --network host \
102
-e MC_HOST_minio="http://minioadmin:minioadmin@127.0.0.1:${MINIO_PORT}" \
103
+
minio/mc:RELEASE.2025-07-16T15-35-03Z mb minio/test-bucket --ignore-existing >/dev/null 2>&1 || true
104
105
cat > "$INFRA_FILE" << EOF
106
export DATABASE_URL="postgres://postgres:postgres@127.0.0.1:${PG_PORT}/postgres"