#!/bin/bash # # APAC Ops Bot Deployment Script # Idempotent deployment for Ubuntu server at /opt/apac-ops-bot/ # # Usage: sudo ./deploy.sh # # Note: Run 'git pull origin main' manually before running this script # (root user typically doesn't have SSH keys for git) set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging functions log_info() { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${BLUE}[INFO]${NC} $1" } log_success() { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${GREEN}[SUCCESS]${NC} $1" } log_warn() { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${RED}[ERROR]${NC} $1" } # Error handler error_exit() { log_error "$1" exit 1 } # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FRONTEND_DEPLOY_PATH="/var/www/html/apac-ops-bot" BACKEND_PORT=8048 HEALTH_CHECK_RETRIES=30 HEALTH_CHECK_INTERVAL=2 log_info "Starting APAC Ops Bot deployment..." log_info "Working directory: $SCRIPT_DIR" # ----------------------------------------------------------------------------- # Pre-flight checks # ----------------------------------------------------------------------------- log_info "Running pre-flight checks..." # Check if running as root if [[ $EUID -ne 0 ]]; then error_exit "This script must be run as root (use sudo)" fi # Check Docker if ! command -v docker &> /dev/null; then error_exit "Docker is not installed" fi log_info "Docker found: $(docker --version)" # Check docker-compose (try both v1 and v2 syntax) if command -v docker-compose &> /dev/null; then DOCKER_COMPOSE="docker-compose" elif docker compose version &> /dev/null; then DOCKER_COMPOSE="docker compose" else error_exit "docker-compose is not installed" fi log_info "Docker Compose found: $($DOCKER_COMPOSE version --short 2>/dev/null || $DOCKER_COMPOSE version)" # Check Node.js if ! command -v node &> /dev/null; then error_exit "Node.js is not installed" fi NODE_VERSION=$(node --version) log_info "Node.js found: $NODE_VERSION" # Check npm if ! command -v npm &> /dev/null; then error_exit "npm is not installed" fi log_info "npm found: $(npm --version)" # Check .env files if [[ ! -f "$SCRIPT_DIR/backend/.env" ]]; then error_exit "Backend .env file not found at $SCRIPT_DIR/backend/.env" fi log_info "Backend .env file found" if [[ ! -f "$SCRIPT_DIR/frontend/.env" ]]; then log_warn "Frontend .env file not found at $SCRIPT_DIR/frontend/.env (may use defaults)" fi # Verify docker-compose.yml exists if [[ ! -f "$SCRIPT_DIR/docker-compose.yml" ]]; then error_exit "docker-compose.yml not found" fi log_success "Pre-flight checks passed" # ----------------------------------------------------------------------------- # Build Docker containers # ----------------------------------------------------------------------------- log_info "Building Docker containers..." cd "$SCRIPT_DIR" $DOCKER_COMPOSE build --pull || error_exit "Docker build failed" log_success "Docker containers built" # ----------------------------------------------------------------------------- # Restart Docker services # ----------------------------------------------------------------------------- log_info "Starting Docker services..." $DOCKER_COMPOSE up -d || error_exit "Failed to start Docker services" # Wait for postgres to be healthy log_info "Waiting for PostgreSQL to be ready..." POSTGRES_READY=false for i in $(seq 1 $HEALTH_CHECK_RETRIES); do if $DOCKER_COMPOSE exec -T postgres pg_isready -U apac_ops_bot &> /dev/null; then POSTGRES_READY=true break fi log_info "Waiting for PostgreSQL... (attempt $i/$HEALTH_CHECK_RETRIES)" sleep $HEALTH_CHECK_INTERVAL done if [[ "$POSTGRES_READY" != "true" ]]; then error_exit "PostgreSQL failed to become ready" fi log_success "Docker services started and PostgreSQL is ready" # ----------------------------------------------------------------------------- # Run database migrations # ----------------------------------------------------------------------------- log_info "Running database migrations..." # Capture migration output to check for specific errors MIGRATION_OUTPUT=$($DOCKER_COMPOSE exec -T backend alembic upgrade head 2>&1) && MIGRATION_SUCCESS=true || MIGRATION_SUCCESS=false if [[ "$MIGRATION_SUCCESS" == "true" ]]; then log_success "Database migrations completed" else # Check if failure is due to tables already existing if echo "$MIGRATION_OUTPUT" | grep -q "already exists"; then log_warn "Tables already exist - checking if we need to stamp the database..." # Check current alembic version CURRENT_VERSION=$($DOCKER_COMPOSE exec -T backend alembic current 2>&1 || echo "none") if echo "$CURRENT_VERSION" | grep -q "(head)"; then log_info "Database is already at head revision" else log_info "Stamping database with current migration head..." $DOCKER_COMPOSE exec -T backend alembic stamp head || error_exit "Failed to stamp database" log_success "Database stamped - tables already existed, marked migrations as complete" fi else # Some other migration error echo "$MIGRATION_OUTPUT" error_exit "Database migrations failed" fi fi # ----------------------------------------------------------------------------- # Build frontend # ----------------------------------------------------------------------------- log_info "Building frontend..." cd "$SCRIPT_DIR/frontend" # Clean install dependencies (--legacy-peer-deps handles react-scripts peer dep conflicts) log_info "Installing frontend dependencies..." npm ci --legacy-peer-deps || error_exit "npm ci failed" # Build production bundle log_info "Creating production build..." npm run build || error_exit "Frontend build failed" if [[ ! -d "$SCRIPT_DIR/frontend/build" ]]; then error_exit "Frontend build directory not found" fi log_success "Frontend built successfully" # ----------------------------------------------------------------------------- # Deploy frontend to Apache # ----------------------------------------------------------------------------- log_info "Deploying frontend to Apache..." # Create deployment directory if it doesn't exist mkdir -p "$FRONTEND_DEPLOY_PATH" # Clear existing files rm -rf "${FRONTEND_DEPLOY_PATH:?}"/* # Copy new build cp -r "$SCRIPT_DIR/frontend/build/"* "$FRONTEND_DEPLOY_PATH/" # Set proper ownership chown -R www-data:www-data "$FRONTEND_DEPLOY_PATH" # Verify index.html exists if [[ ! -f "$FRONTEND_DEPLOY_PATH/index.html" ]]; then error_exit "Frontend deployment verification failed - index.html not found" fi log_success "Frontend deployed to $FRONTEND_DEPLOY_PATH" # ----------------------------------------------------------------------------- # Verification # ----------------------------------------------------------------------------- log_info "Running verification checks..." # Wait for backend to be ready log_info "Waiting for backend health check..." BACKEND_READY=false for i in $(seq 1 $HEALTH_CHECK_RETRIES); do HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$BACKEND_PORT/health" 2>/dev/null || echo "000") if [[ "$HTTP_STATUS" == "200" ]]; then BACKEND_READY=true break fi log_info "Waiting for backend... (attempt $i/$HEALTH_CHECK_RETRIES, status: $HTTP_STATUS)" sleep $HEALTH_CHECK_INTERVAL done if [[ "$BACKEND_READY" != "true" ]]; then log_warn "Backend health check failed - service may still be starting" else log_success "Backend health check passed" fi # Verify Docker containers are running log_info "Docker container status:" cd "$SCRIPT_DIR" $DOCKER_COMPOSE ps # ----------------------------------------------------------------------------- # Summary # ----------------------------------------------------------------------------- echo "" echo "==============================================" COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "unknown") if [[ "$BACKEND_READY" == "true" ]]; then log_success "Deployment completed successfully!" else log_warn "Deployment completed with warnings (backend may still be starting)" fi echo "" log_info "Deployed commit: $COMMIT_HASH - $COMMIT_MSG" log_info "Frontend path: $FRONTEND_DEPLOY_PATH" log_info "Backend URL: http://localhost:$BACKEND_PORT" echo "=============================================="