apac-ops-bot/deploy.sh
michael eb0ae08eac Add --legacy-peer-deps to npm ci for react-scripts compatibility
react-scripts@5.0.1 has peer dependency on TypeScript 4.x but project
uses TypeScript 5.x. The --legacy-peer-deps flag ignores this conflict.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:08:49 -06:00

267 lines
8.4 KiB
Bash
Executable file

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