Files
memory-infrastructure-palace/code/websites/pokedex.online/deploy.sh
FragginWagon fee8fe2551 Add Docker Compose configuration and environment files for local and production setups
- Created docker-compose.docker-local.yml for local testing of frontend and backend services.
- Added .env.development for development environment configuration.
- Introduced .env.docker-local for local Docker environment settings.
- Added .env.production for production environment configuration for Synology deployment.
2026-01-30 11:29:17 -05:00

606 lines
20 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <local|production> Deployment target (default: local)
# --port <number> Frontend HTTP port (default: 8099)
# --backend-port <number> Backend port (default: 3099)
# --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
#
# Deployment Strategies:
# local - Docker deployment on localhost:8099 for production testing
# production - Deploy to Synology at https://app.pokedex.online
#
# Examples:
# ./deploy.sh --target local
# ./deploy.sh --target production
# ./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="local"
PORT=8099
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
;;
--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"
# Validate target
if [ "$TARGET" != "local" ] && [ "$TARGET" != "production" ]; then
log_error "Invalid target: $TARGET"
log_info "Valid targets: local, production"
exit 1
fi
# Check for mode-specific env files
local mode
if [ "$TARGET" = "local" ]; then
mode="docker-local"
else
mode="production"
fi
if [ ! -f "$PROJECT_ROOT/.env.$mode" ]; then
log_error "Environment file .env.$mode not found"
exit 1
fi
log_success "Frontend environment configuration found (.env.$mode)"
if [ ! -f "$PROJECT_ROOT/server/.env.$mode" ]; then
log_error "Environment file server/.env.$mode not found"
exit 1
fi
log_success "Backend environment configuration found (server/.env.$mode)"
}
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_build_mode() {
# Determine Vite build mode based on deployment target
if [ "$TARGET" = "local" ]; then
BUILD_MODE="docker-local"
else
BUILD_MODE="production"
fi
log_info "Preparing build for ${TARGET} deployment..."
log_info "Vite build mode: ${BUILD_MODE}"
log_info "Environment file: .env.${BUILD_MODE}"
}
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 build mode
prepare_build_mode
log_info "Building frontend with mode: $BUILD_MODE..."
npx vite build --mode "$BUILD_MODE" || {
log_error "Frontend build failed"
exit 1
}
log_success "Frontend built successfully"
log_info "Verifying build..."
BUILD_TARGET="$TARGET" 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"
log_info " Backend Port: $BACKEND_PORT"
log_success "Dry run completed"
return
fi
if [ "$TARGET" = "local" ]; then
log_info "Deploying to local Docker..."
# Copy server env file
cp "$PROJECT_ROOT/server/.env.docker-local" "$PROJECT_ROOT/server/.env" || {
log_error "Failed to copy server environment file"
exit 1
}
# Use docker-compose.docker-local.yml
log_info "Starting Docker containers..."
docker compose -f docker-compose.docker-local.yml down || true
docker compose -f docker-compose.docker-local.yml up -d --build || {
log_error "Docker deployment failed"
exit 1
}
else
log_info "Deploying to production (Synology)..."
deploy_to_synology
fi
log_success "Deployment completed successfully"
}
deploy_to_synology() {
# SSH Configuration
local SSH_HOST="10.0.0.81"
local SSH_PORT="2323"
local SSH_USER="GregRJacobs"
local SSH_KEY="$HOME/.ssh/ds3627xs_gregrjacobs"
local REMOTE_PATH="/volume1/docker/pokedex-online"
log_info "SSH Config: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}"
# Verify SSH key exists
if [ ! -f "$SSH_KEY" ]; then
log_error "SSH key not found: $SSH_KEY"
exit 1
fi
# Test SSH connection
log_info "Testing SSH connection..."
if ! ssh -i "$SSH_KEY" -p "$SSH_PORT" -o ConnectTimeout=10 -o BatchMode=yes "$SSH_USER@$SSH_HOST" "echo 'Connection successful'" > /dev/null 2>&1; then
log_error "SSH connection failed"
log_info "Please verify:"
log_info " 1. SSH key exists: $SSH_KEY"
log_info " 2. Key has correct permissions: chmod 600 $SSH_KEY"
log_info " 3. Public key is in authorized_keys on server"
log_info " 4. You can connect manually: ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST"
exit 1
fi
log_success "SSH connection verified"
# Create remote directory
log_info "Creating remote directory..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_PATH/dist $REMOTE_PATH/server" || {
log_error "Failed to create remote directory"
exit 1
}
# Transfer dist directory using tar over SSH
log_info "Transferring frontend build (dist/)..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/dist && mkdir -p $REMOTE_PATH/dist"
tar -czf - -C "$PROJECT_ROOT" dist 2>/dev/null | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || {
log_error "Failed to transfer dist directory"
exit 1
}
log_success "Frontend build transferred"
# Transfer server directory (excluding node_modules) using tar over SSH
log_info "Transferring backend code (server/)..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/server && mkdir -p $REMOTE_PATH/server"
tar -czf - -C "$PROJECT_ROOT" \
--exclude='node_modules' \
--exclude='.env*' \
--exclude='data' \
--exclude='logs' \
server 2>/dev/null | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || {
log_error "Failed to transfer server directory"
exit 1
}
log_success "Backend code transferred"
# Copy production environment file to server (after server code transfer)
log_info "Copying server environment file..."
cat "$PROJECT_ROOT/server/.env.production" | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cat > $REMOTE_PATH/server/.env" || {
log_error "Failed to copy server .env file"
exit 1
}
log_success "Server environment configured"
# Create required directories for volume mounts
log_info "Creating volume mount directories..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"mkdir -p $REMOTE_PATH/server/data $REMOTE_PATH/server/logs" || {
log_error "Failed to create volume mount directories"
exit 1
}
# Transfer Docker configuration files
log_info "Transferring Docker configuration..."
for file in docker-compose.production.yml nginx.conf Dockerfile.frontend; do
cat "$PROJECT_ROOT/$file" | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cat > $REMOTE_PATH/$file" || {
log_error "Failed to transfer $file"
exit 1
}
done
cat "$PROJECT_ROOT/server/Dockerfile" | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cat > $REMOTE_PATH/server/Dockerfile" || {
log_error "Failed to transfer server Dockerfile"
exit 1
}
log_success "Docker configuration transferred"
# Find Docker on remote system
log_info "Locating Docker on remote system..."
DOCKER_PATH=$(ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"which docker 2>/dev/null || echo /usr/local/bin/docker")
log_info "Using Docker at: $DOCKER_PATH"
# Stop and remove existing containers (force cleanup)
log_info "Stopping and removing existing containers..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans 2>/dev/null || true"
# Force remove any lingering containers with matching names
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"$DOCKER_PATH rm -f pokedex-frontend pokedex-backend 2>/dev/null || true"
# Start containers
log_info "Starting Docker containers..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml up -d --build" || {
log_error "Failed to start Docker containers"
log_info "Rolling back..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans"
exit 1
}
log_success "Containers started"
}
###############################################################################
# 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" = "production" ]; then
HOST="10.0.0.81"
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 URLs based on target
if [ "$TARGET" = "production" ]; then
FRONTEND_URL="https://app.pokedex.online"
BACKEND_URL="http://10.0.0.81:$BACKEND_PORT"
else
FRONTEND_URL="http://localhost:$PORT"
BACKEND_URL="http://localhost:$BACKEND_PORT"
fi
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} 🎉 Deployment Successful!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e " ${BLUE}Frontend:${NC} $FRONTEND_URL"
echo -e " ${BLUE}Backend:${NC} $BACKEND_URL"
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.local.yml logs -f"
echo -e " • Stop containers: docker compose -f docker-compose.local.yml down"
else
echo -e " • Check logs via SSH or Docker commands on Synology"
fi
echo -e " • Monitor backend: curl $BACKEND_URL/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 "$@"