mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:11:13 -05:00
- Add complete backend/ directory with full Django application - Add frontend/ directory with Vite + TypeScript setup ready for Next.js - Add comprehensive shared/ directory with: - Complete documentation and memory-bank archives - Media files and avatars (letters, park/ride images) - Deployment scripts and automation tools - Shared types and utilities - Add architecture/ directory with migration guides - Configure pnpm workspace for monorepo development - Update .gitignore to exclude .django_tailwind_cli/ build artifacts - Preserve all historical documentation in shared/docs/memory-bank/ - Set up proper structure for full-stack development with shared resources
7145 lines
253 KiB
Bash
Executable File
7145 lines
253 KiB
Bash
Executable File
#!/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_host> [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_host> [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}@<host>"
|
||
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 |