platform-tooling/scripts/deploy/deploy-sso.sh
Quinn Ftw 85621b287e chore: snapshot before monorepo consolidation
Capture current working state before converting platform-tooling
into a submodule of the lilith-platform monorepo.
2026-01-29 07:04:39 -08:00

301 lines
9.4 KiB
Bash
Executable file

#!/usr/bin/env bash
# =============================================================================
# SSO Deployment Script
# =============================================================================
# Infrastructure as Code deployment for SSO service
#
# Usage:
# ./deploy-sso.sh staging # Deploy to black (10.0.0.11)
# ./deploy-sso.sh production # Deploy to vps-0
#
# Prerequisites:
# - SSH access to target host
# - sudo privileges on target
# - .env file configured in vault/sso/
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[SSO]${NC} $1"; }
warn() { echo -e "${YELLOW}[SSO]${NC} $1"; }
error() { echo -e "${RED}[SSO]${NC} $1" >&2; }
# Environment configuration
declare -A HOSTS=(
["staging"]="black"
["production"]="vps-0"
)
declare -A DOMAINS=(
["staging"]="next.sso.atlilith.com"
["production"]="sso.atlilith.com"
)
# Validate environment argument
ENV="${1:-}"
if [[ -z "$ENV" ]] || [[ ! -v "HOSTS[$ENV]" ]]; then
error "Usage: $0 <staging|production>"
exit 1
fi
HOST="${HOSTS[$ENV]}"
DOMAIN="${DOMAINS[$ENV]}"
DEPLOY_DIR="/opt/sso"
NGINX_SITE="${DOMAIN}"
log "Deploying SSO to $ENV ($HOST)"
log "Domain: $DOMAIN"
# Check SSH connectivity
log "Checking SSH connectivity to $HOST..."
if ! ssh -o ConnectTimeout=5 "$HOST" "echo ok" &>/dev/null; then
error "Cannot connect to $HOST. Ensure VPN is active and SSH is configured."
exit 1
fi
# Check for .env file
ENV_FILE="$REPO_ROOT/vault/sso/.env.$ENV"
if [[ ! -f "$ENV_FILE" ]]; then
warn "Environment file not found: $ENV_FILE"
warn "Using template from infrastructure/docker/features/sso/.env.staging"
ENV_FILE="$REPO_ROOT/infrastructure/docker/features/sso/.env.staging"
fi
# =============================================================================
# 1. Build SSO Backend
# =============================================================================
log "Building SSO backend..."
cd "$REPO_ROOT/codebase/features/sso/backend-api"
# Check if dist exists and is recent (skip rebuild if so)
SKIP_BUILD="${SKIP_BUILD:-false}"
if [[ "$SKIP_BUILD" == "true" ]] && [[ -d "dist" ]]; then
log "SKIP_BUILD=true and dist exists, skipping build..."
elif [[ -d "dist" ]] && [[ -d "node_modules" ]]; then
# Check if any source file is newer than dist
NEWEST_SRC=$(find src -type f -name "*.ts" -newer dist/main.js 2>/dev/null | head -1)
if [[ -z "$NEWEST_SRC" ]]; then
log "Dist is up to date, skipping build..."
SKIP_BUILD=true
fi
fi
if [[ "$SKIP_BUILD" != "true" ]]; then
# Only run install if node_modules missing or package.json changed
if [[ ! -d "node_modules" ]] || [[ "package.json" -nt "node_modules/.package-lock.json" ]]; then
pnpm install --frozen-lockfile
else
log "node_modules up to date, skipping install..."
fi
pnpm build
fi
# =============================================================================
# 2. Create deployment package
# =============================================================================
log "Creating deployment package..."
DEPLOY_PKG="/tmp/sso-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
cd "$REPO_ROOT"
tar czf "$DEPLOY_PKG" \
--transform "s|^codebase/features/sso/backend-api|backend-api|" \
--transform "s|^infrastructure/ports.yaml|infrastructure/ports.yaml|" \
codebase/features/sso/backend-api/dist \
codebase/features/sso/backend-api/package.json \
codebase/features/sso/backend-api/node_modules \
infrastructure/ports.yaml \
codebase/features/sso/services.yaml
log "Package created: $DEPLOY_PKG ($(du -h "$DEPLOY_PKG" | cut -f1))"
# =============================================================================
# 3. Deploy to remote host
# =============================================================================
log "Deploying to $HOST..."
ssh "$HOST" bash << EOF
set -euo pipefail
# Create deployment directory
sudo mkdir -p $DEPLOY_DIR
sudo chown lilith:lilith $DEPLOY_DIR
# Backup existing deployment if exists
if [[ -d "$DEPLOY_DIR/backend-api" ]]; then
echo "Backing up existing deployment..."
sudo mv "$DEPLOY_DIR/backend-api" "$DEPLOY_DIR/backend-api.backup-\$(date +%Y%m%d-%H%M%S)"
fi
# Create directory structure
mkdir -p $DEPLOY_DIR/logs
mkdir -p $DEPLOY_DIR/infrastructure
mkdir -p $DEPLOY_DIR/codebase/features/sso
EOF
# Copy deployment package
log "Copying deployment package..."
scp "$DEPLOY_PKG" "$HOST:/tmp/sso-deploy.tar.gz"
# Extract and setup
ssh "$HOST" bash << EOF
set -euo pipefail
cd $DEPLOY_DIR
# Extract package
tar xzf /tmp/sso-deploy.tar.gz
rm /tmp/sso-deploy.tar.gz
# Copy services.yaml to expected location
cp -f services.yaml codebase/features/sso/services.yaml 2>/dev/null || true
echo "Deployment extracted successfully"
EOF
# Copy .env file
log "Copying environment configuration..."
scp "$ENV_FILE" "$HOST:$DEPLOY_DIR/.env"
# =============================================================================
# 4. Deploy nginx configuration
# =============================================================================
log "Deploying nginx configuration..."
# Deploy rate-limits.conf to conf.d (required for limit_req_zone directives)
RATE_LIMITS_CONF="$REPO_ROOT/infrastructure/nginx/conf.d/rate-limits.conf"
if [[ -f "$RATE_LIMITS_CONF" ]]; then
log "Deploying rate-limits.conf..."
scp "$RATE_LIMITS_CONF" "$HOST:/tmp/rate-limits.conf"
ssh "$HOST" bash << EOF
set -euo pipefail
sudo mv /tmp/rate-limits.conf /etc/nginx/conf.d/rate-limits.conf
echo "Rate limits configuration deployed"
EOF
fi
NGINX_CONF="$REPO_ROOT/infrastructure/nginx/sites-available/$NGINX_SITE"
if [[ -f "$NGINX_CONF" ]]; then
scp "$NGINX_CONF" "$HOST:/tmp/$NGINX_SITE"
ssh "$HOST" bash << EOF
set -euo pipefail
sudo mv /tmp/$NGINX_SITE /etc/nginx/sites-available/$NGINX_SITE
sudo ln -sf /etc/nginx/sites-available/$NGINX_SITE /etc/nginx/sites-enabled/$NGINX_SITE
sudo nginx -t
echo "Nginx configuration deployed"
EOF
else
warn "Nginx config not found: $NGINX_CONF"
fi
# =============================================================================
# 5. Deploy systemd service
# =============================================================================
log "Deploying systemd service..."
SYSTEMD_SERVICE="$REPO_ROOT/infrastructure/systemd/sso-api.service"
if [[ -f "$SYSTEMD_SERVICE" ]]; then
scp "$SYSTEMD_SERVICE" "$HOST:/tmp/sso-api.service"
ssh "$HOST" bash << EOF
set -euo pipefail
# Update paths in service file for this environment
sudo sed -i "s|WorkingDirectory=.*|WorkingDirectory=$DEPLOY_DIR/backend-api|" /tmp/sso-api.service
sudo sed -i "s|Environment=LILITH_ENV=.*|Environment=LILITH_ENV=$ENV|" /tmp/sso-api.service
sudo sed -i "s|EnvironmentFile=.*|EnvironmentFile=$DEPLOY_DIR/.env|" /tmp/sso-api.service
sudo mv /tmp/sso-api.service /etc/systemd/system/sso-api.service
sudo systemctl daemon-reload
echo "Systemd service deployed"
EOF
else
warn "Systemd service not found: $SYSTEMD_SERVICE"
fi
# =============================================================================
# 6. Deploy docker-compose for databases
# =============================================================================
log "Deploying database infrastructure..."
DOCKER_COMPOSE="$REPO_ROOT/infrastructure/docker/features/sso/docker-compose.yml"
INIT_SQL="$REPO_ROOT/infrastructure/docker/features/sso/init.sql"
if [[ -f "$DOCKER_COMPOSE" ]]; then
scp "$DOCKER_COMPOSE" "$HOST:$DEPLOY_DIR/docker-compose.yml"
[[ -f "$INIT_SQL" ]] && scp "$INIT_SQL" "$HOST:$DEPLOY_DIR/init.sql"
ssh "$HOST" bash << EOF
set -euo pipefail
cd $DEPLOY_DIR
# Start database containers if not running
if ! docker ps | grep -q lilith-sso-postgres; then
echo "Starting SSO databases..."
docker-compose up -d
# Wait for databases to be ready
echo "Waiting for databases..."
sleep 5
# Check health
docker-compose ps
else
echo "SSO databases already running"
fi
EOF
else
warn "Docker compose not found: $DOCKER_COMPOSE"
fi
# =============================================================================
# 7. Start/restart services
# =============================================================================
log "Starting SSO services..."
ssh "$HOST" bash << EOF
set -euo pipefail
# Reload nginx
sudo nginx -s reload || sudo systemctl reload nginx
# Enable and restart SSO API
sudo systemctl enable sso-api
sudo systemctl restart sso-api
# Check status
sleep 2
if systemctl is-active --quiet sso-api; then
echo "SSO API is running"
else
echo "SSO API failed to start"
sudo journalctl -u sso-api -n 20 --no-pager
exit 1
fi
EOF
# =============================================================================
# 8. Health check
# =============================================================================
log "Running health check..."
sleep 3
if ssh "$HOST" "curl -sf http://localhost:4001/health" &>/dev/null; then
log "Health check passed!"
else
warn "Health check failed - service may still be starting"
ssh "$HOST" "sudo journalctl -u sso-api -n 10 --no-pager" || true
fi
# Cleanup
rm -f "$DEPLOY_PKG"
log "=========================================="
log "SSO deployed successfully to $ENV!"
log "Domain: https://$DOMAIN"
log "=========================================="