this repo has no description

Some theoretical installation instructions, for me to verify

lewis fcaf28f9 2f185d97

+23
README.md
··· 32 32 just lint # clippy + fmt 33 33 ``` 34 34 35 + ## Production Deployment 36 + 37 + ### Quick Deploy (Docker/Podman Compose) 38 + 39 + ```bash 40 + cp .env.prod.example .env.prod 41 + # Edit .env.prod with your values (generate secrets with: openssl rand -base64 48) 42 + podman-compose -f docker-compose.prod.yml up -d 43 + ``` 44 + 45 + ### Full Installation Guides 46 + 47 + | Guide | Best For | 48 + |-------|----------| 49 + | **Native Installation** | Maximum performance, full control | 50 + | [Debian](docs/install-debian.md) | Debian 13+ with systemd | 51 + | [Alpine](docs/install-alpine.md) | Alpine 3.23+ with OpenRC | 52 + | [OpenBSD](docs/install-openbsd.md) | OpenBSD 7.8+ with rc.d | 53 + | **Containerized** | Easier updates, isolation | 54 + | [Containers](docs/install-containers.md) | Podman with quadlets (Debian) or OpenRC (Alpine) | 55 + | **Orchestrated** | High availability, auto-scaling | 56 + | [Kubernetes](docs/install-kubernetes.md) | Multi-node k8s cluster deployment | 57 + 35 58 ## License 36 59 37 60 TBD
+61
deploy/nginx/nginx-quadlet.conf
··· 1 + worker_processes auto; 2 + error_log /var/log/nginx/error.log warn; 3 + 4 + events { 5 + worker_connections 4096; 6 + } 7 + 8 + http { 9 + include /etc/nginx/mime.types; 10 + default_type application/octet-stream; 11 + access_log /var/log/nginx/access.log; 12 + sendfile on; 13 + keepalive_timeout 65; 14 + 15 + gzip on; 16 + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; 17 + 18 + ssl_protocols TLSv1.2 TLSv1.3; 19 + ssl_prefer_server_ciphers off; 20 + ssl_session_cache shared:SSL:10m; 21 + ssl_stapling on; 22 + ssl_stapling_verify on; 23 + 24 + server { 25 + listen 80; 26 + listen [::]:80; 27 + server_name _; 28 + 29 + location /.well-known/acme-challenge/ { 30 + root /var/www/acme; 31 + } 32 + 33 + location / { 34 + return 301 https://$host$request_uri; 35 + } 36 + } 37 + 38 + server { 39 + listen 443 ssl http2; 40 + listen [::]:443 ssl http2; 41 + server_name _; 42 + 43 + ssl_certificate /etc/nginx/certs/fullchain.pem; 44 + ssl_certificate_key /etc/nginx/certs/privkey.pem; 45 + client_max_body_size 100M; 46 + 47 + location / { 48 + proxy_pass http://127.0.0.1:3000; 49 + proxy_http_version 1.1; 50 + proxy_set_header Upgrade $http_upgrade; 51 + proxy_set_header Connection "upgrade"; 52 + proxy_set_header Host $host; 53 + proxy_set_header X-Real-IP $remote_addr; 54 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 55 + proxy_set_header X-Forwarded-Proto $scheme; 56 + proxy_read_timeout 86400; 57 + proxy_send_timeout 86400; 58 + proxy_buffering off; 59 + } 60 + } 61 + }
+28
deploy/quadlets/bspds-app.container
··· 1 + [Unit] 2 + Description=BSPDS AT Protocol PDS 3 + After=bspds-db.service bspds-minio.service bspds-valkey.service 4 + 5 + [Container] 6 + ContainerName=bspds-app 7 + Image=localhost/bspds:latest 8 + Pod=bspds.pod 9 + EnvironmentFile=/srv/bspds/config/bspds.env 10 + Environment=SERVER_HOST=0.0.0.0 11 + Environment=SERVER_PORT=3000 12 + Environment=S3_ENDPOINT=http://localhost:9000 13 + Environment=AWS_REGION=us-east-1 14 + Environment=S3_BUCKET=pds-blobs 15 + Environment=VALKEY_URL=redis://localhost:6379 16 + Environment=FRONTEND_DIR=/app/frontend/dist 17 + HealthCmd=wget -q --spider http://localhost:3000/xrpc/_health 18 + HealthInterval=30s 19 + HealthTimeout=10s 20 + HealthRetries=3 21 + HealthStartPeriod=15s 22 + 23 + [Service] 24 + Restart=always 25 + RestartSec=10 26 + 27 + [Install] 28 + WantedBy=default.target
+23
deploy/quadlets/bspds-db.container
··· 1 + [Unit] 2 + Description=BSPDS postgres database 3 + 4 + [Container] 5 + ContainerName=bspds-db 6 + Image=docker.io/library/postgres:18-alpine 7 + Pod=bspds.pod 8 + Environment=POSTGRES_USER=bspds 9 + Environment=POSTGRES_DB=pds 10 + Secret=bspds-db-password,type=env,target=POSTGRES_PASSWORD 11 + Volume=/srv/bspds/postgres:/var/lib/postgresql/data:Z 12 + HealthCmd=pg_isready -U bspds -d pds 13 + HealthInterval=10s 14 + HealthTimeout=5s 15 + HealthRetries=5 16 + HealthStartPeriod=10s 17 + 18 + [Service] 19 + Restart=always 20 + RestartSec=10 21 + 22 + [Install] 23 + WantedBy=default.target
+23
deploy/quadlets/bspds-minio.container
··· 1 + [Unit] 2 + Description=BSPDS minio object storage 3 + 4 + [Container] 5 + ContainerName=bspds-minio 6 + Image=docker.io/minio/minio:RELEASE.2025-10-15T17-29-55Z 7 + Pod=bspds.pod 8 + Environment=MINIO_ROOT_USER=minioadmin 9 + Secret=bspds-minio-password,type=env,target=MINIO_ROOT_PASSWORD 10 + Volume=/srv/bspds/minio:/data:Z 11 + Exec=server /data --console-address :9001 12 + HealthCmd=curl -f http://localhost:9000/minio/health/live || exit 1 13 + HealthInterval=30s 14 + HealthTimeout=10s 15 + HealthRetries=3 16 + HealthStartPeriod=10s 17 + 18 + [Service] 19 + Restart=always 20 + RestartSec=10 21 + 22 + [Install] 23 + WantedBy=default.target
+18
deploy/quadlets/bspds-nginx.container
··· 1 + [Unit] 2 + Description=BSPDS nginx reverse proxy 3 + After=bspds-app.service 4 + 5 + [Container] 6 + ContainerName=bspds-nginx 7 + Image=docker.io/library/nginx:1.28-alpine 8 + Pod=bspds.pod 9 + Volume=/srv/bspds/config/nginx.conf:/etc/nginx/nginx.conf:ro,Z 10 + Volume=/srv/bspds/certs:/etc/nginx/certs:ro,Z 11 + Volume=/srv/bspds/acme:/var/www/acme:ro,Z 12 + 13 + [Service] 14 + Restart=always 15 + RestartSec=10 16 + 17 + [Install] 18 + WantedBy=default.target
+21
deploy/quadlets/bspds-valkey.container
··· 1 + [Unit] 2 + Description=BSPDS valkey cache 3 + 4 + [Container] 5 + ContainerName=bspds-valkey 6 + Image=docker.io/valkey/valkey:9-alpine 7 + Pod=bspds.pod 8 + Volume=/srv/bspds/valkey:/data:Z 9 + Exec=valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru 10 + HealthCmd=valkey-cli ping 11 + HealthInterval=10s 12 + HealthTimeout=5s 13 + HealthRetries=3 14 + HealthStartPeriod=5s 15 + 16 + [Service] 17 + Restart=always 18 + RestartSec=10 19 + 20 + [Install] 21 + WantedBy=default.target
+7
deploy/quadlets/bspds.pod
··· 1 + [Pod] 2 + PodName=bspds 3 + PublishPort=80:80 4 + PublishPort=443:443 5 + 6 + [Install] 7 + WantedBy=default.target
+173
docker-compose.prod.yml
··· 1 + services: 2 + bspds: 3 + build: 4 + context: . 5 + dockerfile: Dockerfile 6 + image: bspds:latest 7 + restart: unless-stopped 8 + ports: 9 + - "127.0.0.1:3000:3000" 10 + environment: 11 + SERVER_HOST: "0.0.0.0" 12 + SERVER_PORT: "3000" 13 + PDS_HOSTNAME: "${PDS_HOSTNAME:?PDS_HOSTNAME is required}" 14 + DATABASE_URL: "postgres://bspds:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/pds" 15 + S3_ENDPOINT: "http://minio:9000" 16 + AWS_REGION: "us-east-1" 17 + S3_BUCKET: "pds-blobs" 18 + AWS_ACCESS_KEY_ID: "${MINIO_ROOT_USER:-minioadmin}" 19 + AWS_SECRET_ACCESS_KEY: "${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}" 20 + VALKEY_URL: "redis://valkey:6379" 21 + JWT_SECRET: "${JWT_SECRET:?JWT_SECRET is required (min 32 chars)}" 22 + DPOP_SECRET: "${DPOP_SECRET:?DPOP_SECRET is required (min 32 chars)}" 23 + MASTER_KEY: "${MASTER_KEY:?MASTER_KEY is required (min 32 chars)}" 24 + APPVIEW_URL: "${APPVIEW_URL:-https://api.bsky.app}" 25 + CRAWLERS: "${CRAWLERS:-https://bsky.network}" 26 + FRONTEND_DIR: "/app/frontend/dist" 27 + depends_on: 28 + db: 29 + condition: service_healthy 30 + minio: 31 + condition: service_healthy 32 + valkey: 33 + condition: service_healthy 34 + healthcheck: 35 + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/xrpc/_health"] 36 + interval: 30s 37 + timeout: 10s 38 + retries: 3 39 + start_period: 10s 40 + deploy: 41 + resources: 42 + limits: 43 + memory: 1G 44 + reservations: 45 + memory: 256M 46 + 47 + db: 48 + image: postgres:18-alpine 49 + restart: unless-stopped 50 + environment: 51 + POSTGRES_USER: bspds 52 + POSTGRES_PASSWORD: "${DB_PASSWORD:?DB_PASSWORD is required}" 53 + POSTGRES_DB: pds 54 + volumes: 55 + - postgres_data:/var/lib/postgresql/data 56 + healthcheck: 57 + test: ["CMD-SHELL", "pg_isready -U bspds -d pds"] 58 + interval: 10s 59 + timeout: 5s 60 + retries: 5 61 + start_period: 10s 62 + deploy: 63 + resources: 64 + limits: 65 + memory: 512M 66 + reservations: 67 + memory: 128M 68 + 69 + minio: 70 + image: minio/minio:RELEASE.2025-10-15T17-29-55Z 71 + restart: unless-stopped 72 + command: server /data --console-address ":9001" 73 + environment: 74 + MINIO_ROOT_USER: "${MINIO_ROOT_USER:-minioadmin}" 75 + MINIO_ROOT_PASSWORD: "${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}" 76 + volumes: 77 + - minio_data:/data 78 + healthcheck: 79 + test: ["CMD", "mc", "ready", "local"] 80 + interval: 30s 81 + timeout: 10s 82 + retries: 3 83 + start_period: 10s 84 + deploy: 85 + resources: 86 + limits: 87 + memory: 512M 88 + reservations: 89 + memory: 128M 90 + 91 + minio-init: 92 + image: minio/mc:RELEASE.2025-07-16T15-35-03Z 93 + depends_on: 94 + minio: 95 + condition: service_healthy 96 + entrypoint: > 97 + /bin/sh -c " 98 + mc alias set local http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}; 99 + mc mb --ignore-existing local/pds-blobs; 100 + mc anonymous set none local/pds-blobs; 101 + exit 0; 102 + " 103 + environment: 104 + MINIO_ROOT_USER: "${MINIO_ROOT_USER:-minioadmin}" 105 + MINIO_ROOT_PASSWORD: "${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}" 106 + 107 + valkey: 108 + image: valkey/valkey:9-alpine 109 + restart: unless-stopped 110 + command: valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru 111 + volumes: 112 + - valkey_data:/data 113 + healthcheck: 114 + test: ["CMD", "valkey-cli", "ping"] 115 + interval: 10s 116 + timeout: 5s 117 + retries: 3 118 + start_period: 5s 119 + deploy: 120 + resources: 121 + limits: 122 + memory: 300M 123 + reservations: 124 + memory: 64M 125 + 126 + nginx: 127 + image: nginx:1.28-alpine 128 + restart: unless-stopped 129 + ports: 130 + - "80:80" 131 + - "443:443" 132 + volumes: 133 + - ./nginx.prod.conf:/etc/nginx/nginx.conf:ro 134 + - ./certs:/etc/nginx/certs:ro 135 + - acme_challenge:/var/www/acme:ro 136 + depends_on: 137 + - bspds 138 + healthcheck: 139 + test: ["CMD", "nginx", "-t"] 140 + interval: 30s 141 + timeout: 10s 142 + retries: 3 143 + 144 + certbot: 145 + image: certbot/certbot:v5.2.2 146 + volumes: 147 + - ./certs:/etc/letsencrypt 148 + - acme_challenge:/var/www/acme 149 + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/acme; sleep 12h & wait $${!}; done'" 150 + 151 + prometheus: 152 + image: prom/prometheus:v3.8.0 153 + restart: unless-stopped 154 + ports: 155 + - "127.0.0.1:9090:9090" 156 + volumes: 157 + - ./observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro 158 + - prometheus_data:/prometheus 159 + command: 160 + - '--config.file=/etc/prometheus/prometheus.yml' 161 + - '--storage.tsdb.path=/prometheus' 162 + - '--storage.tsdb.retention.time=30d' 163 + deploy: 164 + resources: 165 + limits: 166 + memory: 256M 167 + 168 + volumes: 169 + postgres_data: 170 + minio_data: 171 + valkey_data: 172 + prometheus_data: 173 + acme_challenge:
+2 -2
docker-compose.yaml
··· 18 18 - cache 19 19 20 20 db: 21 - image: postgres:latest 21 + image: postgres:18-alpine 22 22 environment: 23 23 POSTGRES_USER: postgres 24 24 POSTGRES_PASSWORD: postgres ··· 48 48 - valkey_data:/data 49 49 50 50 prometheus: 51 - image: prom/prometheus:latest 51 + image: prom/prometheus:v3.8.0 52 52 ports: 53 53 - "9090:9090" 54 54 volumes:
+313
docs/install-alpine.md
··· 1 + # BSPDS Production Installation on Alpine Linux 2 + 3 + > **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. 4 + 5 + This guide covers installing BSPDS on Alpine Linux 3.23 (current stable as of December 2025). 6 + 7 + ## Choose Your Installation Method 8 + 9 + | Method | Best For | 10 + |--------|----------| 11 + | **Native (this guide)** | Maximum performance, minimal footprint, full control | 12 + | **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments | 13 + | **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling | 14 + 15 + This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md). 16 + 17 + --- 18 + 19 + ## Prerequisites 20 + 21 + - A VPS with at least 2GB RAM and 20GB disk 22 + - A domain name pointing to your server's IP 23 + - Root access 24 + 25 + ## 1. System Setup 26 + 27 + ```sh 28 + apk update && apk upgrade 29 + apk add curl git build-base openssl-dev pkgconf 30 + ``` 31 + 32 + ## 2. Install Rust 33 + 34 + ```sh 35 + apk add rustup 36 + rustup-init -y 37 + source ~/.cargo/env 38 + rustup default stable 39 + ``` 40 + 41 + This installs the latest stable Rust (1.92+ as of December 2025). Alpine 3.23 also ships Rust 1.91 via `apk add rust cargo` if you prefer system packages. 42 + 43 + ## 3. Install postgres 44 + 45 + Alpine 3.23 includes PostgreSQL 18: 46 + 47 + ```sh 48 + apk add postgresql postgresql-contrib 49 + 50 + rc-update add postgresql 51 + /etc/init.d/postgresql setup 52 + rc-service postgresql start 53 + 54 + psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';" 55 + psql -U postgres -c "CREATE DATABASE pds OWNER bspds;" 56 + psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 57 + ``` 58 + 59 + ## 4. Install minio 60 + 61 + ```sh 62 + curl -O https://dl.min.io/server/minio/release/linux-amd64/minio 63 + chmod +x minio 64 + mv minio /usr/local/bin/ 65 + 66 + mkdir -p /var/lib/minio/data 67 + adduser -D -H -s /sbin/nologin minio-user 68 + chown -R minio-user:minio-user /var/lib/minio 69 + 70 + cat > /etc/conf.d/minio << 'EOF' 71 + MINIO_ROOT_USER="minioadmin" 72 + MINIO_ROOT_PASSWORD="your-minio-password" 73 + MINIO_VOLUMES="/var/lib/minio/data" 74 + MINIO_OPTS="--console-address :9001" 75 + EOF 76 + 77 + cat > /etc/init.d/minio << 'EOF' 78 + #!/sbin/openrc-run 79 + 80 + name="minio" 81 + description="MinIO Object Storage" 82 + 83 + command="/usr/local/bin/minio" 84 + command_args="server ${MINIO_VOLUMES} ${MINIO_OPTS}" 85 + command_user="minio-user" 86 + command_background=true 87 + pidfile="/run/${RC_SVCNAME}.pid" 88 + output_log="/var/log/minio.log" 89 + error_log="/var/log/minio.log" 90 + 91 + depend() { 92 + need net 93 + } 94 + 95 + start_pre() { 96 + . /etc/conf.d/minio 97 + export MINIO_ROOT_USER MINIO_ROOT_PASSWORD 98 + } 99 + EOF 100 + 101 + chmod +x /etc/init.d/minio 102 + rc-update add minio 103 + rc-service minio start 104 + ``` 105 + 106 + Create the blob bucket (wait a few seconds for minio to start): 107 + 108 + ```sh 109 + curl -O https://dl.min.io/client/mc/release/linux-amd64/mc 110 + chmod +x mc 111 + mv mc /usr/local/bin/ 112 + 113 + mc alias set local http://localhost:9000 minioadmin your-minio-password 114 + mc mb local/pds-blobs 115 + ``` 116 + 117 + ## 5. Install valkey 118 + 119 + Alpine 3.23 includes Valkey 9: 120 + 121 + ```sh 122 + apk add valkey 123 + 124 + rc-update add valkey 125 + rc-service valkey start 126 + ``` 127 + 128 + ## 6. Install deno (for frontend build) 129 + 130 + ```sh 131 + curl -fsSL https://deno.land/install.sh | sh 132 + export PATH="$HOME/.deno/bin:$PATH" 133 + echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile 134 + ``` 135 + 136 + ## 7. Clone and Build BSPDS 137 + 138 + ```sh 139 + mkdir -p /opt && cd /opt 140 + git clone https://tangled.org/lewis.moe/bspds.git 141 + cd bspds 142 + 143 + cd frontend 144 + deno task build 145 + cd .. 146 + 147 + cargo build --release 148 + ``` 149 + 150 + ## 8. Install sqlx-cli and Run Migrations 151 + 152 + ```sh 153 + cargo install sqlx-cli --no-default-features --features postgres 154 + 155 + export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" 156 + sqlx migrate run 157 + ``` 158 + 159 + ## 9. Configure BSPDS 160 + 161 + ```sh 162 + mkdir -p /etc/bspds 163 + cp /opt/bspds/.env.example /etc/bspds/bspds.env 164 + chmod 600 /etc/bspds/bspds.env 165 + ``` 166 + 167 + Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with: 168 + 169 + ```sh 170 + openssl rand -base64 48 171 + ``` 172 + 173 + ## 10. Create OpenRC Service 174 + 175 + ```sh 176 + adduser -D -H -s /sbin/nologin bspds 177 + 178 + cp /opt/bspds/target/release/bspds /usr/local/bin/ 179 + mkdir -p /var/lib/bspds 180 + cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend 181 + chown -R bspds:bspds /var/lib/bspds 182 + 183 + cat > /etc/init.d/bspds << 'EOF' 184 + #!/sbin/openrc-run 185 + 186 + name="bspds" 187 + description="BSPDS - AT Protocol PDS" 188 + 189 + command="/usr/local/bin/bspds" 190 + command_user="bspds" 191 + command_background=true 192 + pidfile="/run/${RC_SVCNAME}.pid" 193 + output_log="/var/log/bspds.log" 194 + error_log="/var/log/bspds.log" 195 + 196 + depend() { 197 + need net postgresql minio 198 + } 199 + 200 + start_pre() { 201 + export FRONTEND_DIR=/var/lib/bspds/frontend 202 + . /etc/bspds/bspds.env 203 + export SERVER_HOST SERVER_PORT PDS_HOSTNAME DATABASE_URL 204 + export S3_ENDPOINT AWS_REGION S3_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY 205 + export VALKEY_URL JWT_SECRET DPOP_SECRET MASTER_KEY APPVIEW_URL CRAWLERS 206 + } 207 + EOF 208 + 209 + chmod +x /etc/init.d/bspds 210 + rc-update add bspds 211 + rc-service bspds start 212 + ``` 213 + 214 + ## 11. Install and Configure nginx 215 + 216 + Alpine 3.23 includes nginx 1.28: 217 + 218 + ```sh 219 + apk add nginx certbot certbot-nginx 220 + 221 + cat > /etc/nginx/http.d/bspds.conf << 'EOF' 222 + server { 223 + listen 80; 224 + listen [::]:80; 225 + server_name pds.example.com; 226 + 227 + location / { 228 + proxy_pass http://127.0.0.1:3000; 229 + proxy_http_version 1.1; 230 + proxy_set_header Upgrade $http_upgrade; 231 + proxy_set_header Connection "upgrade"; 232 + proxy_set_header Host $host; 233 + proxy_set_header X-Real-IP $remote_addr; 234 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 235 + proxy_set_header X-Forwarded-Proto $scheme; 236 + proxy_read_timeout 86400; 237 + } 238 + } 239 + EOF 240 + 241 + rc-update add nginx 242 + rc-service nginx start 243 + ``` 244 + 245 + ## 12. Obtain SSL Certificate 246 + 247 + ```sh 248 + certbot --nginx -d pds.example.com 249 + ``` 250 + 251 + Set up auto-renewal: 252 + 253 + ```sh 254 + echo "0 0 * * * certbot renew --quiet" | crontab - 255 + ``` 256 + 257 + ## 13. Configure Firewall 258 + 259 + ```sh 260 + apk add iptables ip6tables 261 + 262 + iptables -A INPUT -p tcp --dport 22 -j ACCEPT 263 + iptables -A INPUT -p tcp --dport 80 -j ACCEPT 264 + iptables -A INPUT -p tcp --dport 443 -j ACCEPT 265 + iptables -A INPUT -i lo -j ACCEPT 266 + iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 267 + iptables -P INPUT DROP 268 + 269 + ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT 270 + ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT 271 + ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT 272 + ip6tables -A INPUT -i lo -j ACCEPT 273 + ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 274 + ip6tables -P INPUT DROP 275 + 276 + rc-update add iptables 277 + rc-update add ip6tables 278 + /etc/init.d/iptables save 279 + /etc/init.d/ip6tables save 280 + ``` 281 + 282 + ## 14. Verify Installation 283 + 284 + ```sh 285 + rc-service bspds status 286 + curl -s https://pds.example.com/xrpc/_health 287 + curl -s https://pds.example.com/.well-known/atproto-did 288 + ``` 289 + 290 + ## Maintenance 291 + 292 + View logs: 293 + ```sh 294 + tail -f /var/log/bspds.log 295 + ``` 296 + 297 + Update BSPDS: 298 + ```sh 299 + cd /opt/bspds 300 + git pull 301 + cd frontend && deno task build && cd .. 302 + cargo build --release 303 + rc-service bspds stop 304 + cp target/release/bspds /usr/local/bin/ 305 + cp -r frontend/dist /var/lib/bspds/frontend 306 + DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run 307 + rc-service bspds start 308 + ``` 309 + 310 + Backup database: 311 + ```sh 312 + pg_dump -U postgres pds > /var/backups/pds-$(date +%Y%m%d).sql 313 + ```
+428
docs/install-containers.md
··· 1 + # BSPDS Containerized Production Deployment 2 + 3 + > **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. 4 + 5 + This guide covers deploying BSPDS using containers with podman. 6 + 7 + - **Debian 13+**: Uses systemd quadlets (modern, declarative container management) 8 + - **Alpine 3.23+**: Uses OpenRC service script with podman-compose 9 + 10 + ## Prerequisites 11 + 12 + - A VPS with at least 2GB RAM and 20GB disk 13 + - A domain name pointing to your server's IP 14 + - Root or sudo access 15 + 16 + ## Quick Start (Docker/Podman Compose) 17 + 18 + If you just want to get running quickly: 19 + 20 + ```sh 21 + cp .env.example .env 22 + 23 + # Edit .env with your values 24 + # Generate secrets: openssl rand -base64 48 25 + 26 + # Build and start 27 + podman-compose -f docker-compose.prod.yml up -d 28 + 29 + # Get initial certificate (after DNS is configured) 30 + podman-compose -f docker-compose.prod.yml run --rm certbot certonly \ 31 + --webroot -w /var/www/acme -d pds.example.com 32 + 33 + # Restart nginx to load certificate 34 + podman-compose -f docker-compose.prod.yml restart nginx 35 + ``` 36 + 37 + For production setups with proper service management, continue to either the Debian or Alpine section below. 38 + 39 + --- 40 + 41 + # Debian 13+ with Systemd Quadlets 42 + 43 + Quadlets are the modern way to run podman containers under systemd. 44 + 45 + ## 1. Install Podman 46 + 47 + ```bash 48 + apt update 49 + apt install -y podman 50 + ``` 51 + 52 + ## 2. Create Directory Structure 53 + 54 + ```bash 55 + mkdir -p /etc/containers/systemd 56 + mkdir -p /srv/bspds/{postgres,minio,valkey,certs,acme,config} 57 + ``` 58 + 59 + ## 3. Create Environment File 60 + 61 + ```bash 62 + cp /opt/bspds/.env.example /srv/bspds/config/bspds.env 63 + chmod 600 /srv/bspds/config/bspds.env 64 + ``` 65 + 66 + Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with: 67 + 68 + ```bash 69 + openssl rand -base64 48 70 + ``` 71 + 72 + For quadlets, also add `DATABASE_URL` with the full connection string (systemd doesn't support variable expansion). 73 + 74 + ## 4. Install Quadlet Definitions 75 + 76 + Copy the quadlet files from the repository: 77 + 78 + ```bash 79 + cp /opt/bspds/deploy/quadlets/*.pod /etc/containers/systemd/ 80 + cp /opt/bspds/deploy/quadlets/*.container /etc/containers/systemd/ 81 + ``` 82 + 83 + Note: Systemd doesn't support shell-style variable expansion in `Environment=` lines. The quadlet files expect DATABASE_URL to be set in the environment file. 84 + 85 + ## 5. Create nginx Configuration 86 + 87 + ```bash 88 + cp /opt/bspds/deploy/nginx/nginx-quadlet.conf /srv/bspds/config/nginx.conf 89 + ``` 90 + 91 + ## 6. Build BSPDS Image 92 + 93 + ```bash 94 + cd /opt 95 + git clone https://tangled.org/lewis.moe/bspds.git 96 + cd bspds 97 + podman build -t bspds:latest . 98 + ``` 99 + 100 + ## 7. Create Podman Secrets 101 + 102 + ```bash 103 + source /srv/bspds/config/bspds.env 104 + echo "$DB_PASSWORD" | podman secret create bspds-db-password - 105 + echo "$MINIO_ROOT_PASSWORD" | podman secret create bspds-minio-password - 106 + ``` 107 + 108 + ## 8. Start Services and Initialize 109 + 110 + ```bash 111 + systemctl daemon-reload 112 + systemctl start bspds-db bspds-minio bspds-valkey 113 + 114 + sleep 10 115 + 116 + # Create MinIO bucket 117 + podman run --rm --pod bspds \ 118 + -e MINIO_ROOT_USER=minioadmin \ 119 + -e MINIO_ROOT_PASSWORD=your-minio-password \ 120 + docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ 121 + sh -c "mc alias set local http://localhost:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs" 122 + 123 + # Run migrations 124 + cargo install sqlx-cli --no-default-features --features postgres 125 + DATABASE_URL="postgres://bspds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/bspds/migrations 126 + ``` 127 + 128 + ## 9. Obtain SSL Certificate 129 + 130 + Create temporary self-signed cert: 131 + 132 + ```bash 133 + openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 134 + -keyout /srv/bspds/certs/privkey.pem \ 135 + -out /srv/bspds/certs/fullchain.pem \ 136 + -subj "/CN=pds.example.com" 137 + 138 + systemctl start bspds-app bspds-nginx 139 + 140 + # Get real certificate 141 + podman run --rm \ 142 + -v /srv/bspds/certs:/etc/letsencrypt:Z \ 143 + -v /srv/bspds/acme:/var/www/acme:Z \ 144 + docker.io/certbot/certbot:v5.2.2 certonly \ 145 + --webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com 146 + 147 + # Link certificates 148 + ln -sf /srv/bspds/certs/live/pds.example.com/fullchain.pem /srv/bspds/certs/fullchain.pem 149 + ln -sf /srv/bspds/certs/live/pds.example.com/privkey.pem /srv/bspds/certs/privkey.pem 150 + 151 + systemctl restart bspds-nginx 152 + ``` 153 + 154 + ## 10. Enable All Services 155 + 156 + ```bash 157 + systemctl enable bspds-db bspds-minio bspds-valkey bspds-app bspds-nginx 158 + ``` 159 + 160 + ## 11. Configure Firewall 161 + 162 + ```bash 163 + apt install -y ufw 164 + ufw allow ssh 165 + ufw allow 80/tcp 166 + ufw allow 443/tcp 167 + ufw enable 168 + ``` 169 + 170 + ## 12. Certificate Renewal 171 + 172 + Add to root's crontab (`crontab -e`): 173 + 174 + ``` 175 + 0 0 * * * podman run --rm -v /srv/bspds/certs:/etc/letsencrypt:Z -v /srv/bspds/acme:/var/www/acme:Z docker.io/certbot/certbot:v5.2.2 renew --quiet && systemctl reload bspds-nginx 176 + ``` 177 + 178 + --- 179 + 180 + # Alpine 3.23+ with OpenRC 181 + 182 + Alpine uses OpenRC, not systemd. We'll use podman-compose with an OpenRC service wrapper. 183 + 184 + ## 1. Install Podman 185 + 186 + ```sh 187 + apk update 188 + apk add podman podman-compose fuse-overlayfs cni-plugins 189 + rc-update add cgroups 190 + rc-service cgroups start 191 + ``` 192 + 193 + Enable podman socket for compose: 194 + 195 + ```sh 196 + rc-update add podman 197 + rc-service podman start 198 + ``` 199 + 200 + ## 2. Create Directory Structure 201 + 202 + ```sh 203 + mkdir -p /srv/bspds/{data,config} 204 + mkdir -p /srv/bspds/data/{postgres,minio,valkey,certs,acme} 205 + ``` 206 + 207 + ## 3. Clone Repository and Build 208 + 209 + ```sh 210 + cd /opt 211 + git clone https://tangled.org/lewis.moe/bspds.git 212 + cd bspds 213 + podman build -t bspds:latest . 214 + ``` 215 + 216 + ## 4. Create Environment File 217 + 218 + ```sh 219 + cp /opt/bspds/.env.example /srv/bspds/config/bspds.env 220 + chmod 600 /srv/bspds/config/bspds.env 221 + ``` 222 + 223 + Edit `/srv/bspds/config/bspds.env` and fill in your values. Generate secrets with: 224 + 225 + ```sh 226 + openssl rand -base64 48 227 + ``` 228 + 229 + ## 5. Set Up Compose and nginx 230 + 231 + Copy the production compose and nginx configs: 232 + 233 + ```sh 234 + cp /opt/bspds/docker-compose.prod.yml /srv/bspds/docker-compose.yml 235 + cp /opt/bspds/nginx.prod.conf /srv/bspds/config/nginx.conf 236 + ``` 237 + 238 + Edit `/srv/bspds/docker-compose.yml` to adjust paths if needed: 239 + - Update volume mounts to use `/srv/bspds/data/` paths 240 + - Update nginx cert paths to match `/srv/bspds/data/certs/` 241 + 242 + Edit `/srv/bspds/config/nginx.conf` to update cert paths: 243 + - Change `/etc/nginx/certs/live/${PDS_HOSTNAME}/` to `/etc/nginx/certs/` 244 + 245 + ## 6. Create OpenRC Service 246 + 247 + ```sh 248 + cat > /etc/init.d/bspds << 'EOF' 249 + #!/sbin/openrc-run 250 + 251 + name="bspds" 252 + description="BSPDS AT Protocol PDS (containerized)" 253 + 254 + command="/usr/bin/podman-compose" 255 + command_args="-f /srv/bspds/docker-compose.yml up" 256 + command_background=true 257 + pidfile="/run/${RC_SVCNAME}.pid" 258 + 259 + directory="/srv/bspds" 260 + 261 + depend() { 262 + need net podman 263 + after firewall 264 + } 265 + 266 + start_pre() { 267 + set -a 268 + . /srv/bspds/config/bspds.env 269 + set +a 270 + } 271 + 272 + stop() { 273 + ebegin "Stopping ${name}" 274 + cd /srv/bspds 275 + set -a 276 + . /srv/bspds/config/bspds.env 277 + set +a 278 + podman-compose -f /srv/bspds/docker-compose.yml down 279 + eend $? 280 + } 281 + EOF 282 + 283 + chmod +x /etc/init.d/bspds 284 + ``` 285 + 286 + ## 7. Initialize Services 287 + 288 + ```sh 289 + # Start services 290 + rc-service bspds start 291 + 292 + sleep 15 293 + 294 + # Create MinIO bucket 295 + source /srv/bspds/config/bspds.env 296 + podman run --rm --network bspds_default \ 297 + -e MINIO_ROOT_USER="$MINIO_ROOT_USER" \ 298 + -e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \ 299 + docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ 300 + sh -c 'mc alias set local http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs' 301 + 302 + # Run migrations 303 + apk add rustup 304 + rustup-init -y 305 + source ~/.cargo/env 306 + cargo install sqlx-cli --no-default-features --features postgres 307 + 308 + # Get database container IP 309 + DB_IP=$(podman inspect bspds-db-1 --format '{{.NetworkSettings.Networks.bspds_default.IPAddress}}') 310 + DATABASE_URL="postgres://bspds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/bspds/migrations 311 + ``` 312 + 313 + ## 8. Obtain SSL Certificate 314 + 315 + Create temporary self-signed cert: 316 + 317 + ```sh 318 + openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ 319 + -keyout /srv/bspds/data/certs/privkey.pem \ 320 + -out /srv/bspds/data/certs/fullchain.pem \ 321 + -subj "/CN=pds.example.com" 322 + 323 + rc-service bspds restart 324 + 325 + # Get real certificate 326 + podman run --rm \ 327 + -v /srv/bspds/data/certs:/etc/letsencrypt \ 328 + -v /srv/bspds/data/acme:/var/www/acme \ 329 + --network bspds_default \ 330 + docker.io/certbot/certbot:v5.2.2 certonly \ 331 + --webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com 332 + 333 + # Link certificates 334 + ln -sf /srv/bspds/data/certs/live/pds.example.com/fullchain.pem /srv/bspds/data/certs/fullchain.pem 335 + ln -sf /srv/bspds/data/certs/live/pds.example.com/privkey.pem /srv/bspds/data/certs/privkey.pem 336 + 337 + rc-service bspds restart 338 + ``` 339 + 340 + ## 9. Enable Service at Boot 341 + 342 + ```sh 343 + rc-update add bspds 344 + ``` 345 + 346 + ## 10. Configure Firewall 347 + 348 + ```sh 349 + apk add iptables ip6tables 350 + 351 + iptables -A INPUT -p tcp --dport 22 -j ACCEPT 352 + iptables -A INPUT -p tcp --dport 80 -j ACCEPT 353 + iptables -A INPUT -p tcp --dport 443 -j ACCEPT 354 + iptables -A INPUT -i lo -j ACCEPT 355 + iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 356 + iptables -P INPUT DROP 357 + 358 + ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT 359 + ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT 360 + ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT 361 + ip6tables -A INPUT -i lo -j ACCEPT 362 + ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 363 + ip6tables -P INPUT DROP 364 + 365 + rc-update add iptables 366 + rc-update add ip6tables 367 + /etc/init.d/iptables save 368 + /etc/init.d/ip6tables save 369 + ``` 370 + 371 + ## 11. Certificate Renewal 372 + 373 + Add to root's crontab (`crontab -e`): 374 + 375 + ``` 376 + 0 0 * * * podman run --rm -v /srv/bspds/data/certs:/etc/letsencrypt -v /srv/bspds/data/acme:/var/www/acme docker.io/certbot/certbot:v5.2.2 renew --quiet && rc-service bspds restart 377 + ``` 378 + 379 + --- 380 + 381 + # Verification and Maintenance 382 + 383 + ## Verify Installation 384 + 385 + ```sh 386 + curl -s https://pds.example.com/xrpc/_health | jq 387 + curl -s https://pds.example.com/.well-known/atproto-did 388 + ``` 389 + 390 + ## View Logs 391 + 392 + **Debian:** 393 + ```bash 394 + journalctl -u bspds-app -f 395 + podman logs -f bspds-app 396 + ``` 397 + 398 + **Alpine:** 399 + ```sh 400 + podman-compose -f /srv/bspds/docker-compose.yml logs -f 401 + podman logs -f bspds-bspds-1 402 + ``` 403 + 404 + ## Update BSPDS 405 + 406 + ```sh 407 + cd /opt/bspds 408 + git pull 409 + podman build -t bspds:latest . 410 + 411 + # Debian: 412 + systemctl restart bspds-app 413 + 414 + # Alpine: 415 + rc-service bspds restart 416 + ``` 417 + 418 + ## Backup Database 419 + 420 + **Debian:** 421 + ```bash 422 + podman exec bspds-db pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql 423 + ``` 424 + 425 + **Alpine:** 426 + ```sh 427 + podman exec bspds-db-1 pg_dump -U bspds pds > /var/backups/pds-$(date +%Y%m%d).sql 428 + ```
+280
docs/install-debian.md
··· 1 + # BSPDS Production Installation on Debian 2 + 3 + > **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. 4 + 5 + This guide covers installing BSPDS on Debian 13 "Trixie" (current stable as of December 2025). 6 + 7 + ## Choose Your Installation Method 8 + 9 + | Method | Best For | 10 + |--------|----------| 11 + | **Native (this guide)** | Maximum performance, full control, simpler debugging | 12 + | **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments | 13 + | **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling | 14 + 15 + This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md). 16 + 17 + --- 18 + 19 + ## Prerequisites 20 + 21 + - A VPS with at least 2GB RAM and 20GB disk 22 + - A domain name pointing to your server's IP 23 + - Root or sudo access 24 + 25 + ## 1. System Setup 26 + 27 + ```bash 28 + apt update && apt upgrade -y 29 + apt install -y curl git build-essential pkg-config libssl-dev 30 + ``` 31 + 32 + ## 2. Install Rust 33 + 34 + ```bash 35 + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 36 + source ~/.cargo/env 37 + rustup default stable 38 + ``` 39 + 40 + This installs the latest stable Rust (1.92+ as of December 2025). 41 + 42 + ## 3. Install postgres 43 + 44 + Debian 13 includes PostgreSQL 17: 45 + 46 + ```bash 47 + apt install -y postgresql postgresql-contrib 48 + 49 + systemctl enable postgresql 50 + systemctl start postgresql 51 + 52 + sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';" 53 + sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;" 54 + sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 55 + ``` 56 + 57 + ## 4. Install minio 58 + 59 + ```bash 60 + curl -O https://dl.min.io/server/minio/release/linux-amd64/minio 61 + chmod +x minio 62 + mv minio /usr/local/bin/ 63 + 64 + mkdir -p /var/lib/minio/data 65 + useradd -r -s /sbin/nologin minio-user 66 + chown -R minio-user:minio-user /var/lib/minio 67 + 68 + cat > /etc/default/minio << 'EOF' 69 + MINIO_ROOT_USER=minioadmin 70 + MINIO_ROOT_PASSWORD=your-minio-password 71 + MINIO_VOLUMES="/var/lib/minio/data" 72 + MINIO_OPTS="--console-address :9001" 73 + EOF 74 + 75 + cat > /etc/systemd/system/minio.service << 'EOF' 76 + [Unit] 77 + Description=MinIO Object Storage 78 + After=network.target 79 + 80 + [Service] 81 + User=minio-user 82 + Group=minio-user 83 + EnvironmentFile=/etc/default/minio 84 + ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS 85 + Restart=always 86 + LimitNOFILE=65536 87 + 88 + [Install] 89 + WantedBy=multi-user.target 90 + EOF 91 + 92 + systemctl daemon-reload 93 + systemctl enable minio 94 + systemctl start minio 95 + ``` 96 + 97 + Create the blob bucket (wait a few seconds for minio to start): 98 + 99 + ```bash 100 + curl -O https://dl.min.io/client/mc/release/linux-amd64/mc 101 + chmod +x mc 102 + mv mc /usr/local/bin/ 103 + 104 + mc alias set local http://localhost:9000 minioadmin your-minio-password 105 + mc mb local/pds-blobs 106 + ``` 107 + 108 + ## 5. Install valkey 109 + 110 + Debian 13 includes Valkey 8: 111 + 112 + ```bash 113 + apt install -y valkey 114 + 115 + systemctl enable valkey-server 116 + systemctl start valkey-server 117 + ``` 118 + 119 + ## 6. Install deno (for frontend build) 120 + 121 + ```bash 122 + curl -fsSL https://deno.land/install.sh | sh 123 + export PATH="$HOME/.deno/bin:$PATH" 124 + echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc 125 + ``` 126 + 127 + ## 7. Clone and Build BSPDS 128 + 129 + ```bash 130 + cd /opt 131 + git clone https://tangled.org/lewis.moe/bspds.git 132 + cd bspds 133 + 134 + cd frontend 135 + deno task build 136 + cd .. 137 + 138 + cargo build --release 139 + ``` 140 + 141 + ## 8. Install sqlx-cli and Run Migrations 142 + 143 + ```bash 144 + cargo install sqlx-cli --no-default-features --features postgres 145 + 146 + export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" 147 + sqlx migrate run 148 + ``` 149 + 150 + ## 9. Configure BSPDS 151 + 152 + ```bash 153 + mkdir -p /etc/bspds 154 + cp /opt/bspds/.env.example /etc/bspds/bspds.env 155 + chmod 600 /etc/bspds/bspds.env 156 + ``` 157 + 158 + Edit `/etc/bspds/bspds.env` and fill in your values. Generate secrets with: 159 + 160 + ```bash 161 + openssl rand -base64 48 162 + ``` 163 + 164 + ## 10. Create Systemd Service 165 + 166 + ```bash 167 + useradd -r -s /sbin/nologin bspds 168 + 169 + cp /opt/bspds/target/release/bspds /usr/local/bin/ 170 + mkdir -p /var/lib/bspds 171 + cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend 172 + chown -R bspds:bspds /var/lib/bspds 173 + 174 + cat > /etc/systemd/system/bspds.service << 'EOF' 175 + [Unit] 176 + Description=BSPDS - AT Protocol PDS 177 + After=network.target postgresql.service minio.service 178 + 179 + [Service] 180 + Type=simple 181 + User=bspds 182 + Group=bspds 183 + EnvironmentFile=/etc/bspds/bspds.env 184 + Environment=FRONTEND_DIR=/var/lib/bspds/frontend 185 + ExecStart=/usr/local/bin/bspds 186 + Restart=always 187 + RestartSec=5 188 + 189 + [Install] 190 + WantedBy=multi-user.target 191 + EOF 192 + 193 + systemctl daemon-reload 194 + systemctl enable bspds 195 + systemctl start bspds 196 + ``` 197 + 198 + ## 11. Install and Configure nginx 199 + 200 + Debian 13 includes nginx 1.26: 201 + 202 + ```bash 203 + apt install -y nginx certbot python3-certbot-nginx 204 + 205 + cat > /etc/nginx/sites-available/bspds << 'EOF' 206 + server { 207 + listen 80; 208 + listen [::]:80; 209 + server_name pds.example.com; 210 + 211 + location / { 212 + proxy_pass http://127.0.0.1:3000; 213 + proxy_http_version 1.1; 214 + proxy_set_header Upgrade $http_upgrade; 215 + proxy_set_header Connection "upgrade"; 216 + proxy_set_header Host $host; 217 + proxy_set_header X-Real-IP $remote_addr; 218 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 219 + proxy_set_header X-Forwarded-Proto $scheme; 220 + proxy_read_timeout 86400; 221 + } 222 + } 223 + EOF 224 + 225 + ln -s /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/ 226 + rm -f /etc/nginx/sites-enabled/default 227 + nginx -t 228 + systemctl reload nginx 229 + ``` 230 + 231 + ## 12. Obtain SSL Certificate 232 + 233 + ```bash 234 + certbot --nginx -d pds.example.com 235 + ``` 236 + 237 + Certbot automatically configures nginx for HTTP/2 and sets up auto-renewal. 238 + 239 + ## 13. Configure Firewall 240 + 241 + ```bash 242 + apt install -y ufw 243 + ufw allow ssh 244 + ufw allow 80/tcp 245 + ufw allow 443/tcp 246 + ufw enable 247 + ``` 248 + 249 + ## 14. Verify Installation 250 + 251 + ```bash 252 + systemctl status bspds 253 + curl -s https://pds.example.com/xrpc/_health | jq 254 + curl -s https://pds.example.com/.well-known/atproto-did 255 + ``` 256 + 257 + ## Maintenance 258 + 259 + View logs: 260 + ```bash 261 + journalctl -u bspds -f 262 + ``` 263 + 264 + Update BSPDS: 265 + ```bash 266 + cd /opt/bspds 267 + git pull 268 + cd frontend && deno task build && cd .. 269 + cargo build --release 270 + systemctl stop bspds 271 + cp target/release/bspds /usr/local/bin/ 272 + cp -r frontend/dist /var/lib/bspds/frontend 273 + DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run 274 + systemctl start bspds 275 + ``` 276 + 277 + Backup database: 278 + ```bash 279 + sudo -u postgres pg_dump pds > /var/backups/pds-$(date +%Y%m%d).sql 280 + ```
+956
docs/install-kubernetes.md
··· 1 + # BSPDS Production Kubernetes Deployment 2 + 3 + > **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. 4 + 5 + This guide covers deploying BSPDS on a production multi-node Kubernetes cluster with high availability, auto-scaling, and proper secrets management. 6 + 7 + ## Architecture Overview 8 + 9 + ``` 10 + ┌─────────────────────────────────────────────────┐ 11 + │ Kubernetes Cluster │ 12 + │ │ 13 + Internet ──────►│ Ingress Controller (nginx/traefik) │ 14 + │ │ │ 15 + │ ▼ │ 16 + │ ┌─────────────┐ │ 17 + │ │ Service │◄── HPA (2-10 replicas) │ 18 + │ └──────┬──────┘ │ 19 + │ │ │ 20 + │ ┌────┴────┐ │ 21 + │ ▼ ▼ │ 22 + │ ┌─────┐ ┌─────┐ │ 23 + │ │BSPDS│ │BSPDS│ ... (pods) │ 24 + │ └──┬──┘ └──┬──┘ │ 25 + │ │ │ │ 26 + │ ▼ ▼ │ 27 + │ ┌──────────────────────────────────────┐ │ 28 + │ │ PostgreSQL │ MinIO │ Valkey │ │ 29 + │ │ (HA/Operator)│ (StatefulSet) │ (Sentinel) │ 30 + │ └──────────────────────────────────────┘ │ 31 + └─────────────────────────────────────────────────┘ 32 + ``` 33 + 34 + ## Prerequisites 35 + 36 + - Kubernetes cluster (1.30+) with at least 3 nodes (1.34 is current stable) 37 + - `kubectl` configured to access your cluster 38 + - `helm` 3.x installed 39 + - Storage class that supports `ReadWriteOnce` (for databases) 40 + - Ingress controller installed (nginx-ingress or traefik) 41 + - cert-manager installed for TLS certificates 42 + 43 + ### Quick Prerequisites Setup 44 + 45 + If you need to install prerequisites: 46 + 47 + ```bash 48 + # Install nginx-ingress (chart v4.14.1 - December 2025) 49 + helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 50 + helm repo update 51 + helm install ingress-nginx ingress-nginx/ingress-nginx \ 52 + --namespace ingress-nginx --create-namespace \ 53 + --version 4.14.1 54 + 55 + # Install cert-manager (v1.19.2 - December 2025) 56 + helm repo add jetstack https://charts.jetstack.io 57 + helm repo update 58 + helm install cert-manager jetstack/cert-manager \ 59 + --namespace cert-manager --create-namespace \ 60 + --version v1.19.2 \ 61 + --set installCRDs=true 62 + ``` 63 + 64 + --- 65 + 66 + ## 1. Create Namespace 67 + 68 + ```bash 69 + kubectl create namespace bspds 70 + kubectl config set-context --current --namespace=bspds 71 + ``` 72 + 73 + ## 2. Create Secrets 74 + 75 + Generate secure passwords and secrets: 76 + 77 + ```bash 78 + # Generate secrets 79 + DB_PASSWORD=$(openssl rand -base64 32) 80 + MINIO_PASSWORD=$(openssl rand -base64 32) 81 + JWT_SECRET=$(openssl rand -base64 48) 82 + DPOP_SECRET=$(openssl rand -base64 48) 83 + MASTER_KEY=$(openssl rand -base64 48) 84 + 85 + # Create Kubernetes secrets 86 + kubectl create secret generic bspds-db-credentials \ 87 + --from-literal=username=bspds \ 88 + --from-literal=password="$DB_PASSWORD" 89 + 90 + kubectl create secret generic bspds-minio-credentials \ 91 + --from-literal=root-user=minioadmin \ 92 + --from-literal=root-password="$MINIO_PASSWORD" 93 + 94 + kubectl create secret generic bspds-secrets \ 95 + --from-literal=jwt-secret="$JWT_SECRET" \ 96 + --from-literal=dpop-secret="$DPOP_SECRET" \ 97 + --from-literal=master-key="$MASTER_KEY" 98 + 99 + # Save secrets locally (KEEP SECURE!) 100 + echo "DB_PASSWORD=$DB_PASSWORD" > secrets.txt 101 + echo "MINIO_PASSWORD=$MINIO_PASSWORD" >> secrets.txt 102 + echo "JWT_SECRET=$JWT_SECRET" >> secrets.txt 103 + echo "DPOP_SECRET=$DPOP_SECRET" >> secrets.txt 104 + echo "MASTER_KEY=$MASTER_KEY" >> secrets.txt 105 + chmod 600 secrets.txt 106 + ``` 107 + 108 + ## 3. Deploy PostgreSQL 109 + 110 + ### Option A: CloudNativePG Operator (Recommended for HA) 111 + 112 + ```bash 113 + # Install CloudNativePG operator (v1.28.0 - December 2025) 114 + kubectl apply --server-side -f \ 115 + https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.0.yaml 116 + 117 + # Wait for operator 118 + kubectl wait --for=condition=available --timeout=120s \ 119 + deployment/cnpg-controller-manager -n cnpg-system 120 + ``` 121 + 122 + ```bash 123 + cat <<EOF | kubectl apply -f - 124 + apiVersion: postgresql.cnpg.io/v1 125 + kind: Cluster 126 + metadata: 127 + name: bspds-db 128 + namespace: bspds 129 + spec: 130 + instances: 3 131 + 132 + postgresql: 133 + parameters: 134 + max_connections: "200" 135 + shared_buffers: "256MB" 136 + 137 + bootstrap: 138 + initdb: 139 + database: pds 140 + owner: bspds 141 + secret: 142 + name: bspds-db-credentials 143 + 144 + storage: 145 + size: 20Gi 146 + storageClass: standard # adjust for your cluster 147 + 148 + resources: 149 + requests: 150 + memory: "512Mi" 151 + cpu: "250m" 152 + limits: 153 + memory: "1Gi" 154 + cpu: "1000m" 155 + 156 + affinity: 157 + podAntiAffinityType: required 158 + EOF 159 + ``` 160 + 161 + ### Option B: Simple StatefulSet (Single Instance) 162 + 163 + ```bash 164 + cat <<EOF | kubectl apply -f - 165 + apiVersion: v1 166 + kind: PersistentVolumeClaim 167 + metadata: 168 + name: bspds-db-pvc 169 + namespace: bspds 170 + spec: 171 + accessModes: 172 + - ReadWriteOnce 173 + resources: 174 + requests: 175 + storage: 20Gi 176 + --- 177 + apiVersion: apps/v1 178 + kind: StatefulSet 179 + metadata: 180 + name: bspds-db 181 + namespace: bspds 182 + spec: 183 + serviceName: bspds-db 184 + replicas: 1 185 + selector: 186 + matchLabels: 187 + app: bspds-db 188 + template: 189 + metadata: 190 + labels: 191 + app: bspds-db 192 + spec: 193 + containers: 194 + - name: postgres 195 + image: postgres:18-alpine 196 + ports: 197 + - containerPort: 5432 198 + env: 199 + - name: POSTGRES_DB 200 + value: pds 201 + - name: POSTGRES_USER 202 + valueFrom: 203 + secretKeyRef: 204 + name: bspds-db-credentials 205 + key: username 206 + - name: POSTGRES_PASSWORD 207 + valueFrom: 208 + secretKeyRef: 209 + name: bspds-db-credentials 210 + key: password 211 + - name: PGDATA 212 + value: /var/lib/postgresql/data/pgdata 213 + volumeMounts: 214 + - name: data 215 + mountPath: /var/lib/postgresql/data 216 + resources: 217 + requests: 218 + memory: "256Mi" 219 + cpu: "100m" 220 + limits: 221 + memory: "1Gi" 222 + cpu: "500m" 223 + livenessProbe: 224 + exec: 225 + command: ["pg_isready", "-U", "bspds", "-d", "pds"] 226 + initialDelaySeconds: 30 227 + periodSeconds: 10 228 + readinessProbe: 229 + exec: 230 + command: ["pg_isready", "-U", "bspds", "-d", "pds"] 231 + initialDelaySeconds: 5 232 + periodSeconds: 5 233 + volumes: 234 + - name: data 235 + persistentVolumeClaim: 236 + claimName: bspds-db-pvc 237 + --- 238 + apiVersion: v1 239 + kind: Service 240 + metadata: 241 + name: bspds-db-rw 242 + namespace: bspds 243 + spec: 244 + selector: 245 + app: bspds-db 246 + ports: 247 + - port: 5432 248 + targetPort: 5432 249 + EOF 250 + ``` 251 + 252 + ## 4. Deploy MinIO 253 + 254 + ```bash 255 + cat <<EOF | kubectl apply -f - 256 + apiVersion: v1 257 + kind: PersistentVolumeClaim 258 + metadata: 259 + name: bspds-minio-pvc 260 + namespace: bspds 261 + spec: 262 + accessModes: 263 + - ReadWriteOnce 264 + resources: 265 + requests: 266 + storage: 50Gi 267 + --- 268 + apiVersion: apps/v1 269 + kind: StatefulSet 270 + metadata: 271 + name: bspds-minio 272 + namespace: bspds 273 + spec: 274 + serviceName: bspds-minio 275 + replicas: 1 276 + selector: 277 + matchLabels: 278 + app: bspds-minio 279 + template: 280 + metadata: 281 + labels: 282 + app: bspds-minio 283 + spec: 284 + containers: 285 + - name: minio 286 + image: minio/minio:RELEASE.2025-10-15T17-29-55Z 287 + args: 288 + - server 289 + - /data 290 + - --console-address 291 + - ":9001" 292 + ports: 293 + - containerPort: 9000 294 + name: api 295 + - containerPort: 9001 296 + name: console 297 + env: 298 + - name: MINIO_ROOT_USER 299 + valueFrom: 300 + secretKeyRef: 301 + name: bspds-minio-credentials 302 + key: root-user 303 + - name: MINIO_ROOT_PASSWORD 304 + valueFrom: 305 + secretKeyRef: 306 + name: bspds-minio-credentials 307 + key: root-password 308 + volumeMounts: 309 + - name: data 310 + mountPath: /data 311 + resources: 312 + requests: 313 + memory: "256Mi" 314 + cpu: "100m" 315 + limits: 316 + memory: "512Mi" 317 + cpu: "500m" 318 + livenessProbe: 319 + httpGet: 320 + path: /minio/health/live 321 + port: 9000 322 + initialDelaySeconds: 30 323 + periodSeconds: 10 324 + readinessProbe: 325 + httpGet: 326 + path: /minio/health/ready 327 + port: 9000 328 + initialDelaySeconds: 10 329 + periodSeconds: 5 330 + volumes: 331 + - name: data 332 + persistentVolumeClaim: 333 + claimName: bspds-minio-pvc 334 + --- 335 + apiVersion: v1 336 + kind: Service 337 + metadata: 338 + name: bspds-minio 339 + namespace: bspds 340 + spec: 341 + selector: 342 + app: bspds-minio 343 + ports: 344 + - port: 9000 345 + targetPort: 9000 346 + name: api 347 + - port: 9001 348 + targetPort: 9001 349 + name: console 350 + EOF 351 + ``` 352 + 353 + ### Initialize MinIO Bucket 354 + 355 + ```bash 356 + kubectl run minio-init --rm -it --restart=Never \ 357 + --image=minio/mc:RELEASE.2025-07-16T15-35-03Z \ 358 + --env="MINIO_ROOT_USER=minioadmin" \ 359 + --env="MINIO_ROOT_PASSWORD=$(kubectl get secret bspds-minio-credentials -o jsonpath='{.data.root-password}' | base64 -d)" \ 360 + --command -- sh -c " 361 + mc alias set local http://bspds-minio:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && 362 + mc mb --ignore-existing local/pds-blobs 363 + " 364 + ``` 365 + 366 + ## 5. Deploy Valkey 367 + 368 + ```bash 369 + cat <<EOF | kubectl apply -f - 370 + apiVersion: v1 371 + kind: PersistentVolumeClaim 372 + metadata: 373 + name: bspds-valkey-pvc 374 + namespace: bspds 375 + spec: 376 + accessModes: 377 + - ReadWriteOnce 378 + resources: 379 + requests: 380 + storage: 5Gi 381 + --- 382 + apiVersion: apps/v1 383 + kind: StatefulSet 384 + metadata: 385 + name: bspds-valkey 386 + namespace: bspds 387 + spec: 388 + serviceName: bspds-valkey 389 + replicas: 1 390 + selector: 391 + matchLabels: 392 + app: bspds-valkey 393 + template: 394 + metadata: 395 + labels: 396 + app: bspds-valkey 397 + spec: 398 + containers: 399 + - name: valkey 400 + image: valkey/valkey:9-alpine 401 + args: 402 + - valkey-server 403 + - --appendonly 404 + - "yes" 405 + - --maxmemory 406 + - "256mb" 407 + - --maxmemory-policy 408 + - allkeys-lru 409 + ports: 410 + - containerPort: 6379 411 + volumeMounts: 412 + - name: data 413 + mountPath: /data 414 + resources: 415 + requests: 416 + memory: "128Mi" 417 + cpu: "50m" 418 + limits: 419 + memory: "300Mi" 420 + cpu: "200m" 421 + livenessProbe: 422 + exec: 423 + command: ["valkey-cli", "ping"] 424 + initialDelaySeconds: 10 425 + periodSeconds: 5 426 + readinessProbe: 427 + exec: 428 + command: ["valkey-cli", "ping"] 429 + initialDelaySeconds: 5 430 + periodSeconds: 3 431 + volumes: 432 + - name: data 433 + persistentVolumeClaim: 434 + claimName: bspds-valkey-pvc 435 + --- 436 + apiVersion: v1 437 + kind: Service 438 + metadata: 439 + name: bspds-valkey 440 + namespace: bspds 441 + spec: 442 + selector: 443 + app: bspds-valkey 444 + ports: 445 + - port: 6379 446 + targetPort: 6379 447 + EOF 448 + ``` 449 + 450 + ## 6. Build and Push BSPDS Image 451 + 452 + ```bash 453 + # Build image 454 + cd /path/to/bspds 455 + docker build -t your-registry.com/bspds:latest . 456 + docker push your-registry.com/bspds:latest 457 + ``` 458 + 459 + If using a private registry, create an image pull secret: 460 + 461 + ```bash 462 + kubectl create secret docker-registry regcred \ 463 + --docker-server=your-registry.com \ 464 + --docker-username=your-username \ 465 + --docker-password=your-password \ 466 + --docker-email=your-email 467 + ``` 468 + 469 + ## 7. Run Database Migrations 470 + 471 + BSPDS runs migrations automatically on startup. However, if you want to run migrations separately (recommended for zero-downtime deployments), you can use a Job: 472 + 473 + ```bash 474 + cat <<'EOF' | kubectl apply -f - 475 + apiVersion: batch/v1 476 + kind: Job 477 + metadata: 478 + name: bspds-migrate 479 + namespace: bspds 480 + spec: 481 + ttlSecondsAfterFinished: 300 482 + template: 483 + spec: 484 + restartPolicy: Never 485 + containers: 486 + - name: migrate 487 + image: your-registry.com/bspds:latest 488 + command: ["/usr/local/bin/bspds"] 489 + args: ["--migrate-only"] # Add this flag to your app, or remove this Job 490 + env: 491 + - name: DB_PASSWORD 492 + valueFrom: 493 + secretKeyRef: 494 + name: bspds-db-credentials 495 + key: password 496 + - name: DATABASE_URL 497 + value: "postgres://bspds:$(DB_PASSWORD)@bspds-db-rw:5432/pds" 498 + EOF 499 + 500 + kubectl wait --for=condition=complete --timeout=120s job/bspds-migrate 501 + ``` 502 + 503 + > **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. 504 + 505 + ## 8. Deploy BSPDS Application 506 + 507 + ```bash 508 + cat <<EOF | kubectl apply -f - 509 + apiVersion: v1 510 + kind: ConfigMap 511 + metadata: 512 + name: bspds-config 513 + namespace: bspds 514 + data: 515 + PDS_HOSTNAME: "pds.example.com" 516 + SERVER_HOST: "0.0.0.0" 517 + SERVER_PORT: "3000" 518 + S3_ENDPOINT: "http://bspds-minio:9000" 519 + AWS_REGION: "us-east-1" 520 + S3_BUCKET: "pds-blobs" 521 + VALKEY_URL: "redis://bspds-valkey:6379" 522 + APPVIEW_URL: "https://api.bsky.app" 523 + CRAWLERS: "https://bsky.network" 524 + FRONTEND_DIR: "/app/frontend/dist" 525 + --- 526 + apiVersion: apps/v1 527 + kind: Deployment 528 + metadata: 529 + name: bspds 530 + namespace: bspds 531 + spec: 532 + replicas: 2 533 + selector: 534 + matchLabels: 535 + app: bspds 536 + template: 537 + metadata: 538 + labels: 539 + app: bspds 540 + spec: 541 + imagePullSecrets: 542 + - name: regcred # Remove if using public registry 543 + affinity: 544 + podAntiAffinity: 545 + preferredDuringSchedulingIgnoredDuringExecution: 546 + - weight: 100 547 + podAffinityTerm: 548 + labelSelector: 549 + matchLabels: 550 + app: bspds 551 + topologyKey: kubernetes.io/hostname 552 + containers: 553 + - name: bspds 554 + image: your-registry.com/bspds:latest 555 + ports: 556 + - containerPort: 3000 557 + name: http 558 + envFrom: 559 + - configMapRef: 560 + name: bspds-config 561 + env: 562 + - name: DB_PASSWORD 563 + valueFrom: 564 + secretKeyRef: 565 + name: bspds-db-credentials 566 + key: password 567 + - name: DATABASE_URL 568 + value: "postgres://bspds:$(DB_PASSWORD)@bspds-db-rw:5432/pds" 569 + - name: AWS_ACCESS_KEY_ID 570 + valueFrom: 571 + secretKeyRef: 572 + name: bspds-minio-credentials 573 + key: root-user 574 + - name: AWS_SECRET_ACCESS_KEY 575 + valueFrom: 576 + secretKeyRef: 577 + name: bspds-minio-credentials 578 + key: root-password 579 + - name: JWT_SECRET 580 + valueFrom: 581 + secretKeyRef: 582 + name: bspds-secrets 583 + key: jwt-secret 584 + - name: DPOP_SECRET 585 + valueFrom: 586 + secretKeyRef: 587 + name: bspds-secrets 588 + key: dpop-secret 589 + - name: MASTER_KEY 590 + valueFrom: 591 + secretKeyRef: 592 + name: bspds-secrets 593 + key: master-key 594 + resources: 595 + requests: 596 + memory: "256Mi" 597 + cpu: "100m" 598 + limits: 599 + memory: "1Gi" 600 + cpu: "1000m" 601 + livenessProbe: 602 + httpGet: 603 + path: /xrpc/_health 604 + port: 3000 605 + initialDelaySeconds: 30 606 + periodSeconds: 10 607 + failureThreshold: 3 608 + readinessProbe: 609 + httpGet: 610 + path: /xrpc/_health 611 + port: 3000 612 + initialDelaySeconds: 5 613 + periodSeconds: 5 614 + failureThreshold: 3 615 + securityContext: 616 + runAsNonRoot: true 617 + runAsUser: 1000 618 + allowPrivilegeEscalation: false 619 + --- 620 + apiVersion: v1 621 + kind: Service 622 + metadata: 623 + name: bspds 624 + namespace: bspds 625 + spec: 626 + selector: 627 + app: bspds 628 + ports: 629 + - port: 80 630 + targetPort: 3000 631 + name: http 632 + EOF 633 + ``` 634 + 635 + ## 9. Configure Horizontal Pod Autoscaler 636 + 637 + ```bash 638 + cat <<EOF | kubectl apply -f - 639 + apiVersion: autoscaling/v2 640 + kind: HorizontalPodAutoscaler 641 + metadata: 642 + name: bspds 643 + namespace: bspds 644 + spec: 645 + scaleTargetRef: 646 + apiVersion: apps/v1 647 + kind: Deployment 648 + name: bspds 649 + minReplicas: 2 650 + maxReplicas: 10 651 + metrics: 652 + - type: Resource 653 + resource: 654 + name: cpu 655 + target: 656 + type: Utilization 657 + averageUtilization: 70 658 + - type: Resource 659 + resource: 660 + name: memory 661 + target: 662 + type: Utilization 663 + averageUtilization: 80 664 + behavior: 665 + scaleDown: 666 + stabilizationWindowSeconds: 300 667 + policies: 668 + - type: Pods 669 + value: 1 670 + periodSeconds: 60 671 + scaleUp: 672 + stabilizationWindowSeconds: 0 673 + policies: 674 + - type: Percent 675 + value: 100 676 + periodSeconds: 15 677 + - type: Pods 678 + value: 4 679 + periodSeconds: 15 680 + selectPolicy: Max 681 + EOF 682 + ``` 683 + 684 + ## 10. Configure Pod Disruption Budget 685 + 686 + ```bash 687 + cat <<EOF | kubectl apply -f - 688 + apiVersion: policy/v1 689 + kind: PodDisruptionBudget 690 + metadata: 691 + name: bspds 692 + namespace: bspds 693 + spec: 694 + minAvailable: 1 695 + selector: 696 + matchLabels: 697 + app: bspds 698 + EOF 699 + ``` 700 + 701 + ## 11. Configure TLS with cert-manager 702 + 703 + ```bash 704 + cat <<EOF | kubectl apply -f - 705 + apiVersion: cert-manager.io/v1 706 + kind: ClusterIssuer 707 + metadata: 708 + name: letsencrypt-prod 709 + spec: 710 + acme: 711 + server: https://acme-v02.api.letsencrypt.org/directory 712 + email: your-email@example.com 713 + privateKeySecretRef: 714 + name: letsencrypt-prod 715 + solvers: 716 + - http01: 717 + ingress: 718 + class: nginx 719 + EOF 720 + ``` 721 + 722 + ## 12. Configure Ingress 723 + 724 + ```bash 725 + cat <<EOF | kubectl apply -f - 726 + apiVersion: networking.k8s.io/v1 727 + kind: Ingress 728 + metadata: 729 + name: bspds 730 + namespace: bspds 731 + annotations: 732 + cert-manager.io/cluster-issuer: letsencrypt-prod 733 + nginx.ingress.kubernetes.io/proxy-read-timeout: "86400" 734 + nginx.ingress.kubernetes.io/proxy-send-timeout: "86400" 735 + nginx.ingress.kubernetes.io/proxy-body-size: "100m" 736 + nginx.ingress.kubernetes.io/proxy-buffering: "off" 737 + nginx.ingress.kubernetes.io/websocket-services: "bspds" 738 + spec: 739 + ingressClassName: nginx 740 + tls: 741 + - hosts: 742 + - pds.example.com 743 + secretName: bspds-tls 744 + rules: 745 + - host: pds.example.com 746 + http: 747 + paths: 748 + - path: / 749 + pathType: Prefix 750 + backend: 751 + service: 752 + name: bspds 753 + port: 754 + number: 80 755 + EOF 756 + ``` 757 + 758 + ## 13. Configure Network Policies (Optional but Recommended) 759 + 760 + ```bash 761 + cat <<EOF | kubectl apply -f - 762 + apiVersion: networking.k8s.io/v1 763 + kind: NetworkPolicy 764 + metadata: 765 + name: bspds-network-policy 766 + namespace: bspds 767 + spec: 768 + podSelector: 769 + matchLabels: 770 + app: bspds 771 + policyTypes: 772 + - Ingress 773 + - Egress 774 + ingress: 775 + - from: 776 + - namespaceSelector: 777 + matchLabels: 778 + kubernetes.io/metadata.name: ingress-nginx 779 + ports: 780 + - protocol: TCP 781 + port: 3000 782 + egress: 783 + - to: 784 + - podSelector: 785 + matchLabels: 786 + app: bspds-db 787 + ports: 788 + - protocol: TCP 789 + port: 5432 790 + - to: 791 + - podSelector: 792 + matchLabels: 793 + app: bspds-minio 794 + ports: 795 + - protocol: TCP 796 + port: 9000 797 + - to: 798 + - podSelector: 799 + matchLabels: 800 + app: bspds-valkey 801 + ports: 802 + - protocol: TCP 803 + port: 6379 804 + - to: # Allow DNS 805 + - namespaceSelector: {} 806 + podSelector: 807 + matchLabels: 808 + k8s-app: kube-dns 809 + ports: 810 + - protocol: UDP 811 + port: 53 812 + - to: # Allow external HTTPS (for federation) 813 + - ipBlock: 814 + cidr: 0.0.0.0/0 815 + ports: 816 + - protocol: TCP 817 + port: 443 818 + EOF 819 + ``` 820 + 821 + ## 14. Deploy Prometheus Monitoring (Optional) 822 + 823 + ```bash 824 + cat <<EOF | kubectl apply -f - 825 + apiVersion: monitoring.coreos.com/v1 826 + kind: ServiceMonitor 827 + metadata: 828 + name: bspds 829 + namespace: bspds 830 + labels: 831 + release: prometheus 832 + spec: 833 + selector: 834 + matchLabels: 835 + app: bspds 836 + endpoints: 837 + - port: http 838 + path: /metrics 839 + interval: 30s 840 + EOF 841 + ``` 842 + 843 + --- 844 + 845 + ## Verification 846 + 847 + ```bash 848 + # Check all pods are running 849 + kubectl get pods -n bspds 850 + 851 + # Check services 852 + kubectl get svc -n bspds 853 + 854 + # Check ingress 855 + kubectl get ingress -n bspds 856 + 857 + # Check certificate 858 + kubectl get certificate -n bspds 859 + 860 + # Test health endpoint 861 + curl -s https://pds.example.com/xrpc/_health | jq 862 + 863 + # Test DID endpoint 864 + curl -s https://pds.example.com/.well-known/atproto-did 865 + ``` 866 + 867 + --- 868 + 869 + ## Maintenance 870 + 871 + ### View Logs 872 + 873 + ```bash 874 + # All BSPDS pods 875 + kubectl logs -l app=bspds -n bspds -f 876 + 877 + # Specific pod 878 + kubectl logs -f deployment/bspds -n bspds 879 + ``` 880 + 881 + ### Scale Manually 882 + 883 + ```bash 884 + kubectl scale deployment bspds --replicas=5 -n bspds 885 + ``` 886 + 887 + ### Update BSPDS 888 + 889 + ```bash 890 + # Build and push new image 891 + docker build -t your-registry.com/bspds:v1.2.3 . 892 + docker push your-registry.com/bspds:v1.2.3 893 + 894 + # Update deployment 895 + kubectl set image deployment/bspds bspds=your-registry.com/bspds:v1.2.3 -n bspds 896 + 897 + # Watch rollout 898 + kubectl rollout status deployment/bspds -n bspds 899 + ``` 900 + 901 + ### Backup Database 902 + 903 + ```bash 904 + # For CloudNativePG 905 + kubectl cnpg backup bspds-db -n bspds 906 + 907 + # For StatefulSet 908 + kubectl exec -it bspds-db-0 -n bspds -- pg_dump -U bspds pds > backup-$(date +%Y%m%d).sql 909 + ``` 910 + 911 + ### Run Migrations 912 + 913 + If you have a migration Job defined, you can re-run it: 914 + 915 + ```bash 916 + # Delete old job first (if exists) 917 + kubectl delete job bspds-migrate -n bspds --ignore-not-found 918 + 919 + # Re-apply the migration job from step 7 920 + # Or simply restart the deployment - BSPDS runs migrations on startup 921 + kubectl rollout restart deployment/bspds -n bspds 922 + ``` 923 + 924 + --- 925 + 926 + ## Troubleshooting 927 + 928 + ### Pod Won't Start 929 + 930 + ```bash 931 + kubectl describe pod -l app=bspds -n bspds 932 + kubectl logs -l app=bspds -n bspds --previous 933 + ``` 934 + 935 + ### Database Connection Issues 936 + 937 + ```bash 938 + # Test connectivity from a debug pod 939 + kubectl run debug --rm -it --restart=Never --image=postgres:18-alpine -- \ 940 + psql "postgres://bspds:PASSWORD@bspds-db-rw:5432/pds" -c "SELECT 1" 941 + ``` 942 + 943 + ### Certificate Issues 944 + 945 + ```bash 946 + kubectl describe certificate bspds-tls -n bspds 947 + kubectl describe certificaterequest -n bspds 948 + kubectl logs -l app.kubernetes.io/name=cert-manager -n cert-manager 949 + ``` 950 + 951 + ### View Resource Usage 952 + 953 + ```bash 954 + kubectl top pods -n bspds 955 + kubectl top nodes 956 + ```
+354
docs/install-openbsd.md
··· 1 + # BSPDS Production Installation on OpenBSD 2 + 3 + > **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. 4 + 5 + This guide covers installing BSPDS on OpenBSD 7.8 (current release as of December 2025). 6 + 7 + ## Prerequisites 8 + 9 + - A VPS with at least 2GB RAM and 20GB disk 10 + - A domain name pointing to your server's IP 11 + - Root access (or doas configured) 12 + 13 + ## Why nginx over relayd? 14 + 15 + 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. 16 + 17 + ## 1. System Setup 18 + 19 + ```sh 20 + pkg_add curl git 21 + ``` 22 + 23 + ## 2. Install Rust 24 + 25 + ```sh 26 + pkg_add rust 27 + ``` 28 + 29 + OpenBSD 7.8 ships Rust 1.82+. For the latest stable (1.92+), use rustup: 30 + 31 + ```sh 32 + pkg_add rustup 33 + rustup-init -y 34 + source ~/.cargo/env 35 + rustup default stable 36 + ``` 37 + 38 + ## 3. Install postgres 39 + 40 + OpenBSD 7.8 includes PostgreSQL 17 (PostgreSQL 18 may not yet be in ports): 41 + 42 + ```sh 43 + pkg_add postgresql-server postgresql-client 44 + 45 + mkdir -p /var/postgresql/data 46 + chown _postgresql:_postgresql /var/postgresql/data 47 + su - _postgresql -c "initdb -D /var/postgresql/data -U postgres -A scram-sha-256" 48 + 49 + rcctl enable postgresql 50 + rcctl start postgresql 51 + 52 + psql -U postgres -c "CREATE USER bspds WITH PASSWORD 'your-secure-password';" 53 + psql -U postgres -c "CREATE DATABASE pds OWNER bspds;" 54 + psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" 55 + ``` 56 + 57 + ## 4. Install minio 58 + 59 + OpenBSD doesn't have a minio package. Options: 60 + 61 + **Option A: Use an external S3-compatible service (recommended for production)** 62 + 63 + aws s3, backblaze b2, or upcloud managed object storage. Skip to step 5 and configure the S3 credentials in step 9. 64 + 65 + **Option B: Build minio from source** 66 + 67 + ```sh 68 + pkg_add go 69 + 70 + mkdir -p /tmp/minio-build && cd /tmp/minio-build 71 + ftp -o minio.tar.gz https://github.com/minio/minio/archive/refs/tags/RELEASE.2025-10-15T17-29-55Z.tar.gz 72 + tar xzf minio.tar.gz 73 + cd minio-* 74 + go build -o minio . 75 + cp minio /usr/local/bin/ 76 + 77 + mkdir -p /var/minio/data 78 + useradd -d /var/minio -s /sbin/nologin _minio 79 + chown -R _minio:_minio /var/minio 80 + 81 + cat > /etc/minio.conf << 'EOF' 82 + MINIO_ROOT_USER=minioadmin 83 + MINIO_ROOT_PASSWORD=your-minio-password 84 + EOF 85 + chmod 600 /etc/minio.conf 86 + 87 + cat > /etc/rc.d/minio << 'EOF' 88 + #!/bin/ksh 89 + 90 + daemon="/usr/local/bin/minio" 91 + daemon_user="_minio" 92 + daemon_flags="server /var/minio/data --console-address :9001" 93 + 94 + . /etc/rc.d/rc.subr 95 + 96 + rc_pre() { 97 + . /etc/minio.conf 98 + export MINIO_ROOT_USER MINIO_ROOT_PASSWORD 99 + } 100 + 101 + rc_cmd $1 102 + EOF 103 + 104 + chmod +x /etc/rc.d/minio 105 + rcctl enable minio 106 + rcctl start minio 107 + ``` 108 + 109 + Create the blob bucket: 110 + 111 + ```sh 112 + ftp -o /usr/local/bin/mc https://dl.min.io/client/mc/release/openbsd-amd64/mc 113 + chmod +x /usr/local/bin/mc 114 + 115 + mc alias set local http://localhost:9000 minioadmin your-minio-password 116 + mc mb local/pds-blobs 117 + ``` 118 + 119 + ## 5. Install redis 120 + 121 + OpenBSD has redis in ports (valkey may not be available yet): 122 + 123 + ```sh 124 + pkg_add redis 125 + 126 + rcctl enable redis 127 + rcctl start redis 128 + ``` 129 + 130 + ## 6. Install deno (for frontend build) 131 + 132 + ```sh 133 + curl -fsSL https://deno.land/install.sh | sh 134 + export PATH="$HOME/.deno/bin:$PATH" 135 + echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.profile 136 + ``` 137 + 138 + ## 7. Clone and Build BSPDS 139 + 140 + ```sh 141 + mkdir -p /opt && cd /opt 142 + git clone https://tangled.org/lewis.moe/bspds.git 143 + cd bspds 144 + 145 + cd frontend 146 + deno task build 147 + cd .. 148 + 149 + cargo build --release 150 + ``` 151 + 152 + ## 8. Install sqlx-cli and Run Migrations 153 + 154 + ```sh 155 + cargo install sqlx-cli --no-default-features --features postgres 156 + 157 + export DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" 158 + sqlx migrate run 159 + ``` 160 + 161 + ## 9. Configure BSPDS 162 + 163 + ```sh 164 + mkdir -p /etc/bspds 165 + cp /opt/bspds/.env.example /etc/bspds/bspds.conf 166 + chmod 600 /etc/bspds/bspds.conf 167 + ``` 168 + 169 + Edit `/etc/bspds/bspds.conf` and fill in your values. Generate secrets with: 170 + 171 + ```sh 172 + openssl rand -base64 48 173 + ``` 174 + 175 + ## 10. Create rc.d Service 176 + 177 + ```sh 178 + useradd -d /var/empty -s /sbin/nologin _bspds 179 + 180 + cp /opt/bspds/target/release/bspds /usr/local/bin/ 181 + mkdir -p /var/bspds 182 + cp -r /opt/bspds/frontend/dist /var/bspds/frontend 183 + chown -R _bspds:_bspds /var/bspds 184 + 185 + cat > /etc/rc.d/bspds << 'EOF' 186 + #!/bin/ksh 187 + 188 + daemon="/usr/local/bin/bspds" 189 + daemon_user="_bspds" 190 + daemon_logger="daemon.info" 191 + 192 + . /etc/rc.d/rc.subr 193 + 194 + rc_pre() { 195 + export FRONTEND_DIR=/var/bspds/frontend 196 + while IFS='=' read -r key value; do 197 + case "$key" in 198 + \#*|"") continue ;; 199 + esac 200 + export "$key=$value" 201 + done < /etc/bspds/bspds.conf 202 + } 203 + 204 + rc_cmd $1 205 + EOF 206 + 207 + chmod +x /etc/rc.d/bspds 208 + rcctl enable bspds 209 + rcctl start bspds 210 + ``` 211 + 212 + ## 11. Install and Configure nginx 213 + 214 + ```sh 215 + pkg_add nginx 216 + 217 + cat > /etc/nginx/nginx.conf << 'EOF' 218 + worker_processes 1; 219 + 220 + events { 221 + worker_connections 1024; 222 + } 223 + 224 + http { 225 + include mime.types; 226 + 227 + server { 228 + listen 80; 229 + listen [::]:80; 230 + server_name pds.example.com; 231 + 232 + location /.well-known/acme-challenge/ { 233 + root /var/www/acme; 234 + } 235 + 236 + location / { 237 + return 301 https://$host$request_uri; 238 + } 239 + } 240 + 241 + server { 242 + listen 443 ssl http2; 243 + listen [::]:443 ssl http2; 244 + server_name pds.example.com; 245 + 246 + ssl_certificate /etc/ssl/pds.example.com.fullchain.pem; 247 + ssl_certificate_key /etc/ssl/private/pds.example.com.key; 248 + ssl_protocols TLSv1.2 TLSv1.3; 249 + ssl_ciphers HIGH:!aNULL:!MD5; 250 + ssl_prefer_server_ciphers on; 251 + ssl_session_cache shared:SSL:10m; 252 + 253 + location / { 254 + proxy_pass http://127.0.0.1:3000; 255 + proxy_http_version 1.1; 256 + proxy_set_header Upgrade $http_upgrade; 257 + proxy_set_header Connection "upgrade"; 258 + proxy_set_header Host $host; 259 + proxy_set_header X-Real-IP $remote_addr; 260 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 261 + proxy_set_header X-Forwarded-Proto $scheme; 262 + proxy_read_timeout 86400; 263 + } 264 + } 265 + } 266 + EOF 267 + 268 + mkdir -p /var/www/acme 269 + rcctl enable nginx 270 + ``` 271 + 272 + ## 12. Obtain SSL Certificate with acme-client 273 + 274 + OpenBSD's native acme-client works well: 275 + 276 + ```sh 277 + cat >> /etc/acme-client.conf << 'EOF' 278 + 279 + authority letsencrypt { 280 + api url "https://acme-v02.api.letsencrypt.org/directory" 281 + account key "/etc/acme/letsencrypt-privkey.pem" 282 + } 283 + 284 + domain pds.example.com { 285 + domain key "/etc/ssl/private/pds.example.com.key" 286 + domain full chain certificate "/etc/ssl/pds.example.com.fullchain.pem" 287 + sign with letsencrypt 288 + } 289 + EOF 290 + 291 + mkdir -p /etc/acme 292 + 293 + rcctl start nginx 294 + 295 + acme-client -v pds.example.com 296 + 297 + rcctl restart nginx 298 + ``` 299 + 300 + Set up auto-renewal in root's crontab: 301 + 302 + ```sh 303 + crontab -e 304 + ``` 305 + 306 + Add: 307 + ``` 308 + 0 0 * * * acme-client pds.example.com && rcctl reload nginx 309 + ``` 310 + 311 + ## 13. Configure Packet Filter (pf) 312 + 313 + ```sh 314 + cat >> /etc/pf.conf << 'EOF' 315 + 316 + # BSPDS rules 317 + pass in on egress proto tcp from any to any port { 22, 80, 443 } 318 + EOF 319 + 320 + pfctl -f /etc/pf.conf 321 + ``` 322 + 323 + ## 14. Verify Installation 324 + 325 + ```sh 326 + rcctl check bspds 327 + ftp -o - https://pds.example.com/xrpc/_health 328 + ftp -o - https://pds.example.com/.well-known/atproto-did 329 + ``` 330 + 331 + ## Maintenance 332 + 333 + View logs: 334 + ```sh 335 + tail -f /var/log/daemon 336 + ``` 337 + 338 + Update BSPDS: 339 + ```sh 340 + cd /opt/bspds 341 + git pull 342 + cd frontend && deno task build && cd .. 343 + cargo build --release 344 + rcctl stop bspds 345 + cp target/release/bspds /usr/local/bin/ 346 + cp -r frontend/dist /var/bspds/frontend 347 + DATABASE_URL="postgres://bspds:your-secure-password@localhost:5432/pds" sqlx migrate run 348 + rcctl start bspds 349 + ``` 350 + 351 + Backup database: 352 + ```sh 353 + pg_dump -U postgres pds > /var/backups/pds-$(date +%Y%m%d).sql 354 + ```
+103
nginx.prod.conf
··· 1 + worker_processes auto; 2 + error_log /var/log/nginx/error.log warn; 3 + pid /var/run/nginx.pid; 4 + 5 + events { 6 + worker_connections 4096; 7 + use epoll; 8 + multi_accept on; 9 + } 10 + 11 + http { 12 + include /etc/nginx/mime.types; 13 + default_type application/octet-stream; 14 + 15 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 16 + '$status $body_bytes_sent "$http_referer" ' 17 + '"$http_user_agent" "$http_x_forwarded_for" ' 18 + 'rt=$request_time uct="$upstream_connect_time" ' 19 + 'uht="$upstream_header_time" urt="$upstream_response_time"'; 20 + 21 + access_log /var/log/nginx/access.log main; 22 + 23 + sendfile on; 24 + tcp_nopush on; 25 + tcp_nodelay on; 26 + keepalive_timeout 65; 27 + types_hash_max_size 2048; 28 + 29 + gzip on; 30 + gzip_vary on; 31 + gzip_proxied any; 32 + gzip_comp_level 6; 33 + gzip_types text/plain text/css text/xml application/json application/javascript 34 + application/xml application/xml+rss text/javascript application/activity+json; 35 + 36 + ssl_protocols TLSv1.2 TLSv1.3; 37 + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; 38 + ssl_prefer_server_ciphers off; 39 + ssl_session_cache shared:SSL:10m; 40 + ssl_session_timeout 1d; 41 + ssl_session_tickets off; 42 + ssl_stapling on; 43 + ssl_stapling_verify on; 44 + 45 + upstream bspds { 46 + server bspds:3000; 47 + keepalive 32; 48 + } 49 + 50 + server { 51 + listen 80; 52 + listen [::]:80; 53 + server_name _; 54 + 55 + location /.well-known/acme-challenge/ { 56 + root /var/www/acme; 57 + } 58 + 59 + location / { 60 + return 301 https://$host$request_uri; 61 + } 62 + } 63 + 64 + server { 65 + listen 443 ssl http2; 66 + listen [::]:443 ssl http2; 67 + server_name _; 68 + 69 + ssl_certificate /etc/nginx/certs/live/${PDS_HOSTNAME}/fullchain.pem; 70 + ssl_certificate_key /etc/nginx/certs/live/${PDS_HOSTNAME}/privkey.pem; 71 + 72 + client_max_body_size 100M; 73 + 74 + location / { 75 + proxy_pass http://bspds; 76 + proxy_http_version 1.1; 77 + proxy_set_header Upgrade $http_upgrade; 78 + proxy_set_header Connection "upgrade"; 79 + proxy_set_header Host $host; 80 + proxy_set_header X-Real-IP $remote_addr; 81 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 82 + proxy_set_header X-Forwarded-Proto $scheme; 83 + proxy_read_timeout 86400; 84 + proxy_send_timeout 86400; 85 + proxy_buffering off; 86 + proxy_request_buffering off; 87 + } 88 + 89 + location /xrpc/com.atproto.sync.subscribeRepos { 90 + proxy_pass http://bspds; 91 + proxy_http_version 1.1; 92 + proxy_set_header Upgrade $http_upgrade; 93 + proxy_set_header Connection "upgrade"; 94 + proxy_set_header Host $host; 95 + proxy_set_header X-Real-IP $remote_addr; 96 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 97 + proxy_set_header X-Forwarded-Proto $scheme; 98 + proxy_read_timeout 86400; 99 + proxy_send_timeout 86400; 100 + proxy_buffering off; 101 + } 102 + } 103 + }
+2 -2
scripts/test-infra.sh
··· 57 57 -e MINIO_ROOT_PASSWORD=minioadmin \ 58 58 -P \ 59 59 --label bspds_test=true \ 60 - minio/minio:latest server /data >/dev/null 60 + minio/minio:RELEASE.2025-10-15T17-29-55Z server /data >/dev/null 61 61 62 62 echo "Starting Valkey..." 63 63 $CONTAINER_CMD run -d \ ··· 100 100 echo "Creating MinIO bucket..." 101 101 $CONTAINER_CMD run --rm --network host \ 102 102 -e MC_HOST_minio="http://minioadmin:minioadmin@127.0.0.1:${MINIO_PORT}" \ 103 - minio/mc:latest mb minio/test-bucket --ignore-existing >/dev/null 2>&1 || true 103 + minio/mc:RELEASE.2025-07-16T15-35-03Z mb minio/test-bucket --ignore-existing >/dev/null 2>&1 || true 104 104 105 105 cat > "$INFRA_FILE" << EOF 106 106 export DATABASE_URL="postgres://postgres:postgres@127.0.0.1:${PG_PORT}/postgres"