this repo has no description

Clean up installation instructions

lewis 61e33ca7 004315ce

+21 -12
docs/install-alpine.md
··· 1 1 # BSPDS Production Installation on Alpine Linux 2 2 > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 + 3 4 This guide covers installing BSPDS on Alpine Linux 3.23 (current stable as of December 2025). 4 - ## Choose Your Installation Method 5 - | Method | Best For | 6 - |--------|----------| 7 - | **Native (this guide)** | Maximum performance, minimal footprint, full control | 8 - | **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments | 9 - | **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling | 10 - This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md). 11 - --- 5 + 12 6 ## Prerequisites 13 7 - A VPS with at least 2GB RAM and 20GB disk 14 8 - A domain name pointing to your server's IP 9 + - A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) 15 10 - Root access 16 11 ## 1. System Setup 17 12 ```sh ··· 178 173 rc-update add nginx 179 174 rc-service nginx start 180 175 ``` 181 - ## 12. Obtain SSL Certificate 176 + ## 12. Obtain Wildcard SSL Certificate 177 + User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. 178 + 179 + Wildcard certs require DNS-01 validation. For manual DNS validation (works with any provider): 182 180 ```sh 183 - certbot --nginx -d pds.example.com 181 + certbot certonly --manual --preferred-challenges dns \ 182 + -d pds.example.com -d '*.pds.example.com' 184 183 ``` 185 - Set up auto-renewal: 184 + Follow the prompts to add TXT records to your DNS. 185 + 186 + If your DNS provider has a certbot plugin, you can use that for auto-renewal: 186 187 ```sh 187 - echo "0 0 * * * certbot renew --quiet" | crontab - 188 + apk add certbot-dns-cloudflare 189 + certbot certonly --dns-cloudflare \ 190 + --dns-cloudflare-credentials /etc/cloudflare.ini \ 191 + -d pds.example.com -d '*.pds.example.com' 192 + ``` 193 + 194 + After obtaining the cert, update nginx to use it, then set up auto-renewal: 195 + ```sh 196 + echo "0 0 * * * certbot renew --quiet && rc-service nginx reload" | crontab - 188 197 ``` 189 198 ## 13. Configure Firewall 190 199 ```sh
+72 -28
docs/install-containers.md
··· 6 6 ## Prerequisites 7 7 - A VPS with at least 2GB RAM and 20GB disk 8 8 - A domain name pointing to your server's IP 9 + - A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) 9 10 - Root or sudo access 10 11 ## Quick Start (Docker/Podman Compose) 11 12 If you just want to get running quickly: 12 13 ```sh 13 14 cp .env.example .env 14 - # Edit .env with your values 15 - # Generate secrets: openssl rand -base64 48 16 - # Build and start 15 + ``` 16 + 17 + Edit `.env` with your values. Generate secrets with `openssl rand -base64 48`. 18 + 19 + Build and start: 20 + ```sh 17 21 podman-compose -f docker-compose.prod.yml up -d 18 - # Get initial certificate (after DNS is configured) 22 + ``` 23 + 24 + Get initial certificate (after DNS is configured): 25 + ```sh 19 26 podman-compose -f docker-compose.prod.yml run --rm certbot certonly \ 20 27 --webroot -w /var/www/acme -d pds.example.com 21 - # Restart nginx to load certificate 22 28 podman-compose -f docker-compose.prod.yml restart nginx 23 29 ``` 24 30 For production setups with proper service management, continue to either the Debian or Alpine section below. ··· 74 80 systemctl daemon-reload 75 81 systemctl start bspds-db bspds-minio bspds-valkey 76 82 sleep 10 77 - # Create MinIO bucket 83 + ``` 84 + 85 + Create the minio bucket: 86 + ```bash 78 87 podman run --rm --pod bspds \ 79 88 -e MINIO_ROOT_USER=minioadmin \ 80 89 -e MINIO_ROOT_PASSWORD=your-minio-password \ 81 90 docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ 82 91 sh -c "mc alias set local http://localhost:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs" 83 - # Run migrations 92 + ``` 93 + 94 + Run migrations: 95 + ```bash 84 96 cargo install sqlx-cli --no-default-features --features postgres 85 97 DATABASE_URL="postgres://bspds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/bspds/migrations 86 98 ``` 87 - ## 9. Obtain SSL Certificate 88 - Create temporary self-signed cert: 99 + ## 9. Obtain Wildcard SSL Certificate 100 + User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. 101 + 102 + Create temporary self-signed cert to start services: 89 103 ```bash 90 104 openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 91 105 -keyout /srv/bspds/certs/privkey.pem \ 92 106 -out /srv/bspds/certs/fullchain.pem \ 93 107 -subj "/CN=pds.example.com" 94 108 systemctl start bspds-app bspds-nginx 95 - # Get real certificate 96 - podman run --rm \ 109 + ``` 110 + 111 + Get a wildcard certificate using DNS validation: 112 + ```bash 113 + podman run --rm -it \ 97 114 -v /srv/bspds/certs:/etc/letsencrypt:Z \ 98 - -v /srv/bspds/acme:/var/www/acme:Z \ 99 115 docker.io/certbot/certbot:v5.2.2 certonly \ 100 - --webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com 101 - # Link certificates 116 + --manual --preferred-challenges dns \ 117 + -d pds.example.com -d '*.pds.example.com' \ 118 + --agree-tos --email you@example.com 119 + ``` 120 + Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. 121 + 122 + For automated renewal, use a DNS provider plugin (e.g., cloudflare, route53). 123 + 124 + Link certificates and restart: 125 + ```bash 102 126 ln -sf /srv/bspds/certs/live/pds.example.com/fullchain.pem /srv/bspds/certs/fullchain.pem 103 127 ln -sf /srv/bspds/certs/live/pds.example.com/privkey.pem /srv/bspds/certs/privkey.pem 104 128 systemctl restart bspds-nginx ··· 200 224 chmod +x /etc/init.d/bspds 201 225 ``` 202 226 ## 7. Initialize Services 227 + Start services: 203 228 ```sh 204 - # Start services 205 229 rc-service bspds start 206 230 sleep 15 207 - # Create MinIO bucket 231 + ``` 232 + 233 + Create the minio bucket: 234 + ```sh 208 235 source /srv/bspds/config/bspds.env 209 236 podman run --rm --network bspds_default \ 210 237 -e MINIO_ROOT_USER="$MINIO_ROOT_USER" \ 211 238 -e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \ 212 239 docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ 213 240 sh -c 'mc alias set local http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs' 214 - # Run migrations 241 + ``` 242 + 243 + Run migrations: 244 + ```sh 215 245 apk add rustup 216 246 rustup-init -y 217 247 source ~/.cargo/env 218 248 cargo install sqlx-cli --no-default-features --features postgres 219 - # Get database container IP 220 249 DB_IP=$(podman inspect bspds-db-1 --format '{{.NetworkSettings.Networks.bspds_default.IPAddress}}') 221 250 DATABASE_URL="postgres://bspds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/bspds/migrations 222 251 ``` 223 - ## 8. Obtain SSL Certificate 224 - Create temporary self-signed cert: 252 + ## 8. Obtain Wildcard SSL Certificate 253 + User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. 254 + 255 + Create temporary self-signed cert to start services: 225 256 ```sh 226 257 openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 227 258 -keyout /srv/bspds/data/certs/privkey.pem \ 228 259 -out /srv/bspds/data/certs/fullchain.pem \ 229 260 -subj "/CN=pds.example.com" 230 261 rc-service bspds restart 231 - # Get real certificate 232 - podman run --rm \ 262 + ``` 263 + 264 + Get a wildcard certificate using DNS validation: 265 + ```sh 266 + podman run --rm -it \ 233 267 -v /srv/bspds/data/certs:/etc/letsencrypt \ 234 - -v /srv/bspds/data/acme:/var/www/acme \ 235 - --network bspds_default \ 236 268 docker.io/certbot/certbot:v5.2.2 certonly \ 237 - --webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com 238 - # Link certificates 269 + --manual --preferred-challenges dns \ 270 + -d pds.example.com -d '*.pds.example.com' \ 271 + --agree-tos --email you@example.com 272 + ``` 273 + Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. 274 + 275 + Link certificates and restart: 276 + ```sh 239 277 ln -sf /srv/bspds/data/certs/live/pds.example.com/fullchain.pem /srv/bspds/data/certs/fullchain.pem 240 278 ln -sf /srv/bspds/data/certs/live/pds.example.com/privkey.pem /srv/bspds/data/certs/privkey.pem 241 279 rc-service bspds restart ··· 292 330 cd /opt/bspds 293 331 git pull 294 332 podman build -t bspds:latest . 295 - # Debian: 333 + ``` 334 + 335 + Debian: 336 + ```bash 296 337 systemctl restart bspds-app 297 - # Alpine: 338 + ``` 339 + 340 + Alpine: 341 + ```sh 298 342 rc-service bspds restart 299 343 ``` 300 344 ## Backup Database
+20 -11
docs/install-debian.md
··· 1 1 # BSPDS Production Installation on Debian 2 2 > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 + 3 4 This guide covers installing BSPDS on Debian 13 "Trixie" (current stable as of December 2025). 4 - ## Choose Your Installation Method 5 - | Method | Best For | 6 - |--------|----------| 7 - | **Native (this guide)** | Maximum performance, full control, simpler debugging | 8 - | **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments | 9 - | **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling | 10 - This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md). 11 - --- 5 + 12 6 ## Prerequisites 13 7 - A VPS with at least 2GB RAM and 20GB disk 14 8 - A domain name pointing to your server's IP 9 + - A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) 15 10 - Root or sudo access 16 11 ## 1. System Setup 17 12 ```bash ··· 168 163 nginx -t 169 164 systemctl reload nginx 170 165 ``` 171 - ## 12. Obtain SSL Certificate 166 + ## 12. Obtain Wildcard SSL Certificate 167 + User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. 168 + 169 + Wildcard certs require DNS-01 validation. If your DNS provider has a certbot plugin: 172 170 ```bash 173 - certbot --nginx -d pds.example.com 171 + apt install -y python3-certbot-dns-cloudflare 172 + certbot certonly --dns-cloudflare \ 173 + --dns-cloudflare-credentials /etc/cloudflare.ini \ 174 + -d pds.example.com -d '*.pds.example.com' 174 175 ``` 175 - Certbot automatically configures nginx for HTTP/2 and sets up auto-renewal. 176 + 177 + For manual DNS validation (works with any provider): 178 + ```bash 179 + certbot certonly --manual --preferred-challenges dns \ 180 + -d pds.example.com -d '*.pds.example.com' 181 + ``` 182 + Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. 183 + 184 + After obtaining the cert, update nginx to use it and reload. 176 185 ## 13. Configure Firewall 177 186 ```bash 178 187 apt install -y ufw
+23 -859
docs/install-kubernetes.md
··· 1 - # BSPDS Production Kubernetes Deployment 2 - > **Warning**: These instructions are untested and theoretical, written from the top of Lewis' head. They may contain errors or omissions. This warning will be removed once the guide has been verified. 3 - This guide covers deploying BSPDS on a production multi-node Kubernetes cluster with high availability, auto-scaling, and proper secrets management. 4 - ## Architecture Overview 5 - ``` 6 - ┌─────────────────────────────────────────────────┐ 7 - │ Kubernetes Cluster │ 8 - │ │ 9 - Internet ──────►│ Ingress Controller (nginx/traefik) │ 10 - │ │ │ 11 - │ ▼ │ 12 - │ ┌─────────────┐ │ 13 - │ │ Service │◄── HPA (2-10 replicas) │ 14 - │ └──────┬──────┘ │ 15 - │ │ │ 16 - │ ┌────┴────┐ │ 17 - │ ▼ ▼ │ 18 - │ ┌─────┐ ┌─────┐ │ 19 - │ │BSPDS│ │BSPDS│ ... (pods) │ 20 - │ └──┬──┘ └──┬──┘ │ 21 - │ │ │ │ 22 - │ ▼ ▼ │ 23 - │ ┌──────────────────────────────────────┐ │ 24 - │ │ PostgreSQL │ MinIO │ Valkey │ │ 25 - │ │ (HA/Operator)│ (StatefulSet) │ (Sentinel) │ 26 - │ └──────────────────────────────────────┘ │ 27 - └─────────────────────────────────────────────────┘ 28 - ``` 29 - ## Prerequisites 30 - - Kubernetes cluster (1.30+) with at least 3 nodes (1.34 is current stable) 31 - - `kubectl` configured to access your cluster 32 - - `helm` 3.x installed 33 - - Storage class that supports `ReadWriteOnce` (for databases) 34 - - Ingress controller installed (nginx-ingress or traefik) 35 - - cert-manager installed for TLS certificates 36 - ### Quick Prerequisites Setup 37 - If you need to install prerequisites: 38 - ```bash 39 - # Install nginx-ingress (chart v4.14.1 - December 2025) 40 - helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 41 - helm repo update 42 - helm install ingress-nginx ingress-nginx/ingress-nginx \ 43 - --namespace ingress-nginx --create-namespace \ 44 - --version 4.14.1 45 - # Install cert-manager (v1.19.2 - December 2025) 46 - helm repo add jetstack https://charts.jetstack.io 47 - helm repo update 48 - helm install cert-manager jetstack/cert-manager \ 49 - --namespace cert-manager --create-namespace \ 50 - --version v1.19.2 \ 51 - --set installCRDs=true 52 - ``` 53 - --- 54 - ## 1. Create Namespace 55 - ```bash 56 - kubectl create namespace bspds 57 - kubectl config set-context --current --namespace=bspds 58 - ``` 59 - ## 2. Create Secrets 60 - Generate secure passwords and secrets: 61 - ```bash 62 - # Generate secrets 63 - DB_PASSWORD=$(openssl rand -base64 32) 64 - MINIO_PASSWORD=$(openssl rand -base64 32) 65 - JWT_SECRET=$(openssl rand -base64 48) 66 - DPOP_SECRET=$(openssl rand -base64 48) 67 - MASTER_KEY=$(openssl rand -base64 48) 68 - # Create Kubernetes secrets 69 - kubectl create secret generic bspds-db-credentials \ 70 - --from-literal=username=bspds \ 71 - --from-literal=password="$DB_PASSWORD" 72 - kubectl create secret generic bspds-minio-credentials \ 73 - --from-literal=root-user=minioadmin \ 74 - --from-literal=root-password="$MINIO_PASSWORD" 75 - kubectl create secret generic bspds-secrets \ 76 - --from-literal=jwt-secret="$JWT_SECRET" \ 77 - --from-literal=dpop-secret="$DPOP_SECRET" \ 78 - --from-literal=master-key="$MASTER_KEY" 79 - # Save secrets locally (KEEP SECURE!) 80 - echo "DB_PASSWORD=$DB_PASSWORD" > secrets.txt 81 - echo "MINIO_PASSWORD=$MINIO_PASSWORD" >> secrets.txt 82 - echo "JWT_SECRET=$JWT_SECRET" >> secrets.txt 83 - echo "DPOP_SECRET=$DPOP_SECRET" >> secrets.txt 84 - echo "MASTER_KEY=$MASTER_KEY" >> secrets.txt 85 - chmod 600 secrets.txt 86 - ``` 87 - ## 3. Deploy PostgreSQL 88 - ### Option A: CloudNativePG Operator (Recommended for HA) 89 - ```bash 90 - # Install CloudNativePG operator (v1.28.0 - December 2025) 91 - kubectl apply --server-side -f \ 92 - https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.0.yaml 93 - # Wait for operator 94 - kubectl wait --for=condition=available --timeout=120s \ 95 - deployment/cnpg-controller-manager -n cnpg-system 96 - ``` 97 - ```bash 98 - cat <<EOF | kubectl apply -f - 99 - apiVersion: postgresql.cnpg.io/v1 100 - kind: Cluster 101 - metadata: 102 - name: bspds-db 103 - namespace: bspds 104 - spec: 105 - instances: 3 106 - postgresql: 107 - parameters: 108 - max_connections: "200" 109 - shared_buffers: "256MB" 110 - bootstrap: 111 - initdb: 112 - database: pds 113 - owner: bspds 114 - secret: 115 - name: bspds-db-credentials 116 - storage: 117 - size: 20Gi 118 - storageClass: standard # adjust for your cluster 119 - resources: 120 - requests: 121 - memory: "512Mi" 122 - cpu: "250m" 123 - limits: 124 - memory: "1Gi" 125 - cpu: "1000m" 126 - affinity: 127 - podAntiAffinityType: required 128 - EOF 129 - ``` 130 - ### Option B: Simple StatefulSet (Single Instance) 131 - ```bash 132 - cat <<EOF | kubectl apply -f - 133 - apiVersion: v1 134 - kind: PersistentVolumeClaim 135 - metadata: 136 - name: bspds-db-pvc 137 - namespace: bspds 138 - spec: 139 - accessModes: 140 - - ReadWriteOnce 141 - resources: 142 - requests: 143 - storage: 20Gi 144 - --- 145 - apiVersion: apps/v1 146 - kind: StatefulSet 147 - metadata: 148 - name: bspds-db 149 - namespace: bspds 150 - spec: 151 - serviceName: bspds-db 152 - replicas: 1 153 - selector: 154 - matchLabels: 155 - app: bspds-db 156 - template: 157 - metadata: 158 - labels: 159 - app: bspds-db 160 - spec: 161 - containers: 162 - - name: postgres 163 - image: postgres:18-alpine 164 - ports: 165 - - containerPort: 5432 166 - env: 167 - - name: POSTGRES_DB 168 - value: pds 169 - - name: POSTGRES_USER 170 - valueFrom: 171 - secretKeyRef: 172 - name: bspds-db-credentials 173 - key: username 174 - - name: POSTGRES_PASSWORD 175 - valueFrom: 176 - secretKeyRef: 177 - name: bspds-db-credentials 178 - key: password 179 - - name: PGDATA 180 - value: /var/lib/postgresql/data/pgdata 181 - volumeMounts: 182 - - name: data 183 - mountPath: /var/lib/postgresql/data 184 - resources: 185 - requests: 186 - memory: "256Mi" 187 - cpu: "100m" 188 - limits: 189 - memory: "1Gi" 190 - cpu: "500m" 191 - livenessProbe: 192 - exec: 193 - command: ["pg_isready", "-U", "bspds", "-d", "pds"] 194 - initialDelaySeconds: 30 195 - periodSeconds: 10 196 - readinessProbe: 197 - exec: 198 - command: ["pg_isready", "-U", "bspds", "-d", "pds"] 199 - initialDelaySeconds: 5 200 - periodSeconds: 5 201 - volumes: 202 - - name: data 203 - persistentVolumeClaim: 204 - claimName: bspds-db-pvc 205 - --- 206 - apiVersion: v1 207 - kind: Service 208 - metadata: 209 - name: bspds-db-rw 210 - namespace: bspds 211 - spec: 212 - selector: 213 - app: bspds-db 214 - ports: 215 - - port: 5432 216 - targetPort: 5432 217 - EOF 218 - ``` 219 - ## 4. Deploy MinIO 220 - ```bash 221 - cat <<EOF | kubectl apply -f - 222 - apiVersion: v1 223 - kind: PersistentVolumeClaim 224 - metadata: 225 - name: bspds-minio-pvc 226 - namespace: bspds 227 - spec: 228 - accessModes: 229 - - ReadWriteOnce 230 - resources: 231 - requests: 232 - storage: 50Gi 233 - --- 234 - apiVersion: apps/v1 235 - kind: StatefulSet 236 - metadata: 237 - name: bspds-minio 238 - namespace: bspds 239 - spec: 240 - serviceName: bspds-minio 241 - replicas: 1 242 - selector: 243 - matchLabels: 244 - app: bspds-minio 245 - template: 246 - metadata: 247 - labels: 248 - app: bspds-minio 249 - spec: 250 - containers: 251 - - name: minio 252 - image: minio/minio:RELEASE.2025-10-15T17-29-55Z 253 - args: 254 - - server 255 - - /data 256 - - --console-address 257 - - ":9001" 258 - ports: 259 - - containerPort: 9000 260 - name: api 261 - - containerPort: 9001 262 - name: console 263 - env: 264 - - name: MINIO_ROOT_USER 265 - valueFrom: 266 - secretKeyRef: 267 - name: bspds-minio-credentials 268 - key: root-user 269 - - name: MINIO_ROOT_PASSWORD 270 - valueFrom: 271 - secretKeyRef: 272 - name: bspds-minio-credentials 273 - key: root-password 274 - volumeMounts: 275 - - name: data 276 - mountPath: /data 277 - resources: 278 - requests: 279 - memory: "256Mi" 280 - cpu: "100m" 281 - limits: 282 - memory: "512Mi" 283 - cpu: "500m" 284 - livenessProbe: 285 - httpGet: 286 - path: /minio/health/live 287 - port: 9000 288 - initialDelaySeconds: 30 289 - periodSeconds: 10 290 - readinessProbe: 291 - httpGet: 292 - path: /minio/health/ready 293 - port: 9000 294 - initialDelaySeconds: 10 295 - periodSeconds: 5 296 - volumes: 297 - - name: data 298 - persistentVolumeClaim: 299 - claimName: bspds-minio-pvc 300 - --- 301 - apiVersion: v1 302 - kind: Service 303 - metadata: 304 - name: bspds-minio 305 - namespace: bspds 306 - spec: 307 - selector: 308 - app: bspds-minio 309 - ports: 310 - - port: 9000 311 - targetPort: 9000 312 - name: api 313 - - port: 9001 314 - targetPort: 9001 315 - name: console 316 - EOF 317 - ``` 318 - ### Initialize MinIO Bucket 319 - ```bash 320 - kubectl run minio-init --rm -it --restart=Never \ 321 - --image=minio/mc:RELEASE.2025-07-16T15-35-03Z \ 322 - --env="MINIO_ROOT_USER=minioadmin" \ 323 - --env="MINIO_ROOT_PASSWORD=$(kubectl get secret bspds-minio-credentials -o jsonpath='{.data.root-password}' | base64 -d)" \ 324 - --command -- sh -c " 325 - mc alias set local http://bspds-minio:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && 326 - mc mb --ignore-existing local/pds-blobs 327 - " 328 - ``` 329 - ## 5. Deploy Valkey 330 - ```bash 331 - cat <<EOF | kubectl apply -f - 332 - apiVersion: v1 333 - kind: PersistentVolumeClaim 334 - metadata: 335 - name: bspds-valkey-pvc 336 - namespace: bspds 337 - spec: 338 - accessModes: 339 - - ReadWriteOnce 340 - resources: 341 - requests: 342 - storage: 5Gi 343 - --- 344 - apiVersion: apps/v1 345 - kind: StatefulSet 346 - metadata: 347 - name: bspds-valkey 348 - namespace: bspds 349 - spec: 350 - serviceName: bspds-valkey 351 - replicas: 1 352 - selector: 353 - matchLabels: 354 - app: bspds-valkey 355 - template: 356 - metadata: 357 - labels: 358 - app: bspds-valkey 359 - spec: 360 - containers: 361 - - name: valkey 362 - image: valkey/valkey:9-alpine 363 - args: 364 - - valkey-server 365 - - --appendonly 366 - - "yes" 367 - - --maxmemory 368 - - "256mb" 369 - - --maxmemory-policy 370 - - allkeys-lru 371 - ports: 372 - - containerPort: 6379 373 - volumeMounts: 374 - - name: data 375 - mountPath: /data 376 - resources: 377 - requests: 378 - memory: "128Mi" 379 - cpu: "50m" 380 - limits: 381 - memory: "300Mi" 382 - cpu: "200m" 383 - livenessProbe: 384 - exec: 385 - command: ["valkey-cli", "ping"] 386 - initialDelaySeconds: 10 387 - periodSeconds: 5 388 - readinessProbe: 389 - exec: 390 - command: ["valkey-cli", "ping"] 391 - initialDelaySeconds: 5 392 - periodSeconds: 3 393 - volumes: 394 - - name: data 395 - persistentVolumeClaim: 396 - claimName: bspds-valkey-pvc 397 - --- 398 - apiVersion: v1 399 - kind: Service 400 - metadata: 401 - name: bspds-valkey 402 - namespace: bspds 403 - spec: 404 - selector: 405 - app: bspds-valkey 406 - ports: 407 - - port: 6379 408 - targetPort: 6379 409 - EOF 410 - ``` 411 - ## 6. Build and Push BSPDS Image 412 - ```bash 413 - # Build image 414 - cd /path/to/bspds 415 - docker build -t your-registry.com/bspds:latest . 416 - docker push your-registry.com/bspds:latest 417 - ``` 418 - If using a private registry, create an image pull secret: 419 - ```bash 420 - kubectl create secret docker-registry regcred \ 421 - --docker-server=your-registry.com \ 422 - --docker-username=your-username \ 423 - --docker-password=your-password \ 424 - --docker-email=your-email 425 - ``` 426 - ## 7. Run Database Migrations 427 - BSPDS runs migrations automatically on startup. However, if you want to run migrations separately (recommended for zero-downtime deployments), you can use a Job: 428 - ```bash 429 - cat <<'EOF' | kubectl apply -f - 430 - apiVersion: batch/v1 431 - kind: Job 432 - metadata: 433 - name: bspds-migrate 434 - namespace: bspds 435 - spec: 436 - ttlSecondsAfterFinished: 300 437 - template: 438 - spec: 439 - restartPolicy: Never 440 - containers: 441 - - name: migrate 442 - image: your-registry.com/bspds:latest 443 - command: ["/usr/local/bin/bspds"] 444 - args: ["--migrate-only"] # Add this flag to your app, or remove this Job 445 - env: 446 - - name: DB_PASSWORD 447 - valueFrom: 448 - secretKeyRef: 449 - name: bspds-db-credentials 450 - key: password 451 - - name: DATABASE_URL 452 - value: "postgres://bspds:$(DB_PASSWORD)@bspds-db-rw:5432/pds" 453 - EOF 454 - kubectl wait --for=condition=complete --timeout=120s job/bspds-migrate 455 - ``` 456 - > **Note**: If your BSPDS image doesn't have a `--migrate-only` flag, you can skip this step. The app will run migrations on first startup. Alternatively, build a separate migration image with `sqlx-cli` installed. 457 - ## 8. Deploy BSPDS Application 458 - ```bash 459 - cat <<EOF | kubectl apply -f - 460 - apiVersion: v1 461 - kind: ConfigMap 462 - metadata: 463 - name: bspds-config 464 - namespace: bspds 465 - data: 466 - PDS_HOSTNAME: "pds.example.com" 467 - SERVER_HOST: "0.0.0.0" 468 - SERVER_PORT: "3000" 469 - S3_ENDPOINT: "http://bspds-minio:9000" 470 - AWS_REGION: "us-east-1" 471 - S3_BUCKET: "pds-blobs" 472 - VALKEY_URL: "redis://bspds-valkey:6379" 473 - APPVIEW_URL: "https://api.bsky.app" 474 - CRAWLERS: "https://bsky.network" 475 - FRONTEND_DIR: "/app/frontend/dist" 476 - --- 477 - apiVersion: apps/v1 478 - kind: Deployment 479 - metadata: 480 - name: bspds 481 - namespace: bspds 482 - spec: 483 - replicas: 2 484 - selector: 485 - matchLabels: 486 - app: bspds 487 - template: 488 - metadata: 489 - labels: 490 - app: bspds 491 - spec: 492 - imagePullSecrets: 493 - - name: regcred # Remove if using public registry 494 - affinity: 495 - podAntiAffinity: 496 - preferredDuringSchedulingIgnoredDuringExecution: 497 - - weight: 100 498 - podAffinityTerm: 499 - labelSelector: 500 - matchLabels: 501 - app: bspds 502 - topologyKey: kubernetes.io/hostname 503 - containers: 504 - - name: bspds 505 - image: your-registry.com/bspds:latest 506 - ports: 507 - - containerPort: 3000 508 - name: http 509 - envFrom: 510 - - configMapRef: 511 - name: bspds-config 512 - env: 513 - - name: DB_PASSWORD 514 - valueFrom: 515 - secretKeyRef: 516 - name: bspds-db-credentials 517 - key: password 518 - - name: DATABASE_URL 519 - value: "postgres://bspds:$(DB_PASSWORD)@bspds-db-rw:5432/pds" 520 - - name: AWS_ACCESS_KEY_ID 521 - valueFrom: 522 - secretKeyRef: 523 - name: bspds-minio-credentials 524 - key: root-user 525 - - name: AWS_SECRET_ACCESS_KEY 526 - valueFrom: 527 - secretKeyRef: 528 - name: bspds-minio-credentials 529 - key: root-password 530 - - name: JWT_SECRET 531 - valueFrom: 532 - secretKeyRef: 533 - name: bspds-secrets 534 - key: jwt-secret 535 - - name: DPOP_SECRET 536 - valueFrom: 537 - secretKeyRef: 538 - name: bspds-secrets 539 - key: dpop-secret 540 - - name: MASTER_KEY 541 - valueFrom: 542 - secretKeyRef: 543 - name: bspds-secrets 544 - key: master-key 545 - resources: 546 - requests: 547 - memory: "256Mi" 548 - cpu: "100m" 549 - limits: 550 - memory: "1Gi" 551 - cpu: "1000m" 552 - livenessProbe: 553 - httpGet: 554 - path: /xrpc/_health 555 - port: 3000 556 - initialDelaySeconds: 30 557 - periodSeconds: 10 558 - failureThreshold: 3 559 - readinessProbe: 560 - httpGet: 561 - path: /xrpc/_health 562 - port: 3000 563 - initialDelaySeconds: 5 564 - periodSeconds: 5 565 - failureThreshold: 3 566 - securityContext: 567 - runAsNonRoot: true 568 - runAsUser: 1000 569 - allowPrivilegeEscalation: false 570 - --- 571 - apiVersion: v1 572 - kind: Service 573 - metadata: 574 - name: bspds 575 - namespace: bspds 576 - spec: 577 - selector: 578 - app: bspds 579 - ports: 580 - - port: 80 581 - targetPort: 3000 582 - name: http 583 - EOF 584 - ``` 585 - ## 9. Configure Horizontal Pod Autoscaler 586 - ```bash 587 - cat <<EOF | kubectl apply -f - 588 - apiVersion: autoscaling/v2 589 - kind: HorizontalPodAutoscaler 590 - metadata: 591 - name: bspds 592 - namespace: bspds 593 - spec: 594 - scaleTargetRef: 595 - apiVersion: apps/v1 596 - kind: Deployment 597 - name: bspds 598 - minReplicas: 2 599 - maxReplicas: 10 600 - metrics: 601 - - type: Resource 602 - resource: 603 - name: cpu 604 - target: 605 - type: Utilization 606 - averageUtilization: 70 607 - - type: Resource 608 - resource: 609 - name: memory 610 - target: 611 - type: Utilization 612 - averageUtilization: 80 613 - behavior: 614 - scaleDown: 615 - stabilizationWindowSeconds: 300 616 - policies: 617 - - type: Pods 618 - value: 1 619 - periodSeconds: 60 620 - scaleUp: 621 - stabilizationWindowSeconds: 0 622 - policies: 623 - - type: Percent 624 - value: 100 625 - periodSeconds: 15 626 - - type: Pods 627 - value: 4 628 - periodSeconds: 15 629 - selectPolicy: Max 630 - EOF 631 - ``` 632 - ## 10. Configure Pod Disruption Budget 633 - ```bash 634 - cat <<EOF | kubectl apply -f - 635 - apiVersion: policy/v1 636 - kind: PodDisruptionBudget 637 - metadata: 638 - name: bspds 639 - namespace: bspds 640 - spec: 641 - minAvailable: 1 642 - selector: 643 - matchLabels: 644 - app: bspds 645 - EOF 646 - ``` 647 - ## 11. Configure TLS with cert-manager 648 - ```bash 649 - cat <<EOF | kubectl apply -f - 650 - apiVersion: cert-manager.io/v1 651 - kind: ClusterIssuer 652 - metadata: 653 - name: letsencrypt-prod 654 - spec: 655 - acme: 656 - server: https://acme-v02.api.letsencrypt.org/directory 657 - email: your-email@example.com 658 - privateKeySecretRef: 659 - name: letsencrypt-prod 660 - solvers: 661 - - http01: 662 - ingress: 663 - class: nginx 664 - EOF 665 - ``` 666 - ## 12. Configure Ingress 667 - ```bash 668 - cat <<EOF | kubectl apply -f - 669 - apiVersion: networking.k8s.io/v1 670 - kind: Ingress 671 - metadata: 672 - name: bspds 673 - namespace: bspds 674 - annotations: 675 - cert-manager.io/cluster-issuer: letsencrypt-prod 676 - nginx.ingress.kubernetes.io/proxy-read-timeout: "86400" 677 - nginx.ingress.kubernetes.io/proxy-send-timeout: "86400" 678 - nginx.ingress.kubernetes.io/proxy-body-size: "100m" 679 - nginx.ingress.kubernetes.io/proxy-buffering: "off" 680 - nginx.ingress.kubernetes.io/websocket-services: "bspds" 681 - spec: 682 - ingressClassName: nginx 683 - tls: 684 - - hosts: 685 - - pds.example.com 686 - secretName: bspds-tls 687 - rules: 688 - - host: pds.example.com 689 - http: 690 - paths: 691 - - path: / 692 - pathType: Prefix 693 - backend: 694 - service: 695 - name: bspds 696 - port: 697 - number: 80 698 - EOF 699 - ``` 700 - ## 13. Configure Network Policies (Optional but Recommended) 701 - ```bash 702 - cat <<EOF | kubectl apply -f - 703 - apiVersion: networking.k8s.io/v1 704 - kind: NetworkPolicy 705 - metadata: 706 - name: bspds-network-policy 707 - namespace: bspds 708 - spec: 709 - podSelector: 710 - matchLabels: 711 - app: bspds 712 - policyTypes: 713 - - Ingress 714 - - Egress 715 - ingress: 716 - - from: 717 - - namespaceSelector: 718 - matchLabels: 719 - kubernetes.io/metadata.name: ingress-nginx 720 - ports: 721 - - protocol: TCP 722 - port: 3000 723 - egress: 724 - - to: 725 - - podSelector: 726 - matchLabels: 727 - app: bspds-db 728 - ports: 729 - - protocol: TCP 730 - port: 5432 731 - - to: 732 - - podSelector: 733 - matchLabels: 734 - app: bspds-minio 735 - ports: 736 - - protocol: TCP 737 - port: 9000 738 - - to: 739 - - podSelector: 740 - matchLabels: 741 - app: bspds-valkey 742 - ports: 743 - - protocol: TCP 744 - port: 6379 745 - - to: # Allow DNS 746 - - namespaceSelector: {} 747 - podSelector: 748 - matchLabels: 749 - k8s-app: kube-dns 750 - ports: 751 - - protocol: UDP 752 - port: 53 753 - - to: # Allow external HTTPS (for federation) 754 - - ipBlock: 755 - cidr: 0.0.0.0/0 756 - ports: 757 - - protocol: TCP 758 - port: 443 759 - EOF 760 - ``` 761 - ## 14. Deploy Prometheus Monitoring (Optional) 762 - ```bash 763 - cat <<EOF | kubectl apply -f - 764 - apiVersion: monitoring.coreos.com/v1 765 - kind: ServiceMonitor 766 - metadata: 767 - name: bspds 768 - namespace: bspds 769 - labels: 770 - release: prometheus 771 - spec: 772 - selector: 773 - matchLabels: 774 - app: bspds 775 - endpoints: 776 - - port: http 777 - path: /metrics 778 - interval: 30s 779 - EOF 780 - ``` 781 - --- 782 - ## Verification 783 - ```bash 784 - # Check all pods are running 785 - kubectl get pods -n bspds 786 - # Check services 787 - kubectl get svc -n bspds 788 - # Check ingress 789 - kubectl get ingress -n bspds 790 - # Check certificate 791 - kubectl get certificate -n bspds 792 - # Test health endpoint 793 - curl -s https://pds.example.com/xrpc/_health | jq 794 - # Test DID endpoint 795 - curl -s https://pds.example.com/.well-known/atproto-did 796 - ``` 797 - --- 798 - ## Maintenance 799 - ### View Logs 800 - ```bash 801 - # All BSPDS pods 802 - kubectl logs -l app=bspds -n bspds -f 803 - # Specific pod 804 - kubectl logs -f deployment/bspds -n bspds 805 - ``` 806 - ### Scale Manually 807 - ```bash 808 - kubectl scale deployment bspds --replicas=5 -n bspds 809 - ``` 810 - ### Update BSPDS 811 - ```bash 812 - # Build and push new image 813 - docker build -t your-registry.com/bspds:v1.2.3 . 814 - docker push your-registry.com/bspds:v1.2.3 815 - # Update deployment 816 - kubectl set image deployment/bspds bspds=your-registry.com/bspds:v1.2.3 -n bspds 817 - # Watch rollout 818 - kubectl rollout status deployment/bspds -n bspds 819 - ``` 820 - ### Backup Database 821 - ```bash 822 - # For CloudNativePG 823 - kubectl cnpg backup bspds-db -n bspds 824 - # For StatefulSet 825 - kubectl exec -it bspds-db-0 -n bspds -- pg_dump -U bspds pds > backup-$(date +%Y%m%d).sql 826 - ``` 827 - ### Run Migrations 828 - If you have a migration Job defined, you can re-run it: 829 - ```bash 830 - # Delete old job first (if exists) 831 - kubectl delete job bspds-migrate -n bspds --ignore-not-found 832 - # Re-apply the migration job from step 7 833 - # Or simply restart the deployment - BSPDS runs migrations on startup 834 - kubectl rollout restart deployment/bspds -n bspds 835 - ``` 836 - --- 837 - ## Troubleshooting 838 - ### Pod Won't Start 839 - ```bash 840 - kubectl describe pod -l app=bspds -n bspds 841 - kubectl logs -l app=bspds -n bspds --previous 842 - ``` 843 - ### Database Connection Issues 844 - ```bash 845 - # Test connectivity from a debug pod 846 - kubectl run debug --rm -it --restart=Never --image=postgres:18-alpine -- \ 847 - psql "postgres://bspds:PASSWORD@bspds-db-rw:5432/pds" -c "SELECT 1" 848 - ``` 849 - ### Certificate Issues 850 - ```bash 851 - kubectl describe certificate bspds-tls -n bspds 852 - kubectl describe certificaterequest -n bspds 853 - kubectl logs -l app.kubernetes.io/name=cert-manager -n cert-manager 854 - ``` 855 - ### View Resource Usage 856 - ```bash 857 - kubectl top pods -n bspds 858 - kubectl top nodes 859 - ``` 1 + # BSPDS on Kubernetes 2 + 3 + If you're reaching for kubernetes for this app, you're experienced enough to know how to spin up: 4 + 5 + - cloudnativepg (or your preferred postgres operator) 6 + - valkey 7 + - s3-compatible object storage (minio operator, or just use a managed service) 8 + - the app itself (it's just a container with some env vars) 9 + 10 + You'll need a wildcard TLS certificate for `*.your-pds-hostname.example.com` — user handles are served as subdomains. 11 + 12 + The container image expects: 13 + - `DATABASE_URL` - postgres connection string 14 + - `S3_ENDPOINT`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET` 15 + - `VALKEY_URL` - redis:// connection string 16 + - `PDS_HOSTNAME` - your PDS hostname (without protocol) 17 + - `JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY` - generate with `openssl rand -base64 48` 18 + - `APPVIEW_URL` - typically `https://api.bsky.app` 19 + - `CRAWLERS` - typically `https://bsky.network` 20 + and more, check the .env.example. 21 + 22 + Health check: `GET /xrpc/_health` 23 +
+21 -25
docs/install-openbsd.md
··· 4 4 ## Prerequisites 5 5 - A VPS with at least 2GB RAM and 20GB disk 6 6 - A domain name pointing to your server's IP 7 + - A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) 7 8 - Root access (or doas configured) 8 9 ## Why nginx over relayd? 9 10 OpenBSD's native `relayd` supports WebSockets but does **not** support HTTP/2. For a modern PDS deployment, we recommend nginx which provides HTTP/2, WebSocket support, and automatic OCSP stapling. ··· 80 81 mc mb local/pds-blobs 81 82 ``` 82 83 ## 5. Install redis 83 - OpenBSD has redis in ports (valkey may not be available yet): 84 + OpenBSD has redis in ports (valkey not available yet): 84 85 ```sh 85 86 pkg_add redis 86 87 rcctl enable redis ··· 194 195 mkdir -p /var/www/acme 195 196 rcctl enable nginx 196 197 ``` 197 - ## 12. Obtain SSL Certificate with acme-client 198 - OpenBSD's native acme-client works well: 198 + ## 12. Obtain Wildcard SSL Certificate 199 + User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. 200 + 201 + OpenBSD's native `acme-client` only supports HTTP-01 validation, which can't issue wildcard certs. You have a few options: 202 + 203 + **Option A: Use certbot with DNS validation (recommended)** 199 204 ```sh 200 - cat >> /etc/acme-client.conf << 'EOF' 201 - authority letsencrypt { 202 - api url "https://acme-v02.api.letsencrypt.org/directory" 203 - account key "/etc/acme/letsencrypt-privkey.pem" 204 - } 205 - domain pds.example.com { 206 - domain key "/etc/ssl/private/pds.example.com.key" 207 - domain full chain certificate "/etc/ssl/pds.example.com.fullchain.pem" 208 - sign with letsencrypt 209 - } 210 - EOF 211 - mkdir -p /etc/acme 212 - rcctl start nginx 213 - acme-client -v pds.example.com 214 - rcctl restart nginx 205 + pkg_add certbot 206 + certbot certonly --manual --preferred-challenges dns \ 207 + -d pds.example.com -d '*.pds.example.com' 215 208 ``` 216 - Set up auto-renewal in root's crontab: 209 + Follow the prompts to add TXT records to your DNS. Then update nginx.conf to point to the certbot certs. 210 + 211 + **Option B: Use a managed DNS provider with API** 212 + If your DNS provider has a certbot plugin, you can automate renewal. 213 + 214 + **Option C: Use acme.sh** 215 + [acme.sh](https://github.com/acmesh-official/acme.sh) supports many DNS providers for automated wildcard cert renewal. 216 + 217 + After obtaining the cert, update nginx to use it and restart: 217 218 ```sh 218 - crontab -e 219 - ``` 220 - Add: 221 - ``` 222 - 0 0 * * * acme-client pds.example.com && rcctl reload nginx 219 + rcctl restart nginx 223 220 ``` 224 221 ## 13. Configure Packet Filter (pf) 225 222 ```sh 226 223 cat >> /etc/pf.conf << 'EOF' 227 - # BSPDS rules 228 224 pass in on egress proto tcp from any to any port { 22, 80, 443 } 229 225 EOF 230 226 pfctl -f /etc/pf.conf
+224 -280
scripts/install-debian.sh
··· 1 1 #!/bin/bash 2 2 set -euo pipefail 3 + 3 4 RED='\033[0;31m' 4 5 GREEN='\033[0;32m' 5 6 YELLOW='\033[1;33m' 6 7 BLUE='\033[0;34m' 7 - CYAN='\033[0;36m' 8 8 NC='\033[0m' 9 + 9 10 log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } 10 11 log_success() { echo -e "${GREEN}[OK]${NC} $1"; } 11 12 log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } 12 13 log_error() { echo -e "${RED}[ERROR]${NC} $1"; } 14 + 13 15 if [[ $EUID -ne 0 ]]; then 14 16 log_error "This script must be run as root" 15 17 exit 1 16 18 fi 19 + 17 20 if ! grep -qi "debian" /etc/os-release 2>/dev/null; then 18 21 log_warn "This script is designed for Debian. Proceed with caution on other distros." 19 22 fi 23 + 20 24 nuke_installation() { 21 - echo -e "${RED}" 22 - echo "╔═══════════════════════════════════════════════════════════════════╗" 23 - echo "║ NUKING EXISTING INSTALLATION ║" 24 - echo "╚═══════════════════════════════════════════════════════════════════╝" 25 - echo -e "${NC}" 25 + log_warn "NUKING EXISTING INSTALLATION" 26 26 log_info "Stopping services..." 27 27 systemctl stop bspds 2>/dev/null || true 28 28 systemctl disable bspds 2>/dev/null || true 29 + 29 30 log_info "Removing BSPDS files..." 30 31 rm -rf /opt/bspds 31 32 rm -rf /var/lib/bspds ··· 35 36 rm -rf /var/spool/bspds-mail 36 37 rm -f /etc/systemd/system/bspds.service 37 38 systemctl daemon-reload 39 + 38 40 log_info "Removing BSPDS configuration..." 39 41 rm -rf /etc/bspds 42 + 40 43 log_info "Dropping postgres database and user..." 41 44 sudo -u postgres psql -c "DROP DATABASE IF EXISTS pds;" 2>/dev/null || true 42 45 sudo -u postgres psql -c "DROP USER IF EXISTS bspds;" 2>/dev/null || true 43 - log_info "Removing minio bucket and resetting minio..." 46 + 47 + log_info "Removing minio bucket..." 44 48 if command -v mc &>/dev/null; then 45 49 mc rb local/pds-blobs --force 2>/dev/null || true 46 50 mc alias remove local 2>/dev/null || true ··· 48 52 systemctl stop minio 2>/dev/null || true 49 53 rm -rf /var/lib/minio/data/.minio.sys 2>/dev/null || true 50 54 rm -f /etc/default/minio 2>/dev/null || true 55 + 51 56 log_info "Removing nginx config..." 52 57 rm -f /etc/nginx/sites-enabled/bspds 53 58 rm -f /etc/nginx/sites-available/bspds 54 59 systemctl reload nginx 2>/dev/null || true 55 - log_success "Previous installation nuked!" 56 - echo "" 60 + 61 + log_success "Previous installation nuked" 57 62 } 63 + 58 64 if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/bspds ]]; then 59 - echo -e "${YELLOW}" 60 - echo "╔═══════════════════════════════════════════════════════════════════╗" 61 - echo "║ EXISTING INSTALLATION DETECTED ║" 62 - echo "╚═══════════════════════════════════════════════════════════════════╝" 63 - echo -e "${NC}" 65 + log_warn "Existing installation detected" 64 66 echo "" 65 67 echo "Options:" 66 68 echo " 1) Nuke everything and start fresh (destroys database!)" ··· 68 70 echo " 3) Exit" 69 71 echo "" 70 72 read -p "Choose an option [1/2/3]: " INSTALL_CHOICE 73 + 71 74 case "$INSTALL_CHOICE" in 72 75 1) 73 76 echo "" 74 - echo -e "${RED}WARNING: This will DELETE:${NC}" 77 + log_warn "This will DELETE:" 75 78 echo " - PostgreSQL database 'pds' and all data" 76 79 echo " - All BSPDS configuration and credentials" 77 80 echo " - All source code in /opt/bspds" 78 81 echo " - MinIO bucket 'pds-blobs' and all blobs" 79 - echo " - Mail queue contents" 80 82 echo "" 81 - read -p "Type 'NUKE' to confirm destruction: " CONFIRM_NUKE 83 + read -p "Type 'NUKE' to confirm: " CONFIRM_NUKE 82 84 if [[ "$CONFIRM_NUKE" == "NUKE" ]]; then 83 85 nuke_installation 84 86 else 85 - log_error "Nuke cancelled. Exiting." 87 + log_error "Nuke cancelled" 86 88 exit 1 87 89 fi 88 90 ;; ··· 90 92 log_info "Continuing with existing installation..." 91 93 ;; 92 94 3) 93 - log_info "Exiting." 94 95 exit 0 95 96 ;; 96 97 *) 97 - log_error "Invalid option. Exiting." 98 + log_error "Invalid option" 98 99 exit 1 99 100 ;; 100 101 esac 101 102 fi 102 - echo -e "${CYAN}" 103 - echo "╔═══════════════════════════════════════════════════════════════════╗" 104 - echo "║ BSPDS Installation Script for Debian ║" 105 - echo "║ AT Protocol Personal Data Server in Rust ║" 106 - echo "╚═══════════════════════════════════════════════════════════════════╝" 107 - echo -e "${NC}" 103 + 104 + echo "" 105 + log_info "BSPDS Installation Script for Debian" 106 + echo "" 107 + 108 108 get_public_ips() { 109 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 "Not available") 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 111 } 112 + 112 113 log_info "Detecting public IP addresses..." 113 114 get_public_ips 115 + echo " IPv4: ${IPV4}" 116 + [[ -n "$IPV6" ]] && echo " IPv6: ${IPV6}" 114 117 echo "" 115 - echo -e "${CYAN}Your server's public IPs:${NC}" 116 - echo -e " IPv4: ${GREEN}${IPV4}${NC}" 117 - echo -e " IPv6: ${GREEN}${IPV6}${NC}" 118 - echo "" 118 + 119 119 read -p "Enter your PDS domain (e.g., pds.example.com): " PDS_DOMAIN 120 120 if [[ -z "$PDS_DOMAIN" ]]; then 121 121 log_error "Domain cannot be empty" 122 122 exit 1 123 123 fi 124 - read -p "Enter your email for Let's Encrypt notifications: " CERTBOT_EMAIL 124 + 125 + read -p "Enter your email for Let's Encrypt: " CERTBOT_EMAIL 125 126 if [[ -z "$CERTBOT_EMAIL" ]]; then 126 127 log_error "Email cannot be empty" 127 128 exit 1 128 129 fi 129 - echo "" 130 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 131 - echo -e "${YELLOW}DNS RECORDS REQUIRED${NC}" 132 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 133 - echo "" 134 - echo "Before continuing, create these DNS records at your registrar:" 135 - echo "" 136 - echo -e "${GREEN}A Record:${NC}" 137 - echo " Name: ${PDS_DOMAIN}" 138 - echo " Type: A" 139 - echo " Value: ${IPV4}" 130 + 140 131 echo "" 141 - if [[ "$IPV6" != "Not available" ]]; then 142 - echo -e "${GREEN}AAAA Record:${NC}" 143 - echo " Name: ${PDS_DOMAIN}" 144 - echo " Type: AAAA" 145 - echo " Value: ${IPV6}" 132 + log_info "DNS records required (create these now if you haven't):" 146 133 echo "" 147 - fi 148 - echo -e "${GREEN}Wildcard A Record (for user handles):${NC}" 149 - echo " Name: *.${PDS_DOMAIN}" 150 - echo " Type: A" 151 - echo " Value: ${IPV4}" 152 - echo "" 153 - if [[ "$IPV6" != "Not available" ]]; then 154 - echo -e "${GREEN}Wildcard AAAA Record (for user handles):${NC}" 155 - echo " Name: *.${PDS_DOMAIN}" 156 - echo " Type: AAAA" 157 - echo " Value: ${IPV6}" 158 - echo "" 159 - fi 160 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 134 + echo " ${PDS_DOMAIN} A ${IPV4}" 135 + [[ -n "$IPV6" ]] && echo " ${PDS_DOMAIN} AAAA ${IPV6}" 136 + echo " *.${PDS_DOMAIN} A ${IPV4} (for user handles)" 137 + [[ -n "$IPV6" ]] && echo " *.${PDS_DOMAIN} AAAA ${IPV6} (for user handles)" 161 138 echo "" 162 139 read -p "Have you created these DNS records? (y/N): " DNS_CONFIRMED 163 140 if [[ ! "$DNS_CONFIRMED" =~ ^[Yy]$ ]]; then 164 141 log_warn "Please create the DNS records and run this script again." 165 142 exit 0 166 143 fi 144 + 167 145 CREDENTIALS_FILE="/etc/bspds/.credentials" 168 146 if [[ -f "$CREDENTIALS_FILE" ]]; then 169 - log_info "Loading existing credentials from previous installation..." 147 + log_info "Loading existing credentials..." 170 148 source "$CREDENTIALS_FILE" 171 - log_success "Credentials loaded" 172 149 else 173 - log_info "Generating secure secrets..." 150 + log_info "Generating secrets..." 174 151 JWT_SECRET=$(openssl rand -base64 48) 175 152 DPOP_SECRET=$(openssl rand -base64 48) 176 153 MASTER_KEY=$(openssl rand -base64 48) 177 154 DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) 178 155 MINIO_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) 156 + 179 157 mkdir -p /etc/bspds 180 158 cat > "$CREDENTIALS_FILE" << EOF 181 159 JWT_SECRET="$JWT_SECRET" ··· 185 163 MINIO_PASSWORD="$MINIO_PASSWORD" 186 164 EOF 187 165 chmod 600 "$CREDENTIALS_FILE" 188 - log_success "Secrets generated and saved" 166 + log_success "Secrets generated" 189 167 fi 168 + 190 169 log_info "Checking swap space..." 191 170 TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') 192 171 TOTAL_SWAP_KB=$(grep SwapTotal /proc/meminfo | awk '{print $2}') 172 + 193 173 if [[ $TOTAL_SWAP_KB -lt 2000000 ]]; then 194 - log_info "Adding swap space (needed for compilation)..." 195 174 if [[ ! -f /swapfile ]]; then 175 + log_info "Adding swap space for compilation..." 196 176 SWAP_SIZE="4G" 197 - if [[ $TOTAL_MEM_KB -lt 2000000 ]]; then 198 - SWAP_SIZE="4G" 199 - elif [[ $TOTAL_MEM_KB -lt 4000000 ]]; then 200 - SWAP_SIZE="2G" 201 - fi 177 + [[ $TOTAL_MEM_KB -ge 4000000 ]] && SWAP_SIZE="2G" 202 178 fallocate -l $SWAP_SIZE /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=4096 203 179 chmod 600 /swapfile 204 180 mkswap /swapfile 205 181 swapon /swapfile 206 182 grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab 207 - log_success "Swap space added ($SWAP_SIZE)" 183 + log_success "Swap added ($SWAP_SIZE)" 208 184 else 209 185 swapon /swapfile 2>/dev/null || true 210 - log_success "Existing swap enabled" 211 186 fi 212 - else 213 - log_success "Sufficient swap already configured" 214 187 fi 188 + 215 189 log_info "Updating system packages..." 216 190 apt update && apt upgrade -y 217 - log_success "System updated" 191 + 218 192 log_info "Installing build dependencies..." 219 193 apt install -y curl git build-essential pkg-config libssl-dev ca-certificates gnupg lsb-release unzip xxd 220 - log_success "Build dependencies installed" 194 + 221 195 log_info "Installing postgres..." 222 196 apt install -y postgresql postgresql-contrib 223 197 systemctl enable postgresql ··· 226 200 sudo -u postgres psql -c "ALTER USER bspds WITH PASSWORD '${DB_PASSWORD}';" 227 201 sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;" 2>/dev/null || true 228 202 sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 229 - log_success "postgres installed and configured" 203 + log_success "postgres configured" 204 + 230 205 log_info "Installing valkey..." 231 - apt install -y valkey || { 232 - log_warn "valkey not in repos, trying redis..." 206 + apt install -y valkey 2>/dev/null || { 207 + log_warn "valkey not in repos, installing redis..." 233 208 apt install -y redis-server 234 209 systemctl enable redis-server 235 210 systemctl start redis-server 236 211 } 237 212 systemctl enable valkey-server 2>/dev/null || true 238 213 systemctl start valkey-server 2>/dev/null || true 239 - log_success "valkey/redis installed" 214 + 240 215 log_info "Installing minio..." 241 216 if [[ ! -f /usr/local/bin/minio ]]; then 242 217 ARCH=$(dpkg --print-architecture) 243 - if [[ "$ARCH" == "amd64" ]]; then 244 - curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-amd64/minio 245 - elif [[ "$ARCH" == "arm64" ]]; then 246 - curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-arm64/minio 247 - else 248 - log_error "Unsupported architecture: $ARCH" 249 - exit 1 250 - fi 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 251 223 chmod +x /tmp/minio 252 224 mv /tmp/minio /usr/local/bin/ 253 225 fi 226 + 254 227 mkdir -p /var/lib/minio/data 255 228 id -u minio-user &>/dev/null || useradd -r -s /sbin/nologin minio-user 256 229 chown -R minio-user:minio-user /var/lib/minio 230 + 257 231 cat > /etc/default/minio << EOF 258 232 MINIO_ROOT_USER=minioadmin 259 233 MINIO_ROOT_PASSWORD=${MINIO_PASSWORD} ··· 261 235 MINIO_OPTS="--console-address :9001" 262 236 EOF 263 237 chmod 600 /etc/default/minio 238 + 264 239 cat > /etc/systemd/system/minio.service << 'EOF' 265 240 [Unit] 266 241 Description=MinIO Object Storage 267 242 After=network.target 243 + 268 244 [Service] 269 245 User=minio-user 270 246 Group=minio-user ··· 272 248 ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS 273 249 Restart=always 274 250 LimitNOFILE=65536 251 + 275 252 [Install] 276 253 WantedBy=multi-user.target 277 254 EOF 255 + 278 256 systemctl daemon-reload 279 257 systemctl enable minio 280 258 systemctl start minio 281 259 log_success "minio installed" 282 - log_info "Waiting for minio to start..." 260 + 261 + log_info "Waiting for minio..." 283 262 sleep 5 284 - log_info "Installing minio client and creating bucket..." 263 + 285 264 if [[ ! -f /usr/local/bin/mc ]]; then 286 265 ARCH=$(dpkg --print-architecture) 287 - if [[ "$ARCH" == "amd64" ]]; then 288 - curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc 289 - elif [[ "$ARCH" == "arm64" ]]; then 290 - curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-arm64/mc 291 - fi 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 292 270 chmod +x /tmp/mc 293 271 mv /tmp/mc /usr/local/bin/ 294 272 fi 273 + 295 274 mc alias remove local 2>/dev/null || true 296 275 mc alias set local http://localhost:9000 minioadmin "${MINIO_PASSWORD}" --api S3v4 297 276 mc mb local/pds-blobs --ignore-existing 298 277 log_success "minio bucket created" 278 + 299 279 log_info "Installing rust..." 300 280 if [[ -f "$HOME/.cargo/env" ]]; then 301 281 source "$HOME/.cargo/env" ··· 304 284 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 305 285 source "$HOME/.cargo/env" 306 286 fi 307 - log_success "rust installed" 287 + 308 288 log_info "Installing deno..." 309 289 export PATH="$HOME/.deno/bin:$PATH" 310 290 if ! command -v deno &>/dev/null && [[ ! -f "$HOME/.deno/bin/deno" ]]; then 311 291 curl -fsSL https://deno.land/install.sh | sh 312 292 grep -q 'deno/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc 313 293 fi 314 - log_success "deno installed" 294 + 315 295 log_info "Cloning BSPDS..." 316 296 if [[ ! -d /opt/bspds ]]; then 317 297 git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/bspds 318 298 else 319 - log_warn "/opt/bspds already exists, pulling latest..." 320 299 cd /opt/bspds && git pull 321 300 fi 322 301 cd /opt/bspds 323 - log_success "BSPDS cloned" 302 + 324 303 log_info "Building frontend..." 325 - cd /opt/bspds/frontend 326 - "$HOME/.deno/bin/deno" task build 327 - cd /opt/bspds 304 + "$HOME/.deno/bin/deno" task build --filter=frontend 328 305 log_success "Frontend built" 329 - log_info "Building BSPDS (this may take a while)..." 306 + 307 + log_info "Building BSPDS (this takes a while)..." 330 308 source "$HOME/.cargo/env" 331 - NPROC=$(nproc) 332 309 if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then 333 - log_info "Low memory detected, limiting parallel jobs..." 310 + log_info "Low memory - limiting parallel jobs" 334 311 CARGO_BUILD_JOBS=1 cargo build --release 335 312 else 336 313 cargo build --release 337 314 fi 338 315 log_success "BSPDS built" 339 - log_info "Installing sqlx-cli and running migrations..." 316 + 317 + log_info "Running migrations..." 340 318 cargo install sqlx-cli --no-default-features --features postgres 341 319 export DATABASE_URL="postgres://bspds:${DB_PASSWORD}@localhost:5432/pds" 342 320 "$HOME/.cargo/bin/sqlx" migrate run 343 321 log_success "Migrations complete" 344 - log_info "Setting up mail trap for testing..." 322 + 323 + log_info "Setting up mail trap..." 345 324 mkdir -p /var/spool/bspds-mail 346 - chown root:root /var/spool/bspds-mail 347 325 chmod 1777 /var/spool/bspds-mail 326 + 348 327 cat > /usr/local/bin/bspds-sendmail << 'SENDMAIL_EOF' 349 328 #!/bin/bash 350 329 MAIL_DIR="/var/spool/bspds-mail" ··· 359 338 cat 360 339 } > "$MAIL_FILE" 361 340 chmod 644 "$MAIL_FILE" 362 - echo "Mail saved to: $MAIL_FILE" >&2 363 341 exit 0 364 342 SENDMAIL_EOF 365 343 chmod +x /usr/local/bin/bspds-sendmail 344 + 366 345 cat > /usr/local/bin/bspds-mailq << 'MAILQ_EOF' 367 346 #!/bin/bash 368 347 MAIL_DIR="/var/spool/bspds-mail" 369 - RED='\033[0;31m' 370 - GREEN='\033[0;32m' 371 - YELLOW='\033[1;33m' 372 - BLUE='\033[0;34m' 373 - CYAN='\033[0;36m' 374 - NC='\033[0m' 375 - show_help() { 376 - echo "bspds-mailq - View captured emails from BSPDS mail trap" 377 - echo "" 378 - echo "Usage:" 379 - echo " bspds-mailq List all captured emails" 380 - echo " bspds-mailq <number> View email by number (from list)" 381 - echo " bspds-mailq <filename> View email by filename" 382 - echo " bspds-mailq latest View the most recent email" 383 - echo " bspds-mailq clear Delete all captured emails" 384 - echo " bspds-mailq watch Watch for new emails (tail -f style)" 385 - echo " bspds-mailq count Show count of emails in queue" 386 - echo "" 387 - } 388 - list_emails() { 389 - if [[ ! -d "$MAIL_DIR" ]] || [[ -z "$(ls -A "$MAIL_DIR" 2>/dev/null)" ]]; then 390 - echo -e "${YELLOW}No emails in queue.${NC}" 391 - return 392 - fi 393 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 394 - echo -e "${GREEN} BSPDS Mail Queue${NC}" 395 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 396 - echo "" 397 - local i=1 398 - for f in $(ls -t "$MAIL_DIR"/*.eml 2>/dev/null); do 399 - local filename=$(basename "$f") 400 - local received=$(grep "^X-BSPDS-Received:" "$f" 2>/dev/null | cut -d' ' -f2-) 401 - local to=$(grep -i "^To:" "$f" 2>/dev/null | head -1 | cut -d' ' -f2-) 402 - local subject=$(grep -i "^Subject:" "$f" 2>/dev/null | head -1 | sed 's/^Subject: *//') 403 - echo -e "${BLUE}[$i]${NC} ${filename}" 404 - echo -e " To: ${GREEN}${to:-unknown}${NC}" 405 - echo -e " Subject: ${YELLOW}${subject:-<no subject>}${NC}" 406 - echo -e " Received: ${received:-unknown}" 407 - echo "" 408 - ((i++)) 409 - done 410 - echo -e "${CYAN}Total: $((i-1)) email(s)${NC}" 411 - } 412 - view_email() { 413 - local target="$1" 414 - local file="" 415 - if [[ "$target" == "latest" ]]; then 416 - file=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | head -1) 417 - elif [[ "$target" =~ ^[0-9]+$ ]]; then 418 - file=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | sed -n "${target}p") 419 - elif [[ -f "$MAIL_DIR/$target" ]]; then 420 - file="$MAIL_DIR/$target" 421 - elif [[ -f "$target" ]]; then 422 - file="$target" 423 - fi 424 - if [[ -z "$file" ]] || [[ ! -f "$file" ]]; then 425 - echo -e "${RED}Email not found: $target${NC}" 426 - return 1 427 - fi 428 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 429 - echo -e "${GREEN} $(basename "$file")${NC}" 430 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 431 - cat "$file" 432 - echo "" 433 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 434 - } 435 - clear_queue() { 436 - local count=$(ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l) 437 - if [[ "$count" -eq 0 ]]; then 438 - echo -e "${YELLOW}Queue is already empty.${NC}" 439 - return 440 - fi 441 - rm -f "$MAIL_DIR"/*.eml 442 - echo -e "${GREEN}Cleared $count email(s) from queue.${NC}" 443 - } 444 - watch_queue() { 445 - echo -e "${CYAN}Watching for new emails... (Ctrl+C to stop)${NC}" 446 - echo "" 447 - local last_count=0 448 - while true; do 449 - local current_count=$(ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l) 450 - if [[ "$current_count" -gt "$last_count" ]]; then 451 - echo -e "${GREEN}[$(date +%H:%M:%S)] New email received!${NC}" 452 - view_email latest 453 - last_count=$current_count 454 - fi 455 - sleep 1 456 - done 457 - } 458 - count_queue() { 459 - local count=$(ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l) 460 - echo "$count" 461 - } 462 - case "${1:-}" in 463 - ""|list) 464 - list_emails 348 + case "${1:-list}" in 349 + list) 350 + ls -lt "$MAIL_DIR"/*.eml 2>/dev/null | head -20 || echo "No emails" 465 351 ;; 466 - latest|[0-9]*) 467 - view_email "$1" 352 + latest) 353 + f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | head -1) 354 + [[ -f "$f" ]] && cat "$f" || echo "No emails" 468 355 ;; 469 356 clear) 470 - clear_queue 471 - ;; 472 - watch) 473 - watch_queue 357 + rm -f "$MAIL_DIR"/*.eml 358 + echo "Cleared" 474 359 ;; 475 360 count) 476 - count_queue 361 + ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l 477 362 ;; 478 - help|--help|-h) 479 - show_help 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" 480 366 ;; 481 367 *) 482 - if [[ -f "$MAIL_DIR/$1" ]] || [[ -f "$1" ]]; then 483 - view_email "$1" 484 - else 485 - echo -e "${RED}Unknown command: $1${NC}" 486 - show_help 487 - exit 1 488 - fi 368 + [[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: bspds-mailq [list|latest|clear|count|N]" 489 369 ;; 490 370 esac 491 371 MAILQ_EOF 492 372 chmod +x /usr/local/bin/bspds-mailq 493 - log_success "Mail trap configured" 373 + 494 374 log_info "Creating BSPDS configuration..." 495 - mkdir -p /etc/bspds 496 375 cat > /etc/bspds/bspds.env << EOF 497 376 SERVER_HOST=127.0.0.1 498 377 SERVER_PORT=3000 ··· 518 397 SENDMAIL_PATH=/usr/local/bin/bspds-sendmail 519 398 EOF 520 399 chmod 600 /etc/bspds/bspds.env 521 - log_success "Configuration created" 522 - log_info "Creating BSPDS service user..." 400 + 401 + log_info "Installing BSPDS..." 523 402 id -u bspds &>/dev/null || useradd -r -s /sbin/nologin bspds 524 403 cp /opt/bspds/target/release/bspds /usr/local/bin/ 525 404 mkdir -p /var/lib/bspds 526 405 cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend 527 406 chown -R bspds:bspds /var/lib/bspds 528 - log_success "BSPDS binary installed" 529 - log_info "Creating systemd service..." 407 + 530 408 cat > /etc/systemd/system/bspds.service << 'EOF' 531 409 [Unit] 532 410 Description=BSPDS - AT Protocol PDS 533 411 After=network.target postgresql.service minio.service 412 + 534 413 [Service] 535 414 Type=simple 536 415 User=bspds ··· 540 419 ExecStart=/usr/local/bin/bspds 541 420 Restart=always 542 421 RestartSec=5 422 + 543 423 [Install] 544 424 WantedBy=multi-user.target 545 425 EOF 426 + 546 427 systemctl daemon-reload 547 428 systemctl enable bspds 548 429 systemctl start bspds 549 - log_success "BSPDS service created and started" 430 + log_success "BSPDS service started" 431 + 550 432 log_info "Installing nginx..." 551 - apt install -y nginx certbot python3-certbot-nginx 552 - log_success "nginx installed" 553 - log_info "Configuring nginx..." 433 + apt install -y nginx 554 434 cat > /etc/nginx/sites-available/bspds << EOF 555 435 server { 556 436 listen 80; 557 437 listen [::]:80; 558 438 server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; 439 + 440 + location /.well-known/acme-challenge/ { 441 + root /var/www/html; 442 + } 443 + 559 444 location / { 560 445 proxy_pass http://127.0.0.1:3000; 561 446 proxy_http_version 1.1; ··· 571 456 } 572 457 } 573 458 EOF 459 + 574 460 ln -sf /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/ 575 461 rm -f /etc/nginx/sites-enabled/default 576 462 nginx -t 577 463 systemctl reload nginx 578 464 log_success "nginx configured" 579 - log_info "Configuring firewall (ufw)..." 465 + 466 + log_info "Configuring firewall..." 580 467 apt install -y ufw 581 468 ufw --force reset 582 469 ufw default deny incoming 583 470 ufw default allow outgoing 584 - ufw allow ssh comment 'SSH' 585 - ufw allow 80/tcp comment 'HTTP' 586 - ufw allow 443/tcp comment 'HTTPS' 471 + ufw allow ssh 472 + ufw allow 80/tcp 473 + ufw allow 443/tcp 587 474 ufw --force enable 588 475 log_success "Firewall configured" 589 - log_info "Obtaining SSL certificate..." 590 - certbot --nginx -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" --email "${CERTBOT_EMAIL}" --agree-tos --non-interactive || { 591 - log_warn "Wildcard cert failed (requires DNS challenge). Trying single domain..." 592 - certbot --nginx -d "${PDS_DOMAIN}" --email "${CERTBOT_EMAIL}" --agree-tos --non-interactive 476 + 477 + echo "" 478 + log_info "Obtaining wildcard SSL certificate..." 479 + echo "" 480 + echo "User handles are served as subdomains (e.g., alice.${PDS_DOMAIN})," 481 + echo "so you need a wildcard certificate. This requires DNS validation." 482 + echo "" 483 + echo "You'll need to add a TXT record to your DNS when prompted." 484 + echo "" 485 + read -p "Ready to proceed? (y/N): " CERT_READY 486 + 487 + if [[ "$CERT_READY" =~ ^[Yy]$ ]]; then 488 + apt install -y certbot python3-certbot-nginx 489 + 490 + log_info "Running certbot with DNS challenge..." 491 + echo "" 492 + echo "When prompted, add the TXT record to your DNS, wait a minute" 493 + echo "for propagation, then press Enter to continue." 494 + echo "" 495 + 496 + if certbot certonly --manual --preferred-challenges dns \ 497 + -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" \ 498 + --email "${CERTBOT_EMAIL}" --agree-tos; then 499 + 500 + cat > /etc/nginx/sites-available/bspds << EOF 501 + server { 502 + listen 80; 503 + listen [::]:80; 504 + server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; 505 + 506 + location /.well-known/acme-challenge/ { 507 + root /var/www/html; 508 + } 509 + 510 + location / { 511 + return 301 https://\$host\$request_uri; 512 + } 593 513 } 594 - log_success "SSL certificate obtained" 514 + 515 + server { 516 + listen 443 ssl http2; 517 + listen [::]:443 ssl http2; 518 + server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; 519 + 520 + ssl_certificate /etc/letsencrypt/live/${PDS_DOMAIN}/fullchain.pem; 521 + ssl_certificate_key /etc/letsencrypt/live/${PDS_DOMAIN}/privkey.pem; 522 + ssl_protocols TLSv1.2 TLSv1.3; 523 + ssl_ciphers HIGH:!aNULL:!MD5; 524 + ssl_prefer_server_ciphers on; 525 + ssl_session_cache shared:SSL:10m; 526 + 527 + location / { 528 + proxy_pass http://127.0.0.1:3000; 529 + proxy_http_version 1.1; 530 + proxy_set_header Upgrade \$http_upgrade; 531 + proxy_set_header Connection "upgrade"; 532 + proxy_set_header Host \$host; 533 + proxy_set_header X-Real-IP \$remote_addr; 534 + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 535 + proxy_set_header X-Forwarded-Proto \$scheme; 536 + proxy_read_timeout 86400; 537 + proxy_send_timeout 86400; 538 + client_max_body_size 100M; 539 + } 540 + } 541 + EOF 542 + nginx -t && systemctl reload nginx 543 + log_success "Wildcard SSL certificate installed" 544 + 545 + echo "" 546 + log_warn "Certificate renewal note:" 547 + echo "Manual DNS challenges don't auto-renew. Before expiry, run:" 548 + echo " certbot renew --manual" 549 + echo "" 550 + echo "For auto-renewal, consider using a DNS provider plugin:" 551 + echo " apt install python3-certbot-dns-cloudflare # or your provider" 552 + echo "" 553 + else 554 + log_warn "Wildcard cert failed. You can retry later with:" 555 + echo " certbot certonly --manual --preferred-challenges dns \\" 556 + echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'" 557 + fi 558 + else 559 + log_warn "Skipping SSL. Your PDS is running on HTTP only." 560 + echo "To add SSL later, run:" 561 + echo " certbot certonly --manual --preferred-challenges dns \\" 562 + echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'" 563 + fi 564 + 595 565 log_info "Verifying installation..." 596 566 sleep 3 597 567 if curl -s "http://localhost:3000/xrpc/_health" | grep -q "version"; then 598 - log_success "BSPDS is responding!" 568 + log_success "BSPDS is responding" 599 569 else 600 - log_warn "BSPDS may still be starting up. Check: journalctl -u bspds -f" 570 + log_warn "BSPDS may still be starting. Check: journalctl -u bspds -f" 601 571 fi 572 + 602 573 echo "" 603 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 604 - echo -e "${GREEN} INSTALLATION COMPLETE!${NC}" 605 - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" 574 + log_success "Installation complete" 606 575 echo "" 607 - echo -e "Your PDS is now running at: ${GREEN}https://${PDS_DOMAIN}${NC}" 576 + echo "PDS: https://${PDS_DOMAIN}" 608 577 echo "" 609 - echo -e "${YELLOW}IMPORTANT: Save these credentials securely!${NC}" 578 + echo "Credentials (also in /etc/bspds/.credentials):" 579 + echo " DB password: ${DB_PASSWORD}" 580 + echo " MinIO password: ${MINIO_PASSWORD}" 610 581 echo "" 611 - echo "Database password: ${DB_PASSWORD}" 612 - echo "MinIO password: ${MINIO_PASSWORD}" 582 + echo "Commands:" 583 + echo " journalctl -u bspds -f # logs" 584 + echo " systemctl restart bspds # restart" 585 + echo " bspds-mailq # view trapped emails" 613 586 echo "" 614 - echo "Configuration file: /etc/bspds/bspds.env" 615 - echo "" 616 - echo -e "${CYAN}Useful commands:${NC}" 617 - echo " journalctl -u bspds -f # View BSPDS logs" 618 - echo " systemctl status bspds # Check BSPDS status" 619 - echo " systemctl restart bspds # Restart BSPDS" 620 - echo " curl https://${PDS_DOMAIN}/xrpc/_health # Health check" 621 - echo "" 622 - echo -e "${CYAN}Mail queue (for testing):${NC}" 623 - echo " bspds-mailq # List all captured emails" 624 - echo " bspds-mailq latest # View most recent email" 625 - echo " bspds-mailq 1 # View email #1 from list" 626 - echo " bspds-mailq watch # Watch for new emails live" 627 - echo " bspds-mailq clear # Clear all captured emails" 628 - echo "" 629 - echo " Emails are saved to: /var/spool/bspds-mail/" 630 - echo "" 631 - echo -e "${CYAN}DNS Records Summary:${NC}" 632 - echo "" 633 - echo " ${PDS_DOMAIN} A ${IPV4}" 634 - if [[ "$IPV6" != "Not available" ]]; then 635 - echo " ${PDS_DOMAIN} AAAA ${IPV6}" 636 - fi 637 - echo " *.${PDS_DOMAIN} A ${IPV4}" 638 - if [[ "$IPV6" != "Not available" ]]; then 639 - echo " *.${PDS_DOMAIN} AAAA ${IPV6}" 640 - fi 641 - echo "" 642 - echo -e "${GREEN}Enjoy your new AT Protocol PDS!${NC}"