Our Personal Data Server from scratch!
at main 355 lines 8.8 kB view raw view rendered
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```