#!/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 " 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 "=========================================="