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>
267 lines
8.4 KiB
Bash
Executable file
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 "=============================================="
|