- Backend: Azure AD JWKS validator with 24h cache, new POST /api/v1/auth/sso/login endpoint, sso_login() in AuthService with auto-provisioning, password_hash made nullable, auth_provider column added, Alembic migration c1d2e3f4a5b6 - Frontend: @azure/msal-browser, msal.ts config singleton, ssoLogin() API function, login page updated with SSO button and redirect callback handling - Deploy: frontend Dockerfile and docker-compose.prod.yml updated to bake Azure AD vars into the image at build time; deploy.sh validates SSO config on init/deploy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
304 lines
11 KiB
Bash
Executable file
304 lines
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# ============================================================
|
|
# Amazon Transcreation Platform — Deploy Script
|
|
# ============================================================
|
|
# Usage:
|
|
# First time: ./deploy.sh --init
|
|
# Updates: ./deploy.sh
|
|
# Full rebuild: ./deploy.sh --rebuild
|
|
#
|
|
# Location: /opt/amazon-transcreation
|
|
# URL: https://optical-dev.oliver.solutions/amazon-transcreation
|
|
# ============================================================
|
|
|
|
set -euo pipefail
|
|
|
|
APP_DIR="/opt/amazon-transcreation"
|
|
COMPOSE_FILE="docker-compose.prod.yml"
|
|
COMPOSE="docker compose -f $COMPOSE_FILE"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[DEPLOY]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
|
|
|
cd "$APP_DIR" || error "Directory $APP_DIR not found. Clone the repo first."
|
|
|
|
# ---------------------------------------------------------------
|
|
# Parse arguments
|
|
# ---------------------------------------------------------------
|
|
INIT=false
|
|
REBUILD=false
|
|
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--init) INIT=true ;;
|
|
--rebuild) REBUILD=true ;;
|
|
--help)
|
|
echo "Usage: $0 [--init] [--rebuild]"
|
|
echo " --init First-time setup (create .env, run migrations, seed data, configure Apache)"
|
|
echo " --rebuild Force rebuild all Docker images (no cache)"
|
|
echo " (no args) Pull latest code, rebuild changed images, restart"
|
|
exit 0
|
|
;;
|
|
*) error "Unknown argument: $arg" ;;
|
|
esac
|
|
done
|
|
|
|
# ---------------------------------------------------------------
|
|
# Pre-flight checks
|
|
# ---------------------------------------------------------------
|
|
log "Running pre-flight checks..."
|
|
|
|
command -v docker >/dev/null 2>&1 || error "Docker is not installed"
|
|
command -v git >/dev/null 2>&1 || error "Git is not installed"
|
|
docker info >/dev/null 2>&1 || error "Docker daemon is not running"
|
|
|
|
# ---------------------------------------------------------------
|
|
# First-time init
|
|
# ---------------------------------------------------------------
|
|
if [ "$INIT" = true ]; then
|
|
log "=== FIRST-TIME SETUP ==="
|
|
|
|
# Create .env if it doesn't exist
|
|
if [ ! -f .env ]; then
|
|
if [ -f .env.production.example ]; then
|
|
cp .env.production.example .env
|
|
# Generate a real JWT secret
|
|
JWT_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || openssl rand -hex 32)
|
|
sed -i "s/CHANGE_ME_GENERATE_WITH.*/$JWT_SECRET/" .env
|
|
# Generate a real DB password
|
|
DB_PASS=$(python3 -c "import secrets; print(secrets.token_hex(16))" 2>/dev/null || openssl rand -hex 16)
|
|
sed -i "s/CHANGE_ME_DB_PASSWORD/$DB_PASS/g" .env
|
|
warn ".env created from template. You MUST edit it to set:"
|
|
warn " - ANTHROPIC_API_KEY (your Claude API key)"
|
|
warn ""
|
|
warn "Pre-filled values (verify these are correct):"
|
|
warn " - AZURE_AD_TENANT_ID"
|
|
warn " - AZURE_AD_CLIENT_ID"
|
|
warn " - AZURE_AD_SSO_ENABLED"
|
|
warn ""
|
|
warn "Auto-generated values:"
|
|
warn " - JWT_SECRET_KEY"
|
|
warn " - DB_PASSWORD"
|
|
echo ""
|
|
read -p "Edit .env now? [Y/n] " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
${EDITOR:-nano} .env
|
|
fi
|
|
else
|
|
error ".env.production.example not found. Is this the right directory?"
|
|
fi
|
|
else
|
|
log ".env already exists, skipping creation"
|
|
fi
|
|
|
|
# Storage directories are now tracked in git (storage/amazon/tm + ref)
|
|
# Just ensure the base directory exists for runtime uploads
|
|
mkdir -p storage
|
|
|
|
# Build all images
|
|
log "Building all Docker images (this may take a few minutes)..."
|
|
$COMPOSE build
|
|
|
|
# Start database and redis first
|
|
log "Starting database and redis..."
|
|
$COMPOSE up -d db redis
|
|
log "Waiting for database to be healthy..."
|
|
sleep 5
|
|
|
|
# Start backend for migrations
|
|
log "Starting backend..."
|
|
$COMPOSE up -d backend
|
|
sleep 5
|
|
|
|
# Wait for backend to be ready
|
|
log "Waiting for backend to start..."
|
|
for i in $(seq 1 30); do
|
|
if curl -sf http://127.0.0.1:8040/health >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# Run migrations
|
|
log "Running database migrations..."
|
|
$COMPOSE exec -T backend alembic upgrade head
|
|
|
|
# TM and reference files are now tracked in git — no import step needed.
|
|
# Verify they arrived via git pull:
|
|
TM_COUNT=$(find storage/amazon/tm -name '*.json' 2>/dev/null | wc -l)
|
|
REF_COUNT=$(find storage/amazon/ref -name '*.json' 2>/dev/null | wc -l)
|
|
log "Found $TM_COUNT TM files and $REF_COUNT reference files in storage/amazon/"
|
|
if [ "$TM_COUNT" -eq 0 ]; then
|
|
warn "No TM files found! Check that git pulled storage/amazon/tm/ correctly."
|
|
fi
|
|
|
|
# Seed database
|
|
log "Seeding database (default client + test users)..."
|
|
$COMPOSE exec -T backend python -m seed.create_default_client
|
|
$COMPOSE exec -T backend python -m seed.create_test_users
|
|
|
|
# Register existing TM + reference files in the database
|
|
log "Registering storage files in database..."
|
|
$COMPOSE exec -T backend python -m seed.register_storage_files
|
|
|
|
# Start all remaining services
|
|
log "Starting all services..."
|
|
$COMPOSE up -d --remove-orphans
|
|
|
|
# ---------------------------------------------------------------
|
|
# SSO configuration check
|
|
# ---------------------------------------------------------------
|
|
SSO_ENABLED_VAL=$(grep -E "^AZURE_AD_SSO_ENABLED=" .env | cut -d= -f2 | tr -d '[:space:]' || true)
|
|
TENANT_ID_VAL=$(grep -E "^AZURE_AD_TENANT_ID=" .env | cut -d= -f2 | tr -d '[:space:]' || true)
|
|
CLIENT_ID_VAL=$(grep -E "^AZURE_AD_CLIENT_ID=" .env | cut -d= -f2 | tr -d '[:space:]' || true)
|
|
|
|
if [ "$SSO_ENABLED_VAL" = "true" ]; then
|
|
if [ -z "$TENANT_ID_VAL" ] || [ -z "$CLIENT_ID_VAL" ]; then
|
|
warn "AZURE_AD_SSO_ENABLED=true but AZURE_AD_TENANT_ID or AZURE_AD_CLIENT_ID is empty."
|
|
warn "SSO will not work until these are set in .env."
|
|
else
|
|
log "SSO configured: tenant=${TENANT_ID_VAL:0:8}... client=${CLIENT_ID_VAL:0:8}..."
|
|
fi
|
|
else
|
|
log "SSO disabled (AZURE_AD_SSO_ENABLED != true). Set it in .env to enable."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------
|
|
# Apache configuration
|
|
# ---------------------------------------------------------------
|
|
log "Configuring Apache reverse proxy..."
|
|
|
|
# Enable required modules
|
|
a2enmod proxy proxy_http proxy_wstunnel rewrite headers 2>/dev/null || true
|
|
|
|
# Find the SSL vhost for optical-dev.oliver.solutions
|
|
VHOST_FILE=$(grep -rl "optical-dev.oliver.solutions" /etc/apache2/sites-enabled/ 2>/dev/null | head -1 || true)
|
|
|
|
if [ -n "$VHOST_FILE" ]; then
|
|
# Check if already configured
|
|
if grep -q "amazon-transcreation" "$VHOST_FILE" 2>/dev/null; then
|
|
log "Apache already has amazon-transcreation config in $VHOST_FILE"
|
|
else
|
|
warn "Found vhost at: $VHOST_FILE"
|
|
warn ""
|
|
warn "Add the following INSIDE the <VirtualHost *:443> block, before </VirtualHost>:"
|
|
warn ""
|
|
warn " Include /opt/amazon-transcreation/apache/amazon-transcreation.conf"
|
|
warn ""
|
|
warn "Then run: sudo systemctl reload apache2"
|
|
echo ""
|
|
read -p "Want me to add it automatically? [y/N] " -n 1 -r
|
|
echo
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
# Insert Include line before </VirtualHost>
|
|
sed -i "/<\/VirtualHost>/i\\ Include /opt/amazon-transcreation/apache/amazon-transcreation.conf" "$VHOST_FILE"
|
|
systemctl reload apache2
|
|
log "Apache config added and reloaded."
|
|
fi
|
|
fi
|
|
else
|
|
warn "Could not find Apache vhost for optical-dev.oliver.solutions"
|
|
warn "Manually add this to your SSL vhost:"
|
|
warn " Include /opt/amazon-transcreation/apache/amazon-transcreation.conf"
|
|
warn "Then: sudo systemctl reload apache2"
|
|
fi
|
|
|
|
# Health check
|
|
sleep 3
|
|
if curl -sf http://127.0.0.1:8040/health >/dev/null 2>&1; then
|
|
log "Backend health check passed"
|
|
else
|
|
warn "Backend health check failed — check logs: $COMPOSE logs backend"
|
|
fi
|
|
|
|
echo ""
|
|
log "=== SETUP COMPLETE ==="
|
|
log ""
|
|
log "URL: https://optical-dev.oliver.solutions/amazon-transcreation"
|
|
log ""
|
|
log "Services running:"
|
|
$COMPOSE ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
|
|
log ""
|
|
log "Test credentials:"
|
|
log " admin@amazon.com / admin123!"
|
|
log " manager@amazon.com / manager123!"
|
|
log " reviewer@amazon.com / reviewer123!"
|
|
log ""
|
|
log "Next steps:"
|
|
log " 1. Verify ANTHROPIC_API_KEY is set in .env"
|
|
log " 2. Ensure Apache config is loaded (see above)"
|
|
log " 3. Change default passwords via admin panel"
|
|
log " 4. SSO: verify AZURE_AD_SSO_ENABLED=true and Azure AD vars in .env"
|
|
log " Redirect URI registered in Azure AD:"
|
|
log " https://optical-dev.oliver.solutions/amazon-transcreation/login"
|
|
exit 0
|
|
fi
|
|
|
|
# ---------------------------------------------------------------
|
|
# Standard deploy (git pull + rebuild + restart)
|
|
# ---------------------------------------------------------------
|
|
[ -f .env ] || error ".env not found. Run './deploy.sh --init' first."
|
|
|
|
# Pull latest code
|
|
log "Pulling latest code..."
|
|
git pull --ff-only || error "Git pull failed. Resolve conflicts manually."
|
|
|
|
# Check SSO vars are in .env (they bake into the frontend image at build time)
|
|
SSO_ENABLED_VAL=$(grep -E "^AZURE_AD_SSO_ENABLED=" .env | cut -d= -f2 | tr -d '[:space:]' || true)
|
|
if [ "$SSO_ENABLED_VAL" = "true" ]; then
|
|
TENANT_ID_VAL=$(grep -E "^AZURE_AD_TENANT_ID=" .env | cut -d= -f2 | tr -d '[:space:]' || true)
|
|
CLIENT_ID_VAL=$(grep -E "^AZURE_AD_CLIENT_ID=" .env | cut -d= -f2 | tr -d '[:space:]' || true)
|
|
if [ -z "$TENANT_ID_VAL" ] || [ -z "$CLIENT_ID_VAL" ]; then
|
|
warn "AZURE_AD_SSO_ENABLED=true but AZURE_AD_TENANT_ID or AZURE_AD_CLIENT_ID is empty in .env."
|
|
warn "The SSO button will NOT appear in the frontend build."
|
|
else
|
|
log "SSO enabled — Azure AD vars will be baked into the frontend image."
|
|
fi
|
|
fi
|
|
|
|
# Build images
|
|
# Frontend always gets --no-cache to avoid stale Next.js builds from Docker layer cache
|
|
if [ "$REBUILD" = true ]; then
|
|
log "Rebuilding all images (no cache)..."
|
|
$COMPOSE build --no-cache
|
|
else
|
|
log "Building backend..."
|
|
$COMPOSE build backend celery_worker
|
|
log "Building frontend (no cache)..."
|
|
$COMPOSE build --no-cache frontend
|
|
fi
|
|
|
|
# Run migrations (safe to run even if nothing changed)
|
|
log "Running database migrations..."
|
|
$COMPOSE up -d db redis
|
|
sleep 3
|
|
$COMPOSE up -d backend
|
|
sleep 5
|
|
$COMPOSE exec -T backend alembic upgrade head
|
|
|
|
# Restart all services (remove orphans to avoid stale containers)
|
|
log "Restarting all services..."
|
|
$COMPOSE down --remove-orphans
|
|
$COMPOSE up -d
|
|
|
|
# Health check
|
|
log "Waiting for services..."
|
|
sleep 5
|
|
|
|
if curl -sf http://127.0.0.1:8040/health >/dev/null 2>&1; then
|
|
log "Health check passed"
|
|
else
|
|
warn "Health check failed — check: $COMPOSE logs --tail 50"
|
|
fi
|
|
|
|
echo ""
|
|
$COMPOSE ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
|
|
echo ""
|
|
log "Deploy complete. URL: https://optical-dev.oliver.solutions/amazon-transcreation"
|