#!/bin/bash set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" echo "=== ModComms Deployment ===" echo "Working directory: $SCRIPT_DIR" # --- Check prerequisites --- echo "" echo "[Prerequisites] Checking required tools..." check_command() { if ! command -v "$1" &> /dev/null; then echo "Error: $1 is not installed" exit 1 fi echo " ✓ $1" } check_command git check_command node check_command npm check_command docker # --- Load deployment configuration --- if [ -f .env.deploy ]; then source .env.deploy echo " ✓ .env.deploy loaded" else echo "" echo "Error: .env.deploy not found" echo "Create it from the template:" echo " cp .env.deploy.example .env.deploy" echo " nano .env.deploy" exit 1 fi # Validate required variables if [ -z "$FRONTEND_DEPLOY_DIR" ]; then echo "Error: FRONTEND_DEPLOY_DIR not set in .env.deploy" exit 1 fi # Set defaults for docker compose variables COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-modcomms}" BACKEND_PORT="${BACKEND_PORT:-8000}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" POSTGRES_USER="${POSTGRES_USER:-modcomms}" POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-modcomms_dev}" POSTGRES_DB="${POSTGRES_DB:-modcomms}" echo " Environment: ${COMPOSE_PROJECT_NAME} (backend:${BACKEND_PORT}, postgres:${POSTGRES_PORT})" # Create .env file for docker compose (so manual docker compose commands work) cat > .env << EOF # Auto-generated by deploy.sh - do not edit manually # Edit .env.deploy instead and re-run deploy.sh COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME} BACKEND_PORT=${BACKEND_PORT} POSTGRES_PORT=${POSTGRES_PORT} POSTGRES_USER=${POSTGRES_USER} POSTGRES_PASSWORD=${POSTGRES_PASSWORD} POSTGRES_DB=${POSTGRES_DB} EOF echo " ✓ .env created for docker compose" # --- Check backend/.env exists (required for GEMINI_API_KEY) --- if [ ! -f backend/.env ]; then if [ -f backend/.env.example ]; then echo "" echo "Error: backend/.env not found" echo "Create it from the template:" echo " cp backend/.env.example backend/.env" echo " nano backend/.env # Add your GEMINI_API_KEY" exit 1 else echo "Error: backend/.env not found and no template available" exit 1 fi fi echo " ✓ backend/.env exists" # Validate that critical backend env vars are actually set (not placeholders) check_backend_env() { local var_name="$1" local value value=$(grep -E "^${var_name}=" backend/.env | cut -d= -f2- | tr -d '"' | tr -d "'") if [ -z "$value" ] || [[ "$value" == *"your_"* ]]; then echo "" echo "Error: ${var_name} is not configured in backend/.env" echo " Open backend/.env and set a real value for ${var_name}" exit 1 fi echo " ✓ ${var_name} is set" } check_backend_env GEMINI_API_KEY check_backend_env AZURE_TENANT_ID check_backend_env AZURE_CLIENT_ID # DATABASE_URL is intentionally omitted here — it is injected by docker-compose.yml # --- 1. Pull latest code (skip if not a git repo or no remote) --- echo "" echo "[1/6] Updating code..." if [ -d .git ]; then if git remote -v | grep -q origin; then # Discard local changes to package-lock.json (regenerated by npm install anyway) git checkout -- frontend/package-lock.json 2>/dev/null || true git pull || echo "Warning: git pull failed, continuing with local code" else echo " No remote configured, skipping git pull" fi else echo " Not a git repository, skipping git pull" fi # --- 2. Build frontend --- echo "" echo "[2/6] Building frontend..." cd frontend # Install dependencies (npm install is idempotent) npm install # Check that .env.local exists (must be created manually with credentials) if [ ! -f .env.local ]; then echo "" echo "Error: frontend/.env.local not found" echo "Create it manually with your environment-specific values:" echo "" echo " cat > frontend/.env.local << 'EOF'" echo " VITE_BACKEND_WS_URL=wss://your-domain.com/ws/analyze" echo " VITE_BACKEND_URL=https://your-domain.com" echo " VITE_AZURE_CLIENT_ID=your_azure_client_id" echo " VITE_AZURE_TENANT_ID=your_azure_tenant_id" echo " VITE_AZURE_REDIRECT_URI=https://your-domain.com" echo " EOF" echo "" exit 1 fi echo " ✓ .env.local exists" npm run build cd "$SCRIPT_DIR" # --- 3. Deploy frontend to Apache --- echo "" echo "[3/6] Deploying frontend to ${FRONTEND_DEPLOY_DIR}..." # Create directory if it doesn't exist sudo mkdir -p "$FRONTEND_DEPLOY_DIR" # Clear existing files (safe deletion with variable check) if [ -n "$FRONTEND_DEPLOY_DIR" ] && [ -d "$FRONTEND_DEPLOY_DIR" ]; then sudo find "$FRONTEND_DEPLOY_DIR" -mindepth 1 -delete 2>/dev/null || true fi # Copy new build sudo cp -r frontend/dist/* "$FRONTEND_DEPLOY_DIR/" sudo chown -R www-data:www-data "$FRONTEND_DEPLOY_DIR" echo " ✓ Frontend deployed" # --- 4. Update backend configuration --- echo "" echo "[4/6] Updating backend configuration..." # Update CORS_ORIGINS if specified if [ -n "$CORS_ORIGINS" ]; then if grep -q "^CORS_ORIGINS=" backend/.env; then sed -i "s|^CORS_ORIGINS=.*|CORS_ORIGINS=${CORS_ORIGINS}|" backend/.env else echo "CORS_ORIGINS=${CORS_ORIGINS}" >> backend/.env fi echo " ✓ CORS_ORIGINS updated" fi # --- 5. Build containers and run database migrations --- echo "" echo "[5/6] Building containers and running database migrations..." # Build image (always rebuild to pick up code changes) docker compose build # Start PostgreSQL first and wait for it to be healthy echo " Starting PostgreSQL..." docker compose up -d postgres echo " Waiting for PostgreSQL to be ready..." for i in {1..30}; do if docker compose exec -T postgres pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" > /dev/null 2>&1; then echo " ✓ PostgreSQL is ready" break fi if [ $i -eq 30 ]; then echo " Error: PostgreSQL failed to start" docker compose logs postgres exit 1 fi sleep 1 done # Run database migrations echo " Running database migrations..." if docker compose run --rm backend alembic upgrade head; then echo " ✓ Database migrations complete" else echo " Error: Database migrations failed" exit 1 fi # --- 6. Start backend service --- echo "" echo "[6/6] Starting backend service..." # Start backend container (force recreate to ensure new image is used) docker compose up -d --force-recreate backend # Wait for health check echo " Waiting for backend to be healthy..." for i in {1..30}; do if curl -sf "http://localhost:${BACKEND_PORT}/health" > /dev/null 2>&1; then echo " ✓ Backend is healthy" break fi if [ $i -eq 30 ]; then echo " Warning: Backend health check timed out" fi sleep 1 done # --- Deployment summary --- echo "" echo "=========================================" echo " Deployment Complete!" echo "=========================================" echo "" echo "Environment: ${COMPOSE_PROJECT_NAME}" echo "Frontend: ${FRONTEND_DEPLOY_DIR}" echo "Backend: http://localhost:${BACKEND_PORT}" echo "" docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" echo "" echo "Health check:" curl -s "http://localhost:${BACKEND_PORT}/health" && echo "" || echo "Warning: Backend not responding"