#!/bin/bash ############################################################################### # Pokedex.Online Deployment Automation Script # # This script automates the deployment process with pre-deployment checks, # build verification, and rollback capability. # # Usage: # ./deploy.sh [options] # # Options: # --target Deployment target (default: internal) # --port Frontend HTTP port (default: 8080) # --ssl-port Frontend HTTPS port (optional) # --backend-port Backend port (default: 3000) # --skip-tests Skip test execution # --skip-build Skip build step (use existing dist/) # --no-backup Skip backup creation # --dry-run Show what would be deployed without deploying # # Examples: # ./deploy.sh --target internal # ./deploy.sh --target external --port 8080 --backend-port 3000 # ./deploy.sh --dry-run ############################################################################### set -e # Exit on error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Default configuration TARGET="internal" PORT=8099 SSL_PORT="" BACKEND_PORT=3099 SKIP_TESTS=false SKIP_BUILD=false NO_BACKUP=false DRY_RUN=false # Script directory SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PROJECT_ROOT="$SCRIPT_DIR" ############################################################################### # Helper Functions ############################################################################### log_info() { echo -e "${BLUE}ℹ${NC} $1" } log_success() { echo -e "${GREEN}✅${NC} $1" } log_warning() { echo -e "${YELLOW}⚠${NC} $1" } log_error() { echo -e "${RED}❌${NC} $1" } log_step() { echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" } ############################################################################### # Parse Arguments ############################################################################### parse_args() { while [[ $# -gt 0 ]]; do case $1 in --target) TARGET="$2" shift 2 ;; --port) PORT="$2" shift 2 ;; --ssl-port) SSL_PORT="$2" shift 2 ;; --backend-port) BACKEND_PORT="$2" shift 2 ;; --skip-tests) SKIP_TESTS=true shift ;; --skip-build) SKIP_BUILD=true shift ;; --no-backup) NO_BACKUP=true shift ;; --dry-run) DRY_RUN=true shift ;; --help) head -n 30 "$0" | tail -n 25 exit 0 ;; *) log_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac done } ############################################################################### # Pre-Deployment Checks ############################################################################### check_prerequisites() { log_step "🔍 Checking Prerequisites" # Check Node.js if ! command -v node &> /dev/null; then log_error "Node.js is not installed" exit 1 fi log_success "Node.js $(node --version)" # Check npm if ! command -v npm &> /dev/null; then log_error "npm is not installed" exit 1 fi log_success "npm $(npm --version)" # Check if dependencies are installed if [ ! -d "$PROJECT_ROOT/node_modules" ]; then log_error "Dependencies not installed. Run 'npm install' first" exit 1 fi log_success "Dependencies installed" # Check if server dependencies are installed (workspaces hoist to root node_modules) # Check for key server dependencies in root node_modules if [ ! -d "$PROJECT_ROOT/node_modules/express" ] || [ ! -d "$PROJECT_ROOT/node_modules/cors" ]; then log_error "Server dependencies not installed. Run 'npm install' first" exit 1 fi log_success "Server dependencies installed" } check_environment() { log_step "🔧 Checking Environment Configuration" # Check if .env exists if [ ! -f "$PROJECT_ROOT/server/.env" ]; then log_warning ".env file not found in server/" log_info "Copy server/.env.example to server/.env and configure it" if [ "$DRY_RUN" = false ]; then read -p "Continue anyway? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi fi else log_success "Environment configuration found" fi } run_tests() { if [ "$SKIP_TESTS" = true ]; then log_warning "Skipping tests (--skip-tests flag set)" return fi log_step "🧪 Running Tests" log_info "Running frontend tests..." npm run test:run || { log_error "Frontend tests failed" exit 1 } log_success "Frontend tests passed" log_info "Running backend tests..." npm run test:run --workspace=server || { log_warning "Backend tests failed or not found - continuing anyway" } log_success "Backend checks completed" } ############################################################################### # Build ############################################################################### prepare_env_for_build() { # Create/update .env.local with correct Discord redirect URI for the target local env_file="$PROJECT_ROOT/.env.local" local redirect_uri # Determine correct redirect URI based on target and ports case "$TARGET" in local) # For Docker, use the frontend port redirect_uri="http://localhost:${PORT}/oauth/callback" ;; internal|external) # For dev server, use default Vite port redirect_uri="http://localhost:5173/oauth/callback" ;; *) redirect_uri="http://localhost:5173/oauth/callback" ;; esac log_info "Preparing environment for ${TARGET} deployment..." log_info "Discord redirect URI: ${redirect_uri}" # Create/update .env.local with Discord credentials cat > "$env_file" << EOF # Generated by deploy.sh for ${TARGET} deployment # These variables are embedded into the frontend bundle at build time VITE_DISCORD_CLIENT_ID=1466544972059775223 VITE_DISCORD_REDIRECT_URI=${redirect_uri} EOF log_info ".env.local created with correct redirect URI" } build_application() { if [ "$SKIP_BUILD" = true ]; then log_warning "Skipping build (--skip-build flag set)" # Check if dist exists if [ ! -d "$PROJECT_ROOT/dist" ]; then log_error "dist/ directory not found and --skip-build is set" log_info "Remove --skip-build or run 'npm run build' first" exit 1 fi return fi log_step "🔨 Building Application" # Prepare environment before building prepare_env_for_build log_info "Building frontend..." npm run build:frontend || { log_error "Frontend build failed" exit 1 } log_success "Frontend built successfully" log_info "Verifying build..." npm run build:verify || { log_error "Build verification failed" exit 1 } log_success "Build verified" } ############################################################################### # Backup ############################################################################### create_backup() { if [ "$NO_BACKUP" = true ]; then log_warning "Skipping backup (--no-backup flag set)" return fi log_step "💾 Creating Backup" BACKUP_DIR="$PROJECT_ROOT/backups" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/backup_${TIMESTAMP}.tar.gz" mkdir -p "$BACKUP_DIR" log_info "Creating backup of current deployment..." tar -czf "$BACKUP_FILE" \ --exclude='node_modules' \ --exclude='dist' \ --exclude='.git' \ --exclude='backups' \ -C "$PROJECT_ROOT" . || { log_error "Backup creation failed" exit 1 } log_success "Backup created: $BACKUP_FILE" # Keep only last 5 backups BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | wc -l) if [ "$BACKUP_COUNT" -gt 5 ]; then log_info "Cleaning old backups (keeping last 5)..." ls -1t "$BACKUP_DIR"/backup_*.tar.gz | tail -n +6 | xargs rm -f fi } ############################################################################### # Deployment ############################################################################### deploy_to_server() { log_step "🚀 Deploying to Server" if [ "$DRY_RUN" = true ]; then log_info "DRY RUN - Would deploy with following configuration:" log_info " Target: $TARGET" log_info " Frontend Port: $PORT" [ -n "$SSL_PORT" ] && log_info " SSL Port: $SSL_PORT" log_info " Backend Port: $BACKEND_PORT" log_success "Dry run completed" return fi # Build deployment command DEPLOY_CMD="node ../../utils/deploy-pokedex.js --target $TARGET --port $PORT --backend-port $BACKEND_PORT" [ -n "$SSL_PORT" ] && DEPLOY_CMD="$DEPLOY_CMD --ssl-port $SSL_PORT" log_info "Executing deployment..." log_info "Command: $DEPLOY_CMD" eval "$DEPLOY_CMD" || { log_error "Deployment failed" log_info "Check logs above for details" exit 1 } log_success "Deployment completed successfully" } ############################################################################### # Post-Deployment ############################################################################### verify_deployment() { log_step "🏥 Verifying Deployment" if [ "$DRY_RUN" = true ]; then log_info "DRY RUN - Skipping verification" return fi # Determine host based on target if [ "$TARGET" = "internal" ]; then HOST="10.0.0.81" elif [ "$TARGET" = "external" ]; then HOST="home.gregrjacobs.com" else HOST="localhost" fi log_info "Checking frontend health..." sleep 3 # Give containers time to start if curl -f -s "http://$HOST:$PORT/health" > /dev/null; then log_success "Frontend is responding" else log_warning "Frontend health check failed" fi log_info "Checking backend health..." if curl -f -s "http://$HOST:$BACKEND_PORT/health" > /dev/null; then log_success "Backend is responding" else log_warning "Backend health check failed" fi } print_summary() { log_step "📊 Deployment Summary" if [ "$DRY_RUN" = true ]; then log_info "DRY RUN completed - no changes made" return fi # Determine host based on target if [ "$TARGET" = "internal" ]; then HOST="10.0.0.81" elif [ "$TARGET" = "external" ]; then HOST="home.gregrjacobs.com" else HOST="localhost" fi echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN} 🎉 Deployment Successful!${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e " ${BLUE}Frontend:${NC} http://$HOST:$PORT" [ -n "$SSL_PORT" ] && echo -e " ${BLUE}HTTPS:${NC} https://$HOST:$SSL_PORT" echo -e " ${BLUE}Backend:${NC} http://$HOST:$BACKEND_PORT" echo -e " ${BLUE}Target:${NC} $TARGET" echo "" echo -e " ${YELLOW}Next Steps:${NC}" echo -e " • Test the application manually" if [ "$TARGET" = "local" ]; then echo -e " • Check logs: docker compose -f docker-compose.tmp.yml logs -f" else echo -e " • Check logs: npm run docker:logs" fi echo -e " • Monitor backend: curl http://$HOST:$BACKEND_PORT/health" echo "" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } ############################################################################### # Rollback ############################################################################### rollback() { log_step "🔄 Rollback Instructions" echo "To rollback to a previous version:" echo "" echo "1. List available backups:" echo " ls -lh backups/" echo "" echo "2. Extract backup:" echo " tar -xzf backups/backup_TIMESTAMP.tar.gz -C /tmp/restore" echo "" echo "3. Copy files back:" echo " rsync -av /tmp/restore/ ./" echo "" echo "4. Redeploy:" echo " ./deploy.sh --skip-tests --target $TARGET" echo "" echo "Or use the deployment script's built-in rollback:" echo " node ../../utils/deploy-pokedex.js --target $TARGET" echo " (will auto-rollback on failure)" } ############################################################################### # Main Execution ############################################################################### main() { echo -e "${BLUE}" echo "╔════════════════════════════════════════════════════════════╗" echo "║ ║" echo "║ Pokedex.Online Deployment Automation ║" echo "║ ║" echo "╚════════════════════════════════════════════════════════════╝" echo -e "${NC}\n" # Parse command line arguments parse_args "$@" # Run deployment pipeline check_prerequisites check_environment run_tests build_application create_backup deploy_to_server verify_deployment print_summary log_success "All done! 🚀" } # Run main function main "$@"