this repo has no description
1#!/bin/bash
2set -euo pipefail
3
4RED='\033[0;31m'
5GREEN='\033[0;32m'
6YELLOW='\033[1;33m'
7BLUE='\033[0;34m'
8NC='\033[0m'
9
10log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
11log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
12log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
13log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
14
15if [[ $EUID -ne 0 ]]; then
16 log_error "This script must be run as root"
17 exit 1
18fi
19
20if ! grep -qi "debian" /etc/os-release 2>/dev/null; then
21 log_warn "This script is designed for Debian. Proceed with caution on other distros."
22fi
23
24nuke_installation() {
25 log_warn "NUKING EXISTING INSTALLATION"
26 log_info "Stopping services..."
27 systemctl stop tranquil-pds 2>/dev/null || true
28 systemctl disable tranquil-pds 2>/dev/null || true
29
30 log_info "Removing Tranquil PDS files..."
31 rm -rf /opt/tranquil-pds
32 rm -rf /var/lib/tranquil-pds
33 rm -f /usr/local/bin/tranquil-pds
34 rm -f /usr/local/bin/tranquil-pds-sendmail
35 rm -f /usr/local/bin/tranquil-pds-mailq
36 rm -rf /var/spool/tranquil-pds-mail
37 rm -f /etc/systemd/system/tranquil-pds.service
38 systemctl daemon-reload
39
40 log_info "Removing Tranquil PDS configuration..."
41 rm -rf /etc/tranquil-pds
42
43 log_info "Dropping postgres database and user..."
44 sudo -u postgres psql -c "DROP DATABASE IF EXISTS pds;" 2>/dev/null || true
45 sudo -u postgres psql -c "DROP USER IF EXISTS tranquil_pds;" 2>/dev/null || true
46
47 log_info "Removing minio bucket..."
48 if command -v mc &>/dev/null; then
49 mc rb local/pds-blobs --force 2>/dev/null || true
50 mc alias remove local 2>/dev/null || true
51 fi
52 systemctl stop minio 2>/dev/null || true
53 rm -rf /var/lib/minio/data/.minio.sys 2>/dev/null || true
54 rm -f /etc/default/minio 2>/dev/null || true
55
56 log_info "Removing nginx config..."
57 rm -f /etc/nginx/sites-enabled/tranquil-pds
58 rm -f /etc/nginx/sites-available/tranquil-pds
59 systemctl reload nginx 2>/dev/null || true
60
61 log_success "Previous installation nuked"
62}
63
64if [[ -f /etc/tranquil-pds/tranquil-pds.env ]] || [[ -d /opt/tranquil-pds ]] || [[ -f /usr/local/bin/tranquil-pds ]]; then
65 log_warn "Existing installation detected"
66 echo ""
67 echo "Options:"
68 echo " 1) Nuke everything and start fresh (destroys database!)"
69 echo " 2) Continue with existing installation (idempotent update)"
70 echo " 3) Exit"
71 echo ""
72 read -p "Choose an option [1/2/3]: " INSTALL_CHOICE
73
74 case "$INSTALL_CHOICE" in
75 1)
76 echo ""
77 log_warn "This will DELETE:"
78 echo " - PostgreSQL database 'pds' and all data"
79 echo " - All Tranquil PDS configuration and credentials"
80 echo " - All source code in /opt/tranquil-pds"
81 echo " - MinIO bucket 'pds-blobs' and all blobs"
82 echo ""
83 read -p "Type 'NUKE' to confirm: " CONFIRM_NUKE
84 if [[ "$CONFIRM_NUKE" == "NUKE" ]]; then
85 nuke_installation
86 else
87 log_error "Nuke cancelled"
88 exit 1
89 fi
90 ;;
91 2)
92 log_info "Continuing with existing installation..."
93 ;;
94 3)
95 exit 0
96 ;;
97 *)
98 log_error "Invalid option"
99 exit 1
100 ;;
101 esac
102fi
103
104echo ""
105log_info "Tranquil PDS Installation Script for Debian"
106echo ""
107
108get_public_ips() {
109 IPV4=$(curl -4 -s --max-time 5 ifconfig.me 2>/dev/null || curl -4 -s --max-time 5 icanhazip.com 2>/dev/null || echo "Could not detect")
110 IPV6=$(curl -6 -s --max-time 5 ifconfig.me 2>/dev/null || curl -6 -s --max-time 5 icanhazip.com 2>/dev/null || echo "")
111}
112
113log_info "Detecting public IP addresses..."
114get_public_ips
115echo " IPv4: ${IPV4}"
116[[ -n "$IPV6" ]] && echo " IPv6: ${IPV6}"
117echo ""
118
119read -p "Enter your PDS domain (e.g., pds.example.com): " PDS_DOMAIN
120if [[ -z "$PDS_DOMAIN" ]]; then
121 log_error "Domain cannot be empty"
122 exit 1
123fi
124
125read -p "Enter your email for Let's Encrypt: " CERTBOT_EMAIL
126if [[ -z "$CERTBOT_EMAIL" ]]; then
127 log_error "Email cannot be empty"
128 exit 1
129fi
130
131echo ""
132log_info "DNS records required (create these now if you haven't):"
133echo ""
134echo " ${PDS_DOMAIN} A ${IPV4}"
135[[ -n "$IPV6" ]] && echo " ${PDS_DOMAIN} AAAA ${IPV6}"
136echo " *.${PDS_DOMAIN} A ${IPV4} (for user handles)"
137[[ -n "$IPV6" ]] && echo " *.${PDS_DOMAIN} AAAA ${IPV6} (for user handles)"
138echo ""
139read -p "Have you created these DNS records? (y/N): " DNS_CONFIRMED
140if [[ ! "$DNS_CONFIRMED" =~ ^[Yy]$ ]]; then
141 log_warn "Please create the DNS records and run this script again."
142 exit 0
143fi
144
145CREDENTIALS_FILE="/etc/tranquil-pds/.credentials"
146if [[ -f "$CREDENTIALS_FILE" ]]; then
147 log_info "Loading existing credentials..."
148 source "$CREDENTIALS_FILE"
149else
150 log_info "Generating secrets..."
151 JWT_SECRET=$(openssl rand -base64 48)
152 DPOP_SECRET=$(openssl rand -base64 48)
153 MASTER_KEY=$(openssl rand -base64 48)
154 DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
155 MINIO_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
156
157 mkdir -p /etc/tranquil-pds
158 cat > "$CREDENTIALS_FILE" << EOF
159JWT_SECRET="$JWT_SECRET"
160DPOP_SECRET="$DPOP_SECRET"
161MASTER_KEY="$MASTER_KEY"
162DB_PASSWORD="$DB_PASSWORD"
163MINIO_PASSWORD="$MINIO_PASSWORD"
164EOF
165 chmod 600 "$CREDENTIALS_FILE"
166 log_success "Secrets generated"
167fi
168
169log_info "Checking swap space..."
170TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
171TOTAL_SWAP_KB=$(grep SwapTotal /proc/meminfo | awk '{print $2}')
172
173if [[ $TOTAL_SWAP_KB -lt 2000000 ]]; then
174 if [[ ! -f /swapfile ]]; then
175 log_info "Adding swap space for compilation..."
176 SWAP_SIZE="4G"
177 [[ $TOTAL_MEM_KB -ge 4000000 ]] && SWAP_SIZE="2G"
178 fallocate -l $SWAP_SIZE /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=4096
179 chmod 600 /swapfile
180 mkswap /swapfile
181 swapon /swapfile
182 grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
183 log_success "Swap added ($SWAP_SIZE)"
184 else
185 swapon /swapfile 2>/dev/null || true
186 fi
187fi
188
189log_info "Updating system packages..."
190apt update && apt upgrade -y
191
192log_info "Installing build dependencies..."
193apt install -y curl git build-essential pkg-config libssl-dev ca-certificates gnupg lsb-release unzip xxd
194
195log_info "Installing postgres..."
196apt install -y postgresql postgresql-contrib
197systemctl enable postgresql
198systemctl start postgresql
199sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD '${DB_PASSWORD}';" 2>/dev/null || \
200 sudo -u postgres psql -c "ALTER USER tranquil_pds WITH PASSWORD '${DB_PASSWORD}';"
201sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;" 2>/dev/null || true
202sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;"
203log_success "postgres configured"
204
205log_info "Installing valkey..."
206apt install -y valkey 2>/dev/null || {
207 log_warn "valkey not in repos, installing redis..."
208 apt install -y redis-server
209 systemctl enable redis-server
210 systemctl start redis-server
211}
212systemctl enable valkey-server 2>/dev/null || true
213systemctl start valkey-server 2>/dev/null || true
214
215log_info "Installing minio..."
216if [[ ! -f /usr/local/bin/minio ]]; then
217 ARCH=$(dpkg --print-architecture)
218 case "$ARCH" in
219 amd64) curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-amd64/minio ;;
220 arm64) curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-arm64/minio ;;
221 *) log_error "Unsupported architecture: $ARCH"; exit 1 ;;
222 esac
223 chmod +x /tmp/minio
224 mv /tmp/minio /usr/local/bin/
225fi
226
227mkdir -p /var/lib/minio/data
228id -u minio-user &>/dev/null || useradd -r -s /sbin/nologin minio-user
229chown -R minio-user:minio-user /var/lib/minio
230
231cat > /etc/default/minio << EOF
232MINIO_ROOT_USER=minioadmin
233MINIO_ROOT_PASSWORD=${MINIO_PASSWORD}
234MINIO_VOLUMES="/var/lib/minio/data"
235MINIO_OPTS="--console-address :9001"
236EOF
237chmod 600 /etc/default/minio
238
239cat > /etc/systemd/system/minio.service << 'EOF'
240[Unit]
241Description=MinIO Object Storage
242After=network.target
243
244[Service]
245User=minio-user
246Group=minio-user
247EnvironmentFile=/etc/default/minio
248ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS
249Restart=always
250LimitNOFILE=65536
251
252[Install]
253WantedBy=multi-user.target
254EOF
255
256systemctl daemon-reload
257systemctl enable minio
258systemctl start minio
259log_success "minio installed"
260
261log_info "Waiting for minio..."
262sleep 5
263
264if [[ ! -f /usr/local/bin/mc ]]; then
265 ARCH=$(dpkg --print-architecture)
266 case "$ARCH" in
267 amd64) curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc ;;
268 arm64) curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-arm64/mc ;;
269 esac
270 chmod +x /tmp/mc
271 mv /tmp/mc /usr/local/bin/
272fi
273
274mc alias remove local 2>/dev/null || true
275mc alias set local http://localhost:9000 minioadmin "${MINIO_PASSWORD}" --api S3v4
276mc mb local/pds-blobs --ignore-existing
277log_success "minio bucket created"
278
279log_info "Installing rust..."
280if [[ -f "$HOME/.cargo/env" ]]; then
281 source "$HOME/.cargo/env"
282fi
283if ! command -v rustc &>/dev/null; then
284 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
285 source "$HOME/.cargo/env"
286fi
287
288log_info "Installing deno..."
289export PATH="$HOME/.deno/bin:$PATH"
290if ! command -v deno &>/dev/null && [[ ! -f "$HOME/.deno/bin/deno" ]]; then
291 curl -fsSL https://deno.land/install.sh | sh
292 grep -q 'deno/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
293fi
294
295log_info "Cloning Tranquil PDS..."
296if [[ ! -d /opt/tranquil-pds ]]; then
297 git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/tranquil-pds
298else
299 cd /opt/tranquil-pds && git pull
300fi
301cd /opt/tranquil-pds
302
303log_info "Building frontend..."
304"$HOME/.deno/bin/deno" task build --filter=frontend
305log_success "Frontend built"
306
307log_info "Building Tranquil PDS (this takes a while)..."
308source "$HOME/.cargo/env"
309if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then
310 log_info "Low memory - limiting parallel jobs"
311 CARGO_BUILD_JOBS=1 cargo build --release
312else
313 cargo build --release
314fi
315log_success "Tranquil PDS built"
316
317log_info "Running migrations..."
318cargo install sqlx-cli --no-default-features --features postgres
319export DATABASE_URL="postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds"
320"$HOME/.cargo/bin/sqlx" migrate run
321log_success "Migrations complete"
322
323log_info "Setting up mail trap..."
324mkdir -p /var/spool/tranquil-pds-mail
325chmod 1777 /var/spool/tranquil-pds-mail
326
327cat > /usr/local/bin/tranquil-pds-sendmail << 'SENDMAIL_EOF'
328#!/bin/bash
329MAIL_DIR="/var/spool/tranquil-pds-mail"
330TIMESTAMP=$(date +%Y%m%d-%H%M%S)
331RANDOM_ID=$(head -c 4 /dev/urandom | xxd -p)
332MAIL_FILE="${MAIL_DIR}/${TIMESTAMP}-${RANDOM_ID}.eml"
333mkdir -p "$MAIL_DIR"
334{
335 echo "X-Tranquil-PDS-Received: $(date -Iseconds)"
336 echo "X-Tranquil-PDS-Args: $*"
337 echo ""
338 cat
339} > "$MAIL_FILE"
340chmod 644 "$MAIL_FILE"
341exit 0
342SENDMAIL_EOF
343chmod +x /usr/local/bin/tranquil-pds-sendmail
344
345cat > /usr/local/bin/tranquil-pds-mailq << 'MAILQ_EOF'
346#!/bin/bash
347MAIL_DIR="/var/spool/tranquil-pds-mail"
348case "${1:-list}" in
349 list)
350 ls -lt "$MAIL_DIR"/*.eml 2>/dev/null | head -20 || echo "No emails"
351 ;;
352 latest)
353 f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | head -1)
354 [[ -f "$f" ]] && cat "$f" || echo "No emails"
355 ;;
356 clear)
357 rm -f "$MAIL_DIR"/*.eml
358 echo "Cleared"
359 ;;
360 count)
361 ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l
362 ;;
363 [0-9]*)
364 f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | sed -n "${1}p")
365 [[ -f "$f" ]] && cat "$f" || echo "Not found"
366 ;;
367 *)
368 [[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: tranquil-pds-mailq [list|latest|clear|count|N]"
369 ;;
370esac
371MAILQ_EOF
372chmod +x /usr/local/bin/tranquil-pds-mailq
373
374log_info "Creating Tranquil PDS configuration..."
375cat > /etc/tranquil-pds/tranquil-pds.env << EOF
376SERVER_HOST=127.0.0.1
377SERVER_PORT=3000
378PDS_HOSTNAME=${PDS_DOMAIN}
379DATABASE_URL=postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds
380DATABASE_MAX_CONNECTIONS=100
381DATABASE_MIN_CONNECTIONS=10
382S3_ENDPOINT=http://localhost:9000
383AWS_REGION=us-east-1
384S3_BUCKET=pds-blobs
385AWS_ACCESS_KEY_ID=minioadmin
386AWS_SECRET_ACCESS_KEY=${MINIO_PASSWORD}
387VALKEY_URL=redis://localhost:6379
388JWT_SECRET=${JWT_SECRET}
389DPOP_SECRET=${DPOP_SECRET}
390MASTER_KEY=${MASTER_KEY}
391PLC_DIRECTORY_URL=https://plc.directory
392CRAWLERS=https://bsky.network
393AVAILABLE_USER_DOMAINS=${PDS_DOMAIN}
394MAIL_FROM_ADDRESS=noreply@${PDS_DOMAIN}
395MAIL_FROM_NAME=Tranquil PDS
396SENDMAIL_PATH=/usr/local/bin/tranquil-pds-sendmail
397EOF
398chmod 600 /etc/tranquil-pds/tranquil-pds.env
399
400log_info "Installing Tranquil PDS..."
401id -u tranquil-pds &>/dev/null || useradd -r -s /sbin/nologin tranquil-pds
402cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/
403mkdir -p /var/lib/tranquil-pds
404cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend
405chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds
406
407cat > /etc/systemd/system/tranquil-pds.service << 'EOF'
408[Unit]
409Description=Tranquil PDS - AT Protocol PDS
410After=network.target postgresql.service minio.service
411
412[Service]
413Type=simple
414User=tranquil-pds
415Group=tranquil-pds
416EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env
417Environment=FRONTEND_DIR=/var/lib/tranquil-pds/frontend
418ExecStart=/usr/local/bin/tranquil-pds
419Restart=always
420RestartSec=5
421
422[Install]
423WantedBy=multi-user.target
424EOF
425
426systemctl daemon-reload
427systemctl enable tranquil-pds
428systemctl start tranquil-pds
429log_success "Tranquil PDS service started"
430
431log_info "Installing nginx..."
432apt install -y nginx
433cat > /etc/nginx/sites-available/tranquil-pds << EOF
434server {
435 listen 80;
436 listen [::]:80;
437 server_name ${PDS_DOMAIN} *.${PDS_DOMAIN};
438
439 location /.well-known/acme-challenge/ {
440 root /var/www/html;
441 }
442
443 location / {
444 proxy_pass http://127.0.0.1:3000;
445 proxy_http_version 1.1;
446 proxy_set_header Upgrade \$http_upgrade;
447 proxy_set_header Connection "upgrade";
448 proxy_set_header Host \$host;
449 proxy_set_header X-Real-IP \$remote_addr;
450 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
451 proxy_set_header X-Forwarded-Proto \$scheme;
452 proxy_read_timeout 86400;
453 proxy_send_timeout 86400;
454 client_max_body_size 100M;
455 }
456}
457EOF
458
459ln -sf /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/
460rm -f /etc/nginx/sites-enabled/default
461nginx -t
462systemctl reload nginx
463log_success "nginx configured"
464
465log_info "Configuring firewall..."
466apt install -y ufw
467ufw --force reset
468ufw default deny incoming
469ufw default allow outgoing
470ufw allow ssh
471ufw allow 80/tcp
472ufw allow 443/tcp
473ufw --force enable
474log_success "Firewall configured"
475
476echo ""
477log_info "Obtaining wildcard SSL certificate..."
478echo ""
479echo "User handles are served as subdomains (e.g., alice.${PDS_DOMAIN}),"
480echo "so you need a wildcard certificate. This requires DNS validation."
481echo ""
482echo "You'll need to add a TXT record to your DNS when prompted."
483echo ""
484read -p "Ready to proceed? (y/N): " CERT_READY
485
486if [[ "$CERT_READY" =~ ^[Yy]$ ]]; then
487 apt install -y certbot python3-certbot-nginx
488
489 log_info "Running certbot with DNS challenge..."
490 echo ""
491 echo "When prompted, add the TXT record to your DNS, wait a minute"
492 echo "for propagation, then press Enter to continue."
493 echo ""
494
495 if certbot certonly --manual --preferred-challenges dns \
496 -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" \
497 --email "${CERTBOT_EMAIL}" --agree-tos; then
498
499 cat > /etc/nginx/sites-available/tranquil-pds << EOF
500server {
501 listen 80;
502 listen [::]:80;
503 server_name ${PDS_DOMAIN} *.${PDS_DOMAIN};
504
505 location /.well-known/acme-challenge/ {
506 root /var/www/html;
507 }
508
509 location / {
510 return 301 https://\$host\$request_uri;
511 }
512}
513
514server {
515 listen 443 ssl http2;
516 listen [::]:443 ssl http2;
517 server_name ${PDS_DOMAIN} *.${PDS_DOMAIN};
518
519 ssl_certificate /etc/letsencrypt/live/${PDS_DOMAIN}/fullchain.pem;
520 ssl_certificate_key /etc/letsencrypt/live/${PDS_DOMAIN}/privkey.pem;
521 ssl_protocols TLSv1.2 TLSv1.3;
522 ssl_ciphers HIGH:!aNULL:!MD5;
523 ssl_prefer_server_ciphers on;
524 ssl_session_cache shared:SSL:10m;
525
526 location / {
527 proxy_pass http://127.0.0.1:3000;
528 proxy_http_version 1.1;
529 proxy_set_header Upgrade \$http_upgrade;
530 proxy_set_header Connection "upgrade";
531 proxy_set_header Host \$host;
532 proxy_set_header X-Real-IP \$remote_addr;
533 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
534 proxy_set_header X-Forwarded-Proto \$scheme;
535 proxy_read_timeout 86400;
536 proxy_send_timeout 86400;
537 client_max_body_size 100M;
538 }
539}
540EOF
541 nginx -t && systemctl reload nginx
542 log_success "Wildcard SSL certificate installed"
543
544 echo ""
545 log_warn "Certificate renewal note:"
546 echo "Manual DNS challenges don't auto-renew. Before expiry, run:"
547 echo " certbot renew --manual"
548 echo ""
549 echo "For auto-renewal, consider using a DNS provider plugin:"
550 echo " apt install python3-certbot-dns-cloudflare # or your provider"
551 echo ""
552 else
553 log_warn "Wildcard cert failed. You can retry later with:"
554 echo " certbot certonly --manual --preferred-challenges dns \\"
555 echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'"
556 fi
557else
558 log_warn "Skipping SSL. Your PDS is running on HTTP only."
559 echo "To add SSL later, run:"
560 echo " certbot certonly --manual --preferred-challenges dns \\"
561 echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'"
562fi
563
564log_info "Verifying installation..."
565sleep 3
566if curl -s "http://localhost:3000/xrpc/_health" | grep -q "version"; then
567 log_success "Tranquil PDS is responding"
568else
569 log_warn "Tranquil PDS may still be starting. Check: journalctl -u tranquil-pds -f"
570fi
571
572echo ""
573log_success "Installation complete"
574echo ""
575echo "PDS: https://${PDS_DOMAIN}"
576echo ""
577echo "Credentials (also in /etc/tranquil-pds/.credentials):"
578echo " DB password: ${DB_PASSWORD}"
579echo " MinIO password: ${MINIO_PASSWORD}"
580echo ""
581echo "Commands:"
582echo " journalctl -u tranquil-pds -f # logs"
583echo " systemctl restart tranquil-pds # restart"
584echo " tranquil-pds-mailq # view trapped emails"
585echo ""