Ctrl K
ComunidadGuías…

deploy.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/bash

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

# Deployment start time
DEPLOY_START=$(date +%s)

# Store current directory
ROOT_DIR=$(pwd)

# Defaults (override via env vars on the server if needed)
PM2_APP_ID=${PM2_APP_ID:-4}

# Parse command-line arguments
DEPLOY_FRONTEND=false
DEPLOY_CLIENT_PORTAL=false
DEPLOY_BACKEND=false
DEPLOY_ALL=false
SKIP_GIT_PULL=false

# Show usage
show_usage() {
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --all                Deploy all components (default if no options specified)"
    echo "  --frontend           Deploy frontend only"
    echo "  --client-portal      Deploy client portal only"
    echo "  --backend            Deploy backend only"
    echo "  --skip-git           Skip git pull (useful for local testing)"
    echo "  -h, --help           Show this help message"
    echo ""
    echo "Examples:"
    echo "  $0                          # Deploy all components"
    echo "  $0 --all                    # Deploy all components"
    echo "  $0 --frontend               # Deploy frontend only"
    echo "  $0 --frontend --backend     # Deploy frontend and backend"
    echo "  $0 --backend --skip-git     # Deploy backend without git pull"
    echo ""
    exit 0
}

# Parse arguments
if [ $# -eq 0 ]; then
    DEPLOY_ALL=true
else
    while [[ $# -gt 0 ]]; do
        case $1 in
            --all)
                DEPLOY_ALL=true
                shift
                ;;
            --frontend)
                DEPLOY_FRONTEND=true
                shift
                ;;
            --client-portal)
                DEPLOY_CLIENT_PORTAL=true
                shift
                ;;
            --backend)
                DEPLOY_BACKEND=true
                shift
                ;;
            --skip-git)
                SKIP_GIT_PULL=true
                shift
                ;;
            -h|--help)
                show_usage
                ;;
            *)
                echo -e "${RED}Unknown option: $1${NC}"
                echo "Use --help for usage information"
                exit 1
                ;;
        esac
    done
fi

# If --all is specified, enable all deployments
if [ "$DEPLOY_ALL" = true ]; then
    DEPLOY_FRONTEND=true
    DEPLOY_CLIENT_PORTAL=true
    DEPLOY_BACKEND=true
fi

# Display deployment plan
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo -e "${BLUE}Starting Worklenz Deployment Process${NC}"
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}Deployment Plan:${NC}"
if [ "$DEPLOY_FRONTEND" = true ]; then
    echo -e "  ${GREEN}✓${NC} Frontend"
else
    echo -e "  ${YELLOW}⊘${NC} Frontend (skipped)"
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
    echo -e "  ${GREEN}✓${NC} Client Portal"
else
    echo -e "  ${YELLOW}⊘${NC} Client Portal (skipped)"
fi
if [ "$DEPLOY_BACKEND" = true ]; then
    echo -e "  ${GREEN}✓${NC} Backend"
else
    echo -e "  ${YELLOW}⊘${NC} Backend (skipped)"
fi
if [ "$SKIP_GIT_PULL" = true ]; then
    echo -e "  ${YELLOW}⊘${NC} Git pull (skipped)"
fi
echo ""

# Function to handle errors
handle_error() {
    local component=$1
    local exit_code=$2
    echo ""
    echo -e "${RED}═══ ERROR ═══${NC}"
    echo -e "${RED}Error in: ${component}${NC}"
    echo -e "${RED}Exit code: ${exit_code}${NC}"
    echo -e "${RED}Keeping maintenance mode active for safety${NC}"
    echo -e "${RED}═════════════${NC}"

    # Don't cleanup - keep maintenance mode active
    trap - EXIT
    exit 1
}

# Function to disable maintenance mode on successful exit only
cleanup() {
    if [ $? -eq 0 ]; then
        if [ "${MAINTENANCE_ENABLED:-false}" = true ]; then
            echo -e "${YELLOW}Disabling maintenance mode...${NC}"
            sudo rm -f /var/www/maintenance-mode
            sudo nginx -s reload
            echo -e "${GREEN}✅ Maintenance mode disabled${NC}"
        fi
    fi
}

# Set trap to cleanup only on successful exit
trap cleanup EXIT

MAINTENANCE_ENABLED=false

# Temp files for passing build artifact paths from parallel build jobs
DEPLOY_TMP_DIR="$(mktemp -d -t worklenz-deploy-XXXXXX)"
FRONTEND_BUILD_PATH_FILE="$DEPLOY_TMP_DIR/frontend-build-path"
CLIENT_PORTAL_BUILD_PATH_FILE="$DEPLOY_TMP_DIR/client-portal-build-path"

