Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
238
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(deploy): container-first cleanup, drop debian malware-style install

Lewis: May this revision serve well! <lu5a@proton.me>

authored by did:plc:mb5to35neicxt4gemstoro… and committed by

Tangled 75b9e316 ccc99161

+17 -920
-1
Dockerfile
··· 50 50 && ln -sf /usr/bin/msmtp /usr/sbin/sendmail 51 51 COPY --from=builder /tmp/tranquil-pds /usr/local/bin/tranquil-pds 52 52 COPY --from=frontend /app/dist /var/lib/tranquil-pds/frontend 53 - COPY migrations /app/migrations 54 53 WORKDIR /app 55 54 ENV SERVER_HOST=0.0.0.0 56 55 ENV SERVER_PORT=3000
-1
README.md
··· 62 62 ### Installation Guides 63 63 64 64 - [Nix](docs/install-nix.md) 65 - - [Debian](docs/install-debian.md) 66 65 - [Containers](docs/install-containers.md) 67 66 - [Kubernetes](docs/install-kubernetes.md) 68 67
+3 -2
crates/tranquil-config/src/lib.rs
··· 83 83 /// 84 84 /// Precedence (highest to lowest): 85 85 /// 1. Environment variables 86 - /// 2. TOML config file (if provided) 87 - /// 3. Built-in defaults 86 + /// 2. Toml config file passed as `config_path`, if provided 87 + /// 3. `/etc/tranquil-pds/config.toml` - hardcoded fallback, silently skipped if absent 88 + /// 4. Built-in defaults 88 89 pub fn load(config_path: Option<&PathBuf>) -> Result<TranquilConfig, confique::Error> { 89 90 let mut builder = TranquilConfig::builder().env(); 90 91 if let Some(path) = config_path {
-24
crates/tranquil-pds/tests/common/mod.rs
··· 191 191 async fn setup_with_testcontainers() -> String { 192 192 let temp_dir = std::env::temp_dir().join(format!("tranquil-pds-test-{}", uuid::Uuid::new_v4())); 193 193 let blob_path = temp_dir.join("blobs"); 194 - let backup_path = temp_dir.join("backups"); 195 194 std::fs::create_dir_all(&blob_path).expect("Failed to create blob temp directory"); 196 - std::fs::create_dir_all(&backup_path).expect("Failed to create backup temp directory"); 197 195 TEST_TEMP_DIR.set(temp_dir).ok(); 198 196 let plc_url = setup_mock_plc_directory().await; 199 197 unsafe { 200 198 std::env::set_var("BLOB_STORAGE_BACKEND", "filesystem"); 201 199 std::env::set_var("BLOB_STORAGE_PATH", blob_path.to_str().unwrap()); 202 - std::env::set_var("BACKUP_STORAGE_BACKEND", "filesystem"); 203 - std::env::set_var("BACKUP_STORAGE_PATH", backup_path.to_str().unwrap()); 204 200 std::env::set_var("MAX_IMPORT_SIZE", "100000000"); 205 201 std::env::set_var("SKIP_IMPORT_VERIFICATION", "true"); 206 202 std::env::set_var("PLC_DIRECTORY_URL", &plc_url); ··· 242 238 let plc_url = setup_mock_plc_directory().await; 243 239 unsafe { 244 240 std::env::set_var("BLOB_STORAGE_BACKEND", "s3"); 245 - std::env::set_var("BACKUP_STORAGE_BACKEND", "s3"); 246 - std::env::set_var("BACKUP_S3_BUCKET", "test-backups"); 247 241 std::env::set_var("S3_BUCKET", "test-bucket"); 248 242 std::env::set_var("AWS_ACCESS_KEY_ID", "minioadmin"); 249 243 std::env::set_var("AWS_SECRET_ACCESS_KEY", "minioadmin"); ··· 333 327 if std::env::var("S3_ENDPOINT").is_ok() { 334 328 let s3_endpoint = std::env::var("S3_ENDPOINT").unwrap(); 335 329 std::env::set_var("BLOB_STORAGE_BACKEND", "s3"); 336 - std::env::set_var("BACKUP_STORAGE_BACKEND", "s3"); 337 - std::env::set_var("BACKUP_S3_BUCKET", "test-backups"); 338 330 std::env::set_var( 339 331 "S3_BUCKET", 340 332 std::env::var("S3_BUCKET").unwrap_or_else(|_| "test-bucket".to_string()), ··· 356 348 let process_dir = 357 349 std::env::temp_dir().join(format!("tranquil-pds-test-{}", std::process::id())); 358 350 let blob_path = process_dir.join("blobs"); 359 - let backup_path = process_dir.join("backups"); 360 351 std::fs::create_dir_all(&blob_path).expect("Failed to create blob directory"); 361 - std::fs::create_dir_all(&backup_path).expect("Failed to create backup directory"); 362 352 TEST_TEMP_DIR.set(process_dir).ok(); 363 353 std::env::set_var("BLOB_STORAGE_BACKEND", "filesystem"); 364 354 std::env::set_var("BLOB_STORAGE_PATH", blob_path.to_str().unwrap()); 365 - std::env::set_var("BACKUP_STORAGE_BACKEND", "filesystem"); 366 - std::env::set_var("BACKUP_STORAGE_PATH", backup_path.to_str().unwrap()); 367 355 } 368 356 std::env::set_var("MAX_IMPORT_SIZE", "100000000"); 369 357 std::env::set_var("SKIP_IMPORT_VERIFICATION", "true"); ··· 622 610 let temp_dir = 623 611 std::env::temp_dir().join(format!("tranquil-pds-store-{}", uuid::Uuid::new_v4())); 624 612 let blob_path = temp_dir.join("blobs"); 625 - let backup_path = temp_dir.join("backups"); 626 613 let store_path = temp_dir.join("store"); 627 614 std::fs::create_dir_all(&blob_path).expect("failed to create blob temp directory"); 628 - std::fs::create_dir_all(&backup_path).expect("failed to create backup temp directory"); 629 615 std::fs::create_dir_all(&store_path).expect("failed to create store temp directory"); 630 616 TEST_TEMP_DIR.set(temp_dir).ok(); 631 617 let plc_url = setup_mock_plc_directory().await; 632 618 unsafe { 633 619 std::env::set_var("BLOB_STORAGE_BACKEND", "filesystem"); 634 620 std::env::set_var("BLOB_STORAGE_PATH", blob_path.to_str().unwrap()); 635 - std::env::set_var("BACKUP_STORAGE_BACKEND", "filesystem"); 636 - std::env::set_var("BACKUP_STORAGE_PATH", backup_path.to_str().unwrap()); 637 621 std::env::set_var("MAX_IMPORT_SIZE", "100000000"); 638 622 std::env::set_var("SKIP_IMPORT_VERIFICATION", "true"); 639 623 std::env::set_var("PLC_DIRECTORY_URL", &plc_url); ··· 790 774 uuid::Uuid::new_v4() 791 775 )); 792 776 let blob_path = temp_dir.join("blobs"); 793 - let backup_path = temp_dir.join("backups"); 794 777 let store_path = temp_dir.join("store"); 795 778 std::fs::create_dir_all(&blob_path).expect("failed to create blob temp directory"); 796 - std::fs::create_dir_all(&backup_path).expect("failed to create backup temp directory"); 797 779 std::fs::create_dir_all(&store_path).expect("failed to create store temp directory"); 798 780 TEST_TEMP_DIR.set(temp_dir).ok(); 799 781 let plc_url = setup_mock_plc_directory().await; 800 782 unsafe { 801 783 std::env::set_var("BLOB_STORAGE_BACKEND", "filesystem"); 802 784 std::env::set_var("BLOB_STORAGE_PATH", blob_path.to_str().unwrap()); 803 - std::env::set_var("BACKUP_STORAGE_BACKEND", "filesystem"); 804 - std::env::set_var("BACKUP_STORAGE_PATH", backup_path.to_str().unwrap()); 805 785 std::env::set_var("MAX_IMPORT_SIZE", "100000000"); 806 786 std::env::set_var("SKIP_IMPORT_VERIFICATION", "true"); 807 787 std::env::set_var("PLC_DIRECTORY_URL", &plc_url); ··· 847 827 let temp_dir = 848 828 std::env::temp_dir().join(format!("tranquil-pds-cluster-{}", uuid::Uuid::new_v4())); 849 829 let blob_path = temp_dir.join("blobs"); 850 - let backup_path = temp_dir.join("backups"); 851 830 std::fs::create_dir_all(&blob_path).expect("Failed to create blob temp directory"); 852 - std::fs::create_dir_all(&backup_path).expect("Failed to create backup temp directory"); 853 831 TEST_TEMP_DIR.set(temp_dir).ok(); 854 832 let plc_url = setup_mock_plc_directory().await; 855 833 unsafe { 856 834 std::env::set_var("BLOB_STORAGE_BACKEND", "filesystem"); 857 835 std::env::set_var("BLOB_STORAGE_PATH", blob_path.to_str().unwrap()); 858 - std::env::set_var("BACKUP_STORAGE_BACKEND", "filesystem"); 859 - std::env::set_var("BACKUP_STORAGE_PATH", backup_path.to_str().unwrap()); 860 836 std::env::set_var("MAX_IMPORT_SIZE", "100000000"); 861 837 std::env::set_var("SKIP_IMPORT_VERIFICATION", "true"); 862 838 std::env::set_var("PLC_DIRECTORY_URL", &plc_url);
-1
crates/tranquil-pds/tests/handle_domains.rs
··· 8 8 9 9 fn set_handle_domain() { 10 10 unsafe { 11 - std::env::set_var("AVAILABLE_USER_DOMAINS", HANDLE_DOMAIN); 12 11 std::env::set_var("PDS_USER_HANDLE_DOMAINS", HANDLE_DOMAIN); 13 12 } 14 13 }
+3 -3
deploy/quadlets/tranquil-pds-app.container
··· 5 5 ContainerName=tranquil-pds-app 6 6 Image=localhost/tranquil-pds:latest 7 7 Pod=tranquil-pds.pod 8 - EnvironmentFile=/srv/tranquil-pds/config/tranquil-pds.env 9 8 Environment=SERVER_HOST=0.0.0.0 10 9 Environment=SERVER_PORT=3000 11 - Volume=/srv/tranquil-pds/blobs:/var/lib/tranquil/blobs:Z 12 - Volume=/srv/tranquil-pds/backups:/var/lib/tranquil/backups:Z 10 + Volume=/srv/tranquil-pds/config/config.toml:/etc/tranquil-pds/config.toml:ro,Z 11 + Volume=/srv/tranquil-pds/blobs:/var/lib/tranquil-pds/blobs:Z 12 + Volume=/srv/tranquil-pds/store:/var/lib/tranquil-pds/store:Z 13 13 HealthCmd=wget -q --spider http://localhost:3000/xrpc/_health 14 14 HealthInterval=30s 15 15 HealthTimeout=10s
+4 -1
docker-compose.prod.yaml
··· 9 9 SERVER_HOST: "0.0.0.0" 10 10 volumes: 11 11 - ./config.toml:/etc/tranquil-pds/config.toml:ro 12 - - blob_data:/var/lib/tranquil/blobs 12 + # In memory of @olaren.dev's blobs when lewis forgot to update /tranquil to /tranquil-pds :( 13 + - blob_data:/var/lib/tranquil-pds/blobs 14 + - store_data:/var/lib/tranquil-pds/store 13 15 depends_on: 14 16 db: 15 17 condition: service_healthy ··· 94 96 volumes: 95 97 postgres_data: 96 98 blob_data: 99 + store_data: 97 100 prometheus_data: 98 101 acme_challenge:
+3 -1
docker-compose.yaml
··· 10 10 DATABASE_URL: postgres://postgres:postgres@db:5432/pds 11 11 volumes: 12 12 - ./config.toml:/etc/tranquil-pds/config.toml:ro 13 - - blob_data:/var/lib/tranquil/blobs 13 + - blob_data:/var/lib/tranquil-pds/blobs 14 + - store_data:/var/lib/tranquil-pds/store 14 15 depends_on: 15 16 - db 16 17 ··· 51 52 volumes: 52 53 postgres_data: 53 54 blob_data: 55 + store_data: 54 56 prometheus_data:
+4 -4
docs/install-containers.md
··· 47 47 48 48 ## Standalone containers (no compose) 49 49 50 - If you already have postgres running on the host (eg. from the [Debian install guide](install-debian.md)), you can run just the app containers. 50 + If you already have postgres running on the host, you can run just the app containers. 51 51 52 52 Build the images: 53 53 ```sh ··· 60 60 podman run -d --name tranquil-pds \ 61 61 --network=host \ 62 62 -v /etc/tranquil-pds/config.toml:/etc/tranquil-pds/config.toml:ro,Z \ 63 - -v /var/lib/tranquil:/var/lib/tranquil:Z \ 63 + -v /var/lib/tranquil-pds:/var/lib/tranquil-pds:Z \ 64 64 tranquil-pds:latest 65 65 ``` 66 66 ··· 91 91 } 92 92 ``` 93 93 94 - See the [Debian install guide](install-debian.md) for the full nginx config with all API routes. 94 + See the Debian with systemd quadlets section below for the full nginx config with all API routes. 95 95 96 96 --- 97 97 ··· 110 110 111 111 ```bash 112 112 mkdir -p /etc/containers/systemd 113 - mkdir -p /srv/tranquil-pds/{postgres,blobs,certs,acme,config} 113 + mkdir -p /srv/tranquil-pds/{postgres,blobs,store,certs,acme,config} 114 114 ``` 115 115 116 116 ## Create a configuration file
-370
docs/install-debian.md
··· 1 - # Tranquil PDS production installation on debian 2 - 3 - This guide covers installing Tranquil PDS on Debian. 4 - 5 - It is a "compile the thing on the server itself" -style guide. 6 - This 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 19 - apt update && apt upgrade -y 20 - apt install -y curl git build-essential pkg-config libssl-dev 21 - ``` 22 - 23 - ## Install rust 24 - 25 - ```bash 26 - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 27 - source ~/.cargo/env 28 - rustup default stable 29 - ``` 30 - 31 - This installs the latest stable Rust. 32 - 33 - ## Install postgres 34 - 35 - ```bash 36 - apt install -y postgresql postgresql-contrib 37 - systemctl enable postgresql 38 - systemctl start postgresql 39 - sudo -u postgres psql -c "CREATE USER tranquil_pds WITH PASSWORD 'your-secure-password';" 40 - sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;" 41 - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" 42 - ``` 43 - 44 - ## Create blob storage directories 45 - 46 - ```bash 47 - mkdir -p /var/lib/tranquil/blobs 48 - ``` 49 - 50 - We'll set ownership after creating the service user. 51 - 52 - ## Install Node.js and pnpm (for frontend build) 53 - 54 - ```bash 55 - curl -fsSL https://deb.nodesource.com/setup_24.x | bash - 56 - apt install -y nodejs 57 - npm install -g pnpm 58 - ``` 59 - 60 - ## Clone and build Tranquil PDS 61 - 62 - ```bash 63 - cd /opt 64 - git clone https://tangled.org/tranquil.farm/tranquil-pds tranquil-pds 65 - cd tranquil-pds 66 - cd frontend 67 - pnpm install --frozen-lockfile 68 - pnpm build 69 - cd .. 70 - cargo build --release 71 - ``` 72 - 73 - ## Configure Tranquil PDS 74 - 75 - ```bash 76 - mkdir -p /etc/tranquil-pds 77 - cp /opt/tranquil-pds/example.toml /etc/tranquil-pds/config.toml 78 - chmod 600 /etc/tranquil-pds/config.toml 79 - ``` 80 - 81 - Edit `/etc/tranquil-pds/config.toml` and fill in your values. Generate secrets with: 82 - ```bash 83 - openssl rand -base64 48 84 - ``` 85 - 86 - > **Note:** Every config option can also be set via environment variables 87 - > (see comments in `example.toml`). Environment variables always take 88 - > precedence over the config file. You can also pass the config file path 89 - > via the `TRANQUIL_PDS_CONFIG` env var instead of `--config`. 90 - 91 - You can validate your configuration before starting the service: 92 - ```bash 93 - /usr/local/bin/tranquil-pds --config /etc/tranquil-pds/config.toml validate 94 - ``` 95 - 96 - ## Install frontend files 97 - 98 - ```bash 99 - mkdir -p /var/www/tranquil-pds 100 - cp -r /opt/tranquil-pds/frontend/dist/* /var/www/tranquil-pds/ 101 - chown -R www-data:www-data /var/www/tranquil-pds 102 - ``` 103 - 104 - ## Create systemd service 105 - 106 - ```bash 107 - useradd -r -s /sbin/nologin tranquil-pds 108 - chown -R tranquil-pds:tranquil-pds /var/lib/tranquil 109 - cp /opt/tranquil-pds/target/release/tranquil-pds /usr/local/bin/ 110 - 111 - cat > /etc/systemd/system/tranquil-pds.service << 'EOF' 112 - [Unit] 113 - Description=Tranquil PDS - AT Protocol PDS 114 - After=network.target postgresql.service 115 - [Service] 116 - Type=simple 117 - User=tranquil-pds 118 - Group=tranquil-pds 119 - ExecStart=/usr/local/bin/tranquil-pds --config /etc/tranquil-pds/config.toml 120 - Restart=always 121 - RestartSec=5 122 - ProtectSystem=strict 123 - ProtectHome=true 124 - PrivateTmp=true 125 - ReadWritePaths=/var/lib/tranquil 126 - [Install] 127 - WantedBy=multi-user.target 128 - EOF 129 - 130 - systemctl daemon-reload 131 - systemctl enable tranquil-pds 132 - systemctl start tranquil-pds 133 - ``` 134 - 135 - ## Install and configure nginx 136 - 137 - ```bash 138 - apt install -y nginx certbot python3-certbot-nginx 139 - 140 - cat > /etc/nginx/sites-available/tranquil-pds << 'EOF' 141 - server { 142 - listen 80; 143 - listen [::]:80; 144 - server_name pds.example.com *.pds.example.com; 145 - 146 - location /.well-known/acme-challenge/ { 147 - root /var/www/acme; 148 - } 149 - 150 - location / { 151 - return 301 https://$host$request_uri; 152 - } 153 - } 154 - 155 - server { 156 - listen 443 ssl; 157 - listen [::]:443 ssl; 158 - http2 on; 159 - server_name pds.example.com *.pds.example.com; 160 - 161 - ssl_certificate /etc/letsencrypt/live/pds.example.com/fullchain.pem; 162 - ssl_certificate_key /etc/letsencrypt/live/pds.example.com/privkey.pem; 163 - 164 - client_max_body_size 10G; 165 - 166 - root /var/www/tranquil-pds; 167 - 168 - location /xrpc/ { 169 - proxy_pass http://127.0.0.1:3000; 170 - proxy_http_version 1.1; 171 - proxy_set_header Upgrade $http_upgrade; 172 - proxy_set_header Connection "upgrade"; 173 - proxy_set_header Host $host; 174 - proxy_set_header X-Real-IP $remote_addr; 175 - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 176 - proxy_set_header X-Forwarded-Proto $scheme; 177 - proxy_read_timeout 86400; 178 - proxy_send_timeout 86400; 179 - proxy_buffering off; 180 - proxy_request_buffering off; 181 - } 182 - 183 - location = /oauth-client-metadata.json { 184 - root /var/www/tranquil-pds; 185 - default_type application/json; 186 - sub_filter_once off; 187 - sub_filter_types application/json; 188 - sub_filter '__PDS_HOSTNAME__' $host; 189 - } 190 - 191 - location /oauth/ { 192 - proxy_pass http://127.0.0.1:3000; 193 - proxy_http_version 1.1; 194 - proxy_set_header Host $host; 195 - proxy_set_header X-Real-IP $remote_addr; 196 - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 197 - proxy_set_header X-Forwarded-Proto $scheme; 198 - proxy_read_timeout 300; 199 - proxy_send_timeout 300; 200 - } 201 - 202 - location /.well-known/ { 203 - proxy_pass http://127.0.0.1:3000; 204 - proxy_http_version 1.1; 205 - proxy_set_header Host $host; 206 - proxy_set_header X-Real-IP $remote_addr; 207 - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 208 - proxy_set_header X-Forwarded-Proto $scheme; 209 - } 210 - 211 - location /webhook/ { 212 - proxy_pass http://127.0.0.1:3000; 213 - proxy_http_version 1.1; 214 - proxy_set_header Host $host; 215 - proxy_set_header X-Real-IP $remote_addr; 216 - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 217 - proxy_set_header X-Forwarded-Proto $scheme; 218 - } 219 - 220 - location = /metrics { 221 - proxy_pass http://127.0.0.1:3000; 222 - proxy_http_version 1.1; 223 - proxy_set_header Host $host; 224 - } 225 - 226 - location = /health { 227 - proxy_pass http://127.0.0.1:3000; 228 - proxy_http_version 1.1; 229 - proxy_set_header Host $host; 230 - } 231 - 232 - location = /robots.txt { 233 - proxy_pass http://127.0.0.1:3000; 234 - proxy_http_version 1.1; 235 - proxy_set_header Host $host; 236 - } 237 - 238 - location = /logo { 239 - proxy_pass http://127.0.0.1:3000; 240 - proxy_http_version 1.1; 241 - proxy_set_header Host $host; 242 - } 243 - 244 - location ~ ^/u/[^/]+/did\.json$ { 245 - proxy_pass http://127.0.0.1:3000; 246 - proxy_http_version 1.1; 247 - proxy_set_header Host $host; 248 - proxy_set_header X-Real-IP $remote_addr; 249 - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 250 - proxy_set_header X-Forwarded-Proto $scheme; 251 - } 252 - 253 - location /assets/ { 254 - expires 1y; 255 - add_header Cache-Control "public, immutable"; 256 - try_files $uri =404; 257 - } 258 - 259 - location /app/ { 260 - try_files $uri $uri/ /index.html; 261 - } 262 - 263 - location = / { 264 - try_files /homepage.html /index.html; 265 - } 266 - 267 - location / { 268 - try_files $uri $uri/ /index.html; 269 - } 270 - } 271 - EOF 272 - 273 - ln -sf /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/ 274 - rm -f /etc/nginx/sites-enabled/default 275 - mkdir -p /var/www/acme 276 - nginx -t 277 - systemctl reload nginx 278 - ``` 279 - 280 - ## Obtain a wildcard SSL cert 281 - 282 - User handles are served as subdomains (eg., `alice.pds.example.com`), so you need a wildcard certificate. 283 - 284 - Wildcard certs require DNS-01 validation. If your DNS provider has a certbot plugin: 285 - ```bash 286 - apt install -y python3-certbot-dns-cloudflare 287 - certbot certonly --dns-cloudflare \ 288 - --dns-cloudflare-credentials /etc/cloudflare.ini \ 289 - -d pds.example.com -d '*.pds.example.com' 290 - ``` 291 - 292 - For manual DNS validation (works with any provider): 293 - ```bash 294 - certbot certonly --manual --preferred-challenges dns \ 295 - -d pds.example.com -d '*.pds.example.com' 296 - ``` 297 - 298 - Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. 299 - 300 - After obtaining the cert, reload nginx: 301 - ```bash 302 - systemctl reload nginx 303 - ``` 304 - 305 - ## Configure firewall if you're into that sort of thing 306 - 307 - ```bash 308 - apt install -y ufw 309 - ufw allow ssh 310 - ufw allow 80/tcp 311 - ufw allow 443/tcp 312 - ufw enable 313 - ``` 314 - 315 - ## Verify installation 316 - 317 - ```bash 318 - systemctl status tranquil-pds 319 - curl -s https://pds.example.com/xrpc/_health | jq 320 - curl -s https://pds.example.com/.well-known/atproto-did 321 - ``` 322 - 323 - ## Maintenance 324 - 325 - View logs: 326 - ```bash 327 - journalctl -u tranquil-pds -f 328 - ``` 329 - 330 - Update Tranquil PDS: 331 - ```bash 332 - cd /opt/tranquil-pds 333 - git pull 334 - cd frontend && pnpm install --frozen-lockfile && pnpm build && cd .. 335 - cargo build --release 336 - systemctl stop tranquil-pds 337 - cp target/release/tranquil-pds /usr/local/bin/ 338 - cp -r frontend/dist/* /var/www/tranquil-pds/ 339 - systemctl start tranquil-pds 340 - ``` 341 - 342 - Tranquil should auto-migrate if there are any new migrations to be applied to the db, so you don't need to worry. 343 - 344 - Backup database: 345 - ```bash 346 - sudo -u postgres pg_dump pds > /var/backups/pds-$(date +%Y%m%d).sql 347 - ``` 348 - 349 - ## Custom homepage 350 - 351 - Drop 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. 352 - 353 - ```bash 354 - cat > /var/www/tranquil-pds/homepage.html << 'EOF' 355 - <!DOCTYPE html> 356 - <html> 357 - <head> 358 - <title>Welcome to my PDS</title> 359 - <style> 360 - body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; } 361 - </style> 362 - </head> 363 - <body> 364 - <h1>Welcome to my secret PDS</h1> 365 - <p>This is a <a href="https://atproto.com">AT Protocol</a> Personal Data Server.</p> 366 - <p><a href="/app/">Sign in</a> or learn more at <a href="https://bsky.social">Bluesky</a>.</p> 367 - </body> 368 - </html> 369 - EOF 370 - ```
-512
scripts/install-debian.sh
··· 1 - #!/bin/bash 2 - set -euo pipefail 3 - 4 - RED='\033[0;31m' 5 - GREEN='\033[0;32m' 6 - YELLOW='\033[1;33m' 7 - BLUE='\033[0;34m' 8 - NC='\033[0m' 9 - 10 - log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } 11 - log_success() { echo -e "${GREEN}[OK]${NC} $1"; } 12 - log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } 13 - log_error() { echo -e "${RED}[ERROR]${NC} $1"; } 14 - 15 - if [[ $EUID -ne 0 ]]; then 16 - log_error "This script must be run as root" 17 - exit 1 18 - fi 19 - 20 - if ! 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." 22 - fi 23 - 24 - nuke_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 - 58 - if [[ -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 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 96 - fi 97 - 98 - echo "" 99 - log_info "Tranquil PDS Installation Script for Debian" 100 - echo "" 101 - 102 - get_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 - 107 - log_info "Detecting public IP addresses..." 108 - get_public_ips 109 - echo " IPv4: ${IPV4}" 110 - [[ -n "$IPV6" ]] && echo " IPv6: ${IPV6}" 111 - echo "" 112 - 113 - read -p "Enter your PDS domain (eg., pds.example.com): " PDS_DOMAIN 114 - if [[ -z "$PDS_DOMAIN" ]]; then 115 - log_error "Domain cannot be empty" 116 - exit 1 117 - fi 118 - 119 - read -p "Enter your email for Let's Encrypt: " CERTBOT_EMAIL 120 - if [[ -z "$CERTBOT_EMAIL" ]]; then 121 - log_error "Email cannot be empty" 122 - exit 1 123 - fi 124 - 125 - echo "" 126 - log_info "DNS records required (create these now if you haven't):" 127 - echo "" 128 - echo " ${PDS_DOMAIN} A ${IPV4}" 129 - [[ -n "$IPV6" ]] && echo " ${PDS_DOMAIN} AAAA ${IPV6}" 130 - echo " *.${PDS_DOMAIN} A ${IPV4} (for user handles)" 131 - [[ -n "$IPV6" ]] && echo " *.${PDS_DOMAIN} AAAA ${IPV6} (for user handles)" 132 - echo "" 133 - read -p "Have you created these DNS records? (y/N): " DNS_CONFIRMED 134 - if [[ ! "$DNS_CONFIRMED" =~ ^[Yy]$ ]]; then 135 - log_warn "Please create the DNS records and run this script again." 136 - exit 0 137 - fi 138 - 139 - CREDENTIALS_FILE="/etc/tranquil-pds/.credentials" 140 - if [[ -f "$CREDENTIALS_FILE" ]]; then 141 - log_info "Loading existing credentials..." 142 - source "$CREDENTIALS_FILE" 143 - else 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 152 - JWT_SECRET="$JWT_SECRET" 153 - DPOP_SECRET="$DPOP_SECRET" 154 - MASTER_KEY="$MASTER_KEY" 155 - DB_PASSWORD="$DB_PASSWORD" 156 - EOF 157 - chmod 600 "$CREDENTIALS_FILE" 158 - log_success "Secrets generated" 159 - fi 160 - 161 - log_info "Checking swap space..." 162 - TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') 163 - TOTAL_SWAP_KB=$(grep SwapTotal /proc/meminfo | awk '{print $2}') 164 - 165 - if [[ $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 179 - fi 180 - 181 - log_info "Updating system packages..." 182 - apt update && apt upgrade -y 183 - 184 - log_info "Installing build dependencies..." 185 - apt install -y curl git build-essential pkg-config libssl-dev ca-certificates gnupg lsb-release unzip xxd 186 - 187 - log_info "Installing postgres..." 188 - apt install -y postgresql postgresql-contrib 189 - systemctl enable postgresql 190 - systemctl start postgresql 191 - sudo -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}';" 193 - sudo -u postgres psql -c "CREATE DATABASE pds OWNER tranquil_pds;" 2>/dev/null || true 194 - sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" 195 - log_success "postgres configured" 196 - 197 - log_info "Creating blob storage directories..." 198 - mkdir -p /var/lib/tranquil/blobs 199 - log_success "Blob storage directories created" 200 - 201 - log_info "Installing rust..." 202 - if [[ -f "$HOME/.cargo/env" ]]; then 203 - source "$HOME/.cargo/env" 204 - fi 205 - if ! command -v rustc &>/dev/null; then 206 - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 207 - source "$HOME/.cargo/env" 208 - fi 209 - 210 - log_info "Installing Node.js..." 211 - if ! command -v node &>/dev/null; then 212 - curl -fsSL https://deb.nodesource.com/setup_24.x | bash - 213 - apt install -y nodejs 214 - fi 215 - 216 - log_info "Installing pnpm..." 217 - if ! command -v pnpm &>/dev/null; then 218 - npm install -g pnpm 219 - fi 220 - 221 - log_info "Cloning Tranquil PDS..." 222 - if [[ ! -d /opt/tranquil-pds ]]; then 223 - git clone https://tangled.org/tranquil.farm/tranquil-pds /opt/tranquil-pds 224 - else 225 - cd /opt/tranquil-pds && git pull 226 - fi 227 - cd /opt/tranquil-pds 228 - 229 - log_info "Building frontend..." 230 - cd frontend && pnpm install --frozen-lockfile && pnpm build && cd .. 231 - log_success "Frontend built" 232 - 233 - log_info "Building Tranquil PDS (this takes a while)..." 234 - source "$HOME/.cargo/env" 235 - if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then 236 - log_info "Low memory - limiting parallel jobs" 237 - CARGO_BUILD_JOBS=1 cargo build --release 238 - else 239 - cargo build --release 240 - fi 241 - log_success "Tranquil PDS built" 242 - 243 - log_info "Running migrations..." 244 - cargo install sqlx-cli --no-default-features --features postgres 245 - export DATABASE_URL="postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds" 246 - "$HOME/.cargo/bin/sqlx" migrate run 247 - log_success "Migrations complete" 248 - 249 - log_info "Setting up mail trap..." 250 - mkdir -p /var/spool/tranquil-pds-mail 251 - chmod 1777 /var/spool/tranquil-pds-mail 252 - 253 - cat > /usr/local/bin/tranquil-pds-sendmail << 'SENDMAIL_EOF' 254 - #!/bin/bash 255 - MAIL_DIR="/var/spool/tranquil-pds-mail" 256 - TIMESTAMP=$(date +%Y%m%d-%H%M%S) 257 - RANDOM_ID=$(head -c 4 /dev/urandom | xxd -p) 258 - MAIL_FILE="${MAIL_DIR}/${TIMESTAMP}-${RANDOM_ID}.eml" 259 - mkdir -p "$MAIL_DIR" 260 - { 261 - echo "X-Tranquil-PDS-Received: $(date -Iseconds)" 262 - echo "X-Tranquil-PDS-Args: $*" 263 - echo "" 264 - cat 265 - } > "$MAIL_FILE" 266 - chmod 644 "$MAIL_FILE" 267 - exit 0 268 - SENDMAIL_EOF 269 - chmod +x /usr/local/bin/tranquil-pds-sendmail 270 - 271 - cat > /usr/local/bin/tranquil-pds-mailq << 'MAILQ_EOF' 272 - #!/bin/bash 273 - MAIL_DIR="/var/spool/tranquil-pds-mail" 274 - case "${1:-list}" in 275 - list) 276 - ls -lt "$MAIL_DIR"/*.eml 2>/dev/null | head -20 || echo "No emails" 277 - ;; 278 - latest) 279 - f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | head -1) 280 - [[ -f "$f" ]] && cat "$f" || echo "No emails" 281 - ;; 282 - clear) 283 - rm -f "$MAIL_DIR"/*.eml 284 - echo "Cleared" 285 - ;; 286 - count) 287 - ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l 288 - ;; 289 - [0-9]*) 290 - f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | sed -n "${1}p") 291 - [[ -f "$f" ]] && cat "$f" || echo "Not found" 292 - ;; 293 - *) 294 - [[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: tranquil-pds-mailq [list|latest|clear|count|N]" 295 - ;; 296 - esac 297 - MAILQ_EOF 298 - chmod +x /usr/local/bin/tranquil-pds-mailq 299 - 300 - log_info "Creating Tranquil PDS configuration..." 301 - cat > /etc/tranquil-pds/tranquil-pds.env << EOF 302 - SERVER_HOST=127.0.0.1 303 - SERVER_PORT=3000 304 - PDS_HOSTNAME=${PDS_DOMAIN} 305 - DATABASE_URL=postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds 306 - DATABASE_MAX_CONNECTIONS=100 307 - DATABASE_MIN_CONNECTIONS=10 308 - BLOB_STORAGE_PATH=/var/lib/tranquil/blobs 309 - JWT_SECRET=${JWT_SECRET} 310 - DPOP_SECRET=${DPOP_SECRET} 311 - MASTER_KEY=${MASTER_KEY} 312 - PLC_DIRECTORY_URL=https://plc.directory 313 - CRAWLERS=https://bsky.network 314 - AVAILABLE_USER_DOMAINS=${PDS_DOMAIN} 315 - MAIL_FROM_ADDRESS=noreply@${PDS_DOMAIN} 316 - MAIL_FROM_NAME=Tranquil PDS 317 - SENDMAIL_PATH=/usr/local/bin/tranquil-pds-sendmail 318 - EOF 319 - chmod 600 /etc/tranquil-pds/tranquil-pds.env 320 - 321 - log_info "Installing Tranquil PDS..." 322 - id -u tranquil-pds &>/dev/null || useradd -r -s /sbin/nologin tranquil-pds 323 - cp /opt/tranquil-pds/target/release/tranquil-server /usr/local/bin/tranquil-pds 324 - mkdir -p /var/lib/tranquil-pds 325 - cp -r /opt/tranquil-pds/frontend/dist /var/lib/tranquil-pds/frontend 326 - chown -R tranquil-pds:tranquil-pds /var/lib/tranquil-pds 327 - chown -R tranquil-pds:tranquil-pds /var/lib/tranquil 328 - 329 - cat > /etc/systemd/system/tranquil-pds.service << 'EOF' 330 - [Unit] 331 - Description=Tranquil PDS - AT Protocol PDS 332 - After=network.target postgresql.service 333 - 334 - [Service] 335 - Type=simple 336 - User=tranquil-pds 337 - Group=tranquil-pds 338 - EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env 339 - ExecStart=/usr/local/bin/tranquil-pds 340 - Restart=always 341 - RestartSec=5 342 - ProtectSystem=strict 343 - ProtectHome=true 344 - PrivateTmp=true 345 - ReadWritePaths=/var/lib/tranquil 346 - 347 - [Install] 348 - WantedBy=multi-user.target 349 - EOF 350 - 351 - systemctl daemon-reload 352 - systemctl enable tranquil-pds 353 - systemctl start tranquil-pds 354 - log_success "Tranquil PDS service started" 355 - 356 - log_info "Installing nginx..." 357 - apt install -y nginx 358 - cat > /etc/nginx/sites-available/tranquil-pds << EOF 359 - server { 360 - listen 80; 361 - listen [::]:80; 362 - server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; 363 - 364 - location /.well-known/acme-challenge/ { 365 - root /var/www/html; 366 - } 367 - 368 - location / { 369 - proxy_pass http://127.0.0.1:3000; 370 - proxy_http_version 1.1; 371 - proxy_set_header Upgrade \$http_upgrade; 372 - proxy_set_header Connection "upgrade"; 373 - proxy_set_header Host \$host; 374 - proxy_set_header X-Real-IP \$remote_addr; 375 - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 376 - proxy_set_header X-Forwarded-Proto \$scheme; 377 - proxy_read_timeout 86400; 378 - proxy_send_timeout 86400; 379 - client_max_body_size 100M; 380 - } 381 - } 382 - EOF 383 - 384 - ln -sf /etc/nginx/sites-available/tranquil-pds /etc/nginx/sites-enabled/ 385 - rm -f /etc/nginx/sites-enabled/default 386 - nginx -t 387 - systemctl reload nginx 388 - log_success "nginx configured" 389 - 390 - log_info "Configuring firewall..." 391 - apt install -y ufw 392 - ufw --force reset 393 - ufw default deny incoming 394 - ufw default allow outgoing 395 - ufw allow ssh 396 - ufw allow 80/tcp 397 - ufw allow 443/tcp 398 - ufw --force enable 399 - log_success "Firewall configured" 400 - 401 - echo "" 402 - log_info "Obtaining wildcard SSL certificate..." 403 - echo "" 404 - echo "User handles are served as subdomains (eg., alice.${PDS_DOMAIN})," 405 - echo "so you need a wildcard certificate. This requires DNS validation." 406 - echo "" 407 - echo "You'll need to add a TXT record to your DNS when prompted." 408 - echo "" 409 - read -p "Ready to proceed? (y/N): " CERT_READY 410 - 411 - if [[ "$CERT_READY" =~ ^[Yy]$ ]]; then 412 - apt install -y certbot python3-certbot-nginx 413 - 414 - log_info "Running certbot with DNS challenge..." 415 - echo "" 416 - echo "When prompted, add the TXT record to your DNS, wait a minute" 417 - echo "for propagation, then press Enter to continue." 418 - echo "" 419 - 420 - if certbot certonly --manual --preferred-challenges dns \ 421 - -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" \ 422 - --email "${CERTBOT_EMAIL}" --agree-tos; then 423 - 424 - cat > /etc/nginx/sites-available/tranquil-pds << EOF 425 - server { 426 - listen 80; 427 - listen [::]:80; 428 - server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; 429 - 430 - location /.well-known/acme-challenge/ { 431 - root /var/www/html; 432 - } 433 - 434 - location / { 435 - return 301 https://\$host\$request_uri; 436 - } 437 - } 438 - 439 - server { 440 - listen 443 ssl http2; 441 - listen [::]:443 ssl http2; 442 - server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; 443 - 444 - ssl_certificate /etc/letsencrypt/live/${PDS_DOMAIN}/fullchain.pem; 445 - ssl_certificate_key /etc/letsencrypt/live/${PDS_DOMAIN}/privkey.pem; 446 - ssl_protocols TLSv1.2 TLSv1.3; 447 - ssl_ciphers HIGH:!aNULL:!MD5; 448 - ssl_prefer_server_ciphers on; 449 - ssl_session_cache shared:SSL:10m; 450 - 451 - location / { 452 - proxy_pass http://127.0.0.1:3000; 453 - proxy_http_version 1.1; 454 - proxy_set_header Upgrade \$http_upgrade; 455 - proxy_set_header Connection "upgrade"; 456 - proxy_set_header Host \$host; 457 - proxy_set_header X-Real-IP \$remote_addr; 458 - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 459 - proxy_set_header X-Forwarded-Proto \$scheme; 460 - proxy_read_timeout 86400; 461 - proxy_send_timeout 86400; 462 - client_max_body_size 100M; 463 - } 464 - } 465 - EOF 466 - nginx -t && systemctl reload nginx 467 - log_success "Wildcard SSL certificate installed" 468 - 469 - echo "" 470 - log_warn "Certificate renewal note:" 471 - echo "Manual DNS challenges don't auto-renew. Before expiry, run:" 472 - echo " certbot renew --manual" 473 - echo "" 474 - echo "For auto-renewal, consider using a DNS provider plugin:" 475 - echo " apt install python3-certbot-dns-cloudflare # or your provider" 476 - echo "" 477 - else 478 - log_warn "Wildcard cert failed. You can retry later with:" 479 - echo " certbot certonly --manual --preferred-challenges dns \\" 480 - echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'" 481 - fi 482 - else 483 - log_warn "Skipping SSL. Your PDS is running on HTTP only." 484 - echo "To add SSL later, run:" 485 - echo " certbot certonly --manual --preferred-challenges dns \\" 486 - echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'" 487 - fi 488 - 489 - log_info "Verifying installation..." 490 - sleep 3 491 - if curl -s "http://localhost:3000/xrpc/_health" | grep -q "version"; then 492 - log_success "Tranquil PDS is responding" 493 - else 494 - log_warn "Tranquil PDS may still be starting. Check: journalctl -u tranquil-pds -f" 495 - fi 496 - 497 - echo "" 498 - log_success "Installation complete" 499 - echo "" 500 - echo "PDS: https://${PDS_DOMAIN}" 501 - echo "" 502 - echo "Credentials (also in /etc/tranquil-pds/.credentials):" 503 - echo " DB password: ${DB_PASSWORD}" 504 - echo "" 505 - echo "Data locations:" 506 - echo " Blobs: /var/lib/tranquil/blobs" 507 - echo "" 508 - echo "Commands:" 509 - echo " journalctl -u tranquil-pds -f # logs" 510 - echo " systemctl restart tranquil-pds # restart" 511 - echo " tranquil-pds-mailq # view trapped emails" 512 - echo ""