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