diff --git a/docs/install-alpine.md b/docs/install-alpine.md index 0c49d2c..3209010 100644 --- a/docs/install-alpine.md +++ b/docs/install-alpine.md @@ -1,17 +1,12 @@ # BSPDS Production Installation on Alpine Linux > **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. + This guide covers installing BSPDS on Alpine Linux 3.23 (current stable as of December 2025). -## Choose Your Installation Method -| Method | Best For | -|--------|----------| -| **Native (this guide)** | Maximum performance, minimal footprint, full control | -| **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments | -| **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling | -This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md). ---- + ## Prerequisites - A VPS with at least 2GB RAM and 20GB disk - A domain name pointing to your server's IP +- A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) - Root access ## 1. System Setup ```sh @@ -178,13 +173,27 @@ EOF rc-update add nginx rc-service nginx start ``` -## 12. Obtain SSL Certificate +## 12. Obtain Wildcard SSL Certificate +User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. + +Wildcard certs require DNS-01 validation. For manual DNS validation (works with any provider): ```sh -certbot --nginx -d pds.example.com +certbot certonly --manual --preferred-challenges dns \ + -d pds.example.com -d '*.pds.example.com' ``` -Set up auto-renewal: +Follow the prompts to add TXT records to your DNS. + +If your DNS provider has a certbot plugin, you can use that for auto-renewal: ```sh -echo "0 0 * * * certbot renew --quiet" | crontab - +apk add certbot-dns-cloudflare +certbot certonly --dns-cloudflare \ + --dns-cloudflare-credentials /etc/cloudflare.ini \ + -d pds.example.com -d '*.pds.example.com' +``` + +After obtaining the cert, update nginx to use it, then set up auto-renewal: +```sh +echo "0 0 * * * certbot renew --quiet && rc-service nginx reload" | crontab - ``` ## 13. Configure Firewall ```sh diff --git a/docs/install-containers.md b/docs/install-containers.md index d61a7fb..cf415f2 100644 --- a/docs/install-containers.md +++ b/docs/install-containers.md @@ -6,19 +6,25 @@ This guide covers deploying BSPDS using containers with podman. ## Prerequisites - A VPS with at least 2GB RAM and 20GB disk - A domain name pointing to your server's IP +- A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) - Root or sudo access ## Quick Start (Docker/Podman Compose) If you just want to get running quickly: ```sh cp .env.example .env -# Edit .env with your values -# Generate secrets: openssl rand -base64 48 -# Build and start +``` + +Edit `.env` with your values. Generate secrets with `openssl rand -base64 48`. + +Build and start: +```sh podman-compose -f docker-compose.prod.yml up -d -# Get initial certificate (after DNS is configured) +``` + +Get initial certificate (after DNS is configured): +```sh podman-compose -f docker-compose.prod.yml run --rm certbot certonly \ --webroot -w /var/www/acme -d pds.example.com -# Restart nginx to load certificate podman-compose -f docker-compose.prod.yml restart nginx ``` For production setups with proper service management, continue to either the Debian or Alpine section below. @@ -74,31 +80,49 @@ echo "$MINIO_ROOT_PASSWORD" | podman secret create bspds-minio-password - systemctl daemon-reload systemctl start bspds-db bspds-minio bspds-valkey sleep 10 -# Create MinIO bucket +``` + +Create the minio bucket: +```bash podman run --rm --pod bspds \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=your-minio-password \ docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ sh -c "mc alias set local http://localhost:9000 \$MINIO_ROOT_USER \$MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs" -# Run migrations +``` + +Run migrations: +```bash cargo install sqlx-cli --no-default-features --features postgres DATABASE_URL="postgres://bspds:your-db-password@localhost:5432/pds" sqlx migrate run --source /opt/bspds/migrations ``` -## 9. Obtain SSL Certificate -Create temporary self-signed cert: +## 9. Obtain Wildcard SSL Certificate +User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. + +Create temporary self-signed cert to start services: ```bash openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ -keyout /srv/bspds/certs/privkey.pem \ -out /srv/bspds/certs/fullchain.pem \ -subj "/CN=pds.example.com" systemctl start bspds-app bspds-nginx -# Get real certificate -podman run --rm \ +``` + +Get a wildcard certificate using DNS validation: +```bash +podman run --rm -it \ -v /srv/bspds/certs:/etc/letsencrypt:Z \ - -v /srv/bspds/acme:/var/www/acme:Z \ docker.io/certbot/certbot:v5.2.2 certonly \ - --webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com -# Link certificates + --manual --preferred-challenges dns \ + -d pds.example.com -d '*.pds.example.com' \ + --agree-tos --email you@example.com +``` +Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. + +For automated renewal, use a DNS provider plugin (e.g., cloudflare, route53). + +Link certificates and restart: +```bash ln -sf /srv/bspds/certs/live/pds.example.com/fullchain.pem /srv/bspds/certs/fullchain.pem ln -sf /srv/bspds/certs/live/pds.example.com/privkey.pem /srv/bspds/certs/privkey.pem systemctl restart bspds-nginx @@ -200,42 +224,56 @@ EOF chmod +x /etc/init.d/bspds ``` ## 7. Initialize Services +Start services: ```sh -# Start services rc-service bspds start sleep 15 -# Create MinIO bucket +``` + +Create the minio bucket: +```sh source /srv/bspds/config/bspds.env podman run --rm --network bspds_default \ -e MINIO_ROOT_USER="$MINIO_ROOT_USER" \ -e MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \ docker.io/minio/mc:RELEASE.2025-07-16T15-35-03Z \ sh -c 'mc alias set local http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD && mc mb --ignore-existing local/pds-blobs' -# Run migrations +``` + +Run migrations: +```sh apk add rustup rustup-init -y source ~/.cargo/env cargo install sqlx-cli --no-default-features --features postgres -# Get database container IP DB_IP=$(podman inspect bspds-db-1 --format '{{.NetworkSettings.Networks.bspds_default.IPAddress}}') DATABASE_URL="postgres://bspds:$DB_PASSWORD@$DB_IP:5432/pds" sqlx migrate run --source /opt/bspds/migrations ``` -## 8. Obtain SSL Certificate -Create temporary self-signed cert: +## 8. Obtain Wildcard SSL Certificate +User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. Wildcard certs require DNS-01 validation. + +Create temporary self-signed cert to start services: ```sh openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ -keyout /srv/bspds/data/certs/privkey.pem \ -out /srv/bspds/data/certs/fullchain.pem \ -subj "/CN=pds.example.com" rc-service bspds restart -# Get real certificate -podman run --rm \ +``` + +Get a wildcard certificate using DNS validation: +```sh +podman run --rm -it \ -v /srv/bspds/data/certs:/etc/letsencrypt \ - -v /srv/bspds/data/acme:/var/www/acme \ - --network bspds_default \ docker.io/certbot/certbot:v5.2.2 certonly \ - --webroot -w /var/www/acme -d pds.example.com --agree-tos --email you@example.com -# Link certificates + --manual --preferred-challenges dns \ + -d pds.example.com -d '*.pds.example.com' \ + --agree-tos --email you@example.com +``` +Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. + +Link certificates and restart: +```sh ln -sf /srv/bspds/data/certs/live/pds.example.com/fullchain.pem /srv/bspds/data/certs/fullchain.pem ln -sf /srv/bspds/data/certs/live/pds.example.com/privkey.pem /srv/bspds/data/certs/privkey.pem rc-service bspds restart @@ -292,9 +330,15 @@ podman logs -f bspds-bspds-1 cd /opt/bspds git pull podman build -t bspds:latest . -# Debian: +``` + +Debian: +```bash systemctl restart bspds-app -# Alpine: +``` + +Alpine: +```sh rc-service bspds restart ``` ## Backup Database diff --git a/docs/install-debian.md b/docs/install-debian.md index c5c79de..3985957 100644 --- a/docs/install-debian.md +++ b/docs/install-debian.md @@ -1,17 +1,12 @@ # BSPDS Production Installation on Debian > **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. + This guide covers installing BSPDS on Debian 13 "Trixie" (current stable as of December 2025). -## Choose Your Installation Method -| Method | Best For | -|--------|----------| -| **Native (this guide)** | Maximum performance, full control, simpler debugging | -| **[Containerized](install-containers.md)** | Easier updates, isolation, reproducible deployments | -| **[Kubernetes](install-kubernetes.md)** | Multi-node, high availability, auto-scaling | -This guide covers native installation. For containerized deployment with podman and systemd quadlets, see the [container guide](install-containers.md). ---- + ## Prerequisites - A VPS with at least 2GB RAM and 20GB disk - A domain name pointing to your server's IP +- A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) - Root or sudo access ## 1. System Setup ```bash @@ -168,11 +163,25 @@ rm -f /etc/nginx/sites-enabled/default nginx -t systemctl reload nginx ``` -## 12. Obtain SSL Certificate +## 12. Obtain Wildcard SSL Certificate +User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. + +Wildcard certs require DNS-01 validation. If your DNS provider has a certbot plugin: ```bash -certbot --nginx -d pds.example.com +apt install -y python3-certbot-dns-cloudflare +certbot certonly --dns-cloudflare \ + --dns-cloudflare-credentials /etc/cloudflare.ini \ + -d pds.example.com -d '*.pds.example.com' ``` -Certbot automatically configures nginx for HTTP/2 and sets up auto-renewal. + +For manual DNS validation (works with any provider): +```bash +certbot certonly --manual --preferred-challenges dns \ + -d pds.example.com -d '*.pds.example.com' +``` +Follow the prompts to add TXT records to your DNS. Note: manual mode doesn't auto-renew. + +After obtaining the cert, update nginx to use it and reload. ## 13. Configure Firewall ```bash apt install -y ufw diff --git a/docs/install-kubernetes.md b/docs/install-kubernetes.md index d312741..437e892 100644 --- a/docs/install-kubernetes.md +++ b/docs/install-kubernetes.md @@ -1,859 +1,23 @@ -# BSPDS Production Kubernetes Deployment -> **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. -This guide covers deploying BSPDS on a production multi-node Kubernetes cluster with high availability, auto-scaling, and proper secrets management. -## Architecture Overview -``` - ┌─────────────────────────────────────────────────┐ - │ Kubernetes Cluster │ - │ │ - Internet ──────►│ Ingress Controller (nginx/traefik) │ - │ │ │ - │ ▼ │ - │ ┌─────────────┐ │ - │ │ Service │◄── HPA (2-10 replicas) │ - │ └──────┬──────┘ │ - │ │ │ - │ ┌────┴────┐ │ - │ ▼ ▼ │ - │ ┌─────┐ ┌─────┐ │ - │ │BSPDS│ │BSPDS│ ... (pods) │ - │ └──┬──┘ └──┬──┘ │ - │ │ │ │ - │ ▼ ▼ │ - │ ┌──────────────────────────────────────┐ │ - │ │ PostgreSQL │ MinIO │ Valkey │ │ - │ │ (HA/Operator)│ (StatefulSet) │ (Sentinel) │ - │ └──────────────────────────────────────┘ │ - └─────────────────────────────────────────────────┘ -``` -## Prerequisites -- Kubernetes cluster (1.30+) with at least 3 nodes (1.34 is current stable) -- `kubectl` configured to access your cluster -- `helm` 3.x installed -- Storage class that supports `ReadWriteOnce` (for databases) -- Ingress controller installed (nginx-ingress or traefik) -- cert-manager installed for TLS certificates -### Quick Prerequisites Setup -If you need to install prerequisites: -```bash -# Install nginx-ingress (chart v4.14.1 - December 2025) -helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx -helm repo update -helm install ingress-nginx ingress-nginx/ingress-nginx \ - --namespace ingress-nginx --create-namespace \ - --version 4.14.1 -# Install cert-manager (v1.19.2 - December 2025) -helm repo add jetstack https://charts.jetstack.io -helm repo update -helm install cert-manager jetstack/cert-manager \ - --namespace cert-manager --create-namespace \ - --version v1.19.2 \ - --set installCRDs=true -``` ---- -## 1. Create Namespace -```bash -kubectl create namespace bspds -kubectl config set-context --current --namespace=bspds -``` -## 2. Create Secrets -Generate secure passwords and secrets: -```bash -# Generate secrets -DB_PASSWORD=$(openssl rand -base64 32) -MINIO_PASSWORD=$(openssl rand -base64 32) -JWT_SECRET=$(openssl rand -base64 48) -DPOP_SECRET=$(openssl rand -base64 48) -MASTER_KEY=$(openssl rand -base64 48) -# Create Kubernetes secrets -kubectl create secret generic bspds-db-credentials \ - --from-literal=username=bspds \ - --from-literal=password="$DB_PASSWORD" -kubectl create secret generic bspds-minio-credentials \ - --from-literal=root-user=minioadmin \ - --from-literal=root-password="$MINIO_PASSWORD" -kubectl create secret generic bspds-secrets \ - --from-literal=jwt-secret="$JWT_SECRET" \ - --from-literal=dpop-secret="$DPOP_SECRET" \ - --from-literal=master-key="$MASTER_KEY" -# Save secrets locally (KEEP SECURE!) -echo "DB_PASSWORD=$DB_PASSWORD" > secrets.txt -echo "MINIO_PASSWORD=$MINIO_PASSWORD" >> secrets.txt -echo "JWT_SECRET=$JWT_SECRET" >> secrets.txt -echo "DPOP_SECRET=$DPOP_SECRET" >> secrets.txt -echo "MASTER_KEY=$MASTER_KEY" >> secrets.txt -chmod 600 secrets.txt -``` -## 3. Deploy PostgreSQL -### Option A: CloudNativePG Operator (Recommended for HA) -```bash -# Install CloudNativePG operator (v1.28.0 - December 2025) -kubectl apply --server-side -f \ - https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.0.yaml -# Wait for operator -kubectl wait --for=condition=available --timeout=120s \ - deployment/cnpg-controller-manager -n cnpg-system -``` -```bash -cat < **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. -## 8. Deploy BSPDS Application -```bash -cat < backup-$(date +%Y%m%d).sql -``` -### Run Migrations -If you have a migration Job defined, you can re-run it: -```bash -# Delete old job first (if exists) -kubectl delete job bspds-migrate -n bspds --ignore-not-found -# Re-apply the migration job from step 7 -# Or simply restart the deployment - BSPDS runs migrations on startup -kubectl rollout restart deployment/bspds -n bspds -``` ---- -## Troubleshooting -### Pod Won't Start -```bash -kubectl describe pod -l app=bspds -n bspds -kubectl logs -l app=bspds -n bspds --previous -``` -### Database Connection Issues -```bash -# Test connectivity from a debug pod -kubectl run debug --rm -it --restart=Never --image=postgres:18-alpine -- \ - psql "postgres://bspds:PASSWORD@bspds-db-rw:5432/pds" -c "SELECT 1" -``` -### Certificate Issues -```bash -kubectl describe certificate bspds-tls -n bspds -kubectl describe certificaterequest -n bspds -kubectl logs -l app.kubernetes.io/name=cert-manager -n cert-manager -``` -### View Resource Usage -```bash -kubectl top pods -n bspds -kubectl top nodes -``` +# BSPDS on Kubernetes + +If you're reaching for kubernetes for this app, you're experienced enough to know how to spin up: + +- cloudnativepg (or your preferred postgres operator) +- valkey +- s3-compatible object storage (minio operator, or just use a managed service) +- the app itself (it's just a container with some env vars) + +You'll need a wildcard TLS certificate for `*.your-pds-hostname.example.com` — user handles are served as subdomains. + +The container image expects: +- `DATABASE_URL` - postgres connection string +- `S3_ENDPOINT`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET` +- `VALKEY_URL` - redis:// connection string +- `PDS_HOSTNAME` - your PDS hostname (without protocol) +- `JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY` - generate with `openssl rand -base64 48` +- `APPVIEW_URL` - typically `https://api.bsky.app` +- `CRAWLERS` - typically `https://bsky.network` +and more, check the .env.example. + +Health check: `GET /xrpc/_health` + diff --git a/docs/install-openbsd.md b/docs/install-openbsd.md index 0518ce0..e700b99 100644 --- a/docs/install-openbsd.md +++ b/docs/install-openbsd.md @@ -4,6 +4,7 @@ This guide covers installing BSPDS on OpenBSD 7.8 (current release as of Decembe ## Prerequisites - A VPS with at least 2GB RAM and 20GB disk - A domain name pointing to your server's IP +- A **wildcard TLS certificate** for `*.pds.example.com` (user handles are served as subdomains) - Root access (or doas configured) ## Why nginx over relayd? 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,7 +81,7 @@ mc alias set local http://localhost:9000 minioadmin your-minio-password mc mb local/pds-blobs ``` ## 5. Install redis -OpenBSD has redis in ports (valkey may not be available yet): +OpenBSD has redis in ports (valkey not available yet): ```sh pkg_add redis rcctl enable redis @@ -194,37 +195,32 @@ EOF mkdir -p /var/www/acme rcctl enable nginx ``` -## 12. Obtain SSL Certificate with acme-client -OpenBSD's native acme-client works well: +## 12. Obtain Wildcard SSL Certificate +User handles are served as subdomains (e.g., `alice.pds.example.com`), so you need a wildcard certificate. + +OpenBSD's native `acme-client` only supports HTTP-01 validation, which can't issue wildcard certs. You have a few options: + +**Option A: Use certbot with DNS validation (recommended)** +```sh +pkg_add certbot +certbot certonly --manual --preferred-challenges dns \ + -d pds.example.com -d '*.pds.example.com' +``` +Follow the prompts to add TXT records to your DNS. Then update nginx.conf to point to the certbot certs. + +**Option B: Use a managed DNS provider with API** +If your DNS provider has a certbot plugin, you can automate renewal. + +**Option C: Use acme.sh** +[acme.sh](https://github.com/acmesh-official/acme.sh) supports many DNS providers for automated wildcard cert renewal. + +After obtaining the cert, update nginx to use it and restart: ```sh -cat >> /etc/acme-client.conf << 'EOF' -authority letsencrypt { - api url "https://acme-v02.api.letsencrypt.org/directory" - account key "/etc/acme/letsencrypt-privkey.pem" -} -domain pds.example.com { - domain key "/etc/ssl/private/pds.example.com.key" - domain full chain certificate "/etc/ssl/pds.example.com.fullchain.pem" - sign with letsencrypt -} -EOF -mkdir -p /etc/acme -rcctl start nginx -acme-client -v pds.example.com rcctl restart nginx ``` -Set up auto-renewal in root's crontab: -```sh -crontab -e -``` -Add: -``` -0 0 * * * acme-client pds.example.com && rcctl reload nginx -``` ## 13. Configure Packet Filter (pf) ```sh cat >> /etc/pf.conf << 'EOF' -# BSPDS rules pass in on egress proto tcp from any to any port { 22, 80, 443 } EOF pfctl -f /etc/pf.conf diff --git a/scripts/install-debian.sh b/scripts/install-debian.sh index 053d020..b6fd963 100755 --- a/scripts/install-debian.sh +++ b/scripts/install-debian.sh @@ -1,31 +1,32 @@ #!/bin/bash set -euo pipefail + RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' -CYAN='\033[0;36m' NC='\033[0m' + log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + if [[ $EUID -ne 0 ]]; then log_error "This script must be run as root" exit 1 fi + if ! grep -qi "debian" /etc/os-release 2>/dev/null; then log_warn "This script is designed for Debian. Proceed with caution on other distros." fi + nuke_installation() { - echo -e "${RED}" - echo "╔═══════════════════════════════════════════════════════════════════╗" - echo "║ NUKING EXISTING INSTALLATION ║" - echo "╚═══════════════════════════════════════════════════════════════════╝" - echo -e "${NC}" + log_warn "NUKING EXISTING INSTALLATION" log_info "Stopping services..." systemctl stop bspds 2>/dev/null || true systemctl disable bspds 2>/dev/null || true + log_info "Removing BSPDS files..." rm -rf /opt/bspds rm -rf /var/lib/bspds @@ -35,12 +36,15 @@ nuke_installation() { rm -rf /var/spool/bspds-mail rm -f /etc/systemd/system/bspds.service systemctl daemon-reload + log_info "Removing BSPDS configuration..." rm -rf /etc/bspds + log_info "Dropping postgres database and user..." sudo -u postgres psql -c "DROP DATABASE IF EXISTS pds;" 2>/dev/null || true sudo -u postgres psql -c "DROP USER IF EXISTS bspds;" 2>/dev/null || true - log_info "Removing minio bucket and resetting minio..." + + log_info "Removing minio bucket..." if command -v mc &>/dev/null; then mc rb local/pds-blobs --force 2>/dev/null || true mc alias remove local 2>/dev/null || true @@ -48,19 +52,17 @@ nuke_installation() { systemctl stop minio 2>/dev/null || true rm -rf /var/lib/minio/data/.minio.sys 2>/dev/null || true rm -f /etc/default/minio 2>/dev/null || true + log_info "Removing nginx config..." rm -f /etc/nginx/sites-enabled/bspds rm -f /etc/nginx/sites-available/bspds systemctl reload nginx 2>/dev/null || true - log_success "Previous installation nuked!" - echo "" + + log_success "Previous installation nuked" } + if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/bspds ]]; then - echo -e "${YELLOW}" - echo "╔═══════════════════════════════════════════════════════════════════╗" - echo "║ EXISTING INSTALLATION DETECTED ║" - echo "╚═══════════════════════════════════════════════════════════════════╝" - echo -e "${NC}" + log_warn "Existing installation detected" echo "" echo "Options:" echo " 1) Nuke everything and start fresh (destroys database!)" @@ -68,21 +70,21 @@ if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/ echo " 3) Exit" echo "" read -p "Choose an option [1/2/3]: " INSTALL_CHOICE + case "$INSTALL_CHOICE" in 1) echo "" - echo -e "${RED}WARNING: This will DELETE:${NC}" + log_warn "This will DELETE:" echo " - PostgreSQL database 'pds' and all data" echo " - All BSPDS configuration and credentials" echo " - All source code in /opt/bspds" echo " - MinIO bucket 'pds-blobs' and all blobs" - echo " - Mail queue contents" echo "" - read -p "Type 'NUKE' to confirm destruction: " CONFIRM_NUKE + read -p "Type 'NUKE' to confirm: " CONFIRM_NUKE if [[ "$CONFIRM_NUKE" == "NUKE" ]]; then nuke_installation else - log_error "Nuke cancelled. Exiting." + log_error "Nuke cancelled" exit 1 fi ;; @@ -90,92 +92,68 @@ if [[ -f /etc/bspds/bspds.env ]] || [[ -d /opt/bspds ]] || [[ -f /usr/local/bin/ log_info "Continuing with existing installation..." ;; 3) - log_info "Exiting." exit 0 ;; *) - log_error "Invalid option. Exiting." + log_error "Invalid option" exit 1 ;; esac fi -echo -e "${CYAN}" -echo "╔═══════════════════════════════════════════════════════════════════╗" -echo "║ BSPDS Installation Script for Debian ║" -echo "║ AT Protocol Personal Data Server in Rust ║" -echo "╚═══════════════════════════════════════════════════════════════════╝" -echo -e "${NC}" + +echo "" +log_info "BSPDS Installation Script for Debian" +echo "" + get_public_ips() { 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") - 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") + IPV6=$(curl -6 -s --max-time 5 ifconfig.me 2>/dev/null || curl -6 -s --max-time 5 icanhazip.com 2>/dev/null || echo "") } + log_info "Detecting public IP addresses..." get_public_ips +echo " IPv4: ${IPV4}" +[[ -n "$IPV6" ]] && echo " IPv6: ${IPV6}" echo "" -echo -e "${CYAN}Your server's public IPs:${NC}" -echo -e " IPv4: ${GREEN}${IPV4}${NC}" -echo -e " IPv6: ${GREEN}${IPV6}${NC}" -echo "" + read -p "Enter your PDS domain (e.g., pds.example.com): " PDS_DOMAIN if [[ -z "$PDS_DOMAIN" ]]; then log_error "Domain cannot be empty" exit 1 fi -read -p "Enter your email for Let's Encrypt notifications: " CERTBOT_EMAIL + +read -p "Enter your email for Let's Encrypt: " CERTBOT_EMAIL if [[ -z "$CERTBOT_EMAIL" ]]; then log_error "Email cannot be empty" exit 1 fi + echo "" -echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" -echo -e "${YELLOW}DNS RECORDS REQUIRED${NC}" -echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" +log_info "DNS records required (create these now if you haven't):" echo "" -echo "Before continuing, create these DNS records at your registrar:" -echo "" -echo -e "${GREEN}A Record:${NC}" -echo " Name: ${PDS_DOMAIN}" -echo " Type: A" -echo " Value: ${IPV4}" -echo "" -if [[ "$IPV6" != "Not available" ]]; then -echo -e "${GREEN}AAAA Record:${NC}" -echo " Name: ${PDS_DOMAIN}" -echo " Type: AAAA" -echo " Value: ${IPV6}" -echo "" -fi -echo -e "${GREEN}Wildcard A Record (for user handles):${NC}" -echo " Name: *.${PDS_DOMAIN}" -echo " Type: A" -echo " Value: ${IPV4}" -echo "" -if [[ "$IPV6" != "Not available" ]]; then -echo -e "${GREEN}Wildcard AAAA Record (for user handles):${NC}" -echo " Name: *.${PDS_DOMAIN}" -echo " Type: AAAA" -echo " Value: ${IPV6}" -echo "" -fi -echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" +echo " ${PDS_DOMAIN} A ${IPV4}" +[[ -n "$IPV6" ]] && echo " ${PDS_DOMAIN} AAAA ${IPV6}" +echo " *.${PDS_DOMAIN} A ${IPV4} (for user handles)" +[[ -n "$IPV6" ]] && echo " *.${PDS_DOMAIN} AAAA ${IPV6} (for user handles)" echo "" read -p "Have you created these DNS records? (y/N): " DNS_CONFIRMED if [[ ! "$DNS_CONFIRMED" =~ ^[Yy]$ ]]; then log_warn "Please create the DNS records and run this script again." exit 0 fi + CREDENTIALS_FILE="/etc/bspds/.credentials" if [[ -f "$CREDENTIALS_FILE" ]]; then - log_info "Loading existing credentials from previous installation..." + log_info "Loading existing credentials..." source "$CREDENTIALS_FILE" - log_success "Credentials loaded" else - log_info "Generating secure secrets..." + log_info "Generating secrets..." JWT_SECRET=$(openssl rand -base64 48) DPOP_SECRET=$(openssl rand -base64 48) MASTER_KEY=$(openssl rand -base64 48) DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) MINIO_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) + mkdir -p /etc/bspds cat > "$CREDENTIALS_FILE" << EOF JWT_SECRET="$JWT_SECRET" @@ -185,39 +163,35 @@ DB_PASSWORD="$DB_PASSWORD" MINIO_PASSWORD="$MINIO_PASSWORD" EOF chmod 600 "$CREDENTIALS_FILE" - log_success "Secrets generated and saved" + log_success "Secrets generated" fi + log_info "Checking swap space..." TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') TOTAL_SWAP_KB=$(grep SwapTotal /proc/meminfo | awk '{print $2}') + if [[ $TOTAL_SWAP_KB -lt 2000000 ]]; then - log_info "Adding swap space (needed for compilation)..." if [[ ! -f /swapfile ]]; then + log_info "Adding swap space for compilation..." SWAP_SIZE="4G" - if [[ $TOTAL_MEM_KB -lt 2000000 ]]; then - SWAP_SIZE="4G" - elif [[ $TOTAL_MEM_KB -lt 4000000 ]]; then - SWAP_SIZE="2G" - fi + [[ $TOTAL_MEM_KB -ge 4000000 ]] && SWAP_SIZE="2G" fallocate -l $SWAP_SIZE /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=4096 chmod 600 /swapfile mkswap /swapfile swapon /swapfile grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab - log_success "Swap space added ($SWAP_SIZE)" + log_success "Swap added ($SWAP_SIZE)" else swapon /swapfile 2>/dev/null || true - log_success "Existing swap enabled" fi -else - log_success "Sufficient swap already configured" fi + log_info "Updating system packages..." apt update && apt upgrade -y -log_success "System updated" + log_info "Installing build dependencies..." apt install -y curl git build-essential pkg-config libssl-dev ca-certificates gnupg lsb-release unzip xxd -log_success "Build dependencies installed" + log_info "Installing postgres..." apt install -y postgresql postgresql-contrib systemctl enable postgresql @@ -226,34 +200,34 @@ sudo -u postgres psql -c "CREATE USER bspds WITH PASSWORD '${DB_PASSWORD}';" 2>/ sudo -u postgres psql -c "ALTER USER bspds WITH PASSWORD '${DB_PASSWORD}';" sudo -u postgres psql -c "CREATE DATABASE pds OWNER bspds;" 2>/dev/null || true sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO bspds;" -log_success "postgres installed and configured" +log_success "postgres configured" + log_info "Installing valkey..." -apt install -y valkey || { - log_warn "valkey not in repos, trying redis..." +apt install -y valkey 2>/dev/null || { + log_warn "valkey not in repos, installing redis..." apt install -y redis-server systemctl enable redis-server systemctl start redis-server } systemctl enable valkey-server 2>/dev/null || true systemctl start valkey-server 2>/dev/null || true -log_success "valkey/redis installed" + log_info "Installing minio..." if [[ ! -f /usr/local/bin/minio ]]; then ARCH=$(dpkg --print-architecture) - if [[ "$ARCH" == "amd64" ]]; then - curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-amd64/minio - elif [[ "$ARCH" == "arm64" ]]; then - curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-arm64/minio - else - log_error "Unsupported architecture: $ARCH" - exit 1 - fi + case "$ARCH" in + amd64) curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-amd64/minio ;; + arm64) curl -fsSL -o /tmp/minio https://dl.min.io/server/minio/release/linux-arm64/minio ;; + *) log_error "Unsupported architecture: $ARCH"; exit 1 ;; + esac chmod +x /tmp/minio mv /tmp/minio /usr/local/bin/ fi + mkdir -p /var/lib/minio/data id -u minio-user &>/dev/null || useradd -r -s /sbin/nologin minio-user chown -R minio-user:minio-user /var/lib/minio + cat > /etc/default/minio << EOF MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=${MINIO_PASSWORD} @@ -261,10 +235,12 @@ MINIO_VOLUMES="/var/lib/minio/data" MINIO_OPTS="--console-address :9001" EOF chmod 600 /etc/default/minio + cat > /etc/systemd/system/minio.service << 'EOF' [Unit] Description=MinIO Object Storage After=network.target + [Service] User=minio-user Group=minio-user @@ -272,30 +248,34 @@ EnvironmentFile=/etc/default/minio ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS Restart=always LimitNOFILE=65536 + [Install] WantedBy=multi-user.target EOF + systemctl daemon-reload systemctl enable minio systemctl start minio log_success "minio installed" -log_info "Waiting for minio to start..." + +log_info "Waiting for minio..." sleep 5 -log_info "Installing minio client and creating bucket..." + if [[ ! -f /usr/local/bin/mc ]]; then ARCH=$(dpkg --print-architecture) - if [[ "$ARCH" == "amd64" ]]; then - curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc - elif [[ "$ARCH" == "arm64" ]]; then - curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-arm64/mc - fi + case "$ARCH" in + amd64) curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-amd64/mc ;; + arm64) curl -fsSL -o /tmp/mc https://dl.min.io/client/mc/release/linux-arm64/mc ;; + esac chmod +x /tmp/mc mv /tmp/mc /usr/local/bin/ fi + mc alias remove local 2>/dev/null || true mc alias set local http://localhost:9000 minioadmin "${MINIO_PASSWORD}" --api S3v4 mc mb local/pds-blobs --ignore-existing log_success "minio bucket created" + log_info "Installing rust..." if [[ -f "$HOME/.cargo/env" ]]; then source "$HOME/.cargo/env" @@ -304,47 +284,46 @@ if ! command -v rustc &>/dev/null; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" fi -log_success "rust installed" + log_info "Installing deno..." export PATH="$HOME/.deno/bin:$PATH" if ! command -v deno &>/dev/null && [[ ! -f "$HOME/.deno/bin/deno" ]]; then curl -fsSL https://deno.land/install.sh | sh grep -q 'deno/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc fi -log_success "deno installed" + log_info "Cloning BSPDS..." if [[ ! -d /opt/bspds ]]; then git clone https://tangled.org/lewis.moe/bspds-sandbox /opt/bspds else - log_warn "/opt/bspds already exists, pulling latest..." cd /opt/bspds && git pull fi cd /opt/bspds -log_success "BSPDS cloned" + log_info "Building frontend..." -cd /opt/bspds/frontend -"$HOME/.deno/bin/deno" task build -cd /opt/bspds +"$HOME/.deno/bin/deno" task build --filter=frontend log_success "Frontend built" -log_info "Building BSPDS (this may take a while)..." + +log_info "Building BSPDS (this takes a while)..." source "$HOME/.cargo/env" -NPROC=$(nproc) if [[ $TOTAL_MEM_KB -lt 4000000 ]]; then - log_info "Low memory detected, limiting parallel jobs..." + log_info "Low memory - limiting parallel jobs" CARGO_BUILD_JOBS=1 cargo build --release else cargo build --release fi log_success "BSPDS built" -log_info "Installing sqlx-cli and running migrations..." + +log_info "Running migrations..." cargo install sqlx-cli --no-default-features --features postgres export DATABASE_URL="postgres://bspds:${DB_PASSWORD}@localhost:5432/pds" "$HOME/.cargo/bin/sqlx" migrate run log_success "Migrations complete" -log_info "Setting up mail trap for testing..." + +log_info "Setting up mail trap..." mkdir -p /var/spool/bspds-mail -chown root:root /var/spool/bspds-mail chmod 1777 /var/spool/bspds-mail + cat > /usr/local/bin/bspds-sendmail << 'SENDMAIL_EOF' #!/bin/bash MAIL_DIR="/var/spool/bspds-mail" @@ -359,140 +338,40 @@ mkdir -p "$MAIL_DIR" cat } > "$MAIL_FILE" chmod 644 "$MAIL_FILE" -echo "Mail saved to: $MAIL_FILE" >&2 exit 0 SENDMAIL_EOF chmod +x /usr/local/bin/bspds-sendmail + cat > /usr/local/bin/bspds-mailq << 'MAILQ_EOF' #!/bin/bash MAIL_DIR="/var/spool/bspds-mail" -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' -show_help() { - echo "bspds-mailq - View captured emails from BSPDS mail trap" - echo "" - echo "Usage:" - echo " bspds-mailq List all captured emails" - echo " bspds-mailq View email by number (from list)" - echo " bspds-mailq View email by filename" - echo " bspds-mailq latest View the most recent email" - echo " bspds-mailq clear Delete all captured emails" - echo " bspds-mailq watch Watch for new emails (tail -f style)" - echo " bspds-mailq count Show count of emails in queue" - echo "" -} -list_emails() { - if [[ ! -d "$MAIL_DIR" ]] || [[ -z "$(ls -A "$MAIL_DIR" 2>/dev/null)" ]]; then - echo -e "${YELLOW}No emails in queue.${NC}" - return - fi - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" - echo -e "${GREEN} BSPDS Mail Queue${NC}" - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" - echo "" - local i=1 - for f in $(ls -t "$MAIL_DIR"/*.eml 2>/dev/null); do - local filename=$(basename "$f") - local received=$(grep "^X-BSPDS-Received:" "$f" 2>/dev/null | cut -d' ' -f2-) - local to=$(grep -i "^To:" "$f" 2>/dev/null | head -1 | cut -d' ' -f2-) - local subject=$(grep -i "^Subject:" "$f" 2>/dev/null | head -1 | sed 's/^Subject: *//') - echo -e "${BLUE}[$i]${NC} ${filename}" - echo -e " To: ${GREEN}${to:-unknown}${NC}" - echo -e " Subject: ${YELLOW}${subject:-}${NC}" - echo -e " Received: ${received:-unknown}" - echo "" - ((i++)) - done - echo -e "${CYAN}Total: $((i-1)) email(s)${NC}" -} -view_email() { - local target="$1" - local file="" - if [[ "$target" == "latest" ]]; then - file=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | head -1) - elif [[ "$target" =~ ^[0-9]+$ ]]; then - file=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | sed -n "${target}p") - elif [[ -f "$MAIL_DIR/$target" ]]; then - file="$MAIL_DIR/$target" - elif [[ -f "$target" ]]; then - file="$target" - fi - if [[ -z "$file" ]] || [[ ! -f "$file" ]]; then - echo -e "${RED}Email not found: $target${NC}" - return 1 - fi - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" - echo -e "${GREEN} $(basename "$file")${NC}" - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" - cat "$file" - echo "" - echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" -} -clear_queue() { - local count=$(ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l) - if [[ "$count" -eq 0 ]]; then - echo -e "${YELLOW}Queue is already empty.${NC}" - return - fi - rm -f "$MAIL_DIR"/*.eml - echo -e "${GREEN}Cleared $count email(s) from queue.${NC}" -} -watch_queue() { - echo -e "${CYAN}Watching for new emails... (Ctrl+C to stop)${NC}" - echo "" - local last_count=0 - while true; do - local current_count=$(ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l) - if [[ "$current_count" -gt "$last_count" ]]; then - echo -e "${GREEN}[$(date +%H:%M:%S)] New email received!${NC}" - view_email latest - last_count=$current_count - fi - sleep 1 - done -} -count_queue() { - local count=$(ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l) - echo "$count" -} -case "${1:-}" in - ""|list) - list_emails +case "${1:-list}" in + list) + ls -lt "$MAIL_DIR"/*.eml 2>/dev/null | head -20 || echo "No emails" ;; - latest|[0-9]*) - view_email "$1" + latest) + f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | head -1) + [[ -f "$f" ]] && cat "$f" || echo "No emails" ;; clear) - clear_queue - ;; - watch) - watch_queue + rm -f "$MAIL_DIR"/*.eml + echo "Cleared" ;; count) - count_queue + ls -1 "$MAIL_DIR"/*.eml 2>/dev/null | wc -l ;; - help|--help|-h) - show_help + [0-9]*) + f=$(ls -t "$MAIL_DIR"/*.eml 2>/dev/null | sed -n "${1}p") + [[ -f "$f" ]] && cat "$f" || echo "Not found" ;; *) - if [[ -f "$MAIL_DIR/$1" ]] || [[ -f "$1" ]]; then - view_email "$1" - else - echo -e "${RED}Unknown command: $1${NC}" - show_help - exit 1 - fi + [[ -f "$MAIL_DIR/$1" ]] && cat "$MAIL_DIR/$1" || echo "Usage: bspds-mailq [list|latest|clear|count|N]" ;; esac MAILQ_EOF chmod +x /usr/local/bin/bspds-mailq -log_success "Mail trap configured" + log_info "Creating BSPDS configuration..." -mkdir -p /etc/bspds cat > /etc/bspds/bspds.env << EOF SERVER_HOST=127.0.0.1 SERVER_PORT=3000 @@ -518,19 +397,19 @@ MAIL_FROM_NAME=BSPDS SENDMAIL_PATH=/usr/local/bin/bspds-sendmail EOF chmod 600 /etc/bspds/bspds.env -log_success "Configuration created" -log_info "Creating BSPDS service user..." + +log_info "Installing BSPDS..." id -u bspds &>/dev/null || useradd -r -s /sbin/nologin bspds cp /opt/bspds/target/release/bspds /usr/local/bin/ mkdir -p /var/lib/bspds cp -r /opt/bspds/frontend/dist /var/lib/bspds/frontend chown -R bspds:bspds /var/lib/bspds -log_success "BSPDS binary installed" -log_info "Creating systemd service..." + cat > /etc/systemd/system/bspds.service << 'EOF' [Unit] Description=BSPDS - AT Protocol PDS After=network.target postgresql.service minio.service + [Service] Type=simple User=bspds @@ -540,22 +419,28 @@ Environment=FRONTEND_DIR=/var/lib/bspds/frontend ExecStart=/usr/local/bin/bspds Restart=always RestartSec=5 + [Install] WantedBy=multi-user.target EOF + systemctl daemon-reload systemctl enable bspds systemctl start bspds -log_success "BSPDS service created and started" +log_success "BSPDS service started" + log_info "Installing nginx..." -apt install -y nginx certbot python3-certbot-nginx -log_success "nginx installed" -log_info "Configuring nginx..." +apt install -y nginx cat > /etc/nginx/sites-available/bspds << EOF server { listen 80; listen [::]:80; server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; + + location /.well-known/acme-challenge/ { + root /var/www/html; + } + location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; @@ -571,72 +456,131 @@ server { } } EOF + ln -sf /etc/nginx/sites-available/bspds /etc/nginx/sites-enabled/ rm -f /etc/nginx/sites-enabled/default nginx -t systemctl reload nginx log_success "nginx configured" -log_info "Configuring firewall (ufw)..." + +log_info "Configuring firewall..." apt install -y ufw ufw --force reset ufw default deny incoming ufw default allow outgoing -ufw allow ssh comment 'SSH' -ufw allow 80/tcp comment 'HTTP' -ufw allow 443/tcp comment 'HTTPS' +ufw allow ssh +ufw allow 80/tcp +ufw allow 443/tcp ufw --force enable log_success "Firewall configured" -log_info "Obtaining SSL certificate..." -certbot --nginx -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" --email "${CERTBOT_EMAIL}" --agree-tos --non-interactive || { - log_warn "Wildcard cert failed (requires DNS challenge). Trying single domain..." - certbot --nginx -d "${PDS_DOMAIN}" --email "${CERTBOT_EMAIL}" --agree-tos --non-interactive + +echo "" +log_info "Obtaining wildcard SSL certificate..." +echo "" +echo "User handles are served as subdomains (e.g., alice.${PDS_DOMAIN})," +echo "so you need a wildcard certificate. This requires DNS validation." +echo "" +echo "You'll need to add a TXT record to your DNS when prompted." +echo "" +read -p "Ready to proceed? (y/N): " CERT_READY + +if [[ "$CERT_READY" =~ ^[Yy]$ ]]; then + apt install -y certbot python3-certbot-nginx + + log_info "Running certbot with DNS challenge..." + echo "" + echo "When prompted, add the TXT record to your DNS, wait a minute" + echo "for propagation, then press Enter to continue." + echo "" + + if certbot certonly --manual --preferred-challenges dns \ + -d "${PDS_DOMAIN}" -d "*.${PDS_DOMAIN}" \ + --email "${CERTBOT_EMAIL}" --agree-tos; then + + cat > /etc/nginx/sites-available/bspds << EOF +server { + listen 80; + listen [::]:80; + server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; + + location /.well-known/acme-challenge/ { + root /var/www/html; + } + + location / { + return 301 https://\$host\$request_uri; + } } -log_success "SSL certificate obtained" + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name ${PDS_DOMAIN} *.${PDS_DOMAIN}; + + ssl_certificate /etc/letsencrypt/live/${PDS_DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${PDS_DOMAIN}/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_read_timeout 86400; + proxy_send_timeout 86400; + client_max_body_size 100M; + } +} +EOF + nginx -t && systemctl reload nginx + log_success "Wildcard SSL certificate installed" + + echo "" + log_warn "Certificate renewal note:" + echo "Manual DNS challenges don't auto-renew. Before expiry, run:" + echo " certbot renew --manual" + echo "" + echo "For auto-renewal, consider using a DNS provider plugin:" + echo " apt install python3-certbot-dns-cloudflare # or your provider" + echo "" + else + log_warn "Wildcard cert failed. You can retry later with:" + echo " certbot certonly --manual --preferred-challenges dns \\" + echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'" + fi +else + log_warn "Skipping SSL. Your PDS is running on HTTP only." + echo "To add SSL later, run:" + echo " certbot certonly --manual --preferred-challenges dns \\" + echo " -d ${PDS_DOMAIN} -d '*.${PDS_DOMAIN}'" +fi + log_info "Verifying installation..." sleep 3 if curl -s "http://localhost:3000/xrpc/_health" | grep -q "version"; then - log_success "BSPDS is responding!" + log_success "BSPDS is responding" else - log_warn "BSPDS may still be starting up. Check: journalctl -u bspds -f" + log_warn "BSPDS may still be starting. Check: journalctl -u bspds -f" fi + echo "" -echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" -echo -e "${GREEN} INSTALLATION COMPLETE!${NC}" -echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${NC}" +log_success "Installation complete" echo "" -echo -e "Your PDS is now running at: ${GREEN}https://${PDS_DOMAIN}${NC}" +echo "PDS: https://${PDS_DOMAIN}" echo "" -echo -e "${YELLOW}IMPORTANT: Save these credentials securely!${NC}" +echo "Credentials (also in /etc/bspds/.credentials):" +echo " DB password: ${DB_PASSWORD}" +echo " MinIO password: ${MINIO_PASSWORD}" echo "" -echo "Database password: ${DB_PASSWORD}" -echo "MinIO password: ${MINIO_PASSWORD}" +echo "Commands:" +echo " journalctl -u bspds -f # logs" +echo " systemctl restart bspds # restart" +echo " bspds-mailq # view trapped emails" echo "" -echo "Configuration file: /etc/bspds/bspds.env" -echo "" -echo -e "${CYAN}Useful commands:${NC}" -echo " journalctl -u bspds -f # View BSPDS logs" -echo " systemctl status bspds # Check BSPDS status" -echo " systemctl restart bspds # Restart BSPDS" -echo " curl https://${PDS_DOMAIN}/xrpc/_health # Health check" -echo "" -echo -e "${CYAN}Mail queue (for testing):${NC}" -echo " bspds-mailq # List all captured emails" -echo " bspds-mailq latest # View most recent email" -echo " bspds-mailq 1 # View email #1 from list" -echo " bspds-mailq watch # Watch for new emails live" -echo " bspds-mailq clear # Clear all captured emails" -echo "" -echo " Emails are saved to: /var/spool/bspds-mail/" -echo "" -echo -e "${CYAN}DNS Records Summary:${NC}" -echo "" -echo " ${PDS_DOMAIN} A ${IPV4}" -if [[ "$IPV6" != "Not available" ]]; then -echo " ${PDS_DOMAIN} AAAA ${IPV6}" -fi -echo " *.${PDS_DOMAIN} A ${IPV4}" -if [[ "$IPV6" != "Not available" ]]; then -echo " *.${PDS_DOMAIN} AAAA ${IPV6}" -fi -echo "" -echo -e "${GREEN}Enjoy your new AT Protocol PDS!${NC}"