← back to Handbag Auth Nextjs

scripts/devops/deploy.sh

224 lines

#!/bin/bash
#
# LUXVAULT Zero-Downtime Deployment Script
# Deploys new version with health checks and automatic rollback
#
# Usage: ./deploy.sh [version]
#

set -euo pipefail

PROJECT_DIR="/root/Projects/handbag-auth-nextjs"
AUCTION_VIEWER="$PROJECT_DIR/auction-viewer"
LOG_FILE="$PROJECT_DIR/logs/deployment.log"
BACKUP_DIR="$PROJECT_DIR/backups/deployments"

VERSION="${1:-$(date +%Y%m%d_%H%M%S)}"
HEALTH_URL="http://45.61.58.125:7500/health"
HEALTH_TIMEOUT=30
HEALTH_RETRIES=10

# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

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

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

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

# Create necessary directories
mkdir -p "$BACKUP_DIR" "$PROJECT_DIR/logs"

log "========================================="
log "Starting deployment: $VERSION"
log "========================================="

# Step 1: Pre-deployment checks
log "Step 1: Running pre-deployment checks..."

if ! pm2 status auction-viewer | grep -q "online"; then
    error "auction-viewer is not currently running!"
    exit 1
fi

# Check current health
if ! curl -sf --max-time 5 "$HEALTH_URL" > /dev/null; then
    error "Current deployment is unhealthy, aborting"
    exit 1
fi

log "✓ Pre-deployment checks passed"

# Step 2: Create backup
log "Step 2: Creating deployment backup..."

BACKUP_NAME="deployment_backup_${VERSION}"
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
mkdir -p "$BACKUP_PATH"

# Backup current server files
cp -r "$AUCTION_VIEWER" "$BACKUP_PATH/auction-viewer"

# Backup database
cp "$PROJECT_DIR/data/auction-history/auctions.db" "$BACKUP_PATH/auctions.db" 2>/dev/null || true

# Save PM2 configuration
pm2 save --force > /dev/null 2>&1 || true

log "✓ Backup created: $BACKUP_PATH"

# Step 3: Install dependencies (if package.json changed)
log "Step 3: Checking dependencies..."

cd "$AUCTION_VIEWER"

if [ -f "package.json" ]; then
    if [ ! -d "node_modules" ] || [ "package.json" -nt "node_modules" ]; then
        log "Installing/updating dependencies..."
        npm ci --production 2>&1 | tee -a "$LOG_FILE"
        log "✓ Dependencies updated"
    else
        log "✓ Dependencies up to date"
    fi
fi

# Step 4: Configuration validation
log "Step 4: Validating configuration..."

if [ ! -f ".env" ]; then
    warn "No .env file found, using defaults"
fi

# Check if required files exist
REQUIRED_FILES=("server.js" "package.json")
for file in "${REQUIRED_FILES[@]}"; do
    if [ ! -f "$file" ]; then
        error "Required file missing: $file"
        exit 1
    fi
done

log "✓ Configuration valid"

# Step 5: Reload PM2 with graceful restart
log "Step 5: Deploying new version..."

pm2 reload auction-viewer --update-env 2>&1 | tee -a "$LOG_FILE"

log "✓ PM2 reload initiated"

# Step 6: Health check with retries
log "Step 6: Verifying deployment health..."

sleep 3  # Give server time to start

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

    if curl -sf --max-time 5 "$HEALTH_URL" > /dev/null; then
        HEALTH_RESPONSE=$(curl -s "$HEALTH_URL")

        if echo "$HEALTH_RESPONSE" | grep -q '"status":"healthy"'; then
            log "✓ Deployment healthy!"
            break
        else
            warn "Health endpoint returned non-healthy status"
        fi
    else
        warn "Health check failed"
    fi

    if [ $i -eq $HEALTH_RETRIES ]; then
        error "Deployment failed health checks after $HEALTH_RETRIES attempts"
        log "Rolling back..."

        # Rollback
        pm2 stop auction-viewer
        rm -rf "$AUCTION_VIEWER"
        cp -r "$BACKUP_PATH/auction-viewer" "$AUCTION_VIEWER"
        pm2 restart auction-viewer

        error "Rollback complete. Deployment failed."
        exit 1
    fi

    sleep 3
done

# Step 7: Verify API functionality
log "Step 7: Testing API endpoints..."

API_ENDPOINTS=(
    "/api/auctions?limit=1"
    "/api/stats"
    "/metrics"
)

for endpoint in "${API_ENDPOINTS[@]}"; do
    if curl -sf --max-time 5 "http://45.61.58.125:7500${endpoint}" > /dev/null; then
        log "✓ ${endpoint} responding"
    else
        error "API endpoint failed: ${endpoint}"
        exit 1
    fi
done

log "✓ All API endpoints responding"

# Step 8: Verify database connectivity
log "Step 8: Verifying database connectivity..."

DB_CHECK=$(curl -s "http://45.61.58.125:7500/api/stats" | grep -o '"total":[0-9]*' || echo "")
if [ -n "$DB_CHECK" ]; then
    log "✓ Database connectivity confirmed: $DB_CHECK"
else
    error "Database connectivity check failed"
    exit 1
fi

# Step 9: Monitor for errors
log "Step 9: Monitoring for startup errors..."

sleep 5
ERROR_COUNT=$(pm2 logs auction-viewer --lines 20 --nostream 2>/dev/null | grep -c "ERROR" || true)

if [ "$ERROR_COUNT" -gt 3 ]; then
    warn "High error count detected: $ERROR_COUNT errors"
    pm2 logs auction-viewer --lines 20 --nostream | tee -a "$LOG_FILE"
fi

# Step 10: Cleanup old backups
log "Step 10: Cleanup old deployment backups..."

find "$BACKUP_DIR" -name "deployment_backup_*" -mtime +7 -exec rm -rf {} \; 2>/dev/null || true
BACKUP_COUNT=$(find "$BACKUP_DIR" -name "deployment_backup_*" -type d | wc -l)
log "✓ Cleanup complete. Remaining deployment backups: $BACKUP_COUNT"

# Deployment summary
log "========================================="
log "✅ DEPLOYMENT SUCCESSFUL"
log "========================================="
log "Version: $VERSION"
log "Health URL: $HEALTH_URL"
log "Dashboard: http://45.61.58.125:7500"
log "Backup location: $BACKUP_PATH"
log ""
log "Rollback command (if needed):"
log "  pm2 stop auction-viewer"
log "  rm -rf $AUCTION_VIEWER"
log "  cp -r $BACKUP_PATH/auction-viewer $AUCTION_VIEWER"
log "  pm2 restart auction-viewer"
log "========================================="

exit 0