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