# 1. Pull latest changes
if [ "$SKIP_GIT_PULL" = false ]; then
    echo -e "${YELLOW}▶ Pulling latest changes from git...${NC}"
    git pull
    if [ $? -ne 0 ]; then
        handle_error "Git pull" $?
    fi
    echo -e "${GREEN}✅ Git pull completed${NC}"
    echo ""
else
    echo -e "${YELLOW}⊘ Skipping git pull${NC}"
    echo ""
fi

# 2. Build Frontend and Client Portal in PARALLEL (build first; swap later)
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
    if [ "$DEPLOY_FRONTEND" = true ] && [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
        echo -e "${YELLOW}▶ Building Frontend and Client Portal (parallel)...${NC}"
    elif [ "$DEPLOY_FRONTEND" = true ]; then
        echo -e "${YELLOW}▶ Building Frontend...${NC}"
    else
        echo -e "${YELLOW}▶ Building Client Portal...${NC}"
    fi
fi

# Frontend build function
build_frontend() {
    local start_time=$(date +%s)
    cd "$ROOT_DIR/worklenz-frontend" || return 1

    echo -e "${BLUE}[Frontend]${NC} Installing dependencies..."
    npm ci --prefer-offline --no-audit --progress=false > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo -e "${RED}[Frontend]${NC} npm ci failed"
        return 1
    fi

    # Create temporary build directory with timestamp
    local temp_build="build-$(date +%s)"
    echo -e "${BLUE}[Frontend]${NC} Building to temporary directory: ${temp_build}..."
    
    # Build to temporary directory
    VITE_BUILD_OUTDIR="$temp_build" NODE_OPTIONS="--max-old-space-size=4096" npm run build > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo -e "${RED}[Frontend]${NC} Build failed"
        rm -rf "$temp_build"
        return 1
    fi

    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    echo "$ROOT_DIR/worklenz-frontend/$temp_build" > "$FRONTEND_BUILD_PATH_FILE"
    echo -e "${GREEN}[Frontend]${NC} ✅ Build completed in ${duration}s (ready to swap)"
    return 0
}

# Client Portal build function
build_client_portal() {
    local start_time=$(date +%s)
    cd "$ROOT_DIR/worklenz-client-portal" || return 1

    echo -e "${BLUE}[Client Portal]${NC} Installing dependencies..."
    npm ci --prefer-offline --no-audit --progress=false > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo -e "${RED}[Client Portal]${NC} npm ci failed"
        return 1
    fi

    # Create temporary build directory with timestamp
    local temp_build="dist-$(date +%s)"
    echo -e "${BLUE}[Client Portal]${NC} Building to temporary directory: ${temp_build}..."
    
    # Build to temporary directory
    VITE_BUILD_OUTDIR="$temp_build" npm run build > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo -e "${RED}[Client Portal]${NC} Build failed"
        rm -rf "$temp_build"
        return 1
    fi

    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    echo "$ROOT_DIR/worklenz-client-portal/$temp_build" > "$CLIENT_PORTAL_BUILD_PATH_FILE"
    echo -e "${GREEN}[Client Portal]${NC} ✅ Build completed in ${duration}s (ready to swap)"
    return 0
}

# Run builds in parallel or sequentially based on selection
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
    PARALLEL_START=$(date +%s)
    FRONTEND_EXIT=0
    CLIENT_PORTAL_EXIT=0
    FRONTEND_PID=""
    CLIENT_PORTAL_PID=""

    # Start builds
    if [ "$DEPLOY_FRONTEND" = true ]; then
        build_frontend &
        FRONTEND_PID=$!
    fi

    if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
        build_client_portal &
        CLIENT_PORTAL_PID=$!
    fi

    # Wait for builds to complete
    if [ -n "$FRONTEND_PID" ]; then
        wait $FRONTEND_PID
        FRONTEND_EXIT=$?
    fi

    if [ -n "$CLIENT_PORTAL_PID" ]; then
        wait $CLIENT_PORTAL_PID
        CLIENT_PORTAL_EXIT=$?
    fi

    PARALLEL_END=$(date +%s)
    PARALLEL_DURATION=$((PARALLEL_END - PARALLEL_START))

    # Check if builds succeeded
    if [ "$DEPLOY_FRONTEND" = true ] && [ $FRONTEND_EXIT -ne 0 ]; then
        handle_error "Frontend build" $FRONTEND_EXIT
    fi

    if [ "$DEPLOY_CLIENT_PORTAL" = true ] && [ $CLIENT_PORTAL_EXIT -ne 0 ]; then
        handle_error "Client Portal build" $CLIENT_PORTAL_EXIT
    fi

    # Success message
    if [ "$DEPLOY_FRONTEND" = true ] && [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
        echo -e "${GREEN}✅ Frontend and Client Portal built successfully in ${PARALLEL_DURATION}s${NC}"
    elif [ "$DEPLOY_FRONTEND" = true ]; then
        echo -e "${GREEN}✅ Frontend built successfully in ${PARALLEL_DURATION}s${NC}"
    else
        echo -e "${GREEN}✅ Client Portal built successfully in ${PARALLEL_DURATION}s${NC}"
    fi
    echo ""
fi

# 4. Verify Apple Sign-In key file (if Apple login is enabled)
if [ -n "$APPLE_PRIVATE_KEY_PATH" ] || grep -q "APPLE_CLIENT_ID" "$ROOT_DIR/worklenz-backend/.env" 2>/dev/null; then
    echo -e "${YELLOW}▶ Checking Apple Sign-In key file...${NC}"
    KEY_PATH=$(grep "APPLE_PRIVATE_KEY_PATH" "$ROOT_DIR/worklenz-backend/.env" 2>/dev/null | cut -d '=' -f2 | tr -d '"' | tr -d "'" | xargs)
    if [ -n "$KEY_PATH" ]; then
        # Resolve relative path
        if [[ "$KEY_PATH" == ./* ]]; then
            FULL_KEY_PATH="$ROOT_DIR/worklenz-backend/${KEY_PATH#./}"
        else
            FULL_KEY_PATH="$KEY_PATH"
        fi
        if [ -f "$FULL_KEY_PATH" ]; then
            echo -e "${GREEN}✅ Apple Sign-In key file found${NC}"
        else
            echo -e "${RED}⚠️  WARNING: Apple Sign-In key file not found at: $FULL_KEY_PATH${NC}"
            echo -e "${YELLOW}   Apple Sign-In will not work until the key file is uploaded.${NC}"
            echo -e "${YELLOW}   See docs/APPLE_P8_KEY_MANAGEMENT.md for instructions.${NC}"
        fi
    fi
    echo ""
fi

# 5. Build Backend
if [ "$DEPLOY_BACKEND" = true ]; then
    echo -e "${YELLOW}▶ Building Backend application...${NC}"
    BACKEND_START=$(date +%s)

    cd "$ROOT_DIR/worklenz-backend" || handle_error "Backend directory not found" 1

    echo -e "${BLUE}[Backend]${NC} Installing dependencies..."
    npm ci --prefer-offline --no-audit --progress=false > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        handle_error "Backend npm ci" $?
    fi

    echo -e "${BLUE}[Backend]${NC} Building application..."
    npm run build > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        handle_error "Backend build" $?
    fi

    BACKEND_END=$(date +%s)
    BACKEND_DURATION=$((BACKEND_END - BACKEND_START))
    echo -e "${GREEN}✅ Backend built successfully in ${BACKEND_DURATION}s${NC}"
    echo ""
else
    BACKEND_DURATION=0
fi

# 6. Restart Backend with PM2
if [ "$DEPLOY_BACKEND" = true ]; then
    echo -e "${YELLOW}▶ Restarting Backend service with PM2...${NC}"
    pm2 restart "$PM2_APP_ID" --update-env > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        handle_error "PM2 restart" $?
    fi

    # Wait for service to stabilize
    sleep 5

    # 7. Health Check
    echo -e "${YELLOW}▶ Performing health check...${NC}"
    HEALTH_CHECK_ATTEMPTS=0
    MAX_HEALTH_CHECKS=10

    while [ $HEALTH_CHECK_ATTEMPTS -lt $MAX_HEALTH_CHECKS ]; do
        if pm2 describe "$PM2_APP_ID" 2>&1 | grep -q "online"; then
            echo -e "${GREEN}✅ Backend service is running${NC}"
            break
        fi

        HEALTH_CHECK_ATTEMPTS=$((HEALTH_CHECK_ATTEMPTS + 1))
        if [ $HEALTH_CHECK_ATTEMPTS -lt $MAX_HEALTH_CHECKS ]; then
            echo -e "${YELLOW}Waiting for service to come online (attempt ${HEALTH_CHECK_ATTEMPTS}/${MAX_HEALTH_CHECKS})...${NC}"
            sleep 2
        else
            echo -e "${RED}Health check failed - service not responding${NC}"
            pm2 logs "$PM2_APP_ID" --lines 20
            handle_error "Health check" 1
        fi
    done

    echo ""
fi

# 7. Brief maintenance mode and atomic swap (frontend/client portal)
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
    echo -e "${YELLOW}▶ Enabling maintenance mode (brief) for final swap...${NC}"
    sudo touch /var/www/maintenance-mode
    sudo nginx -s reload
    if [ $? -ne 0 ]; then
        handle_error "Enable maintenance mode" $?
    fi
    MAINTENANCE_ENABLED=true
    echo ""

    if [ "$DEPLOY_FRONTEND" = true ]; then
        if [ ! -f "$FRONTEND_BUILD_PATH_FILE" ]; then
            handle_error "Frontend build artifact missing" 1
        fi
        FRONTEND_BUILD_PATH="$(cat "$FRONTEND_BUILD_PATH_FILE")"
        if [ ! -d "$FRONTEND_BUILD_PATH" ]; then
            handle_error "Frontend build artifact not found" 1
        fi

        echo -e "${YELLOW}▶ Swapping Frontend build...${NC}"
        cd "$ROOT_DIR/worklenz-frontend" || handle_error "Frontend directory not found" 1

        if [ -d "build" ] && [ ! -L "build" ]; then
            mv build "build-backup-$(date +%s)" 2>/dev/null || true
        elif [ -L "build" ]; then
            rm -f build
        fi

        mv "$FRONTEND_BUILD_PATH" build
        ls -t | grep "^build-backup-" | tail -n +3 | xargs -r rm -rf
        echo -e "${GREEN}✅ Frontend deployed${NC}"
        echo ""
    fi

    if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
        if [ ! -f "$CLIENT_PORTAL_BUILD_PATH_FILE" ]; then
            handle_error "Client Portal build artifact missing" 1
        fi
        CLIENT_PORTAL_BUILD_PATH="$(cat "$CLIENT_PORTAL_BUILD_PATH_FILE")"
        if [ ! -d "$CLIENT_PORTAL_BUILD_PATH" ]; then
            handle_error "Client Portal build artifact not found" 1
        fi

        echo -e "${YELLOW}▶ Swapping Client Portal build...${NC}"
        cd "$ROOT_DIR/worklenz-client-portal" || handle_error "Client Portal directory not found" 1

        if [ -d "dist" ] && [ ! -L "dist" ]; then
            mv dist "dist-backup-$(date +%s)" 2>/dev/null || true
        elif [ -L "dist" ]; then
            rm -f dist
        fi

        mv "$CLIENT_PORTAL_BUILD_PATH" dist
        ls -t | grep "^dist-backup-" | tail -n +3 | xargs -r rm -rf
        echo -e "${GREEN}✅ Client Portal deployed${NC}"
        echo ""
    fi

    echo -e "${YELLOW}Disabling maintenance mode...${NC}"
    sudo rm -f /var/www/maintenance-mode
    sudo nginx -s reload
    if [ $? -ne 0 ]; then
        handle_error "Disable maintenance mode" $?
    fi
    MAINTENANCE_ENABLED=false
    echo -e "${GREEN}✅ Maintenance mode disabled${NC}"
    echo ""
fi

rm -rf "$DEPLOY_TMP_DIR" 2>/dev/null || true

# Calculate total deployment time
DEPLOY_END=$(date +%s)
TOTAL_DURATION=$((DEPLOY_END - DEPLOY_START))
MINUTES=$((TOTAL_DURATION / 60))
SECONDS=$((TOTAL_DURATION % 60))

# Return to root directory
cd "$ROOT_DIR"

# Success summary
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN}🚀 Deployment Completed Successfully!${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}📊 Build Time Summary:${NC}"
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
    if [ "$DEPLOY_FRONTEND" = true ] && [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
        echo -e "  Frontend + Client Portal: ${PARALLEL_DURATION}s (parallel)"
    elif [ "$DEPLOY_FRONTEND" = true ]; then
        echo -e "  Frontend: ${PARALLEL_DURATION}s"
    else
        echo -e "  Client Portal: ${PARALLEL_DURATION}s"
    fi
fi
if [ "$DEPLOY_BACKEND" = true ]; then
    echo -e "  Backend: ${BACKEND_DURATION}s"
fi
echo -e "  Total deployment: ${MINUTES}m ${SECONDS}s"
echo ""
if [ "$SKIP_GIT_PULL" = false ]; then
    echo -e "${GREEN}✅ Git pull completed${NC}"
fi
if [ "$DEPLOY_FRONTEND" = true ]; then
    echo -e "${GREEN}✅ Frontend built and deployed${NC}"
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
    echo -e "${GREEN}✅ Client Portal built and deployed${NC}"
fi
if [ "$DEPLOY_BACKEND" = true ]; then
    echo -e "${GREEN}✅ Backend built and restarted${NC}"
    echo -e "${GREEN}✅ Health check passed${NC}"
fi
echo -e "${GREEN}════════════════════════════════════════${NC}"