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