#!/bin/bash # # ROA2WEB Linux Deployment Script # Builds and deploys to Windows IIS server via SSH/SCP # # Usage: # ./deploy.sh # Full deploy (frontend + backend) # ./deploy.sh frontend # Frontend only # ./deploy.sh backend # Backend only # ./deploy.sh test # Test SSH connection only # set -e # ============================================================================= # CONFIGURATION # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # SSH Configuration (matches deploy-config.json) SSH_HOST="roa2web-prod" # Uses ~/.ssh/config REMOTE_PATH="C:/Temp" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color # ============================================================================= # HELPER FUNCTIONS # ============================================================================= log_step() { echo -e "\n${CYAN}[*] $1${NC}" >&2 } log_success() { echo -e "${GREEN} [OK] $1${NC}" >&2 } log_error() { echo -e "${RED} [ERROR] $1${NC}" >&2 } log_warning() { echo -e "${YELLOW} [WARN] $1${NC}" >&2 } log_info() { echo -e " [INFO] $1" >&2 } # ============================================================================= # SSH CONNECTION TEST # ============================================================================= test_ssh_connection() { log_step "Testing SSH connection to $SSH_HOST..." if ssh -o ConnectTimeout=10 "$SSH_HOST" "echo 'Connection successful'" 2>/dev/null; then log_success "SSH connection working" return 0 else log_error "SSH connection failed" echo "" echo "Please ensure:" echo " 1. SSH key is configured in ~/.ssh/config" echo " 2. Public key is added to server's authorized_keys" echo " 3. Server is reachable on port 22122" echo "" echo "SSH config should contain:" echo " Host roa2web-prod" echo " HostName 10.0.20.36" echo " Port 22122" echo " User Administrator" echo " IdentityFile ~/.ssh/roa2web_deploy" return 1 fi } # ============================================================================= # BUILD FRONTEND # ============================================================================= build_frontend() { log_step "Building frontend..." cd "$PROJECT_ROOT" # Check Node.js if ! command -v node &> /dev/null; then log_error "Node.js not found. Please install Node.js 16+" exit 1 fi NODE_VERSION=$(node --version) log_info "Node.js: $NODE_VERSION" # Check if node_modules exists if [ ! -d "node_modules" ]; then log_step "Installing npm dependencies..." npm install fi # Build production log_step "Running production build..." NODE_ENV=production npm run build if [ ! -d "dist" ]; then log_error "Build failed: dist/ directory not created" exit 1 fi # Count files FILE_COUNT=$(find dist -type f | wc -l) TOTAL_SIZE=$(du -sh dist | cut -f1) log_success "Build completed: $FILE_COUNT files ($TOTAL_SIZE)" # Verify web.config if [ -f "dist/web.config" ]; then log_success "web.config present in build" else log_warning "web.config NOT found in build" fi } # ============================================================================= # CREATE DEPLOYMENT PACKAGE # ============================================================================= create_package() { local COMPONENT=$1 local TIMESTAMP=$(date +%Y%m%d-%H%M%S) local PACKAGE_DIR="$PROJECT_ROOT/deploy-package-$TIMESTAMP" log_step "Creating deployment package..." log_info "Package: $PACKAGE_DIR" mkdir -p "$PACKAGE_DIR" # Frontend if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "frontend" ]; then log_step "Packaging frontend..." mkdir -p "$PACKAGE_DIR/frontend" cp -r "$PROJECT_ROOT/dist/"* "$PACKAGE_DIR/frontend/" log_success "Frontend packaged" fi # Backend if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "backend" ]; then log_step "Packaging backend..." mkdir -p "$PACKAGE_DIR/backend" # Copy backend files (excluding venv, __pycache__, logs, .env) rsync -av --progress \ --exclude 'venv' \ --exclude '__pycache__' \ --exclude '*.pyc' \ --exclude '*.pyo' \ --exclude '.pytest_cache' \ --exclude 'logs' \ --exclude '.env' \ --exclude '.env.local' \ --exclude '*.log' \ "$PROJECT_ROOT/backend/" "$PACKAGE_DIR/backend/" # Copy .env.example if exists if [ -f "$PROJECT_ROOT/backend/.env.example" ]; then cp "$PROJECT_ROOT/backend/.env.example" "$PACKAGE_DIR/backend/" fi log_success "Backend packaged" fi # Shared modules if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "backend" ]; then log_step "Packaging shared modules..." mkdir -p "$PACKAGE_DIR/shared" rsync -av --progress \ --exclude '__pycache__' \ --exclude '*.pyc' \ --exclude 'tests' \ "$PROJECT_ROOT/shared/" "$PACKAGE_DIR/shared/" log_success "Shared modules packaged" fi # Config templates log_step "Packaging config templates..." if [ -d "$PROJECT_ROOT/deployment/config" ]; then mkdir -p "$PACKAGE_DIR/config" cp -r "$PROJECT_ROOT/deployment/config/"* "$PACKAGE_DIR/config/" 2>/dev/null || true fi # Deployment scripts log_step "Packaging deployment scripts..." mkdir -p "$PACKAGE_DIR/scripts" SCRIPTS=( "ROA2WEB-Console.ps1" "Install-ROA2WEB.ps1" "Check-And-Deploy.ps1" ) for script in "${SCRIPTS[@]}"; do if [ -f "$PROJECT_ROOT/deployment/windows/scripts/$script" ]; then cp "$PROJECT_ROOT/deployment/windows/scripts/$script" "$PACKAGE_DIR/scripts/" fi done log_success "Deployment scripts packaged" # Create README cat > "$PACKAGE_DIR/README.txt" << EOF ================================================================================ ROA2WEB DEPLOYMENT PACKAGE Generated: $(date '+%Y-%m-%d %H:%M:%S') From: Linux/LXC deployment script ================================================================================ CONTENTS: --------- backend/ Unified FastAPI backend frontend/ Vue.js SPA (production build) shared/ Shared Python modules config/ Configuration templates scripts/ PowerShell deployment scripts DEPLOYMENT: ----------- Server will auto-deploy within 5 minutes (Check-And-Deploy.ps1 scheduled task) Or manually: cd scripts .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll ================================================================================ EOF # Calculate package size PACKAGE_SIZE=$(du -sh "$PACKAGE_DIR" | cut -f1) FILE_COUNT=$(find "$PACKAGE_DIR" -type f | wc -l) log_success "Package created: $FILE_COUNT files ($PACKAGE_SIZE)" # Return path via global variable (avoid stdout pollution from rsync) CREATED_PACKAGE_DIR="$PACKAGE_DIR" } # ============================================================================= # TRANSFER TO SERVER # ============================================================================= transfer_to_server() { local PACKAGE_DIR=$1 local TIMESTAMP=$(date +%Y%m%d-%H%M%S) local REMOTE_DEPLOY_DIR="deploy-$TIMESTAMP" local REMOTE_FULL_PATH="C:\\Temp\\$REMOTE_DEPLOY_DIR" log_step "Transferring to server..." log_info "Remote path: C:\\Temp\\$REMOTE_DEPLOY_DIR" # Create remote directory log_step "Creating remote directory..." ssh "$SSH_HOST" "New-Item -ItemType Directory -Path 'C:\\Temp\\$REMOTE_DEPLOY_DIR' -Force" log_success "Remote directory created" # Transfer files one directory at a time (Bitvise SCP compatibility) log_step "Uploading files via SCP..." for dir in "$PACKAGE_DIR"/*/; do if [ -d "$dir" ]; then local dirname=$(basename "$dir") log_info "Uploading $dirname/..." scp -r "$dir" "roa2web-prod:C:\\Temp\\$REMOTE_DEPLOY_DIR\\$dirname" fi done # Upload root files (README, etc.) for file in "$PACKAGE_DIR"/*; do if [ -f "$file" ]; then local filename=$(basename "$file") scp "$file" "roa2web-prod:C:\\Temp\\$REMOTE_DEPLOY_DIR\\$filename" fi done log_success "Transfer completed" # Verify transfer log_step "Verifying transfer..." REMOTE_FILE_COUNT=$(ssh "$SSH_HOST" "(Get-ChildItem -Path 'C:\\Temp\\$REMOTE_DEPLOY_DIR' -Recurse -File).Count") log_success "Remote files: $REMOTE_FILE_COUNT" echo "" echo "==========================================" echo -e "${GREEN} DEPLOYMENT PACKAGE UPLOADED${NC}" echo "==========================================" echo "" echo " Remote path: C:\\Temp\\$REMOTE_DEPLOY_DIR" echo "" echo " Server will auto-deploy within 5 minutes." echo " Or manually deploy:" echo " ssh $SSH_HOST" echo " cd C:\\Temp\\$REMOTE_DEPLOY_DIR\\scripts" echo " .\\ROA2WEB-Console.ps1" echo "" } # ============================================================================= # CLEANUP # ============================================================================= cleanup_local_package() { local PACKAGE_DIR=$1 log_step "Cleaning up local package..." rm -rf "$PACKAGE_DIR" log_success "Local package removed" } # ============================================================================= # MAIN # ============================================================================= main() { local COMPONENT=${1:-all} echo "" echo "========================================" echo " ROA2WEB Linux Deployment Script" echo " Component: $COMPONENT" echo "========================================" echo "" # Handle test command if [ "$COMPONENT" = "test" ]; then test_ssh_connection exit $? fi # Validate component case $COMPONENT in all|frontend|backend) ;; *) echo "Usage: $0 [all|frontend|backend|test]" exit 1 ;; esac # Test SSH connection first if ! test_ssh_connection; then exit 1 fi # Build frontend if needed if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "frontend" ]; then build_frontend fi # Create package (sets CREATED_PACKAGE_DIR global variable) create_package "$COMPONENT" # Transfer to server transfer_to_server "$CREATED_PACKAGE_DIR" # Cleanup cleanup_local_package "$CREATED_PACKAGE_DIR" echo "" echo -e "${GREEN}Deployment completed successfully!${NC}" echo "" } # Run main with all arguments main "$@"