← back to Watches

scripts/production-deploy.sh

306 lines

#!/bin/bash

################################################################################
# OMEGA WATCHES - PRODUCTION DEPLOYMENT SCRIPT
# Zero-downtime blue-green deployment with automatic rollback
################################################################################

set -euo pipefail

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Configuration
APP_NAME="omega-watches"
APP_DIR="/root/Projects/watches"
BACKUP_DIR="$APP_DIR/backups/deployments"
GREEN_DIR="$APP_DIR/deploy-green"
MAIN_PORT=7500
GREEN_PORT=7502
HEALTH_ENDPOINT="http://localhost"
MAX_HEALTH_RETRIES=10
HEALTH_RETRY_DELAY=5

# Logging
LOG_FILE="$APP_DIR/logs/deployment-$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$(dirname "$LOG_FILE")"

log() {
    echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
}

log_error() {
    echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" | tee -a "$LOG_FILE"
}

log_warning() {
    echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" | tee -a "$LOG_FILE"
}

log_info() {
    echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO:${NC} $1" | tee -a "$LOG_FILE"
}

# Error handler
error_exit() {
    log_error "$1"
    exit 1
}

# Print header
print_header() {
    echo -e "${BLUE}"
    echo "╔════════════════════════════════════════════════════════════════╗"
    echo "║        OMEGA WATCHES - PRODUCTION DEPLOYMENT                   ║"
    echo "╚════════════════════════════════════════════════════════════════╝"
    echo -e "${NC}"
}

# Check prerequisites
check_prerequisites() {
    log_info "Checking prerequisites..."

    # Check if PM2 is installed
    if ! command -v pm2 &> /dev/null; then
        error_exit "PM2 is not installed. Please install PM2 first."
    fi

    # Check if Node.js is installed
    if ! command -v node &> /dev/null; then
        error_exit "Node.js is not installed."
    fi

    # Check if npm is installed
    if ! command -v npm &> /dev/null; then
        error_exit "npm is not installed."
    fi

    log "Prerequisites check passed ✓"
}

# Create backup
create_backup() {
    log_info "Creating backup of current deployment..."

    mkdir -p "$BACKUP_DIR"

    BACKUP_FILE="$BACKUP_DIR/backup-$(date +%Y%m%d_%H%M%S).tar.gz"

    cd "$APP_DIR"
    tar -czf "$BACKUP_FILE" \
        --exclude='node_modules' \
        --exclude='backups' \
        --exclude='logs' \
        --exclude='.git' \
        --exclude='deploy-green' \
        dist/ server.js package.json data/ public/ 2>/dev/null || true

    if [ -f "$BACKUP_FILE" ]; then
        log "Backup created: $BACKUP_FILE ✓"
    else
        log_warning "Backup creation failed, continuing anyway..."
    fi
}

# Install dependencies
install_dependencies() {
    log_info "Installing dependencies..."

    cd "$APP_DIR"
    npm ci --production --silent 2>&1 | tee -a "$LOG_FILE"

    log "Dependencies installed ✓"
}

# Build application
build_application() {
    log_info "Building application..."

    cd "$APP_DIR"
    npm run build 2>&1 | tee -a "$LOG_FILE" || log_warning "Build step skipped or failed"

    log "Build completed ✓"
}

# Deploy to green environment
deploy_green() {
    log_info "Deploying to green environment..."

    # Clean up old green deployment
    rm -rf "$GREEN_DIR"
    mkdir -p "$GREEN_DIR"

    # Copy files to green deployment
    cd "$APP_DIR"
    cp -r dist/ node_modules/ server.js package.json data/ public/ ecosystem.config.cjs "$GREEN_DIR/" 2>/dev/null || true

    log "Green deployment prepared ✓"
}

# Start green instance
start_green_instance() {
    log_info "Starting green instance on port $GREEN_PORT..."

    cd "$GREEN_DIR"

    # Stop any existing green instance
    pm2 delete omega-watches-green 2>/dev/null || true

    # Start green instance with custom port
    PORT=$GREEN_PORT pm2 start ecosystem.config.cjs --name omega-watches-green

    log "Green instance started ✓"
}

