forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
1# Tranquil PDS Production Installation on Debian
2
3This guide covers installing Tranquil PDS on Debian 13.
4
5## Prerequisites
6
7- A VPS with at least 2GB RAM and 20GB disk
8- A domain name pointing to your server's IP
9- A wildcard TLS certificate for `*.pds.example.com` (user handles are served as subdomains)
10- Root or sudo access
11
12## System Setup
13
14```bash
15apt update && apt upgrade -y
16apt install -y curl git build-essential pkg-config libssl-dev
17```
18
19## Install Rust
20
21```bash
22curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
23source ~/.cargo/env
24rustup default stable
25```
26
27This installs the latest stable Rust.
28
29## Install postgres
30
31```bash
32apt install -y postgresql postgresql-contrib
33systemctl enable postgresql
34systemctl start postgresql
35sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';"
36sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;"
37sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;"
38```
39
40## Install minio
41
42```bash
43curl -O https://dl.min.io/server/minio/release/linux-amd64/minio
44chmod +x minio
45mv minio /usr/local/bin/
46mkdir -p /var/lib/minio/data
47useradd -r -s /sbin/nologin minio-user
48chown -R minio-user:minio-user /var/lib/minio
49cat > /etc/default/minio << 'EOF'
50MINIO_ROOT_USER=minioadmin
51MINIO_ROOT_PASSWORD=your-minio-password
52MINIO_VOLUMES="/var/lib/minio/data"
53MINIO_OPTS="--console-address :9001"
54EOF
55cat > /etc/systemd/system/minio.service << 'EOF'
56[Unit]
57Description=MinIO Object Storage
58After=network.target
59[Service]
60User=minio-user
61Group=minio-user
62EnvironmentFile=/etc/default/minio
63ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS
64Restart=always
65LimitNOFILE=65536
66[Install]
67WantedBy=multi-user.target
68EOF
69systemctl daemon-reload
70systemctl enable minio
71systemctl start minio
72```
73
74Create the buckets (wait a few seconds for minio to start):
75```bash
76curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
77chmod +x mc
78mv mc /usr/local/bin/
79mc alias set local http://localhost:9000 minioadmin your-minio-password
80mc mb local/pds-blobs
81mc mb local/pds-backups
82```
83
84## Install valkey
85
86```bash
87apt install -y valkey
88systemctl enable valkey-server
89systemctl start valkey-server
90```
91
92## Install deno (for frontend build)
93
94```bash
95curl -fsSL https://deno.land/install.sh | sh
96export PATH="$HOME/.deno/bin:$PATH"
97echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
98```
99
100## Clone and Build Tranquil PDS
101
102```bash
103cd /opt
104git clone https://tangled.org/tranquil.farm/tranquil-pds tranquil-pds
105cd tranquil-pds
106cd frontend
107deno task build
108cd ..
109cargo build --release
110```
111
112## Install sqlx-cli and Run Migrations
113
114```bash
115cargo install sqlx-cli --no-default-features --features postgres
116export DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds"
117sqlx migrate run
118```
119
120## Configure Tranquil PDS
121
122```bash
123mkdir -p /etc/tranquil-pds
124cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.env
125chmod 600 /etc/tranquil-pds/tranquil-pds.env
126```
127
128Edit `/etc/tranquil-pds/tranquil-pds.env` and fill in your values. Generate secrets with:
129```bash
130openssl rand -base64 48
131```
132
133## Install Frontend Files
134
135```bash
136mkdir -p /var/www/tranquil-pds
137cp -r /opt/tranquil-pds/frontend/dist/* /var/www/tranquil-pds/
138chown -R www-data:www-data /var/www/tranquil-pds
139```
140
141## Create Systemd Service
142
143```bash
144useradd -r -s /sbin/nologin tranquil-pds
145cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/
146
147cat > /etc/systemd/system/tranquil-pds.service << 'EOF'
148[Unit]
149Description=Tranquil PDS - AT Protocol PDS
150After=network.target postgresql.service minio.service
151[Service]
152Type=simple
153User=tranquil-pds
154Group=tranquil-pds
155EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env
156ExecStart=/usr/local/bin/tranquil-pds
157Restart=always
158RestartSec=5
159[Install]
160WantedBy=multi-user.target
161EOF
162
163systemctl daemon-reload
164systemctl enable tranquil-pds
165systemctl start tranquil-pds
166```
167
168## Install and Configure nginx
169
170```bash
171apt install -y nginx certbot python3-certbot-nginx
172
173cat > /etc/nginx/sites-available/tranquil-pds << 'EOF'
174server {
175 listen 80;
176 listen [::]:80;
177 server_name pds.example.com *.pds.example.com;
178
179 location /.well-known/acme-challenge/ {
180 root /var/www/acme;
181 }
182
183 location / {
184 return 301 https://$host$request_uri;
185 }
186}
187
188server {
189 listen 443 ssl;
190 listen [::]:443 ssl;
191 http2 on;
192 server_name pds.example.com *.pds.example.com;
193
194 ssl_certificate /etc/letsencrypt/live/pds.example.com/fullchain.pem;
195 ssl_certificate_key /etc/letsencrypt/live/pds.example.com/privkey.pem;
196
197 client_max_body_size 10G;
198
199 root /var/www/tranquil-pds;
200
201 location /xrpc/ {
202 proxy_pass http://127.0.0.1:3000;
203 proxy_http_version 1.1;
204 proxy_set_header Upgrade $http_upgrade;
205 proxy_set_header Connection "upgrade";
206 proxy_set_header Host $host;
207 proxy_set_header X-Real-IP $remote_addr;
208 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
209 proxy_set_header X-Forwarded-Proto $scheme;
210 proxy_read_timeout 86400;
211 proxy_send_timeout 86400;
212 proxy_buffering off;
213 proxy_request_buffering off;
214 }
215
216 location /oauth/ {
217 proxy_pass http://127.0.0.1:3000;
218 proxy_http_version 1.1;
219 proxy_set_header Host $host;
220 proxy_set_header X-Real-IP $remote_addr;
221 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
222 proxy_set_header X-Forwarded-Proto $scheme;
223 proxy_read_timeout 300;
224 proxy_send_timeout 300;
225 }
226
227 location /.well-known/ {
228 proxy_pass http://127.0.0.1:3000;
229 proxy_http_version 1.1;
230 proxy_set_header Host $host;
231 proxy_set_header X-Real-IP $remote_addr;
232 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
233 proxy_set_header X-Forwarded-Proto $scheme;
234 }
235
236 location = /metrics {
237 proxy_pass http://127.0.0.1:3000;
238 proxy_http_version 1.1;
239 proxy_set_header Host $host;
240 }
241
242 location = /health {
243 proxy_pass http://127.0.0.1:3000;
244 proxy_http_version 1.1;
245 proxy_set_header Host $host;
246 }
247
248 location = /robots.txt {
249 proxy_pass http://127.0.0.1:3000;
250 proxy_http_version 1.1;
251 proxy_set_header Host $host;
252 }
253
254 location = /logo {
255 proxy_pass http://127.0.0.1:3000;
256 proxy_http_version 1.1;
257 proxy_set_header Host $host;
258 }
259
260 location ~ ^/u/[^/]+/did\.json$ {
261 proxy_pass http://127.0.0.1:3000;
262 proxy_http_version 1.1;
263 proxy_set_header Host $host;
264 proxy_set_header X-Real-IP $remote_addr;
265 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
266 proxy_set_header X-Forwarded-Proto $scheme;
267 }
268
269 location /assets/ {
270 expires 1y;
271 add_header Cache-Control "public, immutable";
272 try_files $uri =404;
273 }
274
275 location /app/ {
276 try_files $uri $uri/ /index.html;
277 }
278
279 location = / {
280 try_files /homepage.html /index.html;
281 }
282
283 location / {
284 try_files $uri $uri/ /index.html;
285 }
286}
287EOF
288
289ln -sf /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/
290rm -f /etc/nginx/sites-enabled/default
291mkdir -p /var/www/acme
292nginx -t
293systemctl reload nginx
294```
295
296## Obtain Wildcard SSL Certificate
297
298User handles are served as subdomains (eg., `alice.pds.example.com`), so you need a wildcard certificate.
299
300Wildcard certs require DNS-01 validation. If your DNS provider has a certbot plugin:
301```bash
302apt install -y python3-certbot-dns-cloudflare
303certbot certonly --dns-cloudflare \
304 --dns-cloudflare-credentials /etc/cloudflare.ini \
305 -d pds.example.com -d '*.pds.example.com'
306```
307
308For manual DNS validation (works with any provider):
309```bash
310certbot certonly --manual --preferred-challenges dns \
311 -d pds.example.com -d '*.pds.example.com'
312```
313
314Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew.
315
316After obtaining the cert, reload nginx:
317```bash
318systemctl reload nginx
319```
320
321## Configure Firewall
322
323```bash
324apt install -y ufw
325ufw allow ssh
326ufw allow 80/tcp
327ufw allow 443/tcp
328ufw enable
329```
330
331## Verify Installation
332
333```bash
334systemctl status tranquil-pds
335curl -s https://pds.example.com/xrpc/_health | jq
336curl -s https://pds.example.com/.well-known/atproto-did
337```
338
339## Maintenance
340
341View logs:
342```bash
343journalctl -u tranquil-pds -f
344```
345
346Update Tranquil PDS:
347```bash
348cd /opt/tranquil-pds
349git pull
350cd frontend && deno task build && cd ..
351cargo build --release
352systemctl stop tranquil-pds
353cp target/release/tranquil-pds /usr/local/bin/
354cp -r frontend/dist/* /var/www/tranquil-pds/
355DATABASE_URL="postgres://tranquil_pds:your-secure-password@localhost:5432/pds" sqlx migrate run
356systemctl start tranquil-pds
357```
358
359Backup database:
360```bash
361sudo -u postgres pg_dump pds > /var/backups/pds-$(date +%Y%m%d).sql
362```
363
364## Custom Homepage
365
366Drop a `homepage.html` in `/var/www/tranquil-pds/` and it becomes your landing page. Account dashboard is at `/app/` so you won't break anything.
367
368```bash
369cat > /var/www/tranquil-pds/homepage.html << 'EOF'
370<!DOCTYPE html>
371<html>
372<head>
373 <title>Welcome to my PDS</title>
374 <style>
375 body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; }
376 </style>
377</head>
378<body>
379 <h1>Welcome to my secret PDS</h1>
380 <p>This is a <a href="https://atproto.com">AT Protocol</a> Personal Data Server.</p>
381 <p><a href="/app/">Sign in</a> or learn more at <a href="https://bsky.social">Bluesky</a>.</p>
382</body>
383</html>
384EOF
385```