Clean up installation instructions

This commit is contained in:
lewis
2025-12-16 22:15:50 +02:00
parent 524b99445d
commit 6da77b6565
6 changed files with 382 additions and 1216 deletions
+224 -280
View File
@@ -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 <number> View email by number (from list)"
echo " bspds-mailq <filename> 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:-<no 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}"