# Health check
health_check() {
    local port=$1
    local instance_name=$2

    log_info "Running health check on $instance_name (port $port)..."

    for i in $(seq 1 $MAX_HEALTH_RETRIES); do
        log_info "Health check attempt $i/$MAX_HEALTH_RETRIES..."

        if curl -f -s "$HEALTH_ENDPOINT:$port/api/health" > /dev/null 2>&1; then
            log "Health check passed for $instance_name ✓"
            return 0
        fi

        if [ $i -lt $MAX_HEALTH_RETRIES ]; then
            log_info "Waiting ${HEALTH_RETRY_DELAY}s before retry..."
            sleep $HEALTH_RETRY_DELAY
        fi
    done

    log_error "Health check failed for $instance_name after $MAX_HEALTH_RETRIES attempts"
    return 1
}

# Switch to green (promote)
switch_to_green() {
    log_info "Switching traffic to green deployment..."

    # Stop blue instance
    pm2 stop $APP_NAME 2>/dev/null || true
    pm2 delete $APP_NAME 2>/dev/null || true

    # Move green to production
    cd "$APP_DIR"
    rm -rf dist/ node_modules/ server.js 2>/dev/null || true
    mv "$GREEN_DIR"/* . 2>/dev/null || true
    rmdir "$GREEN_DIR" 2>/dev/null || true

    # Start on main port
    pm2 start ecosystem.config.cjs --name $APP_NAME
    pm2 save --force

    log "Traffic switched to new deployment ✓"
}

# Rollback
rollback() {
    log_error "Initiating rollback..."

    # Stop and delete green instance
    pm2 stop omega-watches-green 2>/dev/null || true
    pm2 delete omega-watches-green 2>/dev/null || true

    # Find latest backup
    LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/backup-*.tar.gz 2>/dev/null | head -1)

    if [ -z "$LATEST_BACKUP" ]; then
        error_exit "No backup found for rollback!"
    fi

    log_info "Rolling back to: $LATEST_BACKUP"

    # Extract backup
    cd "$APP_DIR"
    tar -xzf "$LATEST_BACKUP"

    # Restart main instance
    pm2 restart $APP_NAME 2>/dev/null || pm2 start ecosystem.config.cjs --name $APP_NAME

    log "Rollback completed ✓"
}

# Cleanup
cleanup() {
    log_info "Cleaning up..."

    # Remove green directory
    rm -rf "$GREEN_DIR" 2>/dev/null || true

    # Keep only last 10 backups
    cd "$BACKUP_DIR"
    ls -t backup-*.tar.gz 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true

    log "Cleanup completed ✓"
}

# Main deployment flow
main() {
    print_header

    log "Starting deployment at $(date)"

    # Pre-deployment checks
    check_prerequisites

    # Create backup
    create_backup

    # Build and prepare
    install_dependencies
    build_application

    # Deploy to green
    deploy_green
    start_green_instance

    # Wait for green to be ready
    sleep 5

    # Health check green instance
    if health_check $GREEN_PORT "green"; then
        # Switch to green
        switch_to_green

        # Delete green instance (now running as main)
        pm2 delete omega-watches-green 2>/dev/null || true

        # Final health check
        sleep 3
        if health_check $MAIN_PORT "production"; then
            cleanup

            echo ""
            log "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
            log "${GREEN}║           DEPLOYMENT SUCCESSFUL!                               ║${NC}"
            log "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}"
            echo ""
            log_info "Application URL: http://45.61.58.125:$MAIN_PORT"
            log_info "Health Check: http://45.61.58.125:$MAIN_PORT/api/health"
            log_info "PM2 Status: pm2 list"
            log_info "Logs: pm2 logs $APP_NAME"
            echo ""
        else
            log_error "Production health check failed after switch!"
            rollback
            exit 1
        fi
    else
        log_error "Green deployment health check failed!"
        rollback
        exit 1
    fi
}

# Run main function
main "$@"