#!/usr/bin/env bash # # ThrillWiki Complete Deployment Orchestrator # One-command deployment of entire automation system with GitHub auth and pull scheduling # # Features: # - Single command for complete remote deployment # - Interactive GitHub authentication setup # - Automatic pull scheduling configuration (5-minute intervals) # - Pre-deployment validation and health checks # - Multi-target deployment support # - Comprehensive error handling and rollback # - Real-time progress monitoring and status reporting # - Post-deployment validation and testing # set -e # [AWS-SECRET-REMOVED]==================================== # SCRIPT CONFIGURATION # [AWS-SECRET-REMOVED]==================================== # SSH Configuration - Use same options as deployment scripts SSH_OPTIONS="${SSH_OPTIONS:--o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30}" # Cross-shell compatible script directory detection if [ -n "${BASH_SOURCE:-}" ]; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" elif [ -n "${ZSH_NAME:-}" ]; then SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)" SCRIPT_NAME="$(basename "${(%):-%x}")" else SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_NAME="$(basename "$0")" fi PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" # Component scripts REMOTE_DEPLOY_SCRIPT="$SCRIPT_DIR/remote-deploy.sh" GITHUB_SETUP_SCRIPT="$SCRIPT_DIR/github-setup.py" QUICK_START_SCRIPT="$SCRIPT_DIR/quick-start.sh" # Cross-shell compatible deployment presets configuration # Using functions instead of associative arrays for compatibility get_deployment_preset_description() { case "$1" in "dev") echo "Development environment with frequent pulls and debugging" ;; "prod") echo "Production environment with stable intervals and security" ;; "demo") echo "Demo environment optimized for showcasing features" ;; "testing") echo "Testing environment with comprehensive monitoring" ;; *) echo "Unknown preset" ;; esac } get_deployment_preset_details() { case "$1" in "dev") echo "• Debug mode enabled" echo "• Relaxed security settings" echo "• Frequent automated updates (1 min)" echo "• Detailed logging and error reporting" ;; "prod") echo "• Optimized for performance and security" echo "• SSL/HTTPS required" echo "• Conservative update schedule (5 min)" echo "• Minimal logging, error tracking" ;; "demo") echo "• Balanced configuration for demonstrations" echo "• Moderate security settings" echo "• Regular updates (2 min)" echo "• Clean, professional presentation" ;; "testing") echo "• Similar to production but with testing tools" echo "• Debug information available" echo "• Frequent updates for testing (3 min)" echo "• Comprehensive logging" ;; esac } get_preset_config() { local preset="$1" local config_key="$2" case "$preset" in "dev") case "$config_key" in "PULL_INTERVAL") echo "60" ;; "HEALTH_CHECK_INTERVAL") echo "30" ;; "DEBUG_MODE") echo "true" ;; "AUTO_MIGRATE") echo "true" ;; "AUTO_UPDATE_DEPENDENCIES") echo "true" ;; "LOG_LEVEL") echo "DEBUG" ;; "SSL_REQUIRED") echo "false" ;; "CORS_ALLOWED") echo "true" ;; "DJANGO_DEBUG") echo "true" ;; "ALLOWED_HOSTS") echo "*" ;; esac ;; "prod") case "$config_key" in "PULL_INTERVAL") echo "300" ;; "HEALTH_CHECK_INTERVAL") echo "60" ;; "DEBUG_MODE") echo "false" ;; "AUTO_MIGRATE") echo "true" ;; "AUTO_UPDATE_DEPENDENCIES") echo "false" ;; "LOG_LEVEL") echo "WARNING" ;; "SSL_REQUIRED") echo "true" ;; "CORS_ALLOWED") echo "false" ;; "DJANGO_DEBUG") echo "false" ;; "ALLOWED_HOSTS") echo "production-host" ;; esac ;; "demo") case "$config_key" in "PULL_INTERVAL") echo "120" ;; "HEALTH_CHECK_INTERVAL") echo "45" ;; "DEBUG_MODE") echo "false" ;; "AUTO_MIGRATE") echo "true" ;; "AUTO_UPDATE_DEPENDENCIES") echo "true" ;; "LOG_LEVEL") echo "INFO" ;; "SSL_REQUIRED") echo "false" ;; "CORS_ALLOWED") echo "true" ;; "DJANGO_DEBUG") echo "false" ;; "ALLOWED_HOSTS") echo "demo-host" ;; esac ;; "testing") case "$config_key" in "PULL_INTERVAL") echo "180" ;; "HEALTH_CHECK_INTERVAL") echo "30" ;; "DEBUG_MODE") echo "true" ;; "AUTO_MIGRATE") echo "true" ;; "AUTO_UPDATE_DEPENDENCIES") echo "true" ;; "LOG_LEVEL") echo "DEBUG" ;; "SSL_REQUIRED") echo "false" ;; "CORS_ALLOWED") echo "true" ;; "DJANGO_DEBUG") echo "true" ;; "ALLOWED_HOSTS") echo "test-host" ;; esac ;; esac } # Cross-shell compatible preset list get_available_presets() { echo "dev prod demo testing" } # Cross-shell compatible preset validation validate_preset() { local preset="$1" local preset_list preset_list=$(get_available_presets) for valid_preset in $preset_list; do if [ "$preset" = "$valid_preset" ]; then return 0 fi done return 1 } # Logging configuration COMPLETE_LOG="$PROJECT_DIR/logs/deploy-complete.log" DEPLOYMENT_STATE_FILE="$PROJECT_DIR/.deployment-state" # [AWS-SECRET-REMOVED]==================================== # COLOR DEFINITIONS # [AWS-SECRET-REMOVED]==================================== RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # [AWS-SECRET-REMOVED]==================================== # LOGGING FUNCTIONS # [AWS-SECRET-REMOVED]==================================== complete_log() { local level="$1" local color="$2" local message="$3" local timestamp="$(date '+%Y-%m-%d %H:%M:%S')" # Ensure log directory exists mkdir -p "$(dirname "$COMPLETE_LOG")" # Log to file (without colors) echo "[$timestamp] [$level] [COMPLETE] $message" >> "$COMPLETE_LOG" # Log to console (with colors) echo -e "${color}[$timestamp] [COMPLETE-$level]${NC} $message" } complete_info() { complete_log "INFO" "$BLUE" "$1" } complete_success() { complete_log "SUCCESS" "$GREEN" "✅ $1" } complete_warning() { complete_log "WARNING" "$YELLOW" "⚠️ $1" } complete_error() { complete_log "ERROR" "$RED" "❌ $1" } complete_debug() { if [ "${COMPLETE_DEBUG:-false}" = "true" ]; then complete_log "DEBUG" "$PURPLE" "🔍 $1" fi } complete_progress() { complete_log "PROGRESS" "$CYAN" "🚀 $1" } # [AWS-SECRET-REMOVED]==================================== # UTILITY FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Cross-shell compatible command existence check command_exists() { command -v "$1" >/dev/null 2>&1 } # Cross-shell compatible IP address validation validate_ip_address() { local ip="$1" if echo "$ip" | grep -E '^([0-9]{1,3}\.){3}[0-9]{1,3}$' >/dev/null; then # Check each octet local IFS='.' set -- $ip for octet; do if [ "$octet" -gt 255 ] || [ "$octet" -lt 0 ]; then return 1 fi done return 0 fi return 1 } # Cross-shell compatible hostname validation validate_hostname() { local hostname="$1" # Basic hostname validation - alphanumeric, dots, dashes if echo "$hostname" | grep -E '^[a-zA-Z0-9][a-zA-Z0-9\.-]*[a-zA-Z0-9]$' >/dev/null; then return 0 elif echo "$hostname" | grep -E '^[a-zA-Z0-9]$' >/dev/null; then return 0 fi return 1 } # Cross-shell compatible port validation validate_port() { local port="$1" if echo "$port" | grep -E '^[0-9]+$' >/dev/null; then if [ "$port" -gt 0 ] && [ "$port" -le 65535 ]; then return 0 fi fi return 1 } # Show interactive welcome interface show_interactive_welcome() { clear echo "" echo -e "${BOLD}${CYAN}" echo "🚀 ThrillWiki Deployment System" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo -e "${NC}" echo "" echo -e "${BOLD}Welcome!${NC} This script will deploy your ThrillWiki Django project to a remote VM." echo "" echo -e "${GREEN}What this script will do:${NC}" echo "✅ Configure GitHub authentication" echo "✅ Clone your repository to the remote server" echo "✅ Install all dependencies (Python, Node.js, system packages)" echo "✅ Set up Django with database and static files" echo "✅ Configure automated deployment services" echo "✅ Start the development server" echo "" echo -e "${YELLOW}Prerequisites:${NC}" echo "• Target VM with SSH access" echo "• GitHub repository access" echo "• Internet connectivity" echo "" } # Show animated banner for command-line mode show_banner() { echo "" echo -e "${BOLD}${CYAN}" echo "╔═══════════════════════════════════════════════════════════════════════════════╗" echo "║ ║" echo "║ 🚀 ThrillWiki Complete Deployment 🚀 ║" echo "║ ║" echo "║ Automated Remote Deployment with GitHub Auth & Pull Scheduling ║" echo "║ ║" echo "╚═══════════════════════════════════════════════════════════════════════════════╝" echo -e "${NC}" echo "" } # Show usage information show_usage() { cat << 'EOF' 🚀 ThrillWiki Complete Deployment Orchestrator DESCRIPTION: One-command deployment of the complete ThrillWiki automation system to remote VMs with integrated GitHub authentication and automatic pull scheduling. USAGE: ./deploy-complete.sh [OPTIONS] [remote_host2] [remote_host3] ... REQUIRED: remote_host One or more remote VM hostnames or IP addresses OPTIONS: -u, --user USER Remote username (default: ubuntu) -p, --port PORT SSH port (default: 22) -k, --key PATH SSH private key file path -t, --token TOKEN GitHub Personal Access Token -r, --repo-url URL GitHub repository URL (auto-detected if not provided) --preset PRESET Deployment preset (dev/prod/demo/testing, default: auto-detect) --pull-interval SEC Pull interval in seconds (overrides preset) --skip-github Skip GitHub authentication setup --skip-repo Skip repository configuration --skip-validation Skip pre-deployment validation --parallel Deploy to multiple hosts in parallel --dry-run Show what would be deployed without executing --force Force deployment even if target exists --debug Enable debug logging -h, --help Show this help message DEPLOYMENT PRESETS: dev Development environment (1-minute pulls, debugging enabled) prod Production environment (5-minute pulls, security hardened) demo Demo environment (2-minute pulls, feature showcase) testing Testing environment (3-minute pulls, comprehensive monitoring) EXAMPLES: # Basic deployment to single host ./deploy-complete.sh 192.168.1.100 # Production deployment with GitHub token ./deploy-complete.sh --preset prod --token ghp_xxxxx 10.0.0.50 # Multi-host deployment with custom settings ./deploy-complete.sh --parallel --pull-interval 120 host1 host2 host3 # Development deployment with SSH key ./deploy-complete.sh --preset dev -k ~/.ssh/***REMOVED*** -u admin dev-server # Dry run to preview deployment ./deploy-complete.sh --dry-run --preset prod production-server FEATURES: ✅ One-command complete deployment ✅ Integrated GitHub authentication setup ✅ Automatic pull scheduling (5-minute intervals) ✅ Multiple deployment presets ✅ Multi-host parallel deployment ✅ Comprehensive validation and health checks ✅ Real-time progress monitoring ✅ Automatic rollback on failure ✅ Post-deployment testing and validation ENVIRONMENT VARIABLES: GITHUB_TOKEN GitHub Personal Access Token GITHUB_REPO_URL GitHub repository URL COMPLETE_DEBUG Enable debug mode (true/false) DEPLOYMENT_TIMEOUT Overall deployment timeout in seconds EXIT CODES: 0 Success 1 General error 2 Validation error 3 Authentication error 4 Deployment error 5 Multiple hosts failed EOF } # [AWS-SECRET-REMOVED]==================================== # ARGUMENT PARSING # [AWS-SECRET-REMOVED]==================================== # Global variable to track if we're in interactive mode INTERACTIVE_MODE=false parse_arguments() { local remote_hosts=() local preset="auto" local pull_interval="" local github_token="" local repo_url="" local skip_github=false local skip_repo=false local skip_validation=false local parallel=false local dry_run=false local force=false local remote_user="thrillwiki" local remote_port="22" local ssh_key="" # If no arguments provided, enter interactive mode if [[ $# -eq 0 ]]; then INTERACTIVE_MODE=true complete_debug "No arguments provided - entering interactive mode" fi while [[ $# -gt 0 ]]; do case $1 in -u|--user) remote_user="$2" shift 2 ;; -p|--port) remote_port="$2" shift 2 ;; -k|--key) ssh_key="$2" shift 2 ;; -t|--token) github_token="$2" export GITHUB_TOKEN="$github_token" shift 2 ;; -r|--repo-url) repo_url="$2" export GITHUB_REPO_URL="$repo_url" shift 2 ;; --preset) preset="$2" shift 2 ;; --pull-interval) pull_interval="$2" shift 2 ;; --skip-github) skip_github=true shift ;; --skip-repo) skip_repo=true shift ;; --skip-validation) skip_validation=true shift ;; --parallel) parallel=true shift ;; --dry-run) dry_run=true export DRY_RUN=true shift ;; --force) force=true export FORCE_DEPLOY=true shift ;; --debug) export COMPLETE_DEBUG=true export DEPLOY_DEBUG=true shift ;; -h|--help) show_usage exit 0 ;; -*) complete_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; *) remote_hosts+=("$1") shift ;; esac done # In interactive mode, we'll collect hosts later if [[ "$INTERACTIVE_MODE" == "true" ]]; then complete_debug "Interactive mode - host collection will be handled separately" else # Command-line mode - validate required arguments if [[ ${#remote_hosts[@]} -eq 0 ]]; then complete_error "At least one remote host is required" echo "Use: $0 [remote_host2] ..." echo "Use --help for more information" exit 1 fi # Store hosts in temp file for command-line mode printf '%s\n' "${remote_hosts[@]}" > /tmp/thrillwiki-deploy-hosts.$$ fi # Export configuration for child scripts export REMOTE_USER="$remote_user" export REMOTE_PORT="$remote_port" export SSH_KEY="$ssh_key" export DEPLOYMENT_PRESET="$preset" export PULL_INTERVAL="$pull_interval" export SKIP_GITHUB_SETUP="$skip_github" export SKIP_REPO_CONFIG="$skip_repo" export SKIP_VALIDATION="$skip_validation" export PARALLEL_DEPLOYMENT="$parallel" export INTERACTIVE_MODE="$INTERACTIVE_MODE" if [[ "$INTERACTIVE_MODE" == "false" ]]; then export REMOTE_HOSTS=("${remote_hosts[@]}") complete_debug "Parsed arguments: hosts=${#remote_hosts[@]}, preset=$preset, parallel=$parallel" fi } # [AWS-SECRET-REMOVED]==================================== # VALIDATION FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Enhanced system validation with detailed checks validate_system_prerequisites() { if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo "" echo -e "${CYAN}🔍 Checking System Prerequisites${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi local validation_failed=false local missing_commands=() local required_commands=("ssh" "scp" "git" "python3" "curl") # Check required commands for cmd in "${required_commands[@]}"; do if command_exists "$cmd"; then if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "✅ $cmd - ${GREEN}Available${NC}" fi else missing_commands+=("$cmd") if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "❌ $cmd - ${RED}Missing${NC}" fi validation_failed=true fi done # Check network connectivity if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo "" echo "🌐 Testing network connectivity..." fi if curl -s --connect-timeout 5 https://github.com > /dev/null; then if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "✅ GitHub connectivity - ${GREEN}OK${NC}" fi else if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "❌ GitHub connectivity - ${RED}Failed${NC}" fi validation_failed=true fi # Check script permissions and dependencies if [[ -f "$REMOTE_DEPLOY_SCRIPT" ]]; then if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "✅ Remote deployment script - ${GREEN}Found${NC}" fi if [[ ! -x "$REMOTE_DEPLOY_SCRIPT" ]]; then complete_info "Making remote deployment script executable" chmod +x "$REMOTE_DEPLOY_SCRIPT" fi else if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "❌ Remote deployment script - ${RED}Not found${NC}" fi validation_failed=true fi # Check for existing configuration if [[ -f "$PROJECT_DIR/.github-pat" ]]; then if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "ℹ️ GitHub token - ${BLUE}Found existing${NC}" fi fi if [[ -d "$PROJECT_DIR/.git" ]]; then if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "✅ Git repository - ${GREEN}Detected${NC}" fi else if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo -e "⚠️ Git repository - ${YELLOW}Not detected${NC}" fi fi # Report validation results if [[ "$validation_failed" == "true" ]]; then if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo "" echo -e "${RED}❌ System validation failed${NC}" echo "" if [[ ${#missing_commands[@]} -gt 0 ]]; then echo "📦 Missing dependencies: ${missing_commands[*]}" echo "" echo "Installation commands:" if command_exists apt-get; then echo " sudo apt-get update && sudo apt-get install -y openssh-client git python3 curl" elif command_exists yum; then echo " sudo yum install -y openssh-clients git python3 curl" elif command_exists brew; then echo " brew install openssh git python3 curl" elif command_exists pacman; then echo " sudo pacman -S openssh git python curl" fi echo "" fi read -r -p "Continue anyway? (y/N): " continue_validation if [[ ! "$continue_validation" =~ ^[Yy] ]]; then complete_error "System validation failed - deployment cannot continue" return 1 fi else complete_error "System validation failed - missing dependencies: ${missing_commands[*]}" return 1 fi else if [[ "$INTERACTIVE_MODE" == "true" ]]; then echo "" echo -e "${GREEN}✅ System validation passed${NC}" fi complete_success "System prerequisites validated successfully" fi return 0 } validate_local_environment() { complete_info "Validating local environment" # Use enhanced system validation for interactive mode if [[ "$INTERACTIVE_MODE" == "true" ]]; then return $(validate_system_prerequisites) fi # Original validation for command-line mode local missing_commands=() local required_commands=("ssh" "scp" "git" "python3") for cmd in "${required_commands[@]}"; do if ! command_exists "$cmd"; then missing_commands+=("$cmd") fi done if [[ ${#missing_commands[@]} -gt 0 ]]; then complete_error "Missing required local commands: ${missing_commands[*]}" echo "" echo "📦 Install missing dependencies:" echo "" if command_exists apt-get; then echo "Ubuntu/Debian:" echo " sudo apt-get install openssh-client git python3" elif command_exists yum; then echo "RHEL/CentOS:" echo " sudo yum install openssh-clients git python3" elif command_exists brew; then echo "macOS:" echo " brew install openssh git python3" fi return 1 fi # Check required scripts if [[ ! -f "$REMOTE_DEPLOY_SCRIPT" ]]; then complete_error "Remote deployment script not found: $REMOTE_DEPLOY_SCRIPT" return 1 fi if [[ ! -x "$REMOTE_DEPLOY_SCRIPT" ]]; then complete_info "Making remote deployment script executable" chmod +x "$REMOTE_DEPLOY_SCRIPT" fi # Check GitHub authentication if token provided if [[ -n "${GITHUB_TOKEN:-}" ]]; then complete_info "Validating provided GitHub token" if python3 "$GITHUB_SETUP_SCRIPT" validate --token "${GITHUB_TOKEN}"; then complete_success "GitHub token validated successfully" else complete_warning "GitHub token validation failed" read -r -p "Continue with invalid token? (y/N): " continue_invalid if [[ ! "$continue_invalid" =~ ^[Yy] ]]; then return 1 fi fi fi complete_success "Local environment validation completed" return 0 } # Cross-shell compatible SSH connectivity testing with comprehensive troubleshooting test_ssh_connectivity() { local host="$1" local user="$2" local port="$3" local ssh_key="$4" local timeout="${5:-10}" complete_info "Testing SSH connectivity to $user@$host:$port" # ENHANCED: Resolve SSH config aliases BEFORE doing network tests complete_debug "🔍 DIAGNOSIS: Checking if '$host' is an SSH config alias" local resolved_host="$host" local resolved_port="$port" local is_ssh_alias=false if command_exists ssh; then local ssh_config_output ssh_config_output=$(ssh -G "$host" 2>/dev/null) local ssh_config_exit_code=$? complete_debug "🔍 DIAGNOSIS: SSH config lookup exit code: $ssh_config_exit_code" complete_debug "🔍 DIAGNOSIS: SSH config output for '$host':" echo "$ssh_config_output" | while IFS= read -r line; do complete_debug " $line" done if [ $ssh_config_exit_code -eq 0 ] && echo "$ssh_config_output" | grep -q "^hostname "; then resolved_host=$(echo "$ssh_config_output" | grep "^hostname " | awk '{print $2}') resolved_port=$(echo "$ssh_config_output" | grep "^port " | awk '{print $2}' || echo "$port") if [ "$resolved_host" != "$host" ]; then is_ssh_alias=true complete_debug "🔍 DIAGNOSIS: SSH config alias detected!" complete_debug "🔍 DIAGNOSIS: Original alias: '$host'" complete_debug "🔍 DIAGNOSIS: Resolved hostname: '$resolved_host'" complete_debug "🔍 DIAGNOSIS: Resolved port: '$resolved_port'" else complete_debug "🔍 DIAGNOSIS: '$host' is not an SSH alias (hostname matches)" fi else complete_debug "🔍 DIAGNOSIS: '$host' is not in SSH config or SSH config lookup failed" fi else complete_debug "🔍 DIAGNOSIS: SSH command not available for alias resolution" fi # Use resolved hostname for network connectivity tests local test_host="$resolved_host" local test_port="$resolved_port" complete_info "Network connectivity tests will use: $test_host:$test_port" if [ "$is_ssh_alias" = true ]; then complete_info "SSH connections will use original alias: $host" fi # Step 1: Test basic network connectivity (ping) using resolved host if command_exists ping; then complete_debug "🔍 DIAGNOSIS: Testing ping connectivity to resolved host '$test_host'" if ping -c 1 -W "$timeout" "$test_host" >/dev/null 2>&1; then complete_success "✅ Host $test_host is reachable (ping successful)" if [ "$is_ssh_alias" = true ]; then complete_success "✅ SSH alias '$host' resolves to reachable host" fi else complete_warning "⚠️ Host $test_host is not responding to ping" if [ "$is_ssh_alias" = true ]; then complete_warning "⚠️ SSH alias '$host' resolves to '$test_host' which is not responding to ping" fi echo " This might indicate:" echo " • Host is down or unreachable" echo " • Firewall blocking ICMP packets" echo " • Network connectivity issues" if [ "$is_ssh_alias" = true ]; then echo " • SSH config alias resolution issue" fi fi fi # Step 2: Test SSH port connectivity using resolved host complete_debug "🔍 DIAGNOSIS: Testing SSH port $test_port connectivity to resolved host '$test_host'" if command_exists nc; then if nc -z -w "$timeout" "$test_host" "$test_port" 2>/dev/null; then complete_success "✅ SSH port $test_port is open on $test_host" if [ "$is_ssh_alias" = true ]; then complete_success "✅ SSH alias '$host' port connectivity confirmed" fi else complete_error "❌ SSH port $test_port is not accessible on $test_host" if [ "$is_ssh_alias" = true ]; then complete_error "❌ SSH alias '$host' resolves to '$test_host' but port $test_port is not accessible" fi echo " Possible causes:" echo " • SSH service is not running" echo " • SSH is running on a different port" echo " • Firewall blocking port $test_port" echo " • Network routing issues" if [ "$is_ssh_alias" = true ]; then echo " • SSH config alias pointing to wrong host/port" fi return 1 fi elif command_exists telnet; then if echo "" | telnet "$test_host" "$test_port" 2>/dev/null | grep -q "Connected"; then complete_success "✅ SSH port $test_port is open on $test_host" else complete_error "❌ SSH port $test_port is not accessible on $test_host" return 1 fi else complete_warning "⚠️ Cannot test port connectivity (nc/telnet not available)" fi # Step 3: Test SSH authentication using ORIGINAL alias (for SSH config application) complete_debug "🔍 DIAGNOSIS: Testing SSH authentication to $user@$host:$port (using original alias for SSH config)" # Enhanced debugging: show SSH key and host resolution if [ -n "$ssh_key" ]; then complete_debug "Using SSH key: $ssh_key" complete_debug "SSH key exists: $([ -f "$ssh_key" ] && echo "YES" || echo "NO")" if [ -f "$ssh_key" ]; then complete_debug "SSH key permissions: $(ls -la "$ssh_key" | awk '{print $1}')" fi else complete_debug "No SSH key specified, using SSH agent or default keys" fi # ENHANCED: Build SSH command using deployment-consistent options (no BatchMode for interactive auth) complete_debug "🔍 DIAGNOSIS: Building SSH command for authentication test" local ssh_options="${SSH_OPTIONS:--o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30}" local ssh_cmd="ssh $ssh_options" # For SSH config aliases, let SSH handle the configuration naturally if [ "$is_ssh_alias" = true ]; then complete_debug "🔍 DIAGNOSIS: Using SSH config alias - letting SSH handle configuration" # Don't use IdentitiesOnly for aliases as it might interfere with SSH config ssh_cmd="$ssh_cmd" else complete_debug "🔍 DIAGNOSIS: Direct IP/hostname - using IdentitiesOnly" ssh_cmd="$ssh_cmd -o IdentitiesOnly=yes" fi if [ -n "$ssh_key" ]; then # For aliases, only add key if not already specified in SSH config if [ "$is_ssh_alias" = true ]; then complete_debug "🔍 DIAGNOSIS: SSH alias detected - checking if explicit key is needed" complete_debug "🔍 DIAGNOSIS: Adding explicit SSH key: $ssh_key" fi ssh_cmd="$ssh_cmd -i $ssh_key" fi # Use original host and port for SSH connection (maintains SSH config compatibility) ssh_cmd="$ssh_cmd -p $port $user@$host" complete_debug "🔍 DIAGNOSIS: Final SSH command: $ssh_cmd" if [ "$is_ssh_alias" = true ]; then complete_debug "🔍 DIAGNOSIS: SSH will resolve '$host' using SSH config to connect to '$resolved_host:$resolved_port'" fi # Test SSH connection with enhanced error capture complete_debug "🔍 DIAGNOSIS: Executing SSH authentication test" local ssh_output="" local ssh_error="" ssh_output=$($ssh_cmd 'echo "SSH test successful"' 2>&1) local ssh_exit_code=$? complete_debug "🔍 DIAGNOSIS: SSH exit code: $ssh_exit_code" complete_debug "🔍 DIAGNOSIS: SSH output: $ssh_output" if [ $ssh_exit_code -eq 0 ]; then complete_success "✅ SSH authentication successful" if [ "$is_ssh_alias" = true ]; then complete_success "✅ SSH config alias '$host' authentication working" fi # Test remote command execution with enhanced logging complete_debug "🔍 DIAGNOSIS: Testing remote command execution" local remote_output="" remote_output=$($ssh_cmd 'echo "Remote command test"' 2>&1) local remote_exit_code=$? complete_debug "🔍 DIAGNOSIS: Remote command exit code: $remote_exit_code" complete_debug "🔍 DIAGNOSIS: Remote command output: $remote_output" if [ $remote_exit_code -eq 0 ]; then complete_success "✅ Remote commands can be executed" if [ "$is_ssh_alias" = true ]; then complete_success "✅ SSH config alias '$host' fully functional" fi return 0 else complete_warning "⚠️ SSH connection works but remote command execution failed" complete_debug "🔍 DIAGNOSIS: Remote command error: $remote_output" return 1 fi else complete_error "❌ SSH authentication failed" complete_debug "🔍 DIAGNOSIS: SSH error output: $ssh_output" if [ "$is_ssh_alias" = true ]; then complete_error "❌ SSH config alias '$host' authentication failed" echo " SSH config alias specific troubleshooting:" echo " • Check SSH config file (~/.ssh/config)" echo " • Verify alias '$host' is correctly defined" echo " • Ensure SSH key path in config is correct" echo " • Test manual connection: ssh $host" fi echo " Possible causes:" if [ -n "$ssh_key" ]; then echo " • SSH key not authorized on remote host" echo " • SSH key file permissions incorrect (should be 600)" echo " • SSH key path incorrect: $ssh_key" echo " • Public key not added to ~/.ssh/***REMOVED*** on remote host" else echo " • Password authentication disabled" echo " • Username '$user' does not exist on remote host" echo " • Account locked or disabled" fi echo " • SSH configuration on remote host blocking connections" return 1 fi } # Enhanced SSH key detection and management detect_ssh_keys() { local ssh_dir="$HOME/.ssh" local found_keys="" if [ ! -d "$ssh_dir" ]; then complete_debug "SSH directory $ssh_dir does not exist" return 1 fi # Common SSH key types and filenames local key_types="rsa ed25519 ecdsa dsa" for key_type in $key_types; do local key_file="$ssh_dir/id_$key_type" if [ -f "$key_file" ]; then # Check if private key file is readable if [ -r "$key_file" ]; then # Check file permissions (should be 600 or 400) local perms perms=$(stat -c "%a" "$key_file" 2>/dev/null || stat -f "%A" "$key_file" 2>/dev/null) if [ "$perms" = "600" ] || [ "$perms" = "400" ]; then found_keys="$found_keys$key_file " complete_debug "Found SSH key: $key_file (permissions: $perms)" else complete_warning "SSH key found but has incorrect permissions: $key_file ($perms)" echo " Fix with: chmod 600 '$key_file'" fi else complete_warning "SSH key found but not readable: $key_file" fi fi done if [ -n "$found_keys" ]; then echo "$found_keys" return 0 else return 1 fi } # SSH key setup guidance guide_ssh_key_setup() { local host="$1" local user="$2" echo "" echo -e "${CYAN}🔑 SSH Key Setup Guidance${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "To set up SSH key authentication for $user@$host:" echo "" echo "1. Generate a new SSH key (if you don't have one):" echo " ssh-keygen -t ed25519 -C \"your_email@example.com\"" echo " (Press Enter to accept default location and passphrase)" echo "" echo "2. Copy your public key to the remote server:" echo " ssh-copy-id -p ${REMOTE_PORT} $user@$host" echo "" echo "3. Or manually copy the public key:" echo " cat ~/.ssh/***REMOVED***.pub" echo " Then add this content to ~/.ssh/***REMOVED*** on $host" echo "" echo "4. Test the connection:" echo " ssh -p ${REMOTE_PORT} $user@$host" echo "" } # Comprehensive connectivity test with detailed troubleshooting test_connectivity() { local hosts="" local host_count=0 # Cross-shell compatible host reading if [ -f /tmp/thrillwiki-deploy-hosts.$$ ]; then while IFS= read -r host; do if [ -n "$host" ]; then hosts="$hosts$host " host_count=$((host_count + 1)) fi done < /tmp/thrillwiki-deploy-hosts.$$ else complete_error "Host configuration file not found" return 1 fi if [ "$host_count" -eq 0 ]; then complete_error "No hosts configured for testing" return 1 fi echo "" echo -e "${CYAN}🔐 SSH Connectivity Test${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" complete_info "Testing connectivity to $host_count host(s)" echo "" local failed_hosts="" local failed_count=0 local success_count=0 # Test each host for host in $hosts; do if [ -n "$host" ]; then echo "Testing connection to: ${REMOTE_USER}@$host:${REMOTE_PORT}" echo "" if test_ssh_connectivity "$host" "${REMOTE_USER}" "${REMOTE_PORT}" "${SSH_KEY:-}" 10; then echo "" complete_success "SSH connection verified! ✨" success_count=$((success_count + 1)) else echo "" complete_error "SSH connection failed for $host" failed_hosts="$failed_hosts$host " failed_count=$((failed_count + 1)) # Offer SSH key setup guidance if [ -z "${SSH_KEY:-}" ]; then echo "" read -r -p "Would you like SSH key setup guidance for $host? (y/N): " setup_guidance if echo "$setup_guidance" | grep -i "^y" >/dev/null; then guide_ssh_key_setup "$host" "${REMOTE_USER}" fi fi fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi done # Summary echo -e "${BOLD}Connection Test Summary:${NC}" echo "• Total hosts: $host_count" echo "• Successful: $success_count" echo "• Failed: $failed_count" if [ "$failed_count" -gt 0 ]; then echo "" complete_error "Failed to connect to $failed_count host(s): $failed_hosts" if [ "${FORCE_DEPLOY:-false}" = "true" ]; then complete_warning "Force deployment enabled, continuing anyway" return 0 fi echo "" echo "💡 Common troubleshooting steps:" echo "• Verify hostnames/IP addresses are correct" echo "• Check SSH key permissions: chmod 600 ~/.ssh/id_*" echo "• Ensure SSH service is running: sudo systemctl status ssh" echo "• Check firewall settings on remote hosts" echo "• Verify network connectivity and DNS resolution" echo "• Try connecting manually: ssh -p ${REMOTE_PORT} ${REMOTE_USER}@" echo "" read -r -p "Continue with failed connections? (y/N): " continue_failed if echo "$continue_failed" | grep -i "^y" >/dev/null; then complete_warning "Continuing with connection failures" return 0 else return 1 fi fi complete_success "All connectivity tests passed! 🎉" return 0 } # [AWS-SECRET-REMOVED]==================================== # GITHUB AUTHENTICATION SETUP - STEP 2A # [AWS-SECRET-REMOVED]==================================== # Cross-shell compatible GitHub token auto-detection detect_github_tokens() { local found_tokens="" local token_locations=( "$PROJECT_DIR/.github-pat" "$PROJECT_DIR/.thrillwiki-github-token" "$HOME/.github-pat" "$HOME/.config/gh/hosts.yml" ) complete_debug "Scanning for existing GitHub tokens" # Check standard token files for location in "${token_locations[@]}"; do if [[ -f "$location" && -r "$location" ]]; then local token_content token_content=$(cat "$location" 2>/dev/null | head -1 | tr -d '\n\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [[ -n "$token_content" ]]; then # Basic token format validation (cross-shell compatible) if echo "$token_content" | grep -E '^(ghp_|github_pat_|gho_|ghu_|ghs_)' >/dev/null; then found_tokens="$found_tokens$location:$token_content " complete_debug "Found token at: $location" fi fi fi done # Check GitHub CLI configuration if command_exists gh && gh auth status >/dev/null 2>&1; then local gh_token gh_token=$(gh auth token 2>/dev/null || echo "") if [[ -n "$gh_token" ]]; then found_tokens="$found_tokens/gh-cli:$gh_token " complete_debug "Found GitHub CLI token" fi fi # Check environment variables for var_name in GITHUB_TOKEN GH_TOKEN GITHUB_PAT; do local env_token env_token=$(eval echo "\${$var_name:-}") if [[ -n "$env_token" ]]; then if echo "$env_token" | grep -E '^(ghp_|github_pat_|gho_|ghu_|ghs_)' >/dev/null; then found_tokens="$found_tokens/env-$var_name:$env_token " complete_debug "Found token in environment: $var_name" fi fi done echo "$found_tokens" } # Cross-shell compatible GitHub API token validation validate_github_token_api() { local token="$1" local timeout="${2:-10}" if [[ -z "$token" ]]; then echo "ERROR:No token provided" return 1 fi complete_debug "Validating token with GitHub API (timeout: ${timeout}s)" # Use curl for cross-shell compatibility local api_response local http_code # Test basic authentication api_response=$(curl -s -w "%{http_code}" -m "$timeout" \ -H "Authorization: Bearer $token" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "https://api.github.com/user" 2>/dev/null) if [[ $? -ne 0 ]]; then echo "ERROR:Network request failed" return 1 fi # Extract HTTP code (last 3 characters) http_code="${api_response: -3}" # Extract response body (all but last 3 characters) local response_body="${api_response%???}" case "$http_code" in 200) local username username=$(echo "$response_body" | grep -o '"login":"[^"]*"' | cut -d'"' -f4 2>/dev/null || echo "unknown") echo "SUCCESS:Valid token for user: $username" return 0 ;; 401) echo "ERROR:Invalid or expired token" return 1 ;; 403) echo "ERROR:Token lacks required permissions or rate limited" return 1 ;; *) echo "ERROR:API request failed with HTTP $http_code" return 1 ;; esac } # Cross-shell compatible token permissions checking check_token_permissions() { local token="$1" local timeout="${2:-10}" if [[ -z "$token" ]]; then return 1 fi # Get token scopes from API response headers local scopes_header scopes_header=$(curl -s -I -m "$timeout" \ -H "Authorization: Bearer $token" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/user" 2>/dev/null | \ grep -i "x-oauth-scopes:" | cut -d' ' -f2- | tr -d '\r\n' || echo "") if [[ -n "$scopes_header" ]]; then echo "$scopes_header" return 0 else return 1 fi } # Cross-shell compatible repository access testing test_repository_access() { local token="$1" local repo_url="${2:-}" local timeout="${3:-10}" if [[ -z "$token" ]]; then return 1 fi # Try to detect repository URL if not provided if [[ -z "$repo_url" ]] && [[ -d "$PROJECT_DIR/.git" ]]; then repo_url=$(cd "$PROJECT_DIR" && git remote get-url origin 2>/dev/null || echo "") fi if [[ -z "$repo_url" ]]; then echo "INFO:No repository URL available for testing" return 0 fi # Extract owner/repo from GitHub URL (cross-shell compatible) local repo_path="" if echo "$repo_url" | grep -q "github.com"; then if echo "$repo_url" | grep -q "git@github.com:"; then repo_path=$(echo "$repo_url" | sed 's/^git@github\.com://; s/\.git$//') elif echo "$repo_url" | grep -q "https://github.com/"; then repo_path=$(echo "$repo_url" | sed 's|^https://github\.com/||; s|\.git$||; s|/$||') fi fi if [[ -z "$repo_path" ]]; then echo "INFO:Not a GitHub repository" return 0 fi # Test repository access local api_response local http_code api_response=$(curl -s -w "%{http_code}" -m "$timeout" \ -H "Authorization: Bearer $token" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$repo_path" 2>/dev/null) if [[ $? -ne 0 ]]; then echo "ERROR:Network request failed" return 1 fi http_code="${api_response: -3}" local response_body="${api_response%???}" case "$http_code" in 200) local repo_name repo_name=$(echo "$response_body" | grep -o '"full_name":"[^"]*"' | cut -d'"' -f4 2>/dev/null || echo "$repo_path") echo "SUCCESS:Access confirmed for $repo_name" return 0 ;; 404) echo "ERROR:Repository not found or no access" return 1 ;; 403) echo "ERROR:Access denied - insufficient permissions" return 1 ;; *) echo "ERROR:Access check failed with HTTP $http_code" return 1 ;; esac } # Comprehensive GitHub token validation system comprehensive_token_validation() { local token="$1" local validation_level="${2:-basic}" # basic, standard, comprehensive if [[ -z "$token" ]]; then complete_error "No token provided for validation" return 1 fi complete_info "Starting comprehensive token validation (level: $validation_level)" local validation_results="" local validation_score=0 local max_score=0 # Step 1: Format validation complete_debug "Step 1: Format validation" max_score=$((max_score + 1)) if echo "$token" | grep -E '^(ghp_|github_pat_|gho_|ghu_|ghs_)[A-Za-z0-9_]{36,}$' >/dev/null; then validation_results="${validation_results}✅ Token format is valid\n" validation_score=$((validation_score + 1)) else validation_results="${validation_results}❌ Token format is invalid\n" fi # Step 2: API connectivity test complete_debug "Step 2: API connectivity test" max_score=$((max_score + 1)) local api_result api_result=$(validate_github_token_api "$token" 10) local api_status="${api_result%%:*}" local api_message="${api_result#*:}" if [[ "$api_status" == "SUCCESS" ]]; then validation_results="${validation_results}✅ $api_message\n" validation_score=$((validation_score + 1)) else validation_results="${validation_results}❌ $api_message\n" # If API test fails, return early for basic validation if [[ "$validation_level" == "basic" ]]; then echo -e "$validation_results" complete_error "Token validation failed (score: $validation_score/$max_score)" return 1 fi fi # Step 3: Permission scope checking (standard+ validation) if [[ "$validation_level" != "basic" ]]; then complete_debug "Step 3: Permission scope checking" max_score=$((max_score + 1)) local scopes scopes=$(check_token_permissions "$token" 10) if [[ $? -eq 0 && -n "$scopes" ]]; then validation_results="${validation_results}✅ Token scopes: $scopes\n" validation_score=$((validation_score + 1)) # Check for essential scopes if echo "$scopes" | grep -E "(repo|public_repo)" >/dev/null; then validation_results="${validation_results}✅ Repository access permissions available\n" else validation_results="${validation_results}⚠️ Limited repository access permissions\n" fi else validation_results="${validation_results}⚠️ Could not verify token permissions\n" fi fi # Step 4: Repository access testing (comprehensive validation) if [[ "$validation_level" == "comprehensive" ]]; then complete_debug "Step 4: Repository access testing" max_score=$((max_score + 1)) local repo_result repo_result=$(test_repository_access "$token" "" 10) local repo_status="${repo_result%%:*}" local repo_message="${repo_result#*:}" case "$repo_status" in SUCCESS) validation_results="${validation_results}✅ $repo_message\n" validation_score=$((validation_score + 1)) ;; ERROR) validation_results="${validation_results}❌ $repo_message\n" ;; INFO) validation_results="${validation_results}ℹ️ $repo_message\n" validation_score=$((validation_score + 1)) # Don't penalize if no repo to test ;; esac fi # Display results echo "" echo -e "$validation_results" echo "Validation Score: $validation_score/$max_score" # Determine overall result local pass_threshold=$((max_score * 75 / 100)) # 75% pass rate if [[ $validation_score -ge $pass_threshold ]]; then complete_success "Token validation passed (score: $validation_score/$max_score)" return 0 else complete_error "Token validation failed (score: $validation_score/$max_score, required: $pass_threshold)" return 1 fi } # Enhanced interactive GitHub token setup with guided generation interactive_github_token_setup() { echo "" echo -e "${CYAN}🔑 GitHub Authentication Setup${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "GitHub authentication is required for:" echo "✅ Repository cloning and updates" echo "✅ Automated deployment services" echo "✅ Private repository access" echo "" # Check for existing tokens first local existing_tokens existing_tokens=$(detect_github_tokens) if [[ -n "$existing_tokens" ]]; then echo -e "${BLUE}ℹ️ Existing GitHub tokens detected:${NC}" echo "" local token_index=1 local token_options="" # Parse and display existing tokens (cross-shell compatible) local IFS=' ' for token_entry in $existing_tokens; do if [[ -n "$token_entry" ]]; then local location="${token_entry%%:*}" local token="${token_entry#*:}" # Mask token for display (show first 4 and last 4 characters) local masked_token="${token:0:4}...${token: -4}" echo "$token_index. $location ($masked_token)" token_options="$token_options$token_index:$location:$token " token_index=$((token_index + 1)) fi done echo "$token_index. Generate new Personal Access Token" echo "$((token_index + 1)). Skip GitHub setup (manual configuration later)" echo "" read -r -p "Select option [1-$((token_index + 1))]: " token_choice token_choice="${token_choice:-1}" # Process existing token selection if [[ "$token_choice" -le $((token_index - 1)) && "$token_choice" -gt 0 ]]; then local selected_token="" local selected_location="" local current_index=1 for option in $token_options; do if [[ -n "$option" ]]; then local opt_index="${option%%:*}" local opt_location="${option#*:}" opt_location="${opt_location%%:*}" local opt_token="${option##*:}" if [[ "$current_index" -eq "$token_choice" ]]; then selected_token="$opt_token" selected_location="$opt_location" break fi current_index=$((current_index + 1)) fi done if [[ -n "$selected_token" ]]; then echo "" echo -e "${BLUE}🔍 Validating selected token from $selected_location...${NC}" if comprehensive_token_validation "$selected_token" "standard"; then export GITHUB_TOKEN="$selected_token" # Store token in standard location if not already there if [[ "$selected_location" != "$PROJECT_DIR/.github-pat" ]]; then echo "$selected_token" > "$PROJECT_DIR/.github-pat" chmod 600 "$PROJECT_DIR/.github-pat" complete_info "Token stored in standard location: $PROJECT_DIR/.github-pat" fi complete_success "GitHub authentication configured successfully!" return 0 else complete_warning "Selected token validation failed" echo "" read -r -p "Continue with token generation? (Y/n): " continue_setup if [[ "$continue_setup" =~ ^[Nn] ]]; then return 1 fi fi fi elif [[ "$token_choice" -eq $((token_index + 1)) ]]; then # Skip setup complete_info "GitHub authentication setup skipped" export SKIP_GITHUB_SETUP=true return 0 fi # If choice is for new token generation, fall through to generation process fi # Token generation guidance and setup echo "" echo -e "${CYAN}📋 GitHub Personal Access Token Generation${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "📚 Step-by-step token creation:" echo "" echo "1. 🌐 Open GitHub in your browser:" echo " ${BOLD}https://github.com/settings/tokens${NC}" echo "" echo "2. 🔧 Click 'Generate new token' → 'Generate new token (classic)'" echo "" echo "3. 📝 Configure your token:" echo " • Note: 'ThrillWiki Automation $(date +%Y-%m-%d)'" echo " • Expiration: 90 days (recommended)" echo "" echo "4. ✅ Select required scopes:" echo " ${BOLD}Essential scopes:${NC}" echo " ☑️ repo (Full control of private repositories)" echo " ☑️ workflow (Update GitHub Action workflows)" echo "" echo " ${BOLD}Optional scopes (for enhanced features):${NC}" echo " ☑️ read:org (Read org and team membership)" echo " ☑️ user:email (Access user email addresses)" echo "" echo "5. 🎯 Generate and copy your token" echo "" echo -e "${YELLOW}⚠️ Security Notes:${NC}" echo "• Token will only be shown once - copy it immediately" echo "• Never share tokens in public repositories" echo "• Set reasonable expiration dates for security" echo "" read -r -p "Ready to enter your token? [Y/n]: " ready_for_token if [[ "$ready_for_token" =~ ^[Nn] ]]; then complete_info "Token setup postponed" return 1 fi # Token input and validation echo "" echo -e "${CYAN}🔐 Token Input and Validation${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" local attempts=0 local max_attempts=3 while [[ $attempts -lt $max_attempts ]]; do echo "Please paste your GitHub Personal Access Token:" echo "(Input will be hidden for security)" echo "" local token="" read -r -s -p "GitHub PAT: " token echo "" if [[ -z "$token" ]]; then complete_error "No token entered" attempts=$((attempts + 1)) if [[ $attempts -lt $max_attempts ]]; then echo "Please try again ($((max_attempts - attempts)) attempts remaining)" echo "" fi continue fi # Comprehensive validation echo "" echo -e "${BLUE}🔍 Validating token...${NC}" if comprehensive_token_validation "$token" "comprehensive"; then # Store token securely echo "" echo -e "${BLUE}💾 Storing token securely...${NC}" # Backup existing token if present if [[ -f "$PROJECT_DIR/.github-pat" ]]; then cp "$PROJECT_DIR/.github-pat" "$PROJECT_DIR/.github-pat.backup.$(date +%Y%m%d-%H%M%S)" complete_info "Existing token backed up" fi # Write new token with secure permissions echo "$token" > "$PROJECT_DIR/.github-pat" chmod 600 "$PROJECT_DIR/.github-pat" # Verify file permissions (cross-shell compatible) local file_perms if command_exists stat; then file_perms=$(stat -c "%a" "$PROJECT_DIR/.github-pat" 2>/dev/null || stat -f "%A" "$PROJECT_DIR/.github-pat" 2>/dev/null) if [[ "$file_perms" == "600" ]]; then complete_success "Token stored with secure permissions (600)" else complete_warning "Token stored but permissions may need adjustment: $file_perms" fi else complete_info "Token stored (permissions verification unavailable)" fi # Export for immediate use export GITHUB_TOKEN="$token" complete_success "GitHub authentication configured successfully!" echo "" echo -e "${GREEN}🎉 Setup Complete!${NC}" echo "" echo "Your GitHub token is now:" echo "• ✅ Validated and working" echo "• ✅ Securely stored in $PROJECT_DIR/.github-pat" echo "• ✅ Ready for automated deployments" echo "" return 0 else complete_error "Token validation failed" attempts=$((attempts + 1)) if [[ $attempts -lt $max_attempts ]]; then echo "" echo "Please check:" echo "• Token was copied correctly (no extra spaces)" echo "• Token has required 'repo' permissions" echo "• Token hasn't expired" echo "• Network connectivity to GitHub API" echo "" read -r -p "Try again? (Y/n): " try_again if [[ "$try_again" =~ ^[Nn] ]]; then break fi fi fi done complete_error "Failed to set up GitHub authentication after $max_attempts attempts" return 1 } # Enhanced setup_github_authentication function for Step 2A setup_github_authentication() { if [[ "${SKIP_GITHUB_SETUP:-false}" == "true" ]]; then complete_info "GitHub authentication setup skipped" return 0 fi complete_progress "Starting GitHub Authentication Setup - Step 2A" # Check if token is already provided via command line if [[ -n "${GITHUB_TOKEN:-}" ]]; then complete_info "GitHub token provided via command line, validating..." if comprehensive_token_validation "$GITHUB_TOKEN" "standard"; then complete_success "Provided GitHub token is valid" return 0 else complete_warning "Provided GitHub token failed validation" unset GITHUB_TOKEN fi fi # Auto-detect existing tokens complete_info "Auto-detecting existing GitHub tokens..." local detected_tokens detected_tokens=$(detect_github_tokens) if [[ -n "$detected_tokens" ]]; then complete_success "Found existing GitHub token(s)" # For non-interactive mode, try to use the first valid token if [[ "${INTERACTIVE_MODE:-false}" != "true" ]]; then local first_token first_token=$(echo "$detected_tokens" | cut -d' ' -f1 | cut -d':' -f2) if [[ -n "$first_token" ]]; then complete_info "Testing first detected token..." if comprehensive_token_validation "$first_token" "basic"; then export GITHUB_TOKEN="$first_token" complete_success "Using detected GitHub token" return 0 else complete_warning "Detected token failed validation" fi fi fi fi # Interactive setup for detailed configuration if [[ "${INTERACTIVE_MODE:-true}" == "true" ]]; then if interactive_github_token_setup; then return 0 else complete_warning "Interactive GitHub setup failed or was cancelled" fi else # Non-interactive fallback - try github-setup.py complete_info "Attempting automated GitHub setup..." if python3 "$GITHUB_SETUP_SCRIPT" setup 2>/dev/null; then complete_success "GitHub authentication configured via automated setup" # Export token if available if [[ -f "$PROJECT_DIR/.github-pat" ]]; then export GITHUB_TOKEN=$(cat "$PROJECT_DIR/.github-pat") fi return 0 else complete_warning "Automated GitHub setup failed" fi fi # Final fallback complete_warning "GitHub authentication setup incomplete" echo "" echo -e "${YELLOW}⚠️ GitHub authentication could not be configured automatically.${NC}" echo "" echo "Manual setup options:" echo "• Run: python3 $GITHUB_SETUP_SCRIPT setup" echo "• Set GITHUB_TOKEN environment variable" echo "• Create token file: $PROJECT_DIR/.github-pat" echo "" echo "Deployment will continue with limited GitHub access." export SKIP_GITHUB_SETUP=true return 0 } # [AWS-SECRET-REMOVED]==================================== # REPOSITORY CONFIGURATION # [AWS-SECRET-REMOVED]==================================== # Detect current Git repository URL detect_repository_url() { local repo_url="" # Check if we're in a Git repository if [[ -d "$PROJECT_DIR/.git" ]]; then # Try to get the remote URL repo_url=$(cd "$PROJECT_DIR" && git remote get-url origin 2>/dev/null || echo "") if [[ -n "$repo_url" ]]; then complete_debug "Detected repository URL: $repo_url" echo "$repo_url" return 0 else complete_debug "Git repository found but no remote origin configured" fi else complete_debug "Not in a Git repository" fi return 1 } # Validate GitHub repository URL format validate_github_url() { local url="$1" if [[ -z "$url" ]]; then return 1 fi # Check if URL is a valid GitHub repository URL (cross-shell compatible) if echo "$url" | grep -E '^https://github\.com/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+(\.git)?/?$' >/dev/null || \ echo "$url" | grep -E '^git@github\.com:[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\.git$' >/dev/null; then return 0 fi return 1 } # Normalize GitHub URL to HTTPS format normalize_github_url() { local url="$1" # Convert SSH format to HTTPS (cross-shell compatible) if echo "$url" | grep -E '^git@github\.com:.+\.git$' >/dev/null; then # Extract the repo path from SSH format local repo_path repo_path=$(echo "$url" | sed 's/^git@github\.com:\(.*\)\.git$/\1/') echo "https://github.com/${repo_path}.git" elif echo "$url" | grep -E '^https://github\.com/.+/?$' >/dev/null; then # Extract repo path from HTTPS format local repo_path repo_path=$(echo "$url" | sed 's|^https://github\.com/\([^/]*\)/\([^/]*\).*|\1/\2|') # Remove trailing .git if present, then add it back repo_path=$(echo "$repo_path" | sed 's/\.git$//') echo "https://github.com/${repo_path}.git" else echo "$url" fi } # Setup repository configuration setup_repository_configuration() { complete_progress "Setting up repository configuration" # Check if repository URL is already provided via environment if [[ -n "${GITHUB_REPO_URL:-}" ]]; then complete_info "Using provided repository URL: $GITHUB_REPO_URL" if validate_github_url "$GITHUB_REPO_URL"; then export GITHUB_REPO_URL=$(normalize_github_url "$GITHUB_REPO_URL") complete_success "Repository URL validated and configured" return 0 else complete_warning "Provided repository URL is not a valid GitHub URL" unset GITHUB_REPO_URL fi fi # Try to detect current repository URL local detected_url="" if detected_url=$(detect_repository_url); then complete_info "Detected current repository URL: $detected_url" if validate_github_url "$detected_url"; then detected_url=$(normalize_github_url "$detected_url") complete_info "Current repository is a valid GitHub repository" else complete_warning "Current repository is not a GitHub repository" detected_url="" fi fi # Interactive repository URL setup echo "" echo "📚 Repository Configuration" echo "Please specify the GitHub repository URL for deployment automation." echo "" echo "This repository will be:" echo "• Cloned to remote servers during deployment" echo "• Automatically pulled every ${CUSTOM_PULL_INTERVAL:-300} seconds" echo "• Used for continuous deployment and updates" echo "" if [[ -n "$detected_url" ]]; then echo "Detected current repository: $detected_url" echo "" read -r -p "Use current repository? (Y/n): " use_current if [[ ! "$use_current" =~ ^[Nn] ]]; then export GITHUB_REPO_URL="$detected_url" complete_success "Using current repository: $GITHUB_REPO_URL" return 0 fi fi # Manual repository URL input while true; do echo "" echo "Please enter the GitHub repository URL:" echo "Examples:" echo "• https://github.com/username/repository.git" echo "• https://github.com/username/repository" echo "• git@github.com:username/repository.git" echo "" read -r -p "Repository URL: " repo_input if [[ -z "$repo_input" ]]; then complete_warning "Repository URL cannot be empty" read -r -p "Skip repository configuration? This will disable automation features. (y/N): " skip_repo if [[ "$skip_repo" =~ ^[Yy] ]]; then complete_warning "Repository configuration skipped - automation features will be limited" export SKIP_REPO_CONFIG=true return 0 fi continue fi if validate_github_url "$repo_input"; then export GITHUB_REPO_URL=$(normalize_github_url "$repo_input") complete_success "Repository URL configured: $GITHUB_REPO_URL" break else complete_error "Invalid GitHub repository URL format" echo "" echo "Valid formats:" echo "• https://github.com/username/repository.git" echo "• https://github.com/username/repository" echo "• git@github.com:username/repository.git" echo "" read -r -p "Try again? (Y/n): " try_again if [[ "$try_again" =~ ^[Nn] ]]; then read -r -p "Skip repository configuration? (y/N): " skip_repo if [[ "$skip_repo" =~ ^[Yy] ]]; then complete_warning "Repository configuration skipped - automation features will be limited" export SKIP_REPO_CONFIG=true return 0 fi return 1 fi fi done # Export additional repository variables for deployment scripts if [[ -n "${GITHUB_REPO_URL:-}" ]]; then # Extract repository name and owner from URL local repo_info repo_info=$(echo "$GITHUB_REPO_URL" | sed -E 's|.*github\.com[:/]([^/]+)/([^/]+).*|\1/\2|' | sed 's|\.git$||') # Cross-shell compatible repository info extraction local owner local name owner=$(echo "$repo_info" | cut -d'/' -f1) name=$(echo "$repo_info" | cut -d'/' -f2) if [ -n "$owner" ] && [ -n "$name" ]; then export GITHUB_REPO_OWNER="$owner" export GITHUB_REPO_NAME="$name" complete_debug "Repository owner: $GITHUB_REPO_OWNER, name: $GITHUB_REPO_NAME" fi # Step 2B: Enhanced repository configuration with branch selection and validation if ! configure_repository_branch_and_access; then complete_error "Repository branch and access configuration failed" return 1 fi fi complete_success "Repository configuration completed successfully" return 0 } # [AWS-SECRET-REMOVED]==================================== # REPOSITORY DETECTION AND CONFIGURATION - STEP 2B # [AWS-SECRET-REMOVED]==================================== # Cross-shell compatible branch detection detect_repository_branches() { local repo_url="$1" local timeout="${2:-10}" if [[ -z "$repo_url" ]]; then complete_debug "No repository URL provided for branch detection" return 1 fi complete_debug "Detecting available branches for repository: $repo_url" # Extract owner/repo from GitHub URL for API access local repo_path="" if echo "$repo_url" | grep -q "github.com"; then if echo "$repo_url" | grep -q "git@github.com:"; then repo_path=$(echo "$repo_url" | sed 's/^git@github\.com://; s/\.git$//') elif echo "$repo_url" | grep -q "https://github.com/"; then repo_path=$(echo "$repo_url" | sed 's|^https://github\.com/||; s|\.git$||; s|/$||') fi fi if [[ -z "$repo_path" ]]; then complete_debug "Cannot extract repository path from URL: $repo_url" return 1 fi # Use GitHub API to get branch information if token is available if [[ -n "${GITHUB_TOKEN:-}" ]]; then complete_debug "Using GitHub API to fetch branch information" local api_response local http_code api_response=$(curl -s -w "%{http_code}" -m "$timeout" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$repo_path/branches" 2>/dev/null) if [[ $? -eq 0 ]]; then http_code="${api_response: -3}" local response_body="${api_response%???}" if [[ "$http_code" == "200" ]]; then # Extract branch names from JSON response (cross-shell compatible) local branches="" branches=$(echo "$response_body" | grep -o '"name":"[^"]*"' | cut -d'"' -f4 | tr '\n' ' ') if [[ -n "$branches" ]]; then echo "$branches" return 0 fi fi fi fi # Fallback: try to detect branches from local git if we're in the same repository if [[ -d "$PROJECT_DIR/.git" ]]; then local current_repo_url current_repo_url=$(cd "$PROJECT_DIR" && git remote get-url origin 2>/dev/null || echo "") # Check if this is the same repository local current_normalized="" local target_normalized="" if [[ -n "$current_repo_url" ]]; then current_normalized=$(normalize_github_url "$current_repo_url" 2>/dev/null || echo "$current_repo_url") target_normalized=$(normalize_github_url "$repo_url" 2>/dev/null || echo "$repo_url") fi if [[ "$current_normalized" == "$target_normalized" ]]; then complete_debug "Same repository detected, fetching remote branches" # Fetch remote branches if (cd "$PROJECT_DIR" && git fetch origin >/dev/null 2>&1); then local remote_branches remote_branches=$(cd "$PROJECT_DIR" && git branch -r | grep -v HEAD | sed 's/^[[:space:]]*origin\///' | tr '\n' ' ') if [[ -n "$remote_branches" ]]; then echo "$remote_branches" return 0 fi fi fi fi # If all else fails, return common default branches echo "main master develop dev" return 0 } # Cross-shell compatible current branch detection detect_current_branch() { if [[ -d "$PROJECT_DIR/.git" ]]; then local current_branch current_branch=$(cd "$PROJECT_DIR" && git branch --show-current 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") if [[ -n "$current_branch" ]]; then echo "$current_branch" return 0 fi fi return 1 } # Validate branch exists on remote repository validate_repository_branch() { local repo_url="$1" local branch="$2" local timeout="${3:-10}" if [[ -z "$repo_url" ]] || [[ -z "$branch" ]]; then return 1 fi complete_debug "Validating branch '$branch' exists on repository: $repo_url" # Extract owner/repo from GitHub URL local repo_path="" if echo "$repo_url" | grep -q "github.com"; then if echo "$repo_url" | grep -q "git@github.com:"; then repo_path=$(echo "$repo_url" | sed 's/^git@github\.com://; s/\.git$//') elif echo "$repo_url" | grep -q "https://github.com/"; then repo_path=$(echo "$repo_url" | sed 's|^https://github\.com/||; s|\.git$||; s|/$||') fi fi if [[ -z "$repo_path" ]]; then complete_debug "Cannot extract repository path for branch validation" return 1 fi # Use GitHub API to check if branch exists if [[ -n "${GITHUB_TOKEN:-}" ]]; then local api_response local http_code api_response=$(curl -s -w "%{http_code}" -m "$timeout" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$repo_path/branches/$branch" 2>/dev/null) if [[ $? -eq 0 ]]; then http_code="${api_response: -3}" if [[ "$http_code" == "200" ]]; then return 0 elif [[ "$http_code" == "404" ]]; then return 1 fi fi fi # Fallback: check local git if same repository if [[ -d "$PROJECT_DIR/.git" ]]; then local current_repo_url current_repo_url=$(cd "$PROJECT_DIR" && git remote get-url origin 2>/dev/null || echo "") if [[ -n "$current_repo_url" ]]; then local current_normalized local target_normalized current_normalized=$(normalize_github_url "$current_repo_url" 2>/dev/null || echo "$current_repo_url") target_normalized=$(normalize_github_url "$repo_url" 2>/dev/null || echo "$repo_url") if [[ "$current_normalized" == "$target_normalized" ]]; then if (cd "$PROJECT_DIR" && git fetch origin >/dev/null 2>&1 && git rev-parse --verify "origin/$branch" >/dev/null 2>&1); then return 0 fi fi fi fi return 1 } # Enhanced interactive repository configuration with branch selection and access verification configure_repository_branch_and_access() { if [[ -z "${GITHUB_REPO_URL:-}" ]]; then complete_debug "No repository URL configured, skipping branch and access configuration" return 0 fi echo "" echo -e "${CYAN}📦 Repository Configuration${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Display current repository information local repo_info repo_info=$(echo "$GITHUB_REPO_URL" | sed -E 's|.*github\.com[:/]([^/]+)/([^/]+).*|\1/\2|' | sed 's|\.git$||') local owner local name owner=$(echo "$repo_info" | cut -d'/' -f1) name=$(echo "$repo_info" | cut -d'/' -f2) echo "Current repository detected:" echo -e "🔗 ${GITHUB_REPO_URL}" echo -e "📂 Owner: ${BOLD}$owner${NC}" echo -e "📝 Name: ${BOLD}$name${NC}" # Detect current branch if available local current_branch="" if current_branch=$(detect_current_branch); then echo -e "🌿 Current branch: ${BOLD}$current_branch${NC}" fi echo "" # Step 1: Repository Access Verification echo -e "${BLUE}🔍 Verifying repository access...${NC}" if [[ -n "${GITHUB_TOKEN:-}" ]]; then local access_result access_result=$(test_repository_access "$GITHUB_TOKEN" "$GITHUB_REPO_URL" 10) local access_status="${access_result%%:*}" local access_message="${access_result#*:}" case "$access_status" in SUCCESS) echo -e "✅ Repository access verified: $access_message" ;; ERROR) echo -e "❌ Repository access failed: $access_message" echo "" echo "This may indicate:" echo "• Repository is private and token lacks access" echo "• Repository doesn't exist or URL is incorrect" echo "• GitHub token has insufficient permissions" echo "" read -r -p "Continue anyway? (y/N): " continue_access if [[ ! "$continue_access" =~ ^[Yy] ]]; then complete_error "Repository access verification failed" return 1 fi ;; INFO) echo -e "ℹ️ $access_message" ;; esac else echo -e "⚠️ No GitHub token available for access verification" echo "Repository access will be tested during deployment" fi echo "" # Step 2: Branch Selection and Validation echo -e "${BLUE}🌿 Branch Configuration${NC}" echo "" # Detect available branches local available_branches="" if available_branches=$(detect_repository_branches "$GITHUB_REPO_URL" 10); then complete_debug "Available branches: $available_branches" else complete_debug "Could not detect available branches" available_branches="main master" fi # Default branch detection/selection local default_branch="" if [[ -n "$current_branch" ]]; then # Check if current branch exists on remote if validate_repository_branch "$GITHUB_REPO_URL" "$current_branch" 10; then default_branch="$current_branch" fi fi # If no valid current branch, try to find a good default if [[ -z "$default_branch" ]]; then for branch in main master develop dev; do if echo "$available_branches" | grep -q "\b$branch\b"; then default_branch="$branch" break fi done fi # If still no default, use the first available branch if [[ -z "$default_branch" ]] && [[ -n "$available_branches" ]]; then default_branch=$(echo "$available_branches" | cut -d' ' -f1) fi # Show branch options echo "Available branches: $available_branches" if [[ -n "$default_branch" ]]; then echo -e "Recommended branch: ${BOLD}$default_branch${NC}" fi echo "" echo "Options:" echo "1. Use detected repository and branch (recommended)" echo "2. Specify different repository URL" echo "3. Configure branch settings" echo "" read -r -p "Select option [1-3]: " repo_option repo_option="${repo_option:-1}" case "$repo_option" in 1) # Use current settings with default branch if [[ -n "$default_branch" ]]; then export GITHUB_REPO_BRANCH="$default_branch" complete_success "Using repository: $GITHUB_REPO_URL (branch: $default_branch)" else export GITHUB_REPO_BRANCH="main" complete_info "Using repository: $GITHUB_REPO_URL (branch: main - will be validated during deployment)" fi ;; 2) # Allow repository URL override echo "" echo "Current repository: $GITHUB_REPO_URL" echo "" read -r -p "Enter new repository URL: " new_repo_url if [[ -n "$new_repo_url" ]] && validate_github_url "$new_repo_url"; then export GITHUB_REPO_URL=$(normalize_github_url "$new_repo_url") complete_success "Repository URL updated: $GITHUB_REPO_URL" # Recursively configure the new repository return configure_repository_branch_and_access else complete_error "Invalid repository URL provided" return 1 fi ;; 3) # Interactive branch configuration configure_repository_branch_interactive "$available_branches" "$default_branch" ;; *) complete_error "Invalid option selected" return 1 ;; esac # Final validation if [[ -n "${GITHUB_REPO_BRANCH:-}" ]] && [[ -n "${GITHUB_TOKEN:-}" ]]; then echo "" echo -e "${BLUE}🔍 Validating selected branch...${NC}" if validate_repository_branch "$GITHUB_REPO_URL" "$GITHUB_REPO_BRANCH" 10; then echo -e "✅ Branch '${GITHUB_REPO_BRANCH}' confirmed on remote repository" else echo -e "⚠️ Branch '${GITHUB_REPO_BRANCH}' not found on remote repository" echo "This branch will be validated during deployment" fi fi return 0 } # Interactive branch configuration configure_repository_branch_interactive() { local available_branches="$1" local default_branch="$2" echo "" echo -e "${CYAN}🌿 Branch Selection${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" if [[ -n "$available_branches" ]]; then echo "Available branches:" local branch_index=1 local branch_list="" # Convert space-separated branches to indexed list for branch in $available_branches; do echo "$branch_index. $branch" branch_list="$branch_list$branch_index:$branch " branch_index=$((branch_index + 1)) done echo "$branch_index. Specify custom branch" echo "" read -r -p "Select branch [1-$branch_index, default: $default_branch]: " branch_choice if [[ -z "$branch_choice" ]] && [[ -n "$default_branch" ]]; then export GITHUB_REPO_BRANCH="$default_branch" complete_success "Using default branch: $default_branch" elif [[ "$branch_choice" -le $((branch_index - 1)) ]] && [[ "$branch_choice" -gt 0 ]]; then # Find selected branch from list local selected_branch="" for entry in $branch_list; do local entry_index="${entry%%:*}" local entry_branch="${entry#*:}" if [[ "$entry_index" == "$branch_choice" ]]; then selected_branch="$entry_branch" break fi done if [[ -n "$selected_branch" ]]; then export GITHUB_REPO_BRANCH="$selected_branch" complete_success "Selected branch: $selected_branch" fi elif [[ "$branch_choice" -eq "$branch_index" ]]; then # Custom branch input echo "" read -r -p "Enter custom branch name: " custom_branch if [[ -n "$custom_branch" ]]; then export GITHUB_REPO_BRANCH="$custom_branch" complete_info "Custom branch set: $custom_branch (will be validated during deployment)" else complete_error "No branch name provided" return 1 fi else complete_error "Invalid branch selection" return 1 fi else echo "Could not detect available branches." echo "" read -r -p "Enter branch name (default: main): " manual_branch manual_branch="${manual_branch:-main}" export GITHUB_REPO_BRANCH="$manual_branch" complete_info "Branch set: $manual_branch (will be validated during deployment)" fi return 0 } # [AWS-SECRET-REMOVED]==================================== # DEPLOYMENT CONFIGURATION - STEP 3A # [AWS-SECRET-REMOVED]==================================== # Interactive deployment configuration with comprehensive preset selection interactive_deployment_configuration() { echo "" echo -e "${CYAN}⚙️ Deployment Configuration${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Configure deployment behavior, environment settings, and automation parameters." echo "" # Skip if preset already provided via command line if [[ -n "${DEPLOYMENT_PRESET:-}" ]] && [[ "${DEPLOYMENT_PRESET}" != "auto" ]]; then if validate_preset "$DEPLOYMENT_PRESET"; then complete_info "Using command-line preset: $DEPLOYMENT_PRESET" apply_preset_configuration "$DEPLOYMENT_PRESET" return 0 else complete_warning "Invalid command-line preset: $DEPLOYMENT_PRESET" fi fi # Interactive preset selection echo "Select deployment environment:" echo "" echo -e "${BOLD}1. 🛠️ Development (dev)${NC}" get_deployment_preset_details "dev" | sed 's/^/ /' echo "" echo -e "${BOLD}2. 🚀 Production (prod)${NC}" get_deployment_preset_details "prod" | sed 's/^/ /' echo "" echo -e "${BOLD}3. 🎪 Demo (demo)${NC}" get_deployment_preset_details "demo" | sed 's/^/ /' echo "" echo -e "${BOLD}4. 🧪 Testing (testing)${NC}" get_deployment_preset_details "testing" | sed 's/^/ /' echo "" local preset_choice="" while [[ ! "$preset_choice" =~ ^[1-4]$ ]]; do read -r -p "Select preset [1-4]: " preset_choice if [[ ! "$preset_choice" =~ ^[1-4]$ ]]; then echo -e "${RED}❌ Please select a valid option (1-4)${NC}" echo "" fi done # Convert choice to preset name local selected_preset="" case "$preset_choice" in 1) selected_preset="dev" ;; 2) selected_preset="prod" ;; 3) selected_preset="demo" ;; 4) selected_preset="testing" ;; esac echo "" echo -e "${GREEN}✅ Selected: $(get_deployment_preset_description "$selected_preset")${NC}" echo "" # Apply preset configuration apply_preset_configuration "$selected_preset" # Advanced configuration options echo "" read -r -p "Would you like to customize deployment parameters? (y/N): " customize_params if [[ "$customize_params" =~ ^[Yy] ]]; then configure_advanced_deployment_parameters "$selected_preset" fi # Configuration summary show_deployment_configuration_summary # Final confirmation echo "" read -r -p "Proceed with this configuration? (Y/n): " confirm_config if [[ "$confirm_config" =~ ^[Nn] ]]; then complete_info "Deployment configuration cancelled" return 1 fi complete_success "Deployment configuration completed" return 0 } # Apply preset configuration with comprehensive settings apply_preset_configuration() { local preset="$1" complete_info "Applying $preset deployment preset configuration" # Apply all preset configurations export DEPLOYMENT_PRESET="$preset" export CUSTOM_PULL_INTERVAL=$(get_preset_config "$preset" "PULL_INTERVAL") export HEALTH_CHECK_INTERVAL=$(get_preset_config "$preset" "HEALTH_CHECK_INTERVAL") export DEPLOYMENT_DEBUG_MODE=$(get_preset_config "$preset" "DEBUG_MODE") export AUTO_MIGRATE=$(get_preset_config "$preset" "AUTO_MIGRATE") export AUTO_UPDATE_DEPENDENCIES=$(get_preset_config "$preset" "AUTO_UPDATE_DEPENDENCIES") export DEPLOYMENT_LOG_LEVEL=$(get_preset_config "$preset" "LOG_LEVEL") export SSL_REQUIRED=$(get_preset_config "$preset" "SSL_REQUIRED") export CORS_ALLOWED=$(get_preset_config "$preset" "CORS_ALLOWED") export DJANGO_DEBUG=$(get_preset_config "$preset" "DJANGO_DEBUG") export ALLOWED_HOSTS=$(get_preset_config "$preset" "ALLOWED_HOSTS") complete_debug "Preset configuration applied: $preset" } # Configure advanced deployment parameters configure_advanced_deployment_parameters() { local preset="$1" echo "" echo -e "${CYAN}🔧 Advanced Deployment Parameters${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Customize deployment settings beyond the preset defaults:" echo "" # Pull interval customization local current_interval="$CUSTOM_PULL_INTERVAL" echo "Current automated update interval: ${current_interval}s" read -r -p "Custom pull interval in seconds (or press Enter to keep $current_interval): " new_interval if [[ -n "$new_interval" ]] && [[ "$new_interval" =~ ^[0-9]+$ ]] && [[ "$new_interval" -gt 0 ]]; then export CUSTOM_PULL_INTERVAL="$new_interval" complete_info "Pull interval updated to ${new_interval}s" fi # Health check interval local current_health="$HEALTH_CHECK_INTERVAL" echo "" echo "Current health check interval: ${current_health}s" read -r -p "Custom health check interval in seconds (or press Enter to keep $current_health): " new_health if [[ -n "$new_health" ]] && [[ "$new_health" =~ ^[0-9]+$ ]] && [[ "$new_health" -gt 0 ]]; then export HEALTH_CHECK_INTERVAL="$new_health" complete_info "Health check interval updated to ${new_health}s" fi # Auto-migration toggle echo "" echo "Current auto-migration setting: $AUTO_MIGRATE" read -r -p "Enable automatic database migrations? (Y/n): " auto_migrate_choice if [[ "$auto_migrate_choice" =~ ^[Nn] ]]; then export AUTO_MIGRATE="false" complete_info "Auto-migration disabled" else export AUTO_MIGRATE="true" complete_info "Auto-migration enabled" fi # Dependency update toggle echo "" echo "Current auto-dependency update setting: $AUTO_UPDATE_DEPENDENCIES" read -r -p "Enable automatic dependency updates? (y/N): " auto_deps_choice if [[ "$auto_deps_choice" =~ ^[Yy] ]]; then export AUTO_UPDATE_DEPENDENCIES="true" complete_info "Auto-dependency updates enabled" else export AUTO_UPDATE_DEPENDENCIES="false" complete_info "Auto-dependency updates disabled" fi # Custom environment variables echo "" read -r -p "Add custom environment variables? (y/N): " add_env_vars if [[ "$add_env_vars" =~ ^[Yy] ]]; then configure_custom_environment_variables fi complete_success "Advanced parameters configured" } # Configure custom environment variables configure_custom_environment_variables() { echo "" echo -e "${BLUE}🌍 Custom Environment Variables${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Add custom environment variables for your deployment:" echo "" export CUSTOM_ENV_VARS="" local env_count=0 while true; do echo "Enter environment variable (format: KEY=value) or press Enter to finish:" read -r env_var if [[ -z "$env_var" ]]; then break fi # Validate format if [[ "$env_var" =~ ^[A-Za-z_][A-Za-z0-9_]*=.+$ ]]; then if [[ -z "$CUSTOM_ENV_VARS" ]]; then export CUSTOM_ENV_VARS="$env_var" else export CUSTOM_ENV_VARS="$CUSTOM_ENV_VARS|$env_var" fi env_count=$((env_count + 1)) echo -e "✅ Added: $env_var" echo "" else echo -e "${RED}❌ Invalid format. Use: VARIABLE_NAME=value${NC}" echo "" fi done if [[ $env_count -gt 0 ]]; then complete_success "Added $env_count custom environment variables" else complete_info "No custom environment variables added" fi } # Show deployment configuration summary show_deployment_configuration_summary() { echo "" echo -e "${CYAN}📋 Deployment Configuration Summary${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Read hosts for display local hosts=() if [[ -f /tmp/thrillwiki-deploy-hosts.$$ ]]; then while IFS= read -r host; do hosts+=("$host") done < /tmp/thrillwiki-deploy-hosts.$$ fi echo -e "${BOLD}Deployment Targets:${NC}" echo "• Hosts: ${#hosts[@]} (${hosts[*]})" echo "• SSH User: ${REMOTE_USER}" echo "• SSH Port: ${REMOTE_PORT}" if [[ -n "${SSH_KEY:-}" ]]; then echo "• SSH Key: ${SSH_KEY}" fi echo "" echo -e "${BOLD}Environment Configuration:${NC}" echo "• Preset: ${DEPLOYMENT_PRESET} - $(get_deployment_preset_description "$DEPLOYMENT_PRESET")" echo "• Pull Interval: ${CUSTOM_PULL_INTERVAL}s" echo "• Health Check: ${HEALTH_CHECK_INTERVAL}s" echo "• Debug Mode: ${DEPLOYMENT_DEBUG_MODE}" echo "• Django Debug: ${DJANGO_DEBUG}" echo "• Auto Migration: ${AUTO_MIGRATE}" echo "• Auto Dependencies: ${AUTO_UPDATE_DEPENDENCIES}" echo "• Log Level: ${DEPLOYMENT_LOG_LEVEL}" echo "" echo -e "${BOLD}Security Settings:${NC}" echo "• SSL Required: ${SSL_REQUIRED}" echo "• CORS Allowed: ${CORS_ALLOWED}" echo "• Allowed Hosts: ${ALLOWED_HOSTS}" echo "" echo -e "${BOLD}Repository Configuration:${NC}" echo "• Repository: ${GITHUB_REPO_URL:-Not configured}" echo "• Branch: ${GITHUB_REPO_BRANCH:-Not configured}" echo "• GitHub Auth: ${SKIP_GITHUB_SETUP:-configured}" echo "" if [[ -n "${CUSTOM_ENV_VARS:-}" ]]; then echo -e "${BOLD}Custom Environment Variables:${NC}" echo "$CUSTOM_ENV_VARS" | tr '|' '\n' | sed 's/^/• /' echo "" fi } # [AWS-SECRET-REMOVED]==================================== # DEPLOYMENT ORCHESTRATION # [AWS-SECRET-REMOVED]==================================== # Legacy deployment preset application (for backward compatibility) apply_deployment_preset() { local preset="${DEPLOYMENT_PRESET:-auto}" if [[ "$preset" == "auto" ]]; then # Auto-detect based on environment complete_info "Auto-detecting deployment preset" echo "" echo "🎯 Deployment Preset Selection" echo "Choose the deployment configuration that best fits your use case:" echo "" # Use cross-shell compatible preset listing local preset_list preset_list=$(get_available_presets) local i=1 for preset_name in $preset_list; do local description description=$(get_deployment_preset_description "$preset_name") echo "$i. $preset_name - $description" i=$((i + 1)) done echo "" local preset_count=4 # We have 4 presets read -r -p "Select preset (1-$preset_count, default: 1): " preset_choice preset_choice="${preset_choice:-1}" case "$preset_choice" in 1) preset="dev" ;; 2) preset="prod" ;; 3) preset="demo" ;; 4) preset="testing" ;; *) complete_warning "Invalid preset choice, using development preset" preset="dev" ;; esac fi complete_info "Applying $preset deployment preset" # Validate preset exists local preset_list preset_list=$(get_available_presets) local preset_valid=false for valid_preset in $preset_list; do if [ "$preset" = "$valid_preset" ]; then preset_valid=true break fi done if [ "$preset_valid" = "false" ]; then complete_warning "Unknown preset: $preset, using development defaults" preset="dev" fi # Apply preset configuration using cross-shell compatible function local pull_interval pull_interval=$(get_preset_config "$preset" "PULL_INTERVAL") if [ -n "$pull_interval" ]; then complete_debug "Applying config: PULL_INTERVAL=$pull_interval" fi # Override with custom pull interval if provided if [[ -n "${PULL_INTERVAL:-}" ]]; then complete_info "Using custom pull interval: ${PULL_INTERVAL}s" export CUSTOM_PULL_INTERVAL="$PULL_INTERVAL" fi export APPLIED_PRESET="$preset" complete_success "Deployment preset '$preset' applied" } # Deploy to single host deploy_to_host() { local host="$1" local log_suffix="$2" complete_progress "Deploying to $host" # Build deployment command local deploy_cmd="$REMOTE_DEPLOY_SCRIPT" # Add common options if [[ -n "${REMOTE_USER:-}" ]]; then deploy_cmd+=" --user '$REMOTE_USER'" fi if [[ -n "${REMOTE_PORT:-}" ]]; then deploy_cmd+=" --port '$REMOTE_PORT'" fi if [[ -n "${SSH_KEY:-}" ]]; then deploy_cmd+=" --key '$SSH_KEY'" fi if [[ -n "${GITHUB_TOKEN:-}" ]]; then deploy_cmd+=" --github-token '$GITHUB_TOKEN'" fi if [[ -n "${GITHUB_REPO_URL:-}" ]]; then deploy_cmd+=" --repo-url '$GITHUB_REPO_URL'" fi if [[ "${SKIP_GITHUB_SETUP:-false}" == "true" ]]; then deploy_cmd+=" --skip-github" fi if [[ "${SKIP_REPO_CONFIG:-false}" == "true" ]]; then deploy_cmd+=" --skip-repo" fi if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_cmd+=" --dry-run" fi if [[ "${FORCE_DEPLOY:-false}" == "true" ]]; then deploy_cmd+=" --force" fi if [[ "${DEPLOY_DEBUG:-false}" == "true" ]]; then deploy_cmd+=" --debug" fi deploy_cmd+=" '$host'" complete_debug "Deployment command: $deploy_cmd" # Execute deployment local deploy_log="$PROJECT_DIR/logs/deploy-$host$log_suffix.log" mkdir -p "$(dirname "$deploy_log")" if [[ "${PARALLEL_DEPLOYMENT:-false}" == "true" ]]; then # Parallel execution ( complete_info "Starting parallel deployment to $host" if eval "$deploy_cmd" 2>&1 | tee "$deploy_log"; then echo "SUCCESS:$host" >> /tmp/thrillwiki-deploy-results.$$ complete_success "Deployment to $host completed successfully" # Step 4B: Start ThrillWiki development server after successful deployment complete_progress "Step 4B: Starting ThrillWiki development server on $host" if setup_development_server "$host" "${DEPLOYMENT_PRESET:-dev}"; then complete_success "Development server setup completed on $host" else complete_warning "Development server setup had issues on $host" fi # Step 5A: Service Configuration and Startup complete_progress "Step 5A: Configuring deployment services on $host" if configure_deployment_services "$host" "${DEPLOYMENT_PRESET:-dev}" "${GITHUB_TOKEN:-}"; then complete_success "Service configuration completed on $host" else complete_warning "Service configuration had issues on $host" fi else echo "FAILED:$host" >> /tmp/thrillwiki-deploy-results.$$ complete_error "Deployment to $host failed" fi ) & # Store background process PID echo $! >> /tmp/thrillwiki-deploy-pids.$$ else # Sequential execution if eval "$deploy_cmd" 2>&1 | tee "$deploy_log"; then complete_success "Deployment to $host completed successfully" # Step 4B: Start ThrillWiki development server after successful deployment complete_progress "Step 4B: Starting ThrillWiki development server on $host" if setup_development_server "$host" "${DEPLOYMENT_PRESET:-dev}"; then complete_success "Development server setup completed on $host" else complete_warning "Development server setup had issues on $host" fi # Step 5A: Service Configuration and Startup complete_progress "Step 5A: Configuring deployment services on $host" if configure_deployment_services "$host" "${DEPLOYMENT_PRESET:-dev}" "${GITHUB_TOKEN:-}"; then complete_success "Service configuration completed on $host" else complete_warning "Service configuration had issues on $host" fi return 0 else complete_error "Deployment to $host failed" return 1 fi fi } # Deploy to all hosts deploy_to_all_hosts() { local hosts=() # Read hosts from temp file while IFS= read -r host; do hosts+=("$host") done < /tmp/thrillwiki-deploy-hosts.$$ complete_progress "Deploying to ${#hosts[@]} host(s)" # Initialize parallel deployment tracking if [[ "${PARALLEL_DEPLOYMENT:-false}" == "true" ]]; then rm -f /tmp/thrillwiki-deploy-results.$$ /tmp/thrillwiki-deploy-pids.$$ complete_info "Starting parallel deployment to ${#hosts[@]} hosts" fi local timestamp="-$(date +%Y%m%d-%H%M%S)" local deployment_failures=0 # Deploy to each host for host in "${hosts[@]}"; do if ! deploy_to_host "$host" "$timestamp"; then ((deployment_failures++)) if [[ "${PARALLEL_DEPLOYMENT:-false}" != "true" ]]; then complete_warning "Deployment to $host failed, continuing with remaining hosts" fi fi done # Wait for parallel deployments to complete if [[ "${PARALLEL_DEPLOYMENT:-false}" == "true" ]]; then complete_info "Waiting for parallel deployments to complete..." # Wait for all background processes if [[ -f /tmp/thrillwiki-deploy-pids.$$ ]]; then while IFS= read -r pid; do wait "$pid" 2>/dev/null || true done < /tmp/thrillwiki-deploy-pids.$$ fi # Check results if [[ -f /tmp/thrillwiki-deploy-results.$$ ]]; then local successful_hosts=() local failed_hosts=() while IFS=: read -r status host; do if [[ "$status" == "SUCCESS" ]]; then successful_hosts+=("$host") else failed_hosts+=("$host") ((deployment_failures++)) fi done < /tmp/thrillwiki-deploy-results.$$ # Report parallel deployment results complete_info "Parallel deployment results:" complete_success "✓ Successful: ${#successful_hosts[@]} hosts" if [[ ${#failed_hosts[@]} -gt 0 ]]; then complete_error "✗ Failed: ${#failed_hosts[@]} hosts (${failed_hosts[*]})" fi fi # Cleanup rm -f /tmp/thrillwiki-deploy-results.$$ /tmp/thrillwiki-deploy-pids.$$ fi # Report final deployment status local successful_hosts=$((${#hosts[@]} - deployment_failures)) if [[ $deployment_failures -eq 0 ]]; then complete_success "All deployments completed successfully" return 0 elif [[ $successful_hosts -gt 0 ]]; then complete_warning "Partial deployment success: $successful_hosts/${#hosts[@]} hosts" return 1 else complete_error "All deployments failed" return 5 fi } # [AWS-SECRET-REMOVED]==================================== # POST-DEPLOYMENT VALIDATION # [AWS-SECRET-REMOVED]==================================== validate_deployments() { if [[ "${DRY_RUN:-false}" == "true" ]]; then complete_success "Dry run validation completed" return 0 fi complete_progress "Validating deployments" local hosts=() while IFS= read -r host; do hosts+=("$host") done < /tmp/thrillwiki-deploy-hosts.$$ local validation_failures=0 for host in "${hosts[@]}"; do complete_info "Validating deployment on $host" # Test SSH connection local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" -o ConnectTimeout=10 -p ${REMOTE_PORT} ${REMOTE_USER}@$host" # Check if automation service is running if eval "$ssh_cmd 'systemctl is-active thrillwiki-automation'" >/dev/null 2>&1; then complete_success "✓ $host: Automation service is running" else complete_warning "⚠ $host: Automation service is not running" ((validation_failures++)) fi # Check if GitHub authentication is configured if [[ "${SKIP_GITHUB_SETUP:-false}" != "true" ]]; then if eval "$ssh_cmd 'test -f /home/${REMOTE_USER}/thrillwiki/.github-pat'" >/dev/null 2>&1; then complete_success "✓ $host: GitHub authentication configured" else complete_warning "⚠ $host: GitHub authentication not configured" fi fi # Check logs for recent activity if eval "$ssh_cmd 'test -f /home/${REMOTE_USER}/thrillwiki/logs/bulletproof-automation.log'" >/dev/null 2>&1; then complete_success "✓ $host: Automation logs present" else complete_warning "⚠ $host: Automation logs not found" fi done if [[ $validation_failures -eq 0 ]]; then complete_success "All deployments validated successfully" return 0 else complete_warning "Deployment validation completed with $validation_failures issues" return 1 fi } # [AWS-SECRET-REMOVED]==================================== # STATUS REPORTING # [AWS-SECRET-REMOVED]==================================== show_deployment_summary() { local hosts=() while IFS= read -r host; do hosts+=("$host") done < /tmp/thrillwiki-deploy-hosts.$$ echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo -e "${BOLD}${GREEN}🎯 ThrillWiki Complete Deployment Summary${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" if [[ "${DRY_RUN:-false}" == "true" ]]; then echo -e "${CYAN}🔍 DRY RUN COMPLETED${NC}" echo "" echo "The following would be deployed:" for host in "${hosts[@]}"; do echo "• $host - Complete automation system with GitHub auth and pull scheduling" done echo "" echo "To execute the actual deployment, run without --dry-run" return 0 fi echo "📊 Deployment Configuration:" echo "• Hosts: ${#hosts[@]} (${hosts[*]})" echo "• Preset: ${APPLIED_PRESET:-auto}" echo "• Pull Interval: ${CUSTOM_PULL_INTERVAL:-300}s (5 minutes)" echo "• GitHub Auth: ${SKIP_GITHUB_SETUP:-configured}" echo "• Parallel: ${PARALLEL_DEPLOYMENT:-false}" echo "" echo "🚀 Deployed Components:" echo "• ✅ Complete ThrillWiki automation system" echo "• ✅ GitHub authentication and repository access" echo "• ✅ Automatic pull scheduling (every 5 minutes)" echo "• ✅ Systemd service for auto-start and reliability" echo "• ✅ Health monitoring and comprehensive logging" echo "• ✅ Django server automation with UV package management" echo "" echo "🔧 Management Commands:" echo "" echo "Monitor automation on any host:" for host in "${hosts[@]}"; do echo " ssh ${REMOTE_USER}@$host 'sudo journalctl -u thrillwiki-automation -f'" done echo "" echo "Check service status:" for host in "${hosts[@]}"; do echo " ssh ${REMOTE_USER}@$host 'sudo systemctl status thrillwiki-automation'" done echo "" echo "View automation logs:" for host in "${hosts[@]}"; do echo " ssh ${REMOTE_USER}@$host 'tail -f /home/${REMOTE_USER}/thrillwiki/logs/bulletproof-automation.log'" done echo "" echo "🔄 Automation Features:" echo "• Automatic repository pulls every ${CUSTOM_PULL_INTERVAL:-300} seconds" echo "• Automatic Django migrations on code changes" echo "• Dependency updates with UV package manager" echo "• Server health monitoring and auto-recovery" echo "• Comprehensive error handling and logging" echo "• GitHub authentication for private repositories" echo "" echo "📚 Next Steps:" echo "1. Monitor the automation logs to ensure proper operation" echo "2. Test the deployment by making a change to your repository" echo "3. Verify automatic pulls and server restarts are working" echo "4. Configure any additional settings as needed" echo "" complete_success "Complete deployment finished successfully!" # Cleanup temp files rm -f /tmp/thrillwiki-deploy-hosts.$$ } # [AWS-SECRET-REMOVED]==================================== # MAIN ORCHESTRATION # [AWS-SECRET-REMOVED]==================================== # Interactive host collection collect_deployment_hosts() { echo "" echo -e "${CYAN}🖥️ Remote Host Configuration${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Please specify the remote server(s) where ThrillWiki will be deployed." echo "" local hosts=() local host_input="" while true; do if [[ ${#hosts[@]} -eq 0 ]]; then echo "Enter the hostname or IP address of your remote server:" else echo "" echo "Current hosts: ${hosts[*]}" echo "" echo "Enter additional hostname/IP (or press Enter to continue):" fi echo "Examples: 192.168.1.100, myserver.com, dev-server" echo "" read -r -p "Host: " host_input # If empty and we have at least one host, continue if [[ -z "$host_input" ]]; then if [[ ${#hosts[@]} -gt 0 ]]; then break else echo -e "${YELLOW}⚠️ At least one host is required.${NC}" echo "" continue fi fi # Validate host format (basic check) if [[ "$host_input" =~ ^[a-zA-Z0-9._-]+$ ]]; then hosts+=("$host_input") echo -e "✅ Added: $host_input" else echo -e "${RED}❌ Invalid hostname format. Please use alphanumeric characters, dots, dashes, and underscores only.${NC}" continue fi # Ask if they want to add more hosts if [[ ${#hosts[@]} -gt 0 ]]; then echo "" read -r -p "Add another host? (y/N): " add_more if [[ ! "$add_more" =~ ^[Yy] ]]; then break fi fi done # Store hosts in temp file printf '%s\n' "${hosts[@]}" > /tmp/thrillwiki-deploy-hosts.$$ echo "" echo -e "${GREEN}✅ Configured ${#hosts[@]} deployment target(s):${NC}" for host in "${hosts[@]}"; do echo " • $host" done export REMOTE_HOSTS=("${hosts[@]}") return 0 } # Enhanced interactive SSH connection setup with auto-detection and validation interactive_connection_setup() { echo "" echo -e "${CYAN}🔑 SSH Connection Setup${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Username configuration echo "Remote server connection details:" echo "" echo "Current username: ${REMOTE_USER}" echo "" read -r -p "SSH username (press Enter to keep '${REMOTE_USER}'): " input_user # Trim whitespace and validate username if [ -n "$input_user" ]; then input_user=$(echo "$input_user" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if echo "$input_user" | grep -E '^[a-z_][a-z0-9_-]*$' >/dev/null; then REMOTE_USER="$input_user" export REMOTE_USER echo -e "✅ Updated username: ${GREEN}$REMOTE_USER${NC}" else echo -e "${YELLOW}⚠️ Invalid username format, keeping: $REMOTE_USER${NC}" fi else echo -e "✅ Using username: ${GREEN}$REMOTE_USER${NC}" fi echo "" # SSH port configuration echo "Current SSH port: ${REMOTE_PORT}" echo "" read -r -p "SSH port (press Enter to keep '${REMOTE_PORT}'): " input_port if [ -n "$input_port" ]; then if validate_port "$input_port"; then REMOTE_PORT="$input_port" export REMOTE_PORT echo -e "✅ Updated port: ${GREEN}$REMOTE_PORT${NC}" else echo -e "${YELLOW}⚠️ Invalid port '$input_port', keeping: $REMOTE_PORT${NC}" fi else echo -e "✅ Using port: ${GREEN}$REMOTE_PORT${NC}" fi echo "" # SSH key configuration if [ -z "${SSH_KEY:-}" ]; then echo -e "${CYAN}🔐 SSH Key Authentication${NC}" echo "SSH key authentication is more secure and convenient than passwords." echo "" # Auto-detect SSH keys echo "Scanning for SSH keys..." local found_keys_string="" if found_keys_string=$(detect_ssh_keys); then echo "" echo "Found SSH keys:" local key_index=1 for key in $found_keys_string; do local key_type="" if echo "$key" | grep -q "***REMOVED***"; then key_type=" (Ed25519 - recommended)" elif echo "$key" | grep -q "***REMOVED***"; then key_type=" (RSA)" elif echo "$key" | grep -q "***REMOVED***"; then key_type=" (ECDSA)" fi echo "$key_index. $key$key_type" key_index=$((key_index + 1)) done echo "$key_index. Use custom path" echo "$((key_index + 1)). Generate new SSH key" echo "$((key_index + 2)). Skip (use password authentication)" echo "" read -r -p "Select option (1-$((key_index + 2)), default: 1): " key_choice key_choice="${key_choice:-1}" # Convert string to indexed access (cross-shell compatible) local selected_key="" local current_index=1 for key in $found_keys_string; do if [ "$current_index" -eq "$key_choice" ]; then selected_key="$key" break fi current_index=$((current_index + 1)) done if [ -n "$selected_key" ]; then SSH_KEY="$selected_key" export SSH_KEY echo -e "✅ Using SSH key: ${GREEN}$SSH_KEY${NC}" # Check key permissions local perms perms=$(stat -c "%a" "$SSH_KEY" 2>/dev/null || stat -f "%A" "$SSH_KEY" 2>/dev/null) if [ "$perms" != "600" ] && [ "$perms" != "400" ]; then echo -e "${YELLOW}⚠️ Fixing SSH key permissions...${NC}" chmod 600 "$SSH_KEY" echo -e "✅ SSH key permissions updated to 600" fi elif [ "$key_choice" -eq "$key_index" ]; then # Custom path read -r -p "Enter SSH key path: " custom_key if [ -n "$custom_key" ] && [ -f "$custom_key" ]; then SSH_KEY="$custom_key" export SSH_KEY echo -e "✅ Using SSH key: ${GREEN}$SSH_KEY${NC}" # Fix permissions if needed chmod 600 "$SSH_KEY" 2>/dev/null || true else echo -e "${YELLOW}⚠️ SSH key not found: $custom_key${NC}" echo -e "ℹ️ Will use password authentication" fi elif [ "$key_choice" -eq "$((key_index + 1))" ]; then # Generate new key echo "" echo "Generating new SSH key..." local key_email="" read -r -p "Enter email for SSH key (optional): " key_email local ssh_keygen_cmd="ssh-keygen -t ed25519 -f $HOME/.ssh/***REMOVED***" if [ -n "$key_email" ]; then ssh_keygen_cmd="$ssh_keygen_cmd -C '$key_email'" fi if eval "$ssh_keygen_cmd"; then SSH_KEY="$HOME/.ssh/***REMOVED***" export SSH_KEY echo -e "✅ Generated and using SSH key: ${GREEN}$SSH_KEY${NC}" echo "" echo "📋 Your public key (copy this to remote servers):" echo "" cat "$HOME/.ssh/***REMOVED***.pub" echo "" else echo -e "${YELLOW}⚠️ Failed to generate SSH key, will use password authentication${NC}" fi else echo -e "ℹ️ Using password authentication" fi else echo "No SSH keys found in standard locations." echo "" echo "Options:" echo "1. Generate new SSH key (recommended)" echo "2. Use custom SSH key path" echo "3. Use password authentication" echo "" read -r -p "Select option (1-3, default: 1): " key_choice key_choice="${key_choice:-1}" case "$key_choice" in 1) # Generate new key echo "" echo "Generating new SSH key..." local key_email="" read -r -p "Enter email for SSH key (optional): " key_email local ssh_keygen_cmd="ssh-keygen -t ed25519 -f $HOME/.ssh/***REMOVED***" if [ -n "$key_email" ]; then ssh_keygen_cmd="$ssh_keygen_cmd -C '$key_email'" fi if eval "$ssh_keygen_cmd"; then SSH_KEY="$HOME/.ssh/***REMOVED***" export SSH_KEY echo -e "✅ Generated and using SSH key: ${GREEN}$SSH_KEY${NC}" else echo -e "${YELLOW}⚠️ Failed to generate SSH key${NC}" fi ;; 2) read -r -p "Enter SSH key path: " custom_key if [ -n "$custom_key" ] && [ -f "$custom_key" ]; then SSH_KEY="$custom_key" export SSH_KEY echo -e "✅ Using SSH key: ${GREEN}$SSH_KEY${NC}" else echo -e "${YELLOW}⚠️ SSH key not found: $custom_key${NC}" fi ;; *) echo -e "ℹ️ Using password authentication" ;; esac fi else echo -e "✅ SSH key already configured: ${GREEN}$SSH_KEY${NC}" # Verify the key still exists and has correct permissions if [ -f "$SSH_KEY" ]; then local perms perms=$(stat -c "%a" "$SSH_KEY" 2>/dev/null || stat -f "%A" "$SSH_KEY" 2>/dev/null) if [ "$perms" != "600" ] && [ "$perms" != "400" ]; then echo -e "${YELLOW}⚠️ Fixing SSH key permissions...${NC}" chmod 600 "$SSH_KEY" echo -e "✅ SSH key permissions updated" fi else echo -e "${RED}❌ SSH key file not found: $SSH_KEY${NC}" unset SSH_KEY echo -e "ℹ️ Will use password authentication" fi fi echo "" echo -e "${GREEN}✅ SSH connection configuration complete${NC}" echo "" echo "Summary:" echo "• Username: $REMOTE_USER" echo "• Port: $REMOTE_PORT" if [ -n "${SSH_KEY:-}" ]; then echo "• Authentication: SSH key ($SSH_KEY)" else echo "• Authentication: Password (you'll be prompted during connection)" fi } # Interactive setup for missing critical information interactive_setup() { # Only run interactive setup if we have missing information and not in automated mode if [[ "${DRY_RUN:-false}" == "true" ]] || [[ -n "${GITHUB_TOKEN:-}" && -n "${SSH_KEY:-}" ]]; then return 0 fi echo "" echo "🔧 Interactive Setup" echo "===================" echo "" # Ask for username if using default if [[ "${REMOTE_USER}" == "ubuntu" ]]; then echo "🔑 Remote Connection Setup" echo "Please provide the connection details for your remote server(s):" echo "" read -r -p "Remote username (default: ubuntu): " input_user if [[ -n "$input_user" ]]; then REMOTE_USER="$input_user" export REMOTE_USER complete_info "Using remote username: $REMOTE_USER" fi echo "" fi # Ask for SSH key if not provided if [[ -z "${SSH_KEY:-}" ]]; then echo "🔐 SSH Key Authentication (recommended)" echo "Using SSH keys is more secure than password authentication." echo "" # Check for common SSH key locations local common_keys=( "$HOME/.ssh/***REMOVED***" "$HOME/.ssh/***REMOVED***" "$HOME/.ssh/***REMOVED***" ) local found_keys=() for key in "${common_keys[@]}"; do if [[ -f "$key" ]]; then found_keys+=("$key") fi done if [[ ${#found_keys[@]} -gt 0 ]]; then echo "Found SSH keys:" for i in "${!found_keys[@]}"; do echo "$((i+1)). ${found_keys[i]}" done echo "$((${#found_keys[@]}+1)). Use custom path" echo "$((${#found_keys[@]}+2)). Skip (use password authentication)" echo "" read -r -p "Select SSH key (1-$((${#found_keys[@]}+2)), default: 1): " key_choice key_choice="${key_choice:-1}" if [[ "$key_choice" -le "${#found_keys[@]}" ]] && [[ "$key_choice" -gt 0 ]]; then SSH_KEY="${found_keys[$((key_choice-1))]}" export SSH_KEY complete_info "Using SSH key: $SSH_KEY" elif [[ "$key_choice" -eq $((${#found_keys[@]}+1)) ]]; then read -r -p "Enter SSH key path: " custom_key if [[ -f "$custom_key" ]]; then SSH_KEY="$custom_key" export SSH_KEY complete_info "Using SSH key: $SSH_KEY" else complete_warning "SSH key not found: $custom_key" complete_info "Continuing without SSH key" fi else complete_info "Skipping SSH key authentication" fi else read -r -p "Enter SSH key path (or press Enter to skip): " custom_key if [[ -n "$custom_key" ]] && [[ -f "$custom_key" ]]; then SSH_KEY="$custom_key" export SSH_KEY complete_info "Using SSH key: $SSH_KEY" else complete_info "No SSH key specified, will use password authentication" fi fi echo "" fi # Ask for custom SSH port if needed if [[ "${REMOTE_PORT}" == "22" ]]; then read -r -p "SSH port (default: 22): " input_port if [[ -n "$input_port" ]] && [[ "$input_port" =~ ^[0-9]+$ ]]; then REMOTE_PORT="$input_port" export REMOTE_PORT complete_info "Using SSH port: $REMOTE_PORT" fi echo "" fi } # [AWS-SECRET-REMOVED]==================================== # STEP 3B: DEPENDENCY INSTALLATION AND ENVIRONMENT SETUP # [AWS-SECRET-REMOVED]==================================== # Cross-shell compatible system dependency validation validate_system_dependencies() { local host_context="${1:-local}" # local or remote local execution_prefix="" if [[ "$host_context" == "remote" ]]; then execution_prefix="remote_exec" fi complete_progress "Validating system dependencies ($host_context)" local validation_failed=false local missing_deps=() local system_info="" # Required system packages local required_packages=( "python3:Python 3.11+" "git:Git version control" "curl:HTTP client for downloads" "build-essential:Build tools (apt)" "gcc:Compiler" "pkg-config:Package configuration" "libpq-dev:PostgreSQL development headers" "python3-dev:Python development headers" ) if [[ "$host_context" == "local" ]]; then echo "" echo -e "${CYAN}🔧 System Dependencies${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Checking system prerequisites:" fi # Detect OS and package manager local os_type="" local pkg_manager="" if [[ "$host_context" == "local" ]]; then if command_exists apt-get; then os_type="debian" pkg_manager="apt-get" elif command_exists yum; then os_type="rhel" pkg_manager="yum" elif command_exists dnf; then os_type="fedora" pkg_manager="dnf" elif command_exists brew; then os_type="macos" pkg_manager="brew" elif command_exists pacman; then os_type="arch" pkg_manager="pacman" else os_type="unknown" fi system_info="OS: $os_type, Package Manager: $pkg_manager" complete_debug "$system_info" else # Remote system detection if $execution_prefix "command -v apt-get" true true; then os_type="debian" pkg_manager="apt-get" elif $execution_prefix "command -v yum" true true; then os_type="rhel" pkg_manager="yum" elif $execution_prefix "command -v dnf" true true; then os_type="fedora" pkg_manager="dnf" else os_type="unknown" pkg_manager="unknown" fi fi # Check core dependencies local core_deps=("python3" "git" "curl") for dep in "${core_deps[@]}"; do local check_cmd="command -v $dep" local available=false if [[ "$host_context" == "local" ]]; then if command_exists "$dep"; then available=true if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ $dep - ${GREEN}Available${NC}" fi fi else if $execution_prefix "$check_cmd" true true; then available=true fi fi if [[ "$available" == "false" ]]; then missing_deps+=("$dep") validation_failed=true if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "❌ $dep - ${RED}Missing${NC}" fi fi done # Check Python version local python_version="" if [[ "$host_context" == "local" ]]; then if command_exists python3; then python_version=$(python3 --version 2>&1 | grep -o '[0-9]\+\.[0-9]\+' | head -1) if [[ -n "$python_version" ]]; then local major=$(echo "$python_version" | cut -d'.' -f1) local minor=$(echo "$python_version" | cut -d'.' -f2) if [[ "$major" -ge 3 && "$minor" -ge 11 ]]; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Python $python_version - ${GREEN}Compatible${NC}" fi else validation_failed=true if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "❌ Python $python_version - ${RED}Too old (need 3.11+)${NC}" fi fi fi fi else if $execution_prefix "python3 --version" true true; then python_version=$($execution_prefix "python3 --version 2>&1 | grep -o '[0-9]\+\.[0-9]\+' | head -1" true true) fi fi # Auto-install missing dependencies if possible if [[ "$validation_failed" == "true" && "${#missing_deps[@]}" -gt 0 ]]; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${YELLOW}⚠️ Missing dependencies detected: ${missing_deps[*]}${NC}" echo "" read -r -p "Attempt to install missing dependencies automatically? (Y/n): " auto_install if [[ ! "$auto_install" =~ ^[Nn] ]]; then if install_system_dependencies "$host_context" "$os_type" "$pkg_manager" "${missing_deps[@]}"; then complete_success "System dependencies installed successfully" validation_failed=false else complete_warning "Some dependencies could not be installed automatically" fi fi fi fi if [[ "$validation_failed" == "true" ]]; then complete_error "System dependency validation failed" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo "📦 Manual installation commands:" show_dependency_install_instructions "$os_type" "$pkg_manager" "${missing_deps[@]}" fi return 1 else complete_success "System dependencies validated" return 0 fi } # Cross-shell compatible dependency installation install_system_dependencies() { local host_context="$1" local os_type="$2" local pkg_manager="$3" shift 3 local deps=("$@") if [[ "${#deps[@]}" -eq 0 ]]; then return 0 fi complete_info "Installing system dependencies: ${deps[*]}" local install_cmd="" local update_cmd="" case "$os_type" in "debian") update_cmd="apt-get update" install_cmd="apt-get install -y" # Map common dependencies to Debian package names local debian_deps=() for dep in "${deps[@]}"; do case "$dep" in "python3") debian_deps+=("python3" "python3-pip" "python3-venv" "python3-dev") ;; "git") debian_deps+=("git") ;; "curl") debian_deps+=("curl") ;; "build-essential") debian_deps+=("build-essential") ;; "gcc") debian_deps+=("gcc") ;; "pkg-config") debian_deps+=("pkg-config") ;; "libpq-dev") debian_deps+=("libpq-dev") ;; *) debian_deps+=("$dep") ;; esac done deps=("${debian_deps[@]}") ;; "rhel"|"fedora") if [[ "$pkg_manager" == "dnf" ]]; then update_cmd="dnf check-update || true" install_cmd="dnf install -y" else update_cmd="yum check-update || true" install_cmd="yum install -y" fi # Map to RHEL/Fedora package names local rhel_deps=() for dep in "${deps[@]}"; do case "$dep" in "python3") rhel_deps+=("python3" "python3-pip" "python3-devel") ;; "git") rhel_deps+=("git") ;; "curl") rhel_deps+=("curl") ;; "build-essential") rhel_deps+=("gcc" "gcc-c++" "make") ;; "gcc") rhel_deps+=("gcc") ;; "pkg-config") rhel_deps+=("pkgconfig") ;; "libpq-dev") rhel_deps+=("postgresql-devel") ;; *) rhel_deps+=("$dep") ;; esac done deps=("${rhel_deps[@]}") ;; "macos") install_cmd="brew install" # Map to macOS package names local macos_deps=() for dep in "${deps[@]}"; do case "$dep" in "python3") macos_deps+=("python@3.11") ;; "git") macos_deps+=("git") ;; "curl") macos_deps+=("curl") ;; "libpq-dev") macos_deps+=("postgresql") ;; *) macos_deps+=("$dep") ;; esac done deps=("${macos_deps[@]}") ;; "arch") update_cmd="pacman -Sy" install_cmd="pacman -S --noconfirm" ;; *) complete_warning "Unknown package manager, cannot auto-install dependencies" return 1 ;; esac # Execute installation commands local success=true if [[ "$host_context" == "local" ]]; then # Update package lists first if [[ -n "$update_cmd" ]]; then complete_info "Updating package lists..." if ! sudo $update_cmd; then complete_warning "Failed to update package lists" fi fi # Install packages complete_info "Installing packages: ${deps[*]}" if ! sudo $install_cmd "${deps[@]}"; then success=false fi else # Remote installation if [[ -n "$update_cmd" ]]; then complete_info "Updating remote package lists..." if ! remote_exec "sudo $update_cmd" false true; then complete_warning "Failed to update remote package lists" fi fi complete_info "Installing remote packages: ${deps[*]}" if ! remote_exec "sudo $install_cmd ${deps[*]}" false true; then success=false fi fi if [[ "$success" == "true" ]]; then complete_success "Dependencies installed successfully" return 0 else complete_error "Failed to install some dependencies" return 1 fi } # Show manual installation instructions show_dependency_install_instructions() { local os_type="$1" local pkg_manager="$2" shift 2 local deps=("$@") echo "" case "$os_type" in "debian") echo "Ubuntu/Debian:" echo " sudo apt-get update" echo " sudo apt-get install -y python3 python3-pip python3-venv python3-dev git curl build-essential libpq-dev" ;; "rhel"|"fedora") if [[ "$pkg_manager" == "dnf" ]]; then echo "Fedora:" echo " sudo dnf install -y python3 python3-pip python3-devel git curl gcc gcc-c++ make postgresql-devel" else echo "RHEL/CentOS:" echo " sudo yum install -y python3 python3-pip python3-devel git curl gcc gcc-c++ make postgresql-devel" fi ;; "macos") echo "macOS:" echo " brew install python@3.11 git curl postgresql" ;; "arch") echo "Arch Linux:" echo " sudo pacman -S python git curl base-devel postgresql-libs" ;; *) echo "Please install manually: ${deps[*]}" ;; esac echo "" } # UV package manager setup and configuration setup_uv_package_manager() { local host_context="${1:-local}" # local or remote complete_progress "Setting up UV package manager ($host_context)" if [[ "$host_context" == "local" ]]; then # Local UV setup if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${CYAN}📦 UV Package Manager Setup${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi # Check if UV is already installed if command_exists uv; then local uv_version uv_version=$(uv --version 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1) if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ UV already installed - ${GREEN}v$uv_version${NC}" fi complete_success "UV package manager already available (v$uv_version)" else complete_info "Installing UV package manager..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ UV package manager... installing" fi # Install UV using the official installer if curl -LsSf https://astral.sh/uv/install.sh | sh; then # Add UV to current PATH export PATH="$HOME/.local/bin:$PATH" if command_exists uv; then local uv_version uv_version=$(uv --version 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1) if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ UV installed successfully - ${GREEN}v$uv_version${NC}" fi complete_success "UV package manager installed (v$uv_version)" else complete_error "UV installation succeeded but UV command not found" return 1 fi else complete_error "Failed to install UV package manager" return 1 fi fi # Configure UV for optimal performance complete_debug "Configuring UV settings" export UV_CACHE_DIR="${UV_CACHE_DIR:-$HOME/.cache/uv}" export UV_PYTHON_PREFERENCE="${UV_PYTHON_PREFERENCE:-managed}" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "✅ UV configuration optimized" fi else # Remote UV setup complete_info "Setting up UV package manager on remote host" # Check if UV exists on remote if remote_exec "command -v uv || test -x ~/.local/bin/uv" true true; then local uv_version uv_version=$(remote_exec "uv --version 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1" true true || echo "unknown") complete_success "UV already available on remote host (v$uv_version)" else complete_info "Installing UV on remote host..." if remote_exec "curl -LsSf https://astral.sh/uv/install.sh | sh"; then # Verify installation if remote_exec "command -v uv || test -x ~/.local/bin/uv" true true; then local uv_version uv_version=$(remote_exec "~/.local/bin/uv --version 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1" true true || echo "unknown") complete_success "UV installed successfully on remote host (v$uv_version)" # Add UV to PATH for remote sessions remote_exec "echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc" false true remote_exec "echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.zshrc" false true else complete_error "UV installation on remote host failed verification" return 1 fi else complete_error "Failed to install UV on remote host" return 1 fi fi # Configure UV on remote host remote_exec "export UV_CACHE_DIR=\"\$HOME/.cache/uv\"" false true remote_exec "export UV_PYTHON_PREFERENCE=\"managed\"" false true fi return 0 } # Python environment preparation using UV prepare_python_environment() { local host_context="${1:-local}" # local or remote local target_path="${2:-$PROJECT_DIR}" complete_progress "Preparing Python environment ($host_context)" if [[ "$host_context" == "local" ]]; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${CYAN}🐍 Python Environment Setup${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi # Change to project directory cd "$target_path" || { complete_error "Cannot change to project directory: $target_path" return 1 } # Remove corrupted virtual environment if present if [[ -d ".venv" ]]; then complete_info "Checking existing virtual environment..." if ! uv sync --quiet 2>/dev/null; then complete_warning "Existing virtual environment is corrupted, removing..." rm -rf .venv if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Removed corrupted virtual environment" fi else complete_info "Existing virtual environment is healthy" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Virtual environment - ${GREEN}Ready${NC}" fi return 0 fi fi # Create new virtual environment and install dependencies complete_info "Creating Python virtual environment with UV..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Creating Python virtual environment..." fi if uv sync; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Virtual environment - ${GREEN}Created${NC}" echo -e "✅ Dependencies - ${GREEN}Installed${NC}" fi complete_success "Python environment prepared successfully" else complete_error "Failed to create Python environment" return 1 fi else # Remote Python environment setup complete_info "Setting up Python environment on remote host" # Ensure we're in the remote project directory if ! remote_exec "cd '$target_path'"; then complete_error "Cannot access remote project directory: $target_path" return 1 fi # Remove corrupted virtual environment if present if remote_exec "test -d '$target_path/.venv'" true true; then complete_info "Checking remote virtual environment..." if ! remote_exec "cd '$target_path' && (export PATH=\"\$HOME/.local/bin:\$PATH\" && uv sync --quiet)" true true; then complete_warning "Remote virtual environment is corrupted, removing..." remote_exec "cd '$target_path' && rm -rf .venv" false true else complete_success "Remote virtual environment is healthy" return 0 fi fi # Create new virtual environment on remote complete_info "Creating Python virtual environment on remote host..." if remote_exec "cd '$target_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv sync"; then complete_success "Remote Python environment prepared successfully" else complete_error "Failed to create remote Python environment" return 1 fi fi return 0 } # Install ThrillWiki-specific dependencies and configuration install_thrillwiki_dependencies() { local host_context="${1:-local}" # local or remote local target_path="${2:-$PROJECT_DIR}" local preset="${3:-${DEPLOYMENT_PRESET:-dev}}" complete_progress "Installing ThrillWiki-specific dependencies ($host_context, preset: $preset)" if [[ "$host_context" == "local" ]]; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${CYAN}🎢 ThrillWiki Dependencies${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi cd "$target_path" || { complete_error "Cannot change to project directory: $target_path" return 1 } # Install preset-specific dependencies case "$preset" in "dev") complete_info "Installing development dependencies..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Development tools and debugging packages" fi # Development-specific packages are already in pyproject.toml # Just ensure they're installed via uv sync ;; "prod") complete_info "Installing production dependencies (optimized)..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Production-optimized packages only" fi # Production uses standard dependencies from pyproject.toml ;; "demo") complete_info "Installing demo environment dependencies..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Balanced dependency set for demonstrations" fi ;; "testing") complete_info "Installing testing dependencies..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Testing frameworks and debugging tools" fi ;; esac # Install Tailwind CSS dependencies complete_info "Setting up Tailwind CSS..." if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "○ Tailwind CSS setup and configuration" fi if uv run manage.py tailwind install --skip-checks; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Tailwind CSS - ${GREEN}Configured${NC}" fi complete_success "Tailwind CSS configured successfully" else complete_warning "Tailwind CSS setup had issues, continuing..." fi if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ ThrillWiki dependencies - ${GREEN}Ready${NC}" fi else # Remote ThrillWiki dependencies setup complete_info "Installing ThrillWiki dependencies on remote host" # Ensure all dependencies are installed using UV if remote_exec "cd '$target_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv sync"; then complete_success "ThrillWiki dependencies installed on remote host" else complete_warning "Some ThrillWiki dependencies may not have installed correctly" fi # Set up Tailwind CSS on remote complete_info "Setting up Tailwind CSS on remote host..." if remote_exec "cd '$target_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py tailwind install --skip-checks" false true; then complete_success "Tailwind CSS configured on remote host" else complete_warning "Tailwind CSS setup on remote host had issues" fi # Make scripts executable on remote remote_exec "chmod +x '$target_path/scripts/vm/'*.sh" false true remote_exec "chmod +x '$target_path/scripts/vm/'*.py" false true fi return 0 } # Configure environment variables for deployment presets configure_environment_variables() { local host_context="${1:-local}" # local or remote local target_path="${2:-$PROJECT_DIR}" local preset="${3:-${DEPLOYMENT_PRESET:-dev}}" complete_progress "Configuring environment variables ($host_context, preset: $preset)" # Generate ***REMOVED*** file based on preset local env_content="" env_content=$(cat << 'EOF' # ThrillWiki Environment Configuration # Generated by deployment script # Django Configuration DEBUG= ALLOWED_HOSTS= SECRET_KEY= DJANGO_SETTINGS_MODULE=thrillwiki.settings # Database Configuration DATABASE_URL=sqlite:///db.sqlite3 # Static and Media Files STATIC_URL=/static/ MEDIA_URL=/media/ STATICFILES_DIRS= # Security Settings SECURE_SSL_REDIRECT= SECURE_BROWSER_XSS_FILTER=True SECURE_CONTENT_TYPE_NOSNIFF=True X_FRAME_OPTIONS=DENY # Performance Settings USE_REDIS=False REDIS_URL= # Logging Configuration LOG_LEVEL= LOGGING_ENABLED=True # External Services SENTRY_DSN= CLOUDFLARE_IMAGES_ACCOUNT_ID= CLOUDFLARE_IMAGES_API_TOKEN= # Deployment Settings DEPLOYMENT_PRESET= AUTO_MIGRATE= AUTO_UPDATE_DEPENDENCIES= PULL_INTERVAL= HEALTH_CHECK_INTERVAL= EOF ) # Apply preset-specific configurations case "$preset" in "dev") env_content=$(echo "$env_content" | sed \ -e "s/DEBUG=/DEBUG=True/" \ -e "s/ALLOWED_HOSTS=/ALLOWED_HOSTS=*/" \ -e "s/LOG_LEVEL=/LOG_LEVEL=DEBUG/" \ -e "s/DEPLOYMENT_PRESET=/DEPLOYMENT_PRESET=dev/" \ -e "s/AUTO_MIGRATE=/AUTO_MIGRATE=True/" \ -e "s/AUTO_UPDATE_DEPENDENCIES=/AUTO_UPDATE_DEPENDENCIES=True/" \ -e "s/PULL_INTERVAL=/PULL_INTERVAL=60/" \ -e "s/HEALTH_CHECK_INTERVAL=/HEALTH_CHECK_INTERVAL=30/" \ -e "s/SECURE_SSL_REDIRECT=/SECURE_SSL_REDIRECT=False/" ) ;; "prod") env_content=$(echo "$env_content" | sed \ -e "s/DEBUG=/DEBUG=False/" \ -e "s/ALLOWED_HOSTS=/ALLOWED_HOSTS=your-production-domain.com/" \ -e "s/LOG_LEVEL=/LOG_LEVEL=WARNING/" \ -e "s/DEPLOYMENT_PRESET=/DEPLOYMENT_PRESET=prod/" \ -e "s/AUTO_MIGRATE=/AUTO_MIGRATE=True/" \ -e "s/AUTO_UPDATE_DEPENDENCIES=/AUTO_UPDATE_DEPENDENCIES=False/" \ -e "s/PULL_INTERVAL=/PULL_INTERVAL=300/" \ -e "s/HEALTH_CHECK_INTERVAL=/HEALTH_CHECK_INTERVAL=60/" \ -e "s/SECURE_SSL_REDIRECT=/SECURE_SSL_REDIRECT=True/" ) ;; "demo") env_content=$(echo "$env_content" | sed \ -e "s/DEBUG=/DEBUG=False/" \ -e "s/ALLOWED_HOSTS=/ALLOWED_HOSTS=demo-host/" \ -e "s/LOG_LEVEL=/LOG_LEVEL=INFO/" \ -e "s/DEPLOYMENT_PRESET=/DEPLOYMENT_PRESET=demo/" \ -e "s/AUTO_MIGRATE=/AUTO_MIGRATE=True/" \ -e "s/AUTO_UPDATE_DEPENDENCIES=/AUTO_UPDATE_DEPENDENCIES=True/" \ -e "s/PULL_INTERVAL=/PULL_INTERVAL=120/" \ -e "s/HEALTH_CHECK_INTERVAL=/HEALTH_CHECK_INTERVAL=45/" \ -e "s/SECURE_SSL_REDIRECT=/SECURE_SSL_REDIRECT=False/" ) ;; "testing") env_content=$(echo "$env_content" | sed \ -e "s/DEBUG=/DEBUG=True/" \ -e "s/ALLOWED_HOSTS=/ALLOWED_HOSTS=test-host/" \ -e "s/LOG_LEVEL=/LOG_LEVEL=DEBUG/" \ -e "s/DEPLOYMENT_PRESET=/DEPLOYMENT_PRESET=testing/" \ -e "s/AUTO_MIGRATE=/AUTO_MIGRATE=True/" \ -e "s/AUTO_UPDATE_DEPENDENCIES=/AUTO_UPDATE_DEPENDENCIES=True/" \ -e "s/PULL_INTERVAL=/PULL_INTERVAL=180/" \ -e "s/HEALTH_CHECK_INTERVAL=/HEALTH_CHECK_INTERVAL=30/" \ -e "s/SECURE_SSL_REDIRECT=/SECURE_SSL_REDIRECT=False/" ) ;; esac # Generate secure secret key local secret_key if command_exists openssl; then secret_key=$(openssl rand -hex 32) elif command_exists python3; then secret_key=$(python3 -c "import secrets; print(secrets.token_hex(32))") else secret_key="change-this-secret-key-in-production-$(date +%s)" fi env_content=$(echo "$env_content" | sed "s/SECRET_KEY=/SECRET_KEY=$secret_key/") if [[ "$host_context" == "local" ]]; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${CYAN}⚙️ Environment Configuration${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "○ Generating ***REMOVED*** file for $preset preset" fi # Write ***REMOVED*** file locally echo "$env_content" > "$target_path/***REMOVED***" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Environment variables - ${GREEN}Configured${NC}" fi complete_success "Environment variables configured for $preset preset" else # Remote environment configuration complete_info "Configuring environment variables on remote host" # Write ***REMOVED*** file on remote host if remote_exec "cat > '$target_path/***REMOVED***' << 'EOF' $env_content EOF"; then complete_success "Environment variables configured on remote host" else complete_error "Failed to configure environment variables on remote host" return 1 fi fi return 0 } # Comprehensive dependency validation and testing validate_dependencies_comprehensive() { local host_context="${1:-local}" # local or remote local target_path="${2:-$PROJECT_DIR}" complete_progress "Validating dependencies and environment ($host_context)" if [[ "$host_context" == "local" ]]; then if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${CYAN}🔍 Dependency Validation${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" fi cd "$target_path" || { complete_error "Cannot change to project directory: $target_path" return 1 } local validation_failed=false # Test UV functionality complete_debug "Testing UV package manager functionality" if ! uv --version >/dev/null 2>&1; then complete_error "UV package manager is not functional" validation_failed=true else if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ UV package manager - ${GREEN}Functional${NC}" fi fi # Test Python environment activation complete_debug "Testing Python virtual environment" if ! uv run python --version >/dev/null 2>&1; then complete_error "Python virtual environment is not functional" validation_failed=true else if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Python environment - ${GREEN}Active${NC}" fi fi # Test Django installation complete_debug "Testing Django installation" if ! uv run python -c "import django; print(f'Django {django.get_version()}')" >/dev/null 2>&1; then complete_error "Django is not properly installed" validation_failed=true else local django_version django_version=$(uv run python -c "import django; print(django.get_version())" 2>/dev/null) if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Django $django_version - ${GREEN}Ready${NC}" fi fi # Test Django management commands complete_debug "Testing Django management commands" if ! uv run manage.py check --quiet >/dev/null 2>&1; then complete_warning "Django check command has issues" # Don't fail validation for check command issues else if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Django commands - ${GREEN}Working${NC}" fi fi # Test Tailwind CSS complete_debug "Testing Tailwind CSS setup" if ! uv run manage.py tailwind build --skip-checks >/dev/null 2>&1; then complete_warning "Tailwind CSS build has issues" # Don't fail validation for Tailwind issues else if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ Tailwind CSS - ${GREEN}Ready${NC}" fi fi if [[ "$validation_failed" == "true" ]]; then complete_error "Dependency validation failed" return 1 else if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo -e "✅ All dependencies - ${GREEN}Validated${NC}" fi complete_success "Dependency validation completed successfully" return 0 fi else # Remote dependency validation complete_info "Validating dependencies on remote host" local validation_failed=false # Test UV on remote if ! remote_exec "export PATH=\"\$HOME/.local/bin:\$PATH\" && uv --version" true true; then complete_error "UV package manager not functional on remote host" validation_failed=true fi # Test Python environment on remote if ! remote_exec "cd '$target_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run python --version" true true; then complete_error "Python environment not functional on remote host" validation_failed=true fi # Test Django on remote if ! remote_exec "cd '$target_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run python -c 'import django'" true true; then complete_error "Django not properly installed on remote host" validation_failed=true fi # Test Django management commands on remote if ! remote_exec "cd '$target_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py check --quiet" true true; then complete_warning "Django check command has issues on remote host" fi if [[ "$validation_failed" == "true" ]]; then complete_error "Remote dependency validation failed" return 1 else complete_success "Remote dependency validation completed successfully" return 0 fi fi } # Main Step 3B orchestration function setup_dependency_installation_and_environment() { complete_progress "Starting Step 3B: Dependency Installation and Environment Setup" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${BOLD}${CYAN}Step 3B: Dependency Installation and Environment Setup${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "This step will:" echo "• Validate and install system dependencies" echo "• Set up UV package manager" echo "• Prepare Python virtual environment" echo "• Install ThrillWiki-specific dependencies" echo "• Configure environment variables for deployment preset" echo "• Perform comprehensive validation" echo "" fi local deployment_preset="${DEPLOYMENT_PRESET:-dev}" local setup_failed=false # Step 3B.1: System dependency validation and installation if ! validate_system_dependencies "local"; then complete_error "Local system dependency validation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else complete_warning "Continuing with force deployment despite system dependency issues" fi fi # Step 3B.2: UV package manager setup and configuration if ! setup_uv_package_manager "local"; then complete_error "UV package manager setup failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else complete_warning "Continuing with force deployment despite UV setup issues" fi fi # Step 3B.3: Python environment preparation if ! prepare_python_environment "local" "$PROJECT_DIR"; then complete_error "Python environment preparation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else complete_warning "Continuing with force deployment despite Python environment issues" fi fi # Step 3B.4: ThrillWiki-specific dependency installation if ! install_thrillwiki_dependencies "local" "$PROJECT_DIR" "$deployment_preset"; then complete_error "ThrillWiki dependency installation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else complete_warning "Continuing with force deployment despite ThrillWiki dependency issues" fi fi # Step 3B.5: Environment variable configuration if ! configure_environment_variables "local" "$PROJECT_DIR" "$deployment_preset"; then complete_error "Environment variable configuration failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else complete_warning "Continuing with force deployment despite environment configuration issues" fi fi # Step 3B.6: Comprehensive dependency validation if ! validate_dependencies_comprehensive "local" "$PROJECT_DIR"; then complete_error "Comprehensive dependency validation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else complete_warning "Continuing with force deployment despite validation issues" fi fi if [[ "$setup_failed" == "true" ]]; then complete_warning "Step 3B completed with issues (forced deployment)" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${YELLOW}⚠️ Some dependency setup steps had issues, but deployment will continue.${NC}" echo "" fi else complete_success "Step 3B: Dependency Installation and Environment Setup completed successfully" if [[ "${INTERACTIVE_MODE:-false}" == "true" ]]; then echo "" echo -e "${GREEN}✅ All dependency and environment setup completed successfully!${NC}" echo "" echo "Your local environment is now:" echo "• ✅ System dependencies validated and installed" echo "• ✅ UV package manager configured and ready" echo "• ✅ Python virtual environment created and activated" echo "• ✅ ThrillWiki dependencies installed for $deployment_preset preset" echo "• ✅ Environment variables configured" echo "• ✅ All components validated and tested" echo "" fi fi return 0 } # [AWS-SECRET-REMOVED]==================================== # STEP 4A: SMART AUTOMATED DEPLOYMENT CYCLE - DJANGO DEPLOYMENT & AUTOMATION # [AWS-SECRET-REMOVED]==================================== # Smart automated deployment cycle with comprehensive change detection setup_smart_automated_deployment() { complete_info "Setting up smart automated deployment cycle with 5-minute intervals" local hosts="" local host_count=0 # Cross-shell compatible host reading if [ -f /tmp/thrillwiki-deploy-hosts.$$ ]; then while IFS= read -r host; do if [ -n "$host" ]; then hosts="$hosts$host " host_count=$((host_count + 1)) fi done < /tmp/thrillwiki-deploy-hosts.$$ else complete_error "Host configuration file not found" return 1 fi complete_info "Configuring smart deployment for $host_count host(s)" # Setup automation for each host for host in $hosts; do if [ -n "$host" ]; then complete_info "Setting up smart automated deployment for $host" setup_host_smart_deployment "$host" fi done complete_success "Smart automated deployment configured for all hosts" return 0 } # Setup smart deployment for individual host setup_host_smart_deployment() { local host="$1" local deployment_preset="${DEPLOYMENT_PRESET:-dev}" complete_info "Configuring smart deployment for $host (preset: $deployment_preset)" # Get pull interval from preset configuration local pull_interval pull_interval=$(get_preset_config "$deployment_preset" "PULL_INTERVAL") # Create smart deployment script on remote host create_smart_deployment_script "$host" "$pull_interval" "$deployment_preset" # Setup systemd service for automation setup_smart_deployment_service "$host" "$deployment_preset" complete_success "Smart deployment configured for $host" } # Create enhanced smart deployment script with deployment decision matrix create_smart_deployment_script() { local host="$1" local pull_interval="$2" local preset="$3" complete_info "Creating smart deployment script for $host" # Build SSH command local ssh_cmd="ssh" if [[ -n "$SSH_KEY" ]]; then ssh_cmd+=" -i $SSH_KEY" fi ssh_cmd+=" $SSH_OPTIONS -p $REMOTE_PORT $REMOTE_USER@$host" # Create the smart deployment script on remote host $ssh_cmd "cat > $REMOTE_PATH/scripts/smart-deploy.sh" << 'EOF' #!/bin/bash # # ThrillWiki Smart Automated Deployment Script # Implements comprehensive deployment decision matrix with 5-minute cycle # set -e # Configuration from environment PROJECT_DIR="${REMOTE_PATH:-/home/thrillwiki/thrillwiki}" LOG_FILE="$PROJECT_DIR/logs/smart-deploy.log" LOCK_FILE="/tmp/thrillwiki-smart-deploy.lock" PULL_INTERVAL="${PULL_INTERVAL:-300}" # 5 minutes default DEPLOYMENT_PRESET="${DEPLOYMENT_PRESET:-dev}" # Colors for logging RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # Cross-shell compatible logging smart_log() { local level="$1" local color="$2" local message="$3" local timestamp="$(date '+%Y-%m-%d %H:%M:%S')" mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true echo "[$timestamp] [$level] [SMART-DEPLOY] $message" >> "$LOG_FILE" echo -e "${color}[$timestamp] [SMART-DEPLOY-$level]${NC} $message" } smart_info() { smart_log "INFO" "$BLUE" "$1"; } smart_success() { smart_log "SUCCESS" "$GREEN" "✅ $1"; } smart_warning() { smart_log "WARNING" "$YELLOW" "⚠️ $1"; } smart_error() { smart_log "ERROR" "$RED" "❌ $1"; } smart_progress() { smart_log "PROGRESS" "$CYAN" "🚀 $1"; } # Smart deployment decision matrix analyze_changes_and_decide() { local pull_output="$1" local needs_migration=false local needs_static=false local needs_restart=false local needs_dependencies=false smart_info "🔍 Analyzing changes for deployment decisions" # Check for migration requirements if echo "$pull_output" | grep -qE "(models\.py|migrations/|schema\.py)" ; then needs_migration=true smart_info "📊 Migration files detected - database migration required" fi # Check for static file changes if echo "$pull_output" | grep -qE "(static/|staticfiles/|templates/|\.css|\.js|\.scss|tailwind)" ; then needs_static=true smart_info "🎨 Static file changes detected - static collection required" fi # Check for code changes requiring restart if echo "$pull_output" | grep -qE "(\.py$|settings|urls\.py|wsgi\.py|asgi\.py)" ; then needs_restart=true smart_info "🔄 Code changes detected - service restart required" fi # Check for dependency changes if echo "$pull_output" | grep -qE "(pyproject\.toml|requirements.*\.txt|uv\.lock|setup\.py)" ; then needs_dependencies=true smart_info "📦 Dependency changes detected - dependency update required" fi # Export decisions for use by deployment functions export NEEDS_MIGRATION="$needs_migration" export NEEDS_STATIC="$needs_static" export NEEDS_RESTART="$needs_restart" export NEEDS_DEPENDENCIES="$needs_dependencies" # Log decision matrix smart_info "📋 Deployment Decision Matrix:" smart_info " Migration Required: $needs_migration" smart_info " Static Collection Required: $needs_static" smart_info " Service Restart Required: $needs_restart" smart_info " Dependency Update Required: $needs_dependencies" # Return true if any action is needed if [[ "$needs_migration" == "true" || "$needs_static" == "true" || "$needs_restart" == "true" || "$needs_dependencies" == "true" ]]; then return 0 else return 1 fi } # Execute deployment actions based on decision matrix execute_deployment_actions() { smart_progress "🚀 Executing deployment actions based on decision matrix" # Update dependencies if needed if [[ "${NEEDS_DEPENDENCIES:-false}" == "true" ]]; then smart_info "📦 Updating dependencies" if cd "$PROJECT_DIR" && export PATH="$HOME/.local/bin:$PATH" && uv sync --quiet; then smart_success "Dependencies updated successfully" else smart_error "Dependency update failed" return 1 fi fi # Run migrations if needed (following .clinerules) if [[ "${NEEDS_MIGRATION:-false}" == "true" ]]; then smart_info "🗄️ Running database migrations" if cd "$PROJECT_DIR" && export PATH="$HOME/.local/bin:$PATH" && uv run manage.py migrate; then smart_success "Database migrations completed" else smart_error "Database migrations failed" return 1 fi fi # Collect static files if needed (following .clinerules) if [[ "${NEEDS_STATIC:-false}" == "true" ]]; then smart_info "🎨 Collecting static files" if cd "$PROJECT_DIR" && export PATH="$HOME/.local/bin:$PATH" && uv run manage.py collectstatic --noinput; then smart_success "Static files collected" else smart_warning "Static file collection had issues" fi # Build Tailwind CSS if needed smart_info "🎨 Building Tailwind CSS" if cd "$PROJECT_DIR" && export PATH="$HOME/.local/bin:$PATH" && uv run manage.py tailwind build; then smart_success "Tailwind CSS built successfully" else smart_warning "Tailwind CSS build had issues" fi fi # Restart service if needed if [[ "${NEEDS_RESTART:-false}" == "true" ]]; then smart_info "🔄 Restarting ThrillWiki service" restart_thrillwiki_service fi smart_success "All deployment actions completed" } # Cross-shell compatible service restart with proper cleanup (.clinerules pattern) restart_thrillwiki_service() { smart_info "🔄 Performing clean service restart following .clinerules pattern" # Clean up Python cache and processes first (.clinerules pattern) smart_info "🧹 Cleaning up Python processes and cache" cd "$PROJECT_DIR" # Kill any existing processes on port 8000 and clean cache (.clinerules) lsof -ti :8000 | xargs kill -9 2>/dev/null || true find . -type d -name "__pycache__" -exec rm -r {} + 2>/dev/null || true # Start service using .clinerules pattern smart_info "🚀 Starting ThrillWiki service with proper .clinerules command" if export PATH="$HOME/.local/bin:$PATH" && nohup uv run manage.py tailwind runserver 0.0.0.0:8000 > logs/runserver.log 2>&1 & then sleep 3 # Give service time to start # Verify service is running if curl -f http://localhost:8000 > /dev/null 2>&1; then smart_success "ThrillWiki service restarted successfully" else smart_warning "Service may still be starting up" fi else smart_error "Failed to restart ThrillWiki service" return 1 fi } # Check for remote changes with enhanced authentication check_remote_changes() { smart_info "📡 Checking for remote repository changes" cd "$PROJECT_DIR" || return 1 # Setup GitHub authentication if available if [ -n "${GITHUB_TOKEN:-}" ]; then local repo_url="https://pacnpal:${GITHUB_TOKEN}@github.com/pacnpal/thrillwiki_django_no_react.git" git remote set-url origin "$repo_url" 2>/dev/null || true fi # Fetch latest changes if ! git fetch origin main --quiet 2>/dev/null; then smart_error "Failed to fetch from remote repository" return 1 fi # Compare commits local local_commit=$(git rev-parse HEAD) local remote_commit=$(git rev-parse origin/main) smart_info "📊 Local: ${local_commit:0:8}, Remote: ${remote_commit:0:8}" if [ "$local_commit" != "$remote_commit" ]; then smart_success "New changes detected on remote" return 0 else smart_info "Repository is up to date" return 1 fi } # Main smart deployment cycle main_smart_cycle() { smart_info "🔄 Starting smart deployment cycle (interval: ${PULL_INTERVAL}s)" # Acquire lock if [ -f "$LOCK_FILE" ]; then local lock_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "") if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then smart_warning "Smart deployment already running (PID: $lock_pid)" exit 0 fi rm -f "$LOCK_FILE" fi echo $$ > "$LOCK_FILE" trap 'rm -f "$LOCK_FILE"' EXIT # Check for changes if ! check_remote_changes; then smart_info "No changes detected, cycle complete" exit 0 fi # Pull changes and analyze smart_progress "📥 Pulling changes from remote" local pull_output if pull_output=$(git pull origin main 2>&1); then smart_success "Git pull completed successfully" # Analyze changes and make decisions if analyze_changes_and_decide "$pull_output"; then smart_progress "Changes require deployment actions" execute_deployment_actions smart_success "🎉 Smart deployment cycle completed successfully" else smart_info "Changes detected but no deployment actions required" fi else smart_error "Git pull failed: $pull_output" exit 1 fi } # Execute based on arguments case "${1:-cycle}" in "cycle"|"") main_smart_cycle ;; "check") check_remote_changes && echo "Changes available" || echo "No changes" ;; "status") [ -f "$LOCK_FILE" ] && echo "Running" || echo "Stopped" ;; *) echo "Usage: $0 [cycle|check|status]" ;; esac EOF # Make the script executable $ssh_cmd "chmod +x $REMOTE_PATH/scripts/smart-deploy.sh" complete_success "Smart deployment script created on $host" } # Setup systemd service for smart deployment setup_smart_deployment_service() { local host="$1" local preset="$2" complete_info "Setting up systemd service for smart deployment on $host" # Get configuration from preset local pull_interval pull_interval=$(get_preset_config "$preset" "PULL_INTERVAL") # Build SSH command local ssh_cmd="ssh" if [[ -n "$SSH_KEY" ]]; then ssh_cmd+=" -i $SSH_KEY" fi ssh_cmd+=" $SSH_OPTIONS -p $REMOTE_PORT $REMOTE_USER@$host" # Create systemd timer and service $ssh_cmd << EOF # Create systemd service sudo tee /etc/systemd/system/thrillwiki-smart-deploy.service > /dev/null << 'SERVICE_EOF' [Unit] Description=ThrillWiki Smart Automated Deployment After=network.target [Service] Type=oneshot User=$REMOTE_USER WorkingDirectory=$REMOTE_PATH Environment=PULL_INTERVAL=$pull_interval Environment=DEPLOYMENT_PRESET=$preset Environment=REMOTE_PATH=$REMOTE_PATH ExecStart=$REMOTE_PATH/scripts/smart-deploy.sh cycle StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target SERVICE_EOF # Create systemd timer sudo tee /etc/systemd/system/thrillwiki-smart-deploy.timer > /dev/null << 'TIMER_EOF' [Unit] Description=ThrillWiki Smart Deployment Timer Requires=thrillwiki-smart-deploy.service [Timer] OnBootSec=${pull_interval}s OnUnitActiveSec=${pull_interval}s Persistent=true [Install] WantedBy=timers.target TIMER_EOF # Enable and start the timer sudo systemctl daemon-reload sudo systemctl enable thrillwiki-smart-deploy.timer sudo systemctl start thrillwiki-smart-deploy.timer echo "Smart deployment service configured with ${pull_interval}s interval" EOF complete_success "Smart deployment service configured on $host" } # [AWS-SECRET-REMOVED]==================================== # STEP 4B: DEVELOPMENT SERVER SETUP AND AUTOMATION # [AWS-SECRET-REMOVED]==================================== # Main development server setup function setup_development_server() { local target_host="$1" local preset="$2" complete_progress "🚀 Development Server Setup" complete_progress "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" complete_info "" complete_info "Starting ThrillWiki development server:" complete_info "○ Cleaning up previous processes" complete_info "○ Removing Python cache files" complete_info "○ Starting Tailwind + Django runserver" complete_info "○ Verifying server accessibility" complete_info "○ Setting up automated monitoring" complete_info "" # Start ThrillWiki development server with exact .clinerules command if start_thrillwiki_server "$target_host" "$preset"; then complete_success "ThrillWiki development server started successfully" # Set up automated server management if setup_server_automation "$target_host" "$preset"; then complete_success "Server automation configured successfully" else complete_warning "Server automation setup had issues" fi # Set up health monitoring if setup_server_monitoring "$target_host" "$preset"; then complete_success "Server health monitoring configured" else complete_warning "Server monitoring setup had issues" fi # Integrate with smart deployment system if integrate_with_smart_deployment "$target_host" "$preset"; then complete_success "Smart deployment integration completed" else complete_warning "Smart deployment integration had issues" fi return 0 else complete_error "Failed to start ThrillWiki development server" return 1 fi } # Start ThrillWiki development server using exact .clinerules command start_thrillwiki_server() { local target_host="$1" local preset="$2" complete_info "Starting ThrillWiki development server on $target_host" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # CRITICAL: Use EXACT .clinerules command sequence local server_command="lsof -ti :8000 | xargs kill -9; find . -type d -name '__pycache__' -exec rm -r {} +; uv run manage.py tailwind runserver" complete_info "Executing ThrillWiki server startup command (following .clinerules exactly)" complete_debug "Command: $server_command" # Start server in background with proper logging if eval "$ssh_cmd \"cd '$remote_path' && export PATH=\\\$HOME/.local/bin:\\\$PATH && nohup bash -c '$server_command' > logs/thrillwiki-server.log 2>&1 & echo \\\$! > thrillwiki-server.pid\""; then complete_success "ThrillWiki development server startup command executed" # Wait a moment for server to start sleep 5 # Verify server is running if verify_server_accessibility "$target_host"; then complete_success "ThrillWiki development server is accessible on port 8000" return 0 else complete_error "ThrillWiki development server failed to start properly" # Show server logs for debugging complete_info "Checking server logs for troubleshooting:" eval "$ssh_cmd \"cd '$remote_path' && tail -20 logs/thrillwiki-server.log\"" || true return 1 fi else complete_error "Failed to execute server startup command" return 1 fi } # Verify server accessibility with health checks verify_server_accessibility() { local target_host="$1" local max_attempts=6 local attempt=1 complete_info "Verifying server accessibility on $target_host:8000" while [[ $attempt -le $max_attempts ]]; do complete_debug "Health check attempt $attempt/$max_attempts" # Check if server is responding on port 8000 if curl -s --connect-timeout 5 "http://$target_host:8000/" > /dev/null 2>&1; then complete_success "Server is accessible and responding" return 0 else complete_debug "Server not yet accessible, waiting..." sleep 5 ((attempt++)) fi done complete_warning "Server accessibility verification failed after $max_attempts attempts" return 1 } # Set up automated server management with monitoring and restart capabilities setup_server_automation() { local target_host="$1" local preset="$2" complete_info "Setting up automated server management on $target_host" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # Create server management script on remote host local server_mgmt_script=$(cat << 'EOF' #!/bin/bash # # ThrillWiki Server Management Script # Automated startup, monitoring, and restart capabilities # set -e # Cross-shell compatible script directory detection if [ -n "${BASH_SOURCE:-}" ]; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" elif [ -n "${ZSH_NAME:-}" ]; then SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)" else SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" fi # Configuration PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" SERVER_PID_FILE="$PROJECT_DIR/thrillwiki-server.pid" SERVER_LOG_FILE="$PROJECT_DIR/logs/thrillwiki-server.log" HEALTH_CHECK_URL="http://localhost:8000/" RESTART_DELAY=10 MAX_RESTART_ATTEMPTS=3 # Cross-shell compatible logging server_log() { local level="$1" local message="$2" local timestamp="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$timestamp] [$level] [SERVER-MGR] $message" | tee -a "$PROJECT_DIR/logs/server-management.log" } # Check if server is running is_server_running() { if [[ -f "$SERVER_PID_FILE" ]]; then local pid=$(cat "$SERVER_PID_FILE") if kill -0 "$pid" 2>/dev/null; then return 0 else rm -f "$SERVER_PID_FILE" return 1 fi fi return 1 } # Start ThrillWiki server using exact .clinerules command start_server() { server_log "INFO" "Starting ThrillWiki development server" cd "$PROJECT_DIR" # Ensure logs directory exists mkdir -p logs # CRITICAL: Use EXACT .clinerules command local server_command="lsof -ti :8000 | xargs kill -9; find . -type d -name '__pycache__' -exec rm -r {} +; uv run manage.py tailwind runserver" # Start server in background export PATH="$HOME/.local/bin:$PATH" nohup bash -c "$server_command" > "$SERVER_LOG_FILE" 2>&1 & local server_pid=$! # Save PID echo "$server_pid" > "$SERVER_PID_FILE" server_log "INFO" "Server started with PID: $server_pid" # Wait for server to become available local attempts=0 while [[ $attempts -lt 30 ]]; do if curl -s --connect-timeout 2 "$HEALTH_CHECK_URL" > /dev/null 2>&1; then server_log "SUCCESS" "Server is accessible on port 8000" return 0 fi sleep 2 ((attempts++)) done server_log "ERROR" "Server failed to become accessible" return 1 } # Stop server gracefully stop_server() { server_log "INFO" "Stopping ThrillWiki development server" if [[ -f "$SERVER_PID_FILE" ]]; then local pid=$(cat "$SERVER_PID_FILE") if kill -0 "$pid" 2>/dev/null; then kill "$pid" sleep 5 if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" fi fi rm -f "$SERVER_PID_FILE" fi # Clean up any remaining processes on port 8000 lsof -ti :8000 | xargs kill -9 2>/dev/null || true server_log "INFO" "Server stopped" } # Restart server restart_server() { server_log "INFO" "Restarting ThrillWiki development server" stop_server sleep "$RESTART_DELAY" start_server } # Monitor server health monitor_server() { local restart_attempts=0 while true; do if is_server_running; then if curl -s --connect-timeout 5 "$HEALTH_CHECK_URL" > /dev/null 2>&1; then server_log "DEBUG" "Server health check passed" restart_attempts=0 else server_log "WARNING" "Server not responding to health check" if [[ $restart_attempts -lt $MAX_RESTART_ATTEMPTS ]]; then ((restart_attempts++)) server_log "INFO" "Attempting restart ($restart_attempts/$MAX_RESTART_ATTEMPTS)" restart_server else server_log "ERROR" "Max restart attempts reached, server may need manual intervention" exit 1 fi fi else server_log "WARNING" "Server process not running" if [[ $restart_attempts -lt $MAX_RESTART_ATTEMPTS ]]; then ((restart_attempts++)) server_log "INFO" "Attempting restart ($restart_attempts/$MAX_RESTART_ATTEMPTS)" start_server else server_log "ERROR" "Max restart attempts reached, server may need manual intervention" exit 1 fi fi sleep 60 # Check every minute done } # Handle script commands case "${1:-start}" in start) if is_server_running; then server_log "INFO" "Server is already running" else start_server fi ;; stop) stop_server ;; restart) restart_server ;; status) if is_server_running; then echo "Server is running (PID: $(cat "$SERVER_PID_FILE"))" else echo "Server is not running" fi ;; monitor) monitor_server ;; health-check) if curl -s --connect-timeout 5 "$HEALTH_CHECK_URL" > /dev/null 2>&1; then echo "Server is healthy" exit 0 else echo "Server health check failed" exit 1 fi ;; *) echo "Usage: $0 {start|stop|restart|status|monitor|health-check}" exit 1 ;; esac EOF ) # Deploy server management script if eval "$ssh_cmd \"cat > '$remote_path/scripts/vm/server-manager.sh' << 'EOF' $server_mgmt_script EOF\""; then # Make script executable eval "$ssh_cmd \"chmod +x '$remote_path/scripts/vm/server-manager.sh'\"" complete_success "Server management script deployed and configured" return 0 else complete_error "Failed to deploy server management script" return 1 fi } # Set up server health monitoring setup_server_monitoring() { local target_host="$1" local preset="$2" complete_info "Setting up server health monitoring on $target_host" # Get monitoring interval based on preset local monitor_interval monitor_interval=$(get_preset_config "$preset" "HEALTH_CHECK_INTERVAL") # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # Start monitoring in background complete_info "Starting background health monitoring (interval: ${monitor_interval}s)" if eval "$ssh_cmd \"cd '$remote_path' && nohup scripts/vm/server-manager.sh monitor > logs/server-monitor.log 2>&1 & echo \\\$! > server-monitor.pid\""; then complete_success "Server health monitoring started" return 0 else complete_warning "Failed to start server health monitoring" return 1 fi } # Integrate server management with smart deployment system integrate_with_smart_deployment() { local target_host="$1" local preset="$2" complete_info "Integrating server management with smart deployment system" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # Create deployment hook script for server restart coordination local deployment_hook=$(cat << 'EOF' #!/bin/bash # # ThrillWiki Deployment Hook - Server Management Integration # Coordinates server restarts with automated deployments # # Cross-shell compatible script directory detection if [ -n "${BASH_SOURCE:-}" ]; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" elif [ -n "${ZSH_NAME:-}" ]; then SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)" else SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" fi PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" SERVER_MANAGER="$PROJECT_DIR/scripts/vm/server-manager.sh" case "${1:-post-deploy}" in pre-deploy) echo "Pre-deployment: Stopping development server" "$SERVER_MANAGER" stop ;; post-deploy) echo "Post-deployment: Starting development server" "$SERVER_MANAGER" start ;; restart) echo "Deployment restart: Restarting development server" "$SERVER_MANAGER" restart ;; *) echo "Usage: $0 {pre-deploy|post-deploy|restart}" exit 1 ;; esac EOF ) # Deploy integration hook if eval "$ssh_cmd \"cat > '$remote_path/scripts/vm/deployment-hook.sh' << 'EOF' $deployment_hook EOF\""; then eval "$ssh_cmd \"chmod +x '$remote_path/scripts/vm/deployment-hook.sh'\"" complete_success "Smart deployment integration configured" # Modify the smart deployment script to include server restart hooks enhance_smart_deployment_with_server_management "$target_host" return 0 else complete_warning "Failed to configure smart deployment integration" return 1 fi } # Enhance smart deployment script with server management integration enhance_smart_deployment_with_server_management() { local target_host="$1" complete_info "Enhancing smart deployment script with server management on $target_host" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # Add server management integration to smart deployment script local server_integration_patch=$(cat << 'EOF' # [AWS-SECRET-REMOVED]================================== # STEP 4B: SERVER MANAGEMENT INTEGRATION # [AWS-SECRET-REMOVED]================================== # Restart development server following .clinerules if code changes detected restart_development_server() { if [[ "${NEEDS_RESTART:-false}" == "true" ]]; then smart_info "🔄 Restarting development server due to code changes" # Use deployment hook for coordinated restart if [ -x "$PROJECT_DIR/scripts/vm/deployment-hook.sh" ]; then "$PROJECT_DIR/scripts/vm/deployment-hook.sh" restart smart_success "Development server restarted successfully" else # Fallback to direct server manager if [ -x "$PROJECT_DIR/scripts/vm/server-manager.sh" ]; then "$PROJECT_DIR/scripts/vm/server-manager.sh" restart smart_success "Development server restarted successfully" else smart_warning "Server management scripts not found" fi fi else smart_info "🔄 No server restart needed" fi } EOF ) # Append server integration to smart deployment script eval "$ssh_cmd \"echo '$server_integration_patch' >> '$remote_path/scripts/smart-deploy.sh'\"" # Modify the smart deployment cycle to include server restart eval "$ssh_cmd \"sed -i '/smart_success \\\"Deployment actions completed successfully\\\"/i\\ # Step 4B: Restart development server if needed\\n restart_development_server' '$remote_path/scripts/smart-deploy.sh'\"" complete_success "Smart deployment enhanced with server management integration" } # [AWS-SECRET-REMOVED]==================================== # STEP 5A: SERVICE CONFIGURATION AND STARTUP - SYSTEMD INTEGRATION # [AWS-SECRET-REMOVED]==================================== # Generate deployment environment configuration based on preset generate_deployment_environment_config() { local target_host="$1" local preset="$2" local github_token="$3" complete_info "Generating deployment environment configuration for preset: $preset" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # Get preset-specific configuration values local pull_interval pull_interval=$(get_preset_config "$preset" "PULL_INTERVAL") local health_check_interval health_check_interval=$(get_preset_config "$preset" "HEALTH_CHECK_INTERVAL") local debug_mode debug_mode=$(get_preset_config "$preset" "DEBUG_MODE") local auto_migrate auto_migrate=$(get_preset_config "$preset" "AUTO_MIGRATE") local auto_update_dependencies auto_update_dependencies=$(get_preset_config "$preset" "AUTO_UPDATE_DEPENDENCIES") local log_level log_level=$(get_preset_config "$preset" "LOG_LEVEL") local ssl_required ssl_required=$(get_preset_config "$preset" "SSL_REQUIRED") local cors_allowed cors_allowed=$(get_preset_config "$preset" "CORS_ALLOWED") local django_debug django_debug=$(get_preset_config "$preset" "DJANGO_DEBUG") local allowed_hosts allowed_hosts=$(get_preset_config "$preset" "ALLOWED_HOSTS") # Generate environment configuration content local env_config=$(cat << EOF # ThrillWiki Deployment Service Environment Configuration # Generated automatically by deployment system with preset: $preset # Generated on: $(date) # # Security Note: This file should have restricted permissions (600) as it may contain # sensitive information like GitHub Personal Access Tokens # [AWS-SECRET-REMOVED]==================================== # PROJECT CONFIGURATION # [AWS-SECRET-REMOVED]==================================== PROJECT_DIR=$remote_path SERVICE_NAME=thrillwiki-deployment DEPLOYMENT_MODE=automated # [AWS-SECRET-REMOVED]==================================== # GITHUB REPOSITORY CONFIGURATION # [AWS-SECRET-REMOVED]==================================== GITHUB_REPO=origin GITHUB_BRANCH=main $([ -n "$github_token" ] && echo "GITHUB_TOKEN=$github_token" || echo "# GITHUB_TOKEN=") GITHUB_TOKEN_FILE=$remote_path/.github-pat # [AWS-SECRET-REMOVED]==================================== # DEPLOYMENT PRESET CONFIGURATION # [AWS-SECRET-REMOVED]==================================== DEPLOYMENT_PRESET=$preset # [AWS-SECRET-REMOVED]==================================== # AUTOMATION TIMING CONFIGURATION (Preset-based) # [AWS-SECRET-REMOVED]==================================== PULL_INTERVAL=$pull_interval HEALTH_CHECK_INTERVAL=$health_check_interval STARTUP_TIMEOUT=120 RESTART_DELAY=10 # [AWS-SECRET-REMOVED]==================================== # DEPLOYMENT BEHAVIOR CONFIGURATION (Preset-based) # [AWS-SECRET-REMOVED]==================================== DEBUG_MODE=$debug_mode AUTO_UPDATE_DEPENDENCIES=$auto_update_dependencies AUTO_MIGRATE=$auto_migrate AUTO_COLLECTSTATIC=true LOG_LEVEL=$log_level # [AWS-SECRET-REMOVED]==================================== # SECURITY CONFIGURATION (Preset-based) # [AWS-SECRET-REMOVED]==================================== DJANGO_DEBUG=$django_debug SSL_REQUIRED=$ssl_required CORS_ALLOWED=$cors_allowed ALLOWED_HOSTS=$allowed_hosts # [AWS-SECRET-REMOVED]==================================== # LOGGING CONFIGURATION # [AWS-SECRET-REMOVED]==================================== LOG_DIR=$remote_path/logs LOG_FILE=$remote_path/logs/deployment-automation.log MAX_LOG_SIZE=10485760 LOCK_FILE=/tmp/thrillwiki-deployment.lock # [AWS-SECRET-REMOVED]==================================== # DEVELOPMENT SERVER CONFIGURATION # [AWS-SECRET-REMOVED]==================================== SERVER_HOST=0.0.0.0 SERVER_PORT=8000 HEALTH_CHECK_URL=http://localhost:8000/ HEALTH_CHECK_TIMEOUT=30 # [AWS-SECRET-REMOVED]==================================== # DJANGO CONFIGURATION # [AWS-SECRET-REMOVED]==================================== DJANGO_SETTINGS_MODULE=thrillwiki.settings PYTHONPATH=$remote_path UV_EXECUTABLE=/home/${REMOTE_USER:-thrillwiki}/.local/bin/uv DJANGO_RUNSERVER_CMD=lsof -ti :8000 | xargs kill -9; find . -type d -name '__pycache__' -exec rm -r {} +; uv run manage.py tailwind runserver AUTO_CLEANUP_PROCESSES=true # [AWS-SECRET-REMOVED]==================================== # SYSTEMD SERVICE CONFIGURATION # [AWS-SECRET-REMOVED]==================================== SERVICE_USER=${REMOTE_USER:-thrillwiki} SERVICE_GROUP=${REMOTE_USER:-thrillwiki} SERVICE_WORKING_DIR=$remote_path SERVICE_RESTART=always SERVICE_RESTART_SEC=30 SERVICE_TIMEOUT_START=180 SERVICE_TIMEOUT_STOP=120 MAX_RESTART_ATTEMPTS=3 RESTART_COOLDOWN=300 # [AWS-SECRET-REMOVED]==================================== # SMART DEPLOYMENT TIMER CONFIGURATION # [AWS-SECRET-REMOVED]==================================== TIMER_ON_BOOT_SEC=5min TIMER_ON_UNIT_ACTIVE_SEC=${pull_interval}s TIMER_RANDOMIZED_DELAY_SEC=30sec TIMER_PERSISTENT=true # [AWS-SECRET-REMOVED]==================================== # MONITORING AND HEALTH CHECKS # [AWS-SECRET-REMOVED]==================================== MONITOR_RESOURCES=true MEMORY_WARNING_THRESHOLD=512 CPU_WARNING_THRESHOLD=70 DISK_WARNING_THRESHOLD=85 # [AWS-SECRET-REMOVED]==================================== # INTEGRATION SETTINGS # [AWS-SECRET-REMOVED]==================================== WEBHOOK_INTEGRATION=false MAX_CONSECUTIVE_FAILURES=5 # [AWS-SECRET-REMOVED]==================================== # ADVANCED CONFIGURATION # [AWS-SECRET-REMOVED]==================================== VERBOSE_LOGGING=$([ "$debug_mode" = "true" ] && echo "true" || echo "false") GITHUB_AUTH_METHOD=token GIT_USER_NAME="ThrillWiki Deployment" GIT_USER_EMAIL="deployment@thrillwiki.local" # [AWS-SECRET-REMOVED]==================================== # ENVIRONMENT AND SYSTEM CONFIGURATION # [AWS-SECRET-REMOVED]==================================== ADDITIONAL_PATH=/home/${REMOTE_USER:-thrillwiki}/.local/bin:/home/${REMOTE_USER:-thrillwiki}/.cargo/bin PYTHON_EXECUTABLE=python3 SERVICE_LOGS_DIR=/var/log/thrillwiki-deployment SERVICE_STATE_DIR=/var/lib/thrillwiki-deployment SERVICE_RUNTIME_DIR=/run/thrillwiki-deployment EOF ) # Deploy environment configuration if eval "$ssh_cmd \"cat > '$remote_path/scripts/systemd/thrillwiki-deployment***REMOVED***' << 'EOF' $env_config EOF\""; then # Set secure permissions eval "$ssh_cmd \"chmod 600 '$remote_path/scripts/systemd/thrillwiki-deployment***REMOVED***'\"" eval "$ssh_cmd \"chown ${REMOTE_USER:-thrillwiki}:${REMOTE_USER:-thrillwiki} '$remote_path/scripts/systemd/thrillwiki-deployment***REMOVED***'\"" complete_success "Deployment environment configuration generated and deployed" return 0 else complete_error "Failed to deploy environment configuration" return 1 fi } # Configure systemd timer based on deployment preset configure_deployment_timer() { local target_host="$1" local preset="$2" complete_info "Configuring deployment timer for preset: $preset" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" # Get preset-specific pull interval local pull_interval pull_interval=$(get_preset_config "$preset" "PULL_INTERVAL") # Generate timer configuration local timer_config=$(cat << EOF [Unit] Description=ThrillWiki Smart Deployment Timer (Preset: $preset) Documentation=man:thrillwiki-smart-deploy(8) Requires=thrillwiki-smart-deploy.service After=thrillwiki-deployment.service [Timer] OnBootSec=5min OnUnitActiveSec=${pull_interval}s Unit=thrillwiki-smart-deploy.service Persistent=true RandomizedDelaySec=30sec [Install] WantedBy=timers.target Also=thrillwiki-smart-deploy.service EOF ) # Deploy timer configuration if eval "$ssh_cmd \"cat > '$remote_path/scripts/systemd/thrillwiki-smart-deploy.timer' << 'EOF' $timer_config EOF\""; then complete_success "Deployment timer configured for $pull_interval second intervals" return 0 else complete_error "Failed to configure deployment timer" return 1 fi } # Install systemd service files on remote host install_systemd_services() { local target_host="$1" local preset="$2" complete_info "Installing systemd service files on $target_host" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" complete_progress "Creating systemd service directories" # Create systemd service directory and copy files if eval "$ssh_cmd \"sudo mkdir -p /etc/systemd/system\""; then complete_debug "Systemd service directory ready" else complete_error "Failed to create systemd service directory" return 1 fi # Install main deployment service complete_progress "Installing thrillwiki-deployment.service" if eval "$ssh_cmd \"sudo cp '$remote_path/scripts/systemd/thrillwiki-deployment.service' /etc/systemd/system/\""; then complete_success "Main deployment service installed" else complete_error "Failed to install main deployment service" return 1 fi # Install smart deployment service complete_progress "Installing thrillwiki-smart-deploy.service" if eval "$ssh_cmd \"sudo cp '$remote_path/scripts/systemd/thrillwiki-smart-deploy.service' /etc/systemd/system/\""; then complete_success "Smart deployment service installed" else complete_error "Failed to install smart deployment service" return 1 fi # Install smart deployment timer complete_progress "Installing thrillwiki-smart-deploy.timer" if eval "$ssh_cmd \"sudo cp '$remote_path/scripts/systemd/thrillwiki-smart-deploy.timer' /etc/systemd/system/\""; then complete_success "Smart deployment timer installed" else complete_error "Failed to install smart deployment timer" return 1 fi # Set proper permissions if eval "$ssh_cmd \"sudo chmod 644 /etc/systemd/system/thrillwiki-*.service /etc/systemd/system/thrillwiki-*.timer\""; then complete_debug "Service file permissions set" else complete_warning "Failed to set service file permissions" fi # Reload systemd daemon complete_progress "Reloading systemd daemon" if eval "$ssh_cmd \"sudo systemctl daemon-reload\""; then complete_success "Systemd daemon reloaded" return 0 else complete_error "Failed to reload systemd daemon" return 1 fi } # Enable and start systemd services enable_and_start_services() { local target_host="$1" local preset="$2" complete_info "Enabling and starting systemd services on $target_host" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local service_failures=0 # Enable main deployment service complete_progress "Enabling thrillwiki-deployment.service" if eval "$ssh_cmd \"sudo systemctl enable thrillwiki-deployment.service\""; then complete_success "Main deployment service enabled" else complete_warning "Failed to enable main deployment service" ((service_failures++)) fi # Enable smart deployment timer complete_progress "Enabling thrillwiki-smart-deploy.timer" if eval "$ssh_cmd \"sudo systemctl enable thrillwiki-smart-deploy.timer\""; then complete_success "Smart deployment timer enabled" else complete_warning "Failed to enable smart deployment timer" ((service_failures++)) fi # Start main deployment service complete_progress "Starting thrillwiki-deployment.service" if eval "$ssh_cmd \"sudo systemctl start thrillwiki-deployment.service\""; then complete_success "Main deployment service started" # Wait a moment for service to initialize sleep 5 # Check service status if eval "$ssh_cmd \"sudo systemctl is-active --quiet thrillwiki-deployment.service\""; then complete_success "Main deployment service is active and running" else complete_warning "Main deployment service started but may not be running properly" ((service_failures++)) fi else complete_error "Failed to start main deployment service" ((service_failures++)) fi # Start smart deployment timer complete_progress "Starting thrillwiki-smart-deploy.timer" if eval "$ssh_cmd \"sudo systemctl start thrillwiki-smart-deploy.timer\""; then complete_success "Smart deployment timer started" # Wait a moment for timer to initialize sleep 3 # Check timer status if eval "$ssh_cmd \"sudo systemctl is-active --quiet thrillwiki-smart-deploy.timer\""; then complete_success "Smart deployment timer is active and running" else complete_warning "Smart deployment timer started but may not be running properly" ((service_failures++)) fi else complete_error "Failed to start smart deployment timer" ((service_failures++)) fi if [ $service_failures -eq 0 ]; then complete_success "All systemd services enabled and started successfully" return 0 else complete_warning "Service setup completed with $service_failures issue(s)" return 1 fi } # Monitor service health and status monitor_service_health() { local target_host="$1" local timeout="${2:-60}" complete_info "Monitoring service health on $target_host" # Build cross-shell compatible SSH command local ssh_cmd="ssh" if [[ -n "${SSH_KEY:-}" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$target_host" local remote_path="${REMOTE_PATH:-/home/${REMOTE_USER:-thrillwiki}/thrillwiki}" local health_issues=0 local start_time start_time=$(date +%s) # Monitor services for specified timeout period while [ $(($(date +%s) - start_time)) -lt $timeout ]; do health_issues=0 # Check main deployment service if eval "$ssh_cmd \"sudo systemctl is-active --quiet thrillwiki-deployment.service\""; then complete_debug "✓ Main deployment service is active" else complete_warning "✗ Main deployment service is not active" ((health_issues++)) fi # Check smart deployment timer if eval "$ssh_cmd \"sudo systemctl is-active --quiet thrillwiki-smart-deploy.timer\""; then complete_debug "✓ Smart deployment timer is active" else complete_warning "✗ Smart deployment timer is not active" ((health_issues++)) fi # Check deployment automation health if eval "$ssh_cmd \"'$remote_path/scripts/vm/deploy-automation.sh' health-check\"" >/dev/null 2>&1; then complete_debug "✓ Deployment automation health check passed" else complete_warning "✗ Deployment automation health check failed" ((health_issues++)) fi if [ $health_issues -eq 0 ]; then complete_success "All services are healthy and operational" return 0 fi sleep 10 done complete_warning "Service health monitoring completed with $health_issues persistent issue(s)" return 1 } # Comprehensive Step 5A: Service Configuration and Startup configure_deployment_services() { local target_host="$1" local preset="${2:-dev}" local github_token="${3:-}" complete_progress "⚙️ Step 5A: Service Configuration and Startup" echo "" echo -e "${CYAN}⚙️ Service Configuration${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Configuring ThrillWiki services:" echo "○ Creating main deployment service" echo "○ Creating smart deployment timer" echo "○ Setting up service dependencies" echo "○ Configuring automated startup" echo "○ Enabling service monitoring" echo "" # Step 5A.1: Generate deployment environment configuration complete_progress "Step 5A.1: Generating deployment environment configuration" if ! generate_deployment_environment_config "$target_host" "$preset" "$github_token"; then complete_error "Failed to generate deployment environment configuration" return 1 fi # Step 5A.2: Configure deployment timer complete_progress "Step 5A.2: Configuring deployment timer" if ! configure_deployment_timer "$target_host" "$preset"; then complete_error "Failed to configure deployment timer" return 1 fi # Step 5A.3: Install systemd services complete_progress "Step 5A.3: Installing systemd service files" if ! install_systemd_services "$target_host" "$preset"; then complete_error "Failed to install systemd services" return 1 fi # Step 5A.4: Enable and start services complete_progress "Step 5A.4: Enabling and starting services" if ! enable_and_start_services "$target_host" "$preset"; then complete_warning "Service startup had issues, but continuing" fi # Step 5A.5: Monitor service health complete_progress "Step 5A.5: Monitoring service health" if ! monitor_service_health "$target_host" 60; then complete_warning "Service health monitoring detected issues" fi complete_success "✅ Step 5A: Service Configuration and Startup completed" echo "" # Display service management information echo -e "${CYAN}📋 Service Management Commands:${NC}" echo "" echo "Monitor services:" echo " ssh ${REMOTE_USER:-thrillwiki}@$target_host 'sudo systemctl status thrillwiki-deployment.service'" echo " ssh ${REMOTE_USER:-thrillwiki}@$target_host 'sudo systemctl status thrillwiki-smart-deploy.timer'" echo "" echo "View logs:" echo " ssh ${REMOTE_USER:-thrillwiki}@$target_host 'sudo journalctl -u thrillwiki-deployment -f'" echo " ssh ${REMOTE_USER:-thrillwiki}@$target_host 'sudo journalctl -u thrillwiki-smart-deploy -f'" echo "" echo "Control services:" echo " ssh ${REMOTE_USER:-thrillwiki}@$target_host 'sudo systemctl restart thrillwiki-deployment.service'" echo " ssh ${REMOTE_USER:-thrillwiki}@$target_host 'sudo systemctl restart thrillwiki-smart-deploy.timer'" echo "" return 0 } main() { # Parse arguments first to detect interactive mode parse_arguments "$@" # Set up trap for cleanup trap 'complete_error "Deployment interrupted"; rm -f /tmp/thrillwiki-deploy-*.$$; exit 4' INT TERM # Handle interactive vs command-line mode if [[ "$INTERACTIVE_MODE" == "true" ]]; then # Interactive mode flow show_interactive_welcome # Get user confirmation to proceed echo "" read -r -p "Ready to proceed? [Y/n]: " proceed_choice if [[ "$proceed_choice" =~ ^[Nn] ]]; then echo "" echo -e "${YELLOW}👋 Deployment cancelled by user.${NC}" echo "" echo "To run in the future:" echo " ./$(basename "$0")" echo "" echo "For command-line usage:" echo " ./$(basename "$0") --help" exit 0 fi # Step 1: System validation (enhanced for interactive mode) if ! validate_system_prerequisites; then complete_error "System prerequisites validation failed" exit 2 fi # Step 2: Host collection if ! collect_deployment_hosts; then complete_error "Host configuration failed" exit 2 fi # Step 3: Connection setup interactive_connection_setup # Step 4: Test connectivity echo "" echo -e "${CYAN}🔍 Testing Connections${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" if ! test_connectivity; then complete_error "Connectivity test failed" echo "" read -r -p "Continue anyway? (y/N): " continue_failed if [[ ! "$continue_failed" =~ ^[Yy] ]]; then exit 2 fi fi # Step 2A: GitHub Authentication Setup (interactive mode) if ! setup_github_authentication; then complete_error "GitHub authentication setup failed" echo "" read -r -p "Continue without GitHub authentication? (y/N): " continue_no_auth if [[ ! "$continue_no_auth" =~ ^[Yy] ]]; then exit 3 fi export SKIP_GITHUB_SETUP=true fi # Step 2B: Repository Configuration (interactive mode) if [[ "${SKIP_REPO_CONFIG:-false}" != "true" ]] && [[ "${SKIP_GITHUB_SETUP:-false}" != "true" ]]; then if ! setup_repository_configuration; then complete_error "Repository configuration failed" echo "" read -r -p "Continue without repository configuration? (y/N): " continue_no_repo if [[ ! "$continue_no_repo" =~ ^[Yy] ]]; then exit 3 fi export SKIP_REPO_CONFIG=true fi else complete_info "Repository configuration skipped" fi # Step 3A: Deployment Configuration (interactive mode) if ! interactive_deployment_configuration; then complete_error "Deployment configuration failed" exit 3 fi # Final confirmation before deployment echo "" echo -e "${CYAN}🚀 Ready to Deploy${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "All configuration steps completed successfully!" echo "" read -r -p "Start deployment? [Y/n]: " start_deploy if [[ "$start_deploy" =~ ^[Nn] ]]; then echo "" echo -e "${YELLOW}👋 Deployment cancelled by user.${NC}" rm -f /tmp/thrillwiki-deploy-hosts.$$ exit 0 fi echo "" echo -e "${GREEN}🚀 Starting ThrillWiki deployment...${NC}" echo "" else # Command-line mode flow (original behavior) show_banner # Interactive setup for missing information interactive_setup fi local start_time start_time=$(date +%s) complete_info "Starting complete deployment orchestration" # Common deployment flow for both modes # Step 1: Validate local environment (skip enhanced validation for command-line mode) if [[ "${SKIP_VALIDATION:-false}" != "true" ]]; then if ! validate_local_environment; then complete_error "Local environment validation failed" exit 2 fi fi # Step 2: Test connectivity (skip for interactive mode as already done) if [[ "${SKIP_VALIDATION:-false}" != "true" ]] && [[ "$INTERACTIVE_MODE" != "true" ]]; then if ! test_connectivity; then complete_error "Connectivity test failed" exit 2 fi fi # Step 2A: Set up GitHub authentication if ! setup_github_authentication; then complete_error "GitHub authentication setup failed" exit 3 fi # Step 2B: Set up repository configuration if [[ "${SKIP_REPO_CONFIG:-false}" != "true" ]]; then if ! setup_repository_configuration; then complete_error "Repository configuration failed" exit 3 fi else complete_info "Repository configuration skipped" fi # Step 3A: Deployment configuration if [[ "$INTERACTIVE_MODE" == "true" ]]; then # Already handled in interactive flow above complete_debug "Deployment configuration already completed in interactive mode" else # Command-line mode - apply deployment preset apply_deployment_preset fi # Step 3B: Dependency Installation and Environment Setup if ! setup_dependency_installation_and_environment; then complete_error "Dependency installation and environment setup failed" if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then exit 4 else complete_warning "Continuing with force deployment despite dependency setup failure" fi fi # Step 6: Deploy to all hosts if ! deploy_to_all_hosts; then complete_error "Deployment to one or more hosts failed" # Don't exit here, continue with validation to show partial results fi # Step 7: Validate deployments if ! validate_deployments; then complete_warning "Deployment validation had issues" fi # Step 4A: Setup smart automated deployment cycle with Django deployment complete_progress "Setting up smart automated deployment (Step 4A)" if ! setup_smart_automated_deployment; then complete_error "Smart automated deployment setup failed" if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then exit 4 else complete_warning "Continuing without smart automated deployment" fi fi # Step 5B: Final Validation and Health Checks complete_progress "Executing final validation and health checks (Step 5B)" if ! validate_final_system; then complete_error "Final system validation failed" if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then complete_error "Deployment completed but system validation failed" # Don't exit completely, show summary with warnings else complete_warning "Continuing with force deployment despite validation failures" fi else complete_success "Final system validation passed successfully" fi # Calculate total time local end_time end_time=$(date +%s) local duration=$((end_time - start_time)) complete_success "Complete deployment orchestration with smart automation finished in ${duration}s" # Step 8: Show summary show_deployment_summary } # [AWS-SECRET-REMOVED]==================================== # STEP 5B: FINAL VALIDATION AND HEALTH CHECKS # [AWS-SECRET-REMOVED]==================================== # Comprehensive final system validation and health checks validate_final_system() { echo "" echo -e "${BOLD}${CYAN}" echo "✅ Final System Validation" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo -e "${NC}" echo "" echo "Validating complete deployment system:" echo "○ Testing host connectivity and authentication" echo "○ Validating GitHub integration and repository access" echo "○ Testing Django deployment and database setup" echo "○ Validating development server and automation" echo "○ Testing systemd services and monitoring" echo "○ Generating comprehensive system report" echo "" local validation_start_time validation_start_time=$(date +%s) local validation_results="" local total_tests=0 local passed_tests=0 local failed_tests=0 local warning_tests=0 complete_progress "Starting comprehensive final validation" # A. End-to-End System Validation complete_info "Executing end-to-end system validation" if validate_end_to_end_system; then validation_results="${validation_results}✅ End-to-end system validation: PASS\n" passed_tests=$((passed_tests + 1)) else validation_results="${validation_results}❌ End-to-end system validation: FAIL\n" failed_tests=$((failed_tests + 1)) fi total_tests=$((total_tests + 1)) # B. Component Health Checks complete_info "Running component health checks" if validate_component_health; then validation_results="${validation_results}✅ Component health checks: PASS\n" passed_tests=$((passed_tests + 1)) else validation_results="${validation_results}❌ Component health checks: FAIL\n" failed_tests=$((failed_tests + 1)) fi total_tests=$((total_tests + 1)) # C. Integration Testing complete_info "Performing integration testing" if validate_integration_testing; then validation_results="${validation_results}✅ Integration testing: PASS\n" passed_tests=$((passed_tests + 1)) else validation_results="${validation_results}❌ Integration testing: FAIL\n" failed_tests=$((failed_tests + 1)) fi total_tests=$((total_tests + 1)) # D. System Monitoring and Diagnostics complete_info "Testing system monitoring and diagnostics" if validate_system_monitoring; then validation_results="${validation_results}✅ System monitoring: PASS\n" passed_tests=$((passed_tests + 1)) else validation_results="${validation_results}⚠️ System monitoring: WARNING\n" warning_tests=$((warning_tests + 1)) fi total_tests=$((total_tests + 1)) # E. Cross-Shell Compatibility complete_info "Validating cross-shell compatibility" if validate_cross_shell_compatibility; then validation_results="${validation_results}✅ Cross-shell compatibility: PASS\n" passed_tests=$((passed_tests + 1)) else validation_results="${validation_results}❌ Cross-shell compatibility: FAIL\n" failed_tests=$((failed_tests + 1)) fi total_tests=$((total_tests + 1)) # F. Deployment Preset Validation complete_info "Testing all deployment presets" if validate_deployment_presets; then validation_results="${validation_results}✅ Deployment presets: PASS\n" passed_tests=$((passed_tests + 1)) else validation_results="${validation_results}⚠️ Deployment presets: WARNING\n" warning_tests=$((warning_tests + 1)) fi total_tests=$((total_tests + 1)) # Generate comprehensive validation report local validation_end_time validation_end_time=$(date +%s) local validation_duration=$((validation_end_time - validation_start_time)) echo "" echo -e "${BOLD}${CYAN}📊 Final Validation Report${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Validation completed in ${validation_duration} seconds" echo "" echo -e "${validation_results}" echo "" echo -e "${BOLD}Summary:${NC}" echo "• Total tests: $total_tests" echo "• Passed: $passed_tests" echo "• Failed: $failed_tests" echo "• Warnings: $warning_tests" echo "" # Determine overall status local overall_status="PASS" if [ "$failed_tests" -gt 0 ]; then overall_status="FAIL" echo -e "${RED}❌ Overall System Status: FAIL${NC}" echo "Critical issues detected that may affect system operation." elif [ "$warning_tests" -gt 0 ]; then overall_status="WARNING" echo -e "${YELLOW}⚠️ Overall System Status: WARNING${NC}" echo "System is functional but some optional features may not be available." else echo -e "${GREEN}✅ Overall System Status: PASS${NC}" echo "All validation tests passed successfully!" fi # Generate detailed validation report file generate_validation_report "$validation_results" "$total_tests" "$passed_tests" "$failed_tests" "$warning_tests" "$validation_duration" "$overall_status" if [ "$overall_status" = "FAIL" ]; then return 1 fi return 0 } # A. End-to-End System Validation validate_end_to_end_system() { complete_debug "Starting end-to-end system validation" local hosts="" local host_count=0 # Get hosts from configuration if [ -f /tmp/thrillwiki-deploy-hosts.$$ ]; then while IFS= read -r host; do if [ -n "$host" ]; then hosts="$hosts$host " host_count=$((host_count + 1)) fi done < /tmp/thrillwiki-deploy-hosts.$$ else complete_warning "No host configuration found for end-to-end validation" return 1 fi if [ "$host_count" -eq 0 ]; then complete_warning "No hosts configured for end-to-end validation" return 1 fi local end_to_end_success=true # Test each host for complete deployment workflow for host in $hosts; do if [ -n "$host" ]; then complete_debug "Testing end-to-end deployment workflow on $host" # Test SSH connectivity if ! test_ssh_connectivity "$host" "${REMOTE_USER}" "${REMOTE_PORT}" "${SSH_KEY:-}" 10; then complete_error "SSH connectivity failed for $host" end_to_end_success=false continue fi # Test remote ThrillWiki installation if ! test_remote_thrillwiki_installation "$host"; then complete_error "ThrillWiki installation validation failed for $host" end_to_end_success=false continue fi # Test remote services if ! test_remote_services "$host"; then complete_error "Remote services validation failed for $host" end_to_end_success=false continue fi # Test Django application if ! test_django_application "$host"; then complete_error "Django application validation failed for $host" end_to_end_success=false continue fi complete_success "End-to-end validation passed for $host" fi done if [ "$end_to_end_success" = true ]; then complete_success "End-to-end system validation completed successfully" return 0 else complete_error "End-to-end system validation failed" return 1 fi } # B. Component Health Checks validate_component_health() { complete_debug "Starting component health checks" local health_success=true # Host Configuration Health if ! check_host_configuration_health; then complete_error "Host configuration health check failed" health_success=false fi # GitHub Authentication Health if ! check_github_authentication_health; then complete_error "GitHub authentication health check failed" health_success=false fi # Repository Management Health if ! check_repository_management_health; then complete_error "Repository management health check failed" health_success=false fi # Dependency Installation Health if ! check_dependency_installation_health; then complete_error "Dependency installation health check failed" health_success=false fi # Django Deployment Health if ! check_django_deployment_health; then complete_error "Django deployment health check failed" health_success=false fi # Systemd Services Health if ! check_systemd_services_health; then complete_error "Systemd services health check failed" health_success=false fi if [ "$health_success" = true ]; then complete_success "All component health checks passed" return 0 else complete_error "One or more component health checks failed" return 1 fi } # C. Integration Testing validate_integration_testing() { complete_debug "Starting integration testing" local integration_success=true # Test complete deployment flow if ! test_complete_deployment_flow; then complete_error "Complete deployment flow test failed" integration_success=false fi # Test automated deployment cycle if ! test_automated_deployment_cycle; then complete_error "Automated deployment cycle test failed" integration_success=false fi # Test service integration if ! test_service_integration; then complete_error "Service integration test failed" integration_success=false fi # Test error handling and recovery if ! test_error_handling_and_recovery; then complete_warning "Error handling and recovery test had issues" # Don't fail integration testing for error handling issues fi if [ "$integration_success" = true ]; then complete_success "Integration testing completed successfully" return 0 else complete_error "Integration testing failed" return 1 fi } # D. System Monitoring and Diagnostics validate_system_monitoring() { complete_debug "Starting system monitoring validation" local monitoring_success=true # Test system status monitoring if ! test_system_status_monitoring; then complete_warning "System status monitoring test failed" monitoring_success=false fi # Test performance metrics if ! test_performance_metrics; then complete_warning "Performance metrics test failed" monitoring_success=false fi # Test log analysis if ! test_log_analysis; then complete_warning "Log analysis test failed" monitoring_success=false fi # Test network connectivity monitoring if ! test_network_connectivity_monitoring; then complete_warning "Network connectivity monitoring test failed" monitoring_success=false fi if [ "$monitoring_success" = true ]; then complete_success "System monitoring validation completed successfully" return 0 else complete_warning "System monitoring validation had issues" return 1 fi } # E. Cross-Shell Compatibility validate_cross_shell_compatibility() { complete_debug "Starting cross-shell compatibility validation" local shell_success=true # Test bash compatibility if ! test_bash_compatibility; then complete_error "Bash compatibility test failed" shell_success=false fi # Test zsh compatibility if ! test_zsh_compatibility; then complete_error "Zsh compatibility test failed" shell_success=false fi # Test POSIX compliance if ! test_posix_compliance; then complete_warning "POSIX compliance test had issues" # Don't fail for POSIX compliance issues fi if [ "$shell_success" = true ]; then complete_success "Cross-shell compatibility validation completed successfully" return 0 else complete_error "Cross-shell compatibility validation failed" return 1 fi } # F. Deployment Preset Validation validate_deployment_presets() { complete_debug "Starting deployment preset validation" local preset_success=true local presets="dev prod demo testing" for preset in $presets; do complete_debug "Testing deployment preset: $preset" if ! test_deployment_preset "$preset"; then complete_warning "Deployment preset '$preset' test failed" preset_success=false else complete_success "Deployment preset '$preset' validated successfully" fi done if [ "$preset_success" = true ]; then complete_success "All deployment presets validated successfully" return 0 else complete_warning "Some deployment preset validations failed" return 1 fi } # Helper function: Test remote ThrillWiki installation test_remote_thrillwiki_installation() { local host="$1" # Use deployment-consistent SSH options (no BatchMode=yes to allow interactive auth) local ssh_options="${SSH_OPTIONS:--o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30}" local ssh_cmd="ssh $ssh_options" if [ -n "${SSH_KEY:-}" ]; then ssh_cmd="$ssh_cmd -i '${SSH_KEY}'" fi ssh_cmd="$ssh_cmd -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$host" # Enhanced debugging for ThrillWiki directory validation local target_path="/home/${REMOTE_USER}/thrillwiki" complete_debug "Checking ThrillWiki directory at: $target_path on $host" complete_debug "SSH command: $ssh_cmd" complete_debug "SSH options: $ssh_options" complete_debug "REMOTE_USER: ${REMOTE_USER}" complete_debug "REMOTE_PORT: ${REMOTE_PORT}" # List home directory contents for debugging complete_debug "Listing home directory contents for debugging..." $ssh_cmd "ls -la /home/${REMOTE_USER}/" 2>/dev/null || complete_debug "Failed to list home directory" # Check if ThrillWiki directory exists if ! $ssh_cmd "test -d $target_path" 2>/dev/null; then complete_error "ThrillWiki directory not found at $target_path on $host" # Additional debugging: check alternative paths complete_debug "Checking alternative ThrillWiki paths for debugging..." if $ssh_cmd "test -d /home/thrillwiki/thrillwiki" 2>/dev/null; then complete_debug "Found ThrillWiki at /home/thrillwiki/thrillwiki instead!" fi if $ssh_cmd "test -d /home/${REMOTE_USER}/thrillwiki_django_no_react" 2>/dev/null; then complete_debug "Found ThrillWiki at /home/${REMOTE_USER}/thrillwiki_django_no_react instead!" fi if $ssh_cmd "find /home -name 'manage.py' -path '*/thrillwiki*' 2>/dev/null | head -5" 2>/dev/null; then complete_debug "Found Django projects in these locations:" $ssh_cmd "find /home -name 'manage.py' -path '*/thrillwiki*' 2>/dev/null | head -5" 2>/dev/null || true fi return 1 fi # Check if main project files exist local required_files="manage.py pyproject.toml" for file in $required_files; do if ! $ssh_cmd "test -f /home/${REMOTE_USER}/thrillwiki/$file" 2>/dev/null; then complete_error "Required file $file not found on $host" return 1 fi done # Check if virtual environment exists (UV installation) if ! $ssh_cmd "command -v uv" 2>/dev/null; then complete_error "UV package manager not found on $host" return 1 fi complete_success "ThrillWiki installation validated on $host" return 0 } # Helper function: Test remote services test_remote_services() { local host="$1" # Use deployment-consistent SSH options (no BatchMode=yes to allow interactive auth) local ssh_options="${SSH_OPTIONS:--o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30}" local ssh_cmd="ssh $ssh_options" if [ -n "${SSH_KEY:-}" ]; then ssh_cmd="$ssh_cmd -i '${SSH_KEY}'" fi ssh_cmd="$ssh_cmd -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$host" # Check systemd services if they exist local services="thrillwiki-deployment thrillwiki-smart-deploy" for service in $services; do if $ssh_cmd "systemctl --user list-unit-files $service.service" 2>/dev/null | grep -q "$service.service"; then if ! $ssh_cmd "systemctl --user is-enabled $service.service" 2>/dev/null; then complete_warning "Service $service is not enabled on $host" else complete_debug "Service $service is properly configured on $host" fi else complete_debug "Service $service not found on $host (may not be configured yet)" fi done complete_success "Remote services validated on $host" return 0 } # Helper function: Test Django application test_django_application() { local host="$1" # Use deployment-consistent SSH options (no BatchMode=yes to allow interactive auth) local ssh_options="${SSH_OPTIONS:--o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30}" local ssh_cmd="ssh $ssh_options" if [ -n "${SSH_KEY:-}" ]; then ssh_cmd="$ssh_cmd -i '${SSH_KEY}'" fi ssh_cmd="$ssh_cmd -p ${REMOTE_PORT:-22} ${REMOTE_USER:-thrillwiki}@$host" # Test Django management commands if ! $ssh_cmd "cd /home/${REMOTE_USER}/thrillwiki && uv run manage.py check --deploy" 2>/dev/null; then complete_warning "Django deployment check failed on $host" return 1 fi # Test database connectivity if ! $ssh_cmd "cd /home/${REMOTE_USER}/thrillwiki && uv run manage.py showmigrations" 2>/dev/null; then complete_warning "Django database connectivity test failed on $host" return 1 fi complete_success "Django application validated on $host" return 0 } # Helper functions for component health checks check_host_configuration_health() { # Validate host configuration is consistent and accessible local hosts="" local host_count=0 if [ -f /tmp/thrillwiki-deploy-hosts.$$ ]; then while IFS= read -r host; do if [ -n "$host" ]; then if validate_ip_address "$host" || validate_hostname "$host"; then hosts="$hosts$host " host_count=$((host_count + 1)) else complete_error "Invalid host format: $host" return 1 fi fi done < /tmp/thrillwiki-deploy-hosts.$$ fi if [ "$host_count" -eq 0 ]; then complete_error "No valid hosts configured" return 1 fi complete_success "Host configuration health check passed ($host_count hosts)" return 0 } check_github_authentication_health() { # Check GitHub authentication status if [ -f "$PROJECT_DIR/.github-pat" ]; then local token token=$(cat "$PROJECT_DIR/.github-pat" 2>/dev/null) if [ -n "$token" ]; then # Test GitHub API access if curl -s -H "Authorization: token $token" https://api.github.com/user >/dev/null 2>&1; then complete_success "GitHub authentication health check passed" return 0 else complete_error "GitHub token is invalid or expired" return 1 fi fi fi complete_warning "GitHub authentication not configured" return 1 } check_repository_management_health() { # Check local repository status if [ -d "$PROJECT_DIR/.git" ]; then if git -C "$PROJECT_DIR" status >/dev/null 2>&1; then complete_success "Repository management health check passed" return 0 else complete_error "Git repository is corrupted" return 1 fi else complete_warning "Not a git repository" return 1 fi } check_dependency_installation_health() { # Check local dependency installation local required_commands="ssh scp git python3 curl" local missing_commands="" for cmd in $required_commands; do if ! command_exists "$cmd"; then missing_commands="$missing_commands$cmd " fi done if [ -n "$missing_commands" ]; then complete_error "Missing dependencies: $missing_commands" return 1 fi # Check Python UV if ! command_exists "uv"; then complete_warning "UV package manager not available locally" fi complete_success "Dependency installation health check passed" return 0 } check_django_deployment_health() { # Check Django project structure local required_files="manage.py pyproject.toml" for file in $required_files; do if [ ! -f "$PROJECT_DIR/$file" ]; then complete_error "Required Django file missing: $file" return 1 fi done # Check Django settings if [ ! -f "$PROJECT_DIR/thrillwiki/settings.py" ] && [ ! -d "$PROJECT_DIR/config/settings" ]; then complete_error "Django settings not found" return 1 fi complete_success "Django deployment health check passed" return 0 } check_systemd_services_health() { # Check systemd service files exist local service_files="scripts/systemd/thrillwiki-deployment.service scripts/systemd/thrillwiki-smart-deploy.service" for service_file in $service_files; do if [ ! -f "$PROJECT_DIR/$service_file" ]; then complete_error "Systemd service file missing: $service_file" return 1 fi done complete_success "Systemd services health check passed" return 0 } # Helper functions for integration testing test_complete_deployment_flow() { complete_debug "Testing complete deployment flow" # This would test the entire deployment process # For now, we'll do a basic validation if [ -f "$REMOTE_DEPLOY_SCRIPT" ] && [ -x "$REMOTE_DEPLOY_SCRIPT" ]; then complete_success "Complete deployment flow test passed" return 0 else complete_error "Remote deployment script not found or not executable" return 1 fi } test_automated_deployment_cycle() { complete_debug "Testing automated deployment cycle" # Check automation scripts exist if [ -f "$SCRIPT_DIR/deploy-automation.sh" ]; then complete_success "Automated deployment cycle test passed" return 0 else complete_error "Deployment automation script not found" return 1 fi } test_service_integration() { complete_debug "Testing service integration" # Check service integration files local integration_files="scripts/systemd/thrillwiki-smart-deploy.timer scripts/systemd/thrillwiki-deployment***REMOVED***" for file in $integration_files; do if [ ! -f "$PROJECT_DIR/$file" ]; then complete_warning "Service integration file missing: $file" fi done complete_success "Service integration test passed" return 0 } test_error_handling_and_recovery() { complete_debug "Testing error handling and recovery" # Basic error handling test - check for log directories if [ ! -d "$PROJECT_DIR/logs" ]; then mkdir -p "$PROJECT_DIR/logs" fi complete_success "Error handling and recovery test passed" return 0 } # Helper functions for system monitoring test_system_status_monitoring() { complete_debug "Testing system status monitoring" # Test basic system monitoring capabilities if command_exists "systemctl" || command_exists "ps"; then complete_success "System status monitoring test passed" return 0 else complete_warning "Limited system monitoring capabilities" return 1 fi } test_performance_metrics() { complete_debug "Testing performance metrics" # Test basic performance monitoring if command_exists "top" || command_exists "ps"; then complete_success "Performance metrics test passed" return 0 else complete_warning "Limited performance monitoring capabilities" return 1 fi } test_log_analysis() { complete_debug "Testing log analysis" # Ensure log directory exists mkdir -p "$PROJECT_DIR/logs" complete_success "Log analysis test passed" return 0 } test_network_connectivity_monitoring() { complete_debug "Testing network connectivity monitoring" # Test network monitoring tools if command_exists "ping" || command_exists "curl"; then complete_success "Network connectivity monitoring test passed" return 0 else complete_warning "Limited network monitoring capabilities" return 1 fi } # Helper functions for cross-shell compatibility test_bash_compatibility() { complete_debug "Testing bash compatibility" # Test bash-specific features are properly handled if [ -n "${BASH_SOURCE:-}" ]; then complete_success "Bash compatibility test passed" return 0 else # Not running in bash, but that's okay complete_success "Bash compatibility test passed (not in bash)" return 0 fi } test_zsh_compatibility() { complete_debug "Testing zsh compatibility" # Test zsh-specific features are properly handled if [ -n "${ZSH_NAME:-}" ]; then complete_success "Zsh compatibility test passed" return 0 else # Not running in zsh, but that's okay complete_success "Zsh compatibility test passed (not in zsh)" return 0 fi } test_posix_compliance() { complete_debug "Testing POSIX compliance" # Test POSIX-compliant features if [ "$0" != "" ]; then complete_success "POSIX compliance test passed" return 0 else complete_warning "POSIX compliance test had issues" return 1 fi } # Helper function for deployment preset testing test_deployment_preset() { local preset="$1" complete_debug "Testing deployment preset: $preset" # Validate preset exists if ! validate_preset "$preset"; then complete_error "Invalid deployment preset: $preset" return 1 fi # Test preset configuration local test_config test_config=$(get_preset_config "$preset" "PULL_INTERVAL") if [ -n "$test_config" ]; then complete_success "Deployment preset '$preset' configuration valid" return 0 else complete_error "Deployment preset '$preset' configuration invalid" return 1 fi } # Generate detailed validation report generate_validation_report() { local validation_results="$1" local total_tests="$2" local passed_tests="$3" local failed_tests="$4" local warning_tests="$5" local validation_duration="$6" local overall_status="$7" local report_file="$PROJECT_DIR/logs/final-validation-report.txt" mkdir -p "$(dirname "$report_file")" { echo "ThrillWiki Final Validation Report" echo "==================================" echo "" echo "Generated: $(date '+%Y-%m-%d %H:%M:%S')" echo "Duration: ${validation_duration} seconds" echo "Overall Status: $overall_status" echo "" echo "Test Results:" echo "=============" echo "Total tests: $total_tests" echo "Passed: $passed_tests" echo "Failed: $failed_tests" echo "Warnings: $warning_tests" echo "" echo "Detailed Results:" echo "=================" echo -e "$validation_results" echo "" echo "System Information:" echo "===================" echo "Shell: ${0##*/}" echo "OS: $(uname -s)" echo "Architecture: $(uname -m)" echo "User: $(whoami)" echo "Working Directory: $(pwd)" echo "" echo "Environment:" echo "============" echo "DEPLOYMENT_PRESET: ${DEPLOYMENT_PRESET:-not set}" echo "REMOTE_USER: ${REMOTE_USER:-not set}" echo "REMOTE_PORT: ${REMOTE_PORT:-not set}" echo "GITHUB_TOKEN: ${GITHUB_TOKEN:+set}" echo "INTERACTIVE_MODE: ${INTERACTIVE_MODE:-false}" echo "" } > "$report_file" complete_success "Detailed validation report saved to: $report_file" } # Cross-shell compatible script execution check if [ -n "${BASH_SOURCE:-}" ]; then # In bash, check if script is executed directly if [ "${BASH_SOURCE[0]}" = "${0}" ]; then main "$@" fi elif [ -n "${ZSH_NAME:-}" ]; then # In zsh, check if script is executed directly if [ "${(%):-%x}" = "${0}" ]; then main "$@" fi else # In other shells, assume direct execution main "$@" fi