#!/bin/bash # # ThrillWiki Remote Deployment Script # Bulletproof deployment of automation system to remote VM via SSH/SCP # # Features: # - SSH/SCP-based remote deployment with connection testing # - Complete automation system deployment with GitHub auth integration # - Automatic pull scheduling configuration and activation # - Comprehensive error handling with rollback capabilities # - Real-time deployment progress and validation # - Health monitoring and status reporting # - Support for multiple VM targets and configurations # set -e # [AWS-SECRET-REMOVED]==================================== # SCRIPT CONFIGURATION # [AWS-SECRET-REMOVED]==================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" # Remote deployment configuration REMOTE_USER="${REMOTE_USER:-thrillwiki}" REMOTE_HOST="${REMOTE_HOST:-}" REMOTE_PORT="${REMOTE_PORT:-22}" REMOTE_PATH="${REMOTE_PATH:-/home/$REMOTE_USER/thrillwiki}" SSH_KEY="${SSH_KEY:-}" SSH_OPTIONS="${SSH_OPTIONS:--o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30}" # Deployment configuration DEPLOYMENT_TIMEOUT="${DEPLOYMENT_TIMEOUT:-1800}" # 30 minutes CONNECTION_RETRY_COUNT="${CONNECTION_RETRY_COUNT:-3}" CONNECTION_RETRY_DELAY="${CONNECTION_RETRY_DELAY:-10}" HEALTH_CHECK_TIMEOUT="${HEALTH_CHECK_TIMEOUT:-300}" # 5 minutes # Local source files to deploy declare -a DEPLOY_FILES=( "scripts/vm/bulletproof-automation.sh" "scripts/vm/setup-automation.sh" "scripts/vm/automation-config.sh" "scripts/vm/github-setup.py" "scripts/vm/quick-start.sh" "scripts/systemd/thrillwiki-automation.service" "scripts/systemd/thrillwiki-automation***REMOVED***.example" "manage.py" "pyproject.toml" "***REMOVED***.example" ) # Django project configuration DJANGO_PROJECT_SETUP="${DJANGO_PROJECT_SETUP:-true}" DEPLOYMENT_PRESET="${DEPLOYMENT_PRESET:-dev}" # dev, prod, demo, testing # Logging configuration DEPLOY_LOG="$PROJECT_DIR/logs/remote-deploy.log" ROLLBACK_LOG="$PROJECT_DIR/logs/remote-rollback.log" REMOTE_LOG_FILE="/tmp/thrillwiki-remote-deploy.log" # [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]==================================== deploy_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 "$DEPLOY_LOG")" # Log to file (without colors) echo "[$timestamp] [$level] [REMOTE] $message" >> "$DEPLOY_LOG" # Log to console (with colors) echo -e "${color}[$timestamp] [REMOTE-$level]${NC} $message" } deploy_info() { deploy_log "INFO" "$BLUE" "$1" } deploy_success() { deploy_log "SUCCESS" "$GREEN" "βœ… $1" } deploy_warning() { deploy_log "WARNING" "$YELLOW" "⚠️ $1" } deploy_error() { deploy_log "ERROR" "$RED" "❌ $1" } deploy_debug() { if [[ "${DEPLOY_DEBUG:-false}" == "true" ]]; then deploy_log "DEBUG" "$PURPLE" "πŸ” $1" fi } deploy_progress() { deploy_log "PROGRESS" "$CYAN" "πŸš€ $1" } # [AWS-SECRET-REMOVED]==================================== # UTILITY FUNCTIONS # [AWS-SECRET-REMOVED]==================================== command_exists() { command -v "$1" >/dev/null 2>&1 } # Show usage information show_usage() { cat << 'EOF' πŸš€ ThrillWiki Remote Deployment Script DESCRIPTION: Deploys the complete ThrillWiki automation system to a remote VM via SSH/SCP with integrated GitHub authentication and automatic pull scheduling. USAGE: ./remote-deploy.sh [OPTIONS] REQUIRED: remote_host Remote VM hostname or IP address OPTIONS: -u, --user USER Remote username (default: ubuntu) -p, --port PORT SSH port (default: 22) -k, --key PATH SSH private key file path -d, --dest PATH Remote destination path (default: /home/USER/thrillwiki) -t, --timeout SEC Deployment timeout in seconds (default: 1800) --github-token TOK GitHub Personal Access Token for authentication --repo-url URL GitHub repository URL for deployment --repo-branch BRANCH Repository branch to clone (default: main) --preset PRESET Deployment preset: dev, prod, demo, testing (default: dev) --skip-github Skip GitHub authentication setup --skip-repo Skip repository configuration --skip-service Skip systemd service installation --skip-django Skip Django project setup --force Force deployment even if target exists --dry-run Show what would be deployed without executing --debug Enable debug logging -h, --help Show this help message EXAMPLES: # Basic deployment with Django setup ./remote-deploy.sh 192.168.1.100 # Production deployment ./remote-deploy.sh --preset prod 192.168.1.100 # Deployment with custom user and SSH key ./remote-deploy.sh -u admin -k ~/.ssh/***REMOVED*** 192.168.1.100 # Deployment with GitHub token ./remote-deploy.sh --github-token ghp_xxxxx 192.168.1.100 # Skip Django setup (automation only) ./remote-deploy.sh --skip-django 192.168.1.100 # Dry run to see what would be deployed ./remote-deploy.sh --dry-run 192.168.1.100 ENVIRONMENT VARIABLES: REMOTE_USER Default remote username REMOTE_PORT Default SSH port SSH_KEY Default SSH private key path SSH_OPTIONS Additional SSH options GITHUB_TOKEN GitHub Personal Access Token GITHUB_REPO_URL GitHub repository URL DEPLOY_DEBUG Enable debug mode (true/false) DEPENDENCIES: - ssh, scp (OpenSSH client) - git (for repository operations) EXIT CODES: 0 Success 1 General error 2 Connection error 3 Authentication error 4 Deployment error 5 Validation error EOF } # Parse command line arguments parse_arguments() { local skip_github=false local skip_repo=false local skip_service=false local skip_django=false local force_deploy=false local dry_run=false local github_token="" local repo_url="" local repo_branch="main" local deployment_preset="dev" 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 ;; -d|--dest) REMOTE_PATH="$2" shift 2 ;; -t|--timeout) DEPLOYMENT_TIMEOUT="$2" shift 2 ;; --github-token) github_token="$2" export GITHUB_TOKEN="$github_token" shift 2 ;; --repo-url) repo_url="$2" export GITHUB_REPO_URL="$repo_url" shift 2 ;; --repo-branch) repo_branch="$2" export GITHUB_REPO_BRANCH="$repo_branch" shift 2 ;; --preset) deployment_preset="$2" export DEPLOYMENT_PRESET="$deployment_preset" shift 2 ;; --skip-github) skip_github=true export SKIP_GITHUB_SETUP=true shift ;; --skip-repo) skip_repo=true export SKIP_REPO_CONFIG=true shift ;; --skip-service) skip_service=true export SKIP_SERVICE_SETUP=true shift ;; --skip-django) skip_django=true export DJANGO_PROJECT_SETUP=false shift ;; --force) force_deploy=true export FORCE_DEPLOY=true shift ;; --dry-run) dry_run=true export DRY_RUN=true shift ;; --debug) export DEPLOY_DEBUG=true shift ;; -h|--help) show_usage exit 0 ;; -*) deploy_error "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; *) if [[ -z "$REMOTE_HOST" ]]; then REMOTE_HOST="$1" else deploy_error "Multiple hosts specified: $REMOTE_HOST and $1" exit 1 fi shift ;; esac done # Validate required arguments if [[ -z "$REMOTE_HOST" ]]; then deploy_error "Remote host is required" echo "Use: $0 " echo "Use --help for more information" exit 1 fi # Update remote path with actual user REMOTE_PATH="${REMOTE_PATH/\/home\/ubuntu/\/home\/$REMOTE_USER}" deploy_debug "Parsed arguments: user=$REMOTE_USER, host=$REMOTE_HOST, port=$REMOTE_PORT" deploy_debug "Remote path: $REMOTE_PATH" deploy_debug "Repository: url=${GITHUB_REPO_URL:-none}, branch=${GITHUB_REPO_BRANCH:-main}" deploy_debug "Options: skip_github=$skip_github, skip_repo=$skip_repo, skip_service=$skip_service, force=$force_deploy, dry_run=$dry_run" } # [AWS-SECRET-REMOVED]==================================== # SSH CONNECTION MANAGEMENT # [AWS-SECRET-REMOVED]==================================== # Build SSH command with proper options build_ssh_cmd() { local ssh_cmd="ssh" if [[ -n "$SSH_KEY" ]]; then ssh_cmd+=" -i '$SSH_KEY'" fi ssh_cmd+=" $SSH_OPTIONS" ssh_cmd+=" -p $REMOTE_PORT" ssh_cmd+=" $REMOTE_USER@$REMOTE_HOST" echo "$ssh_cmd" } # Build SCP command with proper options build_scp_cmd() { local scp_cmd="scp" if [[ -n "$SSH_KEY" ]]; then scp_cmd+=" -i $SSH_KEY" fi scp_cmd+=" $SSH_OPTIONS" scp_cmd+=" -P $REMOTE_PORT" echo "$scp_cmd" } # Test SSH connection test_ssh_connection() { deploy_info "Testing SSH connection to $REMOTE_USER@$REMOTE_HOST:$REMOTE_PORT" local ssh_cmd ssh_cmd=$(build_ssh_cmd) local retry_count=0 while [[ $retry_count -lt $CONNECTION_RETRY_COUNT ]]; do deploy_debug "Connection attempt $((retry_count + 1))/$CONNECTION_RETRY_COUNT" if eval "$ssh_cmd 'echo \"SSH connection successful\"'" >/dev/null 2>&1; then deploy_success "SSH connection established successfully" return 0 else retry_count=$((retry_count + 1)) if [[ $retry_count -lt $CONNECTION_RETRY_COUNT ]]; then deploy_warning "Connection attempt $retry_count failed, retrying in $CONNECTION_RETRY_DELAY seconds..." sleep $CONNECTION_RETRY_DELAY fi fi done deploy_error "Failed to establish SSH connection after $CONNECTION_RETRY_COUNT attempts" return 2 } # Execute remote command remote_exec() { local command="$1" local capture_output="${2:-false}" local ignore_errors="${3:-false}" deploy_debug "Executing remote command: $command" local ssh_cmd ssh_cmd=$(build_ssh_cmd) if [[ "$capture_output" == "true" ]]; then if eval "$ssh_cmd '$command'" 2>/dev/null; then return 0 else local exit_code=$? if [[ "$ignore_errors" != "true" ]]; then deploy_error "Remote command failed (exit code: $exit_code): $command" fi return $exit_code fi else if eval "$ssh_cmd '$command'"; then return 0 else local exit_code=$? if [[ "$ignore_errors" != "true" ]]; then deploy_error "Remote command failed (exit code: $exit_code): $command" fi return $exit_code fi fi } # Copy file to remote host remote_copy() { local local_file="$1" local remote_file="$2" local create_dirs="${3:-true}" deploy_debug "Copying $local_file to $REMOTE_USER@$REMOTE_HOST:$remote_file" # Create remote directory if needed if [[ "$create_dirs" == "true" ]]; then local remote_dir remote_dir=$(dirname "$remote_file") remote_exec "mkdir -p '$remote_dir'" false true fi local scp_cmd scp_cmd=$(build_scp_cmd) if eval "$scp_cmd '$local_file' '$REMOTE_USER@$REMOTE_HOST:$remote_file'"; then deploy_debug "File copied successfully: $local_file -> $remote_file" return 0 else deploy_error "Failed to copy file: $local_file -> $remote_file" return 1 fi } # [AWS-SECRET-REMOVED]==================================== # REMOTE ENVIRONMENT VALIDATION # [AWS-SECRET-REMOVED]==================================== validate_remote_environment() { deploy_info "Validating remote environment" # Check basic commands local missing_commands=() local required_commands=("git" "curl" "python3" "bash") for cmd in "${required_commands[@]}"; do deploy_debug "Checking for command: $cmd" if ! remote_exec "command -v $cmd" true true; then missing_commands+=("$cmd") fi done if [[ ${#missing_commands[@]} -gt 0 ]]; then deploy_error "Missing required commands on remote host: ${missing_commands[*]}" deploy_info "Install missing commands and try again" return 1 fi # Check for UV package manager deploy_debug "Checking for UV package manager" if ! remote_exec "command -v uv || test -x ~/.local/bin/uv" true true; then deploy_warning "UV package manager not found on remote host" deploy_info "UV will be installed automatically during setup" else deploy_debug "UV package manager found" fi # Check system info deploy_info "Remote system information:" remote_exec "echo ' OS: '\$(lsb_release -d 2>/dev/null | cut -f2 || uname -s)" false true remote_exec "echo ' Kernel: '\$(uname -r)" false true remote_exec "echo ' Architecture: '\$(uname -m)" false true remote_exec "echo ' Python: '\$(python3 --version)" false true remote_exec "echo ' Git: '\$(git --version)" false true deploy_success "Remote environment validation completed" return 0 } # [AWS-SECRET-REMOVED]==================================== # DEPLOYMENT FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Check if target directory exists and handle conflicts check_target_directory() { deploy_info "Checking target directory: $REMOTE_PATH" if remote_exec "test -d '$REMOTE_PATH'" true true; then deploy_warning "Target directory already exists: $REMOTE_PATH" if [[ "${FORCE_DEPLOY:-false}" == "true" ]]; then deploy_info "Force deployment enabled, will overwrite existing installation" return 0 fi if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would overwrite existing installation" return 0 fi echo "" echo "⚠️ Target directory already exists on remote host" echo "This may indicate an existing ThrillWiki installation." echo "" echo "Options:" echo "1. Backup existing installation and continue" echo "2. Overwrite existing installation (DESTRUCTIVE)" echo "3. Abort deployment" echo "" read -r -p "Choose option (1/2/3): " choice case "$choice" in 1) deploy_info "Creating backup of existing installation" local backup_name="thrillwiki-backup-$(date +%Y%m%d-%H%M%S)" if remote_exec "mv '$REMOTE_PATH' '$REMOTE_PATH/../$backup_name'"; then deploy_success "Existing installation backed up to: ../$backup_name" return 0 else deploy_error "Failed to create backup" return 1 fi ;; 2) deploy_warning "Overwriting existing installation" if remote_exec "rm -rf '$REMOTE_PATH'"; then deploy_info "Existing installation removed" return 0 else deploy_error "Failed to remove existing installation" return 1 fi ;; 3|*) deploy_info "Deployment aborted by user" exit 0 ;; esac else deploy_debug "Target directory does not exist, proceeding with deployment" return 0 fi } # Deploy project files deploy_project_files() { deploy_progress "Deploying project files to remote host" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would deploy the following files:" for file in "${DEPLOY_FILES[@]}"; do echo " - $file" done return 0 fi # Create remote project directory deploy_debug "Creating remote project directory" if ! remote_exec "mkdir -p '$REMOTE_PATH'"; then deploy_error "Failed to create remote project directory" return 1 fi # Copy specific deployment files using scp deploy_info "Copying specific deployment files using scp" # Build scp command local scp_cmd scp_cmd=$(build_scp_cmd) # Create remote directory structure first deploy_info "Creating remote directory structure" remote_exec "mkdir -p '$REMOTE_PATH/scripts/vm' '$REMOTE_PATH/scripts/systemd'" # Copy each file individually with retries local max_attempts=3 local failed_files=() for file in "${DEPLOY_FILES[@]}"; do local attempt=1 local file_copied=false while [[ $attempt -le $max_attempts ]]; do deploy_info "Copying $file (attempt $attempt/$max_attempts)" local local_file="$PROJECT_DIR/$file" local remote_file="$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/$file" if timeout 120 bash -c "eval \"$scp_cmd \\\"$local_file\\\" \\\"$remote_file\\\"\""; then deploy_success "Successfully copied $file" file_copied=true break else local exit_code=$? if [[ $exit_code -eq 124 ]]; then deploy_warning "SCP timed out copying $file (attempt $attempt/$max_attempts)" else deploy_warning "SCP failed copying $file with exit code $exit_code (attempt $attempt/$max_attempts)" fi if [[ $attempt -lt $max_attempts ]]; then deploy_info "Retrying in 3 seconds..." sleep 3 fi fi attempt=$((attempt + 1)) done if [[ "$file_copied" != "true" ]]; then failed_files+=("$file") fi done # Check if any files failed to copy if [[ ${#failed_files[@]} -gt 0 ]]; then deploy_error "Failed to copy ${#failed_files[@]} file(s): ${failed_files[*]}" return 1 fi deploy_success "All deployment files copied successfully" return 0 } # Fallback function to deploy only essential files deploy_essential_files_only() { deploy_info "Deploying only essential files" local ssh_opts="$SSH_OPTIONS -o ServerAliveInterval=30 -o ServerAliveCountMax=3" # Essential files to deploy local essential_files=( "scripts/vm/bulletproof-automation.sh" "scripts/vm/setup-automation.sh" "scripts/vm/automation-config.sh" "scripts/vm/github-setup.py" "scripts/vm/quick-start.sh" "scripts/systemd/thrillwiki-automation.service" "manage.py" "pyproject.toml" "requirements.txt" "uv.lock" "***REMOVED***.example" ) # Copy essential files one by one for file in "${essential_files[@]}"; do if [[ -f "$PROJECT_DIR/$file" ]]; then deploy_debug "Copying essential file: $file" if ! remote_copy "$PROJECT_DIR/$file" "$REMOTE_PATH/$file"; then deploy_warning "Failed to copy $file, continuing..." fi fi done # Copy additional essential files using scp local additional_files=( "manage.py" "pyproject.toml" "requirements.txt" "uv.lock" "***REMOVED***.example" ) local scp_cmd scp_cmd=$(build_scp_cmd) for file in "${additional_files[@]}"; do if [[ -f "$PROJECT_DIR/$file" ]]; then deploy_info "Copying additional file: $file" if timeout 60 bash -c "eval \"$scp_cmd \\\"$PROJECT_DIR/$file\\\" \\\"$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/$file\\\"\""; then deploy_success "βœ“ $file copied successfully" else deploy_warning "⚠ Failed to copy $file, continuing..." fi fi done deploy_warning "Minimal deployment completed - you may need to copy additional files manually" return 0 } # Enhanced remote dependencies setup using Step 3B functions setup_remote_dependencies() { deploy_progress "Setting up remote dependencies with comprehensive Step 3B integration" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would perform comprehensive dependency setup on remote host" return 0 fi local deployment_preset="${DEPLOYMENT_PRESET:-dev}" local setup_failed=false deploy_info "Starting comprehensive remote dependency setup (preset: $deployment_preset)" # Step 3B.1: Remote system dependency validation and installation deploy_info "Step 3B.1: Validating and installing system dependencies on remote host" if ! setup_remote_system_dependencies; then deploy_error "Remote system dependency setup failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else deploy_warning "Continuing with force deployment despite system dependency issues" fi fi # Step 3B.2: Remote UV package manager setup deploy_info "Step 3B.2: Setting up UV package manager on remote host" if ! setup_remote_uv_package_manager; then deploy_error "Remote UV package manager setup failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else deploy_warning "Continuing with force deployment despite UV setup issues" fi fi # Step 3B.3: Remote Python environment preparation deploy_info "Step 3B.3: Preparing Python environment on remote host" if ! setup_remote_python_environment; then deploy_error "Remote Python environment preparation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else deploy_warning "Continuing with force deployment despite Python environment issues" fi fi # Step 3B.4: Remote ThrillWiki-specific dependency installation deploy_info "Step 3B.4: Installing ThrillWiki dependencies on remote host" if ! setup_remote_thrillwiki_dependencies; then deploy_error "Remote ThrillWiki dependency installation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else deploy_warning "Continuing with force deployment despite ThrillWiki dependency issues" fi fi # Step 3B.5: Remote environment variable configuration deploy_info "Step 3B.5: Configuring environment variables on remote host" if ! setup_remote_environment_variables; then deploy_error "Remote environment variable configuration failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else deploy_warning "Continuing with force deployment despite environment configuration issues" fi fi # Step 3B.6: Remote comprehensive dependency validation deploy_info "Step 3B.6: Performing comprehensive dependency validation on remote host" if ! validate_remote_dependencies_comprehensive; then deploy_error "Remote dependency validation failed" setup_failed=true if [[ "${FORCE_DEPLOY:-false}" != "true" ]]; then return 1 else deploy_warning "Continuing with force deployment despite validation issues" fi fi if [[ "$setup_failed" == "true" ]]; then deploy_warning "Remote dependency setup completed with issues (forced deployment)" else deploy_success "Remote dependency setup completed successfully" fi return 0 } # Step 3B.1: Remote system dependency validation and installation setup_remote_system_dependencies() { deploy_info "Validating and installing system dependencies on remote host" # Check for basic required commands local missing_commands=() local required_commands=("git" "curl" "python3" "bash") for cmd in "${required_commands[@]}"; do deploy_debug "Checking for remote command: $cmd" if ! remote_exec "command -v $cmd" true true; then missing_commands+=("$cmd") fi done if [[ ${#missing_commands[@]} -gt 0 ]]; then deploy_warning "Missing required commands on remote host: ${missing_commands[*]}" # Attempt to install missing packages deploy_info "Attempting to install missing system dependencies" # Detect remote package manager and install missing packages if remote_exec "command -v apt-get" true true; then deploy_info "Installing packages using apt-get" local pkg_list="" for cmd in "${missing_commands[@]}"; do case "$cmd" in "python3") pkg_list="$pkg_list python3 python3-pip python3-venv python3-dev" ;; "git") pkg_list="$pkg_list git" ;; "curl") pkg_list="$pkg_list curl" ;; "bash") pkg_list="$pkg_list bash" ;; *) pkg_list="$pkg_list $cmd" ;; esac done if remote_exec "sudo apt-get update && sudo apt-get install -y $pkg_list" false true; then deploy_success "System dependencies installed successfully" else deploy_error "Failed to install some system dependencies" return 1 fi elif remote_exec "command -v yum" true true; then deploy_info "Installing packages using yum" local pkg_list="" for cmd in "${missing_commands[@]}"; do case "$cmd" in "python3") pkg_list="$pkg_list python3 python3-pip python3-devel" ;; "git") pkg_list="$pkg_list git" ;; "curl") pkg_list="$pkg_list curl" ;; "bash") pkg_list="$pkg_list bash" ;; *) pkg_list="$pkg_list $cmd" ;; esac done if remote_exec "sudo yum install -y $pkg_list" false true; then deploy_success "System dependencies installed successfully" else deploy_error "Failed to install some system dependencies" return 1 fi else deploy_error "Cannot detect package manager on remote host" return 1 fi else deploy_success "All required system dependencies are available" fi # Verify Python version if remote_exec "python3 --version" true true; then local python_version python_version=$(remote_exec "python3 --version 2>&1 | grep -o '[0-9]\+\.[0-9]\+' | head -1" true true || echo "unknown") deploy_info "Remote Python version: $python_version" 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 deploy_success "Python version is compatible (${python_version})" else deploy_warning "Python version may be too old: $python_version (recommended: 3.11+)" fi fi else deploy_error "Python 3 not available on remote host" return 1 fi return 0 } # Step 3B.2: Remote UV package manager setup setup_remote_uv_package_manager() { deploy_info "Setting up UV package manager on remote host" # Check if UV is already installed if remote_exec "command -v uv || test -x ~/.local/bin/uv" true true; then local uv_version uv_version=$(remote_exec "(command -v uv && uv --version) || (~/.local/bin/uv --version)" true true | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1 || echo "unknown") deploy_success "UV package manager already available on remote host (v$uv_version)" else deploy_info "Installing UV package manager 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") deploy_success "UV package manager 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 deploy_error "UV installation on remote host failed verification" return 1 fi else deploy_error "Failed to install UV package manager 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 return 0 } # Step 3B.3: Remote Python environment preparation setup_remote_python_environment() { deploy_info "Preparing Python environment on remote host" # Ensure we're in the remote project directory if ! remote_exec "cd '$REMOTE_PATH'"; then deploy_error "Cannot access remote project directory: $REMOTE_PATH" return 1 fi # Create logs directory remote_exec "mkdir -p '$REMOTE_PATH/logs'" false true # Remove corrupted virtual environment if present if remote_exec "test -d '$REMOTE_PATH/.venv'" true true; then deploy_info "Checking existing virtual environment on remote host" if ! remote_exec "cd '$REMOTE_PATH' && (export PATH=\"\$HOME/.local/bin:\$PATH\" && uv sync --quiet)" true true; then deploy_warning "Remote virtual environment is corrupted, removing" remote_exec "cd '$REMOTE_PATH' && rm -rf .venv" false true else deploy_success "Remote virtual environment is healthy" return 0 fi fi # Create new virtual environment on remote deploy_info "Creating Python virtual environment on remote host" if remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv sync"; then deploy_success "Remote Python environment prepared successfully" else deploy_error "Failed to create remote Python environment" return 1 fi return 0 } # Step 3B.4: Remote ThrillWiki-specific dependency installation setup_remote_thrillwiki_dependencies() { deploy_info "Installing ThrillWiki-specific dependencies on remote host" local deployment_preset="${DEPLOYMENT_PRESET:-dev}" # Ensure all dependencies are installed using UV if remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv sync"; then deploy_success "ThrillWiki dependencies installed on remote host" else deploy_warning "Some ThrillWiki dependencies may not have installed correctly" fi # Set up Tailwind CSS on remote deploy_info "Setting up Tailwind CSS on remote host" if remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py tailwind install --skip-checks" false true; then deploy_success "Tailwind CSS configured on remote host" else deploy_warning "Tailwind CSS setup on remote host had issues" fi # Make scripts executable on remote deploy_info "Setting script permissions on remote host" remote_exec "chmod +x '$REMOTE_PATH/scripts/vm/'*.sh" false true remote_exec "chmod +x '$REMOTE_PATH/scripts/vm/'*.py" false true deploy_info "Remote ThrillWiki dependencies configured for $deployment_preset preset" return 0 } # Step 3B.5: Remote environment variable configuration setup_remote_environment_variables() { deploy_info "Configuring environment variables on remote host" local deployment_preset="${DEPLOYMENT_PRESET:-dev}" # Generate ***REMOVED*** file content based on preset local env_content="" env_content=$(cat << 'EOF' # ThrillWiki Environment Configuration # Generated by remote 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 "$deployment_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=$REMOTE_HOST/" \ -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=$REMOTE_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=$REMOTE_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 on remote local secret_key secret_key=$(remote_exec "python3 -c 'import secrets; print(secrets.token_hex(32))'" true true 2>/dev/null || echo "change-this-secret-key-in-production-$(date +%s)") # Update DATABASE_URL with correct absolute path for spatialite local database_url="spatialite:///$REMOTE_PATH/db.sqlite3" env_content=$(echo "$env_content" | sed "s|DATABASE_URL=.*|DATABASE_URL=$database_url|") env_content=$(echo "$env_content" | sed "s/SECRET_KEY=/SECRET_KEY=$secret_key/") # Ensure ALLOWED_HOSTS includes the remote host case "$deployment_preset" in "dev") env_content=$(echo "$env_content" | sed "s/ALLOWED_HOSTS=/ALLOWED_HOSTS=localhost,127.0.0.1,$REMOTE_HOST/") ;; *) env_content=$(echo "$env_content" | sed "s/ALLOWED_HOSTS=/ALLOWED_HOSTS=$REMOTE_HOST/") ;; esac # Write ***REMOVED*** file on remote host if remote_exec "cat > '$REMOTE_PATH/***REMOVED***' << 'EOF' $env_content EOF"; then deploy_success "Environment variables configured on remote host for $deployment_preset preset" deploy_info "DATABASE_URL: $database_url" deploy_info "ALLOWED_HOSTS includes: $REMOTE_HOST" # Validate ***REMOVED*** file was created correctly if remote_exec "cd '$REMOTE_PATH' && test -f ***REMOVED*** && test -s ***REMOVED***" true true; then deploy_success "***REMOVED*** file created and contains data" else deploy_error "***REMOVED*** file is missing or empty" return 1 fi else deploy_error "Failed to configure environment variables on remote host" return 1 fi return 0 } # Step 3B.6: Remote comprehensive dependency validation validate_remote_dependencies_comprehensive() { deploy_info "Performing comprehensive dependency validation 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 deploy_error "UV package manager not functional on remote host" validation_failed=true else deploy_success "UV package manager validated on remote host" fi # Test Python environment on remote if ! remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run python --version" true true; then deploy_error "Python environment not functional on remote host" validation_failed=true else deploy_success "Python environment validated on remote host" fi # Test Django on remote if ! remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run python -c 'import django'" true true; then deploy_error "Django not properly installed on remote host" validation_failed=true else local django_version django_version=$(remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run python -c 'import django; print(django.get_version())'" true true 2>/dev/null || echo "unknown") deploy_success "Django validated on remote host (v$django_version)" fi # Check if ***REMOVED*** file exists before testing Django management commands if remote_exec "cd '$REMOTE_PATH' && test -f ***REMOVED***" true true; then deploy_info "Environment file found, testing Django management commands" # Test Django management commands on remote if ! remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py check" true true; then deploy_warning "Django check command has issues on remote host" else deploy_success "Django management commands validated on remote host" fi # Test Tailwind CSS on remote if ! remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py tailwind build --skip-checks" true true; then deploy_warning "Tailwind CSS build has issues on remote host" else deploy_success "Tailwind CSS validated on remote host" fi else deploy_info "Environment file (***REMOVED***) not found - skipping Django command validation" deploy_info "Django commands will be validated after environment setup" fi if [[ "$validation_failed" == "true" ]]; then deploy_error "Remote dependency validation failed" return 1 else deploy_success "All remote dependencies validated successfully" return 0 fi } # Enhanced Django validation after environment setup validate_django_environment_setup() { deploy_info "Validating Django environment configuration after setup" local project_path="$REMOTE_PATH" local validation_failed=false # Ensure ***REMOVED*** file exists if ! remote_exec "cd '$project_path' && test -f ***REMOVED***" true true; then deploy_error "***REMOVED*** file not found after environment setup" return 1 fi # Validate DATABASE_URL is set if ! remote_exec "cd '$project_path' && grep -q '^DATABASE_URL=' ***REMOVED***" true true; then deploy_error "DATABASE_URL not configured in ***REMOVED*** file" validation_failed=true else deploy_success "DATABASE_URL configured in ***REMOVED*** file" fi # Validate SECRET_KEY is set if ! remote_exec "cd '$project_path' && grep -q '^SECRET_KEY=' ***REMOVED***" true true; then deploy_error "SECRET_KEY not configured in ***REMOVED*** file" validation_failed=true else deploy_success "SECRET_KEY configured in ***REMOVED*** file" fi # Test Django configuration loading deploy_info "Testing Django configuration loading" if ! remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py check --quiet" true true; then deploy_error "Django configuration check failed" validation_failed=true # Show detailed error for debugging deploy_info "Attempting to get detailed Django error information" remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py check" false true else deploy_success "Django configuration validated successfully" fi if [[ "$validation_failed" == "true" ]]; then deploy_error "Django environment validation failed" return 1 else deploy_success "Django environment validation completed successfully" return 0 fi } # Configure GitHub authentication on remote host setup_remote_github_auth() { if [[ "${SKIP_GITHUB_SETUP:-false}" == "true" ]]; then deploy_info "Skipping GitHub authentication setup" return 0 fi deploy_progress "Setting up GitHub authentication on remote host" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would configure GitHub authentication" return 0 fi # Check if GitHub token is provided if [[ -n "${GITHUB_TOKEN:-}" ]]; then deploy_info "Configuring GitHub authentication with provided token" # Create secure token file on remote host if remote_exec "echo '$GITHUB_TOKEN' > '$REMOTE_PATH/.github-pat' && chmod 600 '$REMOTE_PATH/.github-pat'"; then deploy_success "GitHub token configured on remote host" # Validate token deploy_info "Validating GitHub token on remote host" if remote_exec "cd '$REMOTE_PATH' && python3 scripts/vm/github-setup.py validate"; then deploy_success "GitHub token validated successfully" else deploy_warning "GitHub token validation failed, but continuing deployment" fi else deploy_error "Failed to configure GitHub token on remote host" return 1 fi else deploy_info "No GitHub token provided, running interactive setup" # Run interactive GitHub setup echo "" echo "πŸ” GitHub Authentication Setup" echo "Setting up GitHub authentication on the remote host..." echo "" if remote_exec "cd '$REMOTE_PATH' && python3 scripts/vm/github-setup.py setup"; then deploy_success "GitHub authentication configured interactively" else deploy_warning "GitHub authentication setup failed or was skipped" deploy_info "You can set it up later using: scripts/vm/github-setup.py setup" fi fi return 0 } # [AWS-SECRET-REMOVED]==================================== # REPOSITORY CLONING FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Clone or update repository on remote host clone_repository_on_remote() { if [[ "${SKIP_REPO_CONFIG:-false}" == "true" ]]; then deploy_info "Skipping repository cloning as requested" return 0 fi if [[ -z "${GITHUB_REPO_URL:-}" ]]; then deploy_info "No repository URL provided, skipping repository cloning" return 0 fi deploy_progress "Setting up project repository on remote host" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would clone repository $GITHUB_REPO_URL" return 0 fi local repo_url="${GITHUB_REPO_URL}" local repo_branch="${GITHUB_REPO_BRANCH:-main}" local project_repo_path="$REMOTE_PATH" deploy_info "Repository: $repo_url" deploy_info "Branch: $repo_branch" deploy_info "Target path: $project_repo_path" # Configure git credentials on remote host if GitHub token is available if [[ -n "${GITHUB_TOKEN:-}" ]]; then deploy_info "Configuring git credentials on remote host with proper authentication format" # Extract repo owner and name for credential configuration local repo_info repo_info=$(echo "$repo_url" | sed -E 's|.*github\.com[:/]([^/]+)/([^/]+).*|\1/\2|' | sed 's|\.git$||') # Configure git credential helper with proper format including username deploy_debug "Setting up git credential helper with oauth2 authentication" if remote_exec "git config --global credential.helper store && echo 'https://oauth2:$GITHUB_TOKEN@github.com' > ~/.git-credentials && chmod 600 ~/.git-credentials"; then deploy_success "Git credentials configured with proper oauth2 format" # Also configure git to use the credential helper if remote_exec "git config --global credential.https://github.com.useHttpPath true"; then deploy_debug "Git credential path configuration set" fi else deploy_warning "Failed to configure git credentials with oauth2 format" # Fallback: try alternative username format deploy_info "Trying alternative git credential format with username" if remote_exec "echo 'https://pacnpal:$GITHUB_TOKEN@github.com' > ~/.git-credentials && chmod 600 ~/.git-credentials"; then deploy_success "Git credentials configured with username format" else deploy_warning "Failed to configure git credentials, will try authenticated URL" fi fi fi # Check if repository directory already exists if remote_exec "test -d '$project_repo_path/.git'" true true; then deploy_info "Repository already exists, updating..." # Backup existing repository if it has uncommitted changes if remote_exec "cd '$project_repo_path' && git status --porcelain" true true | grep -q .; then deploy_warning "Repository has uncommitted changes, creating backup" local backup_name="thrillwiki-repo-backup-$(date +%Y%m%d-%H%M%S)" if remote_exec "cp -r '$project_repo_path' '$project_repo_path/../$backup_name'"; then deploy_success "Repository backed up to: ../$backup_name" else deploy_error "Failed to backup existing repository" return 1 fi fi # Update remote URL to ensure proper authentication if GitHub token is available if [[ -n "${GITHUB_TOKEN:-}" ]]; then deploy_debug "Ensuring remote URL is configured for credential authentication" remote_exec "cd '$project_repo_path' && git remote set-url origin '$repo_url'" false true fi # Update existing repository with enhanced error handling deploy_info "Fetching latest changes from remote repository" local fetch_success=false # First attempt: Use configured git credentials if remote_exec "cd '$project_repo_path' && git fetch origin"; then deploy_success "Repository fetched successfully using git credentials" fetch_success=true else deploy_warning "Git fetch failed using credentials, trying authenticated URL" # Second attempt: Use authenticated URL if GitHub token is available if [[ -n "${GITHUB_TOKEN:-}" ]]; then local auth_url auth_url=$(echo "$repo_url" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|") deploy_info "Attempting fetch with authenticated URL" if remote_exec "cd '$project_repo_path' && git remote set-url origin '$auth_url' && git fetch origin"; then deploy_success "Repository fetched successfully using authenticated URL" fetch_success=true # Restore original URL for future operations remote_exec "cd '$project_repo_path' && git remote set-url origin '$repo_url'" false true else deploy_error "Git fetch failed with authenticated URL" fi else deploy_error "No GitHub token available for authenticated fetch" fi fi if [[ "$fetch_success" == "true" ]]; then # Switch to target branch and pull latest changes deploy_info "Switching to branch: $repo_branch" if remote_exec "cd '$project_repo_path' && git checkout '$repo_branch' && git pull origin '$repo_branch'"; then deploy_success "Repository updated to latest $repo_branch" else deploy_error "Failed to update repository to branch $repo_branch" return 1 fi else deploy_error "Failed to fetch repository updates using all available methods" return 1 fi else deploy_info "Cloning repository for the first time" # Remove any existing non-git directory if remote_exec "test -d '$project_repo_path'" true true; then deploy_warning "Removing existing non-git directory at $project_repo_path" if ! remote_exec "rm -rf '$project_repo_path'"; then deploy_error "Failed to remove existing directory" return 1 fi fi # Clone the repository with enhanced authentication handling deploy_info "Cloning $repo_url (branch: $repo_branch)" local clone_success=false # First attempt: Use configured git credentials if remote_exec "git clone --branch '$repo_branch' '$repo_url' '$project_repo_path'"; then deploy_success "Repository cloned successfully using git credentials" clone_success=true else deploy_warning "Git clone failed using credentials, trying authenticated URL" # Second attempt: Use authenticated URL if GitHub token is available if [[ -n "${GITHUB_TOKEN:-}" ]]; then # Create authenticated URL local auth_url auth_url=$(echo "$repo_url" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|") deploy_info "Attempting clone with embedded authentication" deploy_debug "Using authenticated URL format: ${auth_url/oauth2:${GITHUB_TOKEN}@/oauth2:***@}" if remote_exec "git clone --branch '$repo_branch' '$auth_url' '$project_repo_path'"; then deploy_success "Repository cloned successfully using authenticated URL" clone_success=true # Update remote URL to use credential helper for future operations deploy_debug "Updating remote URL to use credential helper" remote_exec "cd '$project_repo_path' && git remote set-url origin '$repo_url'" false true else deploy_error "Git clone failed with authenticated URL" fi else deploy_error "No GitHub token available for authenticated clone" fi fi # Final check if [[ "$clone_success" != "true" ]]; then deploy_error "Failed to clone repository using all available methods" return 1 fi fi # Set proper ownership and permissions deploy_info "Setting repository permissions" remote_exec "cd '$project_repo_path' && find . -type f -name '*.sh' -exec chmod +x {} \;" false true remote_exec "chown -R $REMOTE_USER:$REMOTE_USER '$project_repo_path'" false true # Validate repository setup if validate_repository_setup; then deploy_success "Repository setup completed successfully" return 0 else deploy_error "Repository validation failed" return 1 fi } # Validate repository setup validate_repository_setup() { deploy_info "Validating repository setup" local project_repo_path="$REMOTE_PATH" # Check if it's a valid git repository if ! remote_exec "cd '$project_repo_path' && git status" true true; then deploy_error "Directory is not a valid git repository" return 1 fi # Check if we're on the correct branch local current_branch current_branch=$(remote_exec "cd '$project_repo_path' && git branch --show-current" true true) local expected_branch="${GITHUB_REPO_BRANCH:-main}" if [[ "$current_branch" != "$expected_branch" ]]; then deploy_warning "Repository is on branch '$current_branch' but expected '$expected_branch'" else deploy_success "Repository is on correct branch: $current_branch" fi # Check for essential project files local essential_files=("manage.py" "pyproject.toml") local missing_files=() for file in "${essential_files[@]}"; do if ! remote_exec "test -f '$project_repo_path/$file'" true true; then missing_files+=("$file") fi done if [[ ${#missing_files[@]} -gt 0 ]]; then deploy_warning "Missing essential project files: ${missing_files[*]}" deploy_info "This might not be a ThrillWiki project repository" else deploy_success "Essential project files found" fi # Check repository remote local remote_url remote_url=$(remote_exec "cd '$project_repo_path' && git remote get-url origin" true true) if [[ -n "$remote_url" ]]; then deploy_success "Repository remote configured: $remote_url" else deploy_warning "No repository remote configured" fi return 0 } # [AWS-SECRET-REMOVED]==================================== # DJANGO PROJECT SETUP FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Set up Django project environment and dependencies setup_django_project() { if [[ "${DJANGO_PROJECT_SETUP:-true}" != "true" ]]; then deploy_info "Skipping Django project setup as requested" return 0 fi deploy_progress "Setting up Django project environment" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would set up Django project environment" return 0 fi local project_path="$REMOTE_PATH" # Ensure we're in the project directory if ! remote_exec "cd '$project_path' && test -f manage.py && test -f pyproject.toml" true true; then deploy_error "Django project files not found at $project_path" return 1 fi # Install system dependencies required by Django/GeoDjango deploy_info "Installing system dependencies for Django/GeoDjango" remote_exec "sudo apt-get update && sudo apt-get install -y \ gdal-bin \ libgdal-dev \ libgeos-dev \ libproj-dev \ postgresql-client \ postgresql-contrib \ postgis \ binutils \ libproj-dev \ gdal-bin \ nodejs \ npm" false true # Set up Python environment with UV deploy_info "Setting up Python virtual environment with UV" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && (uv sync || ~/.local/bin/uv sync)"; then deploy_success "Python virtual environment set up successfully" else deploy_error "Failed to set up Python virtual environment" return 1 fi # Configure environment variables if ! setup_django_environment; then deploy_error "Failed to configure Django environment" return 1 fi # Validate Django environment configuration if ! validate_django_environment_setup; then deploy_error "Django environment validation failed" return 1 fi # Run database migrations if ! setup_django_database; then deploy_error "Failed to set up Django database" return 1 fi # Set up Tailwind CSS if ! setup_tailwind_css; then deploy_error "Failed to set up Tailwind CSS" return 1 fi # Collect static files if ! collect_static_files; then deploy_error "Failed to collect static files" return 1 fi # Set proper file permissions deploy_info "Setting proper file permissions" remote_exec "cd '$project_path' && find . -type f -name '*.py' -exec chmod 644 {} \;" false true remote_exec "cd '$project_path' && find . -type f -name '*.sh' -exec chmod +x {} \;" false true remote_exec "cd '$project_path' && chmod +x manage.py" false true remote_exec "chown -R $REMOTE_USER:$REMOTE_USER '$project_path'" false true deploy_success "Django project setup completed successfully" return 0 } # Configure Django environment variables setup_django_environment() { deploy_info "Configuring Django environment variables" local project_path="$REMOTE_PATH" local preset="${DEPLOYMENT_PRESET:-dev}" # Create ***REMOVED*** file from ***REMOVED***.example if it doesn't exist if ! remote_exec "cd '$project_path' && test -f ***REMOVED***" true true; then deploy_info "Creating ***REMOVED*** file from ***REMOVED***.example" if ! remote_exec "cd '$project_path' && cp ***REMOVED***.example ***REMOVED***"; then deploy_error "Failed to create ***REMOVED*** file" return 1 fi fi # Generate a secure SECRET_KEY deploy_info "Generating Django SECRET_KEY" local secret_key secret_key=$(remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && (uv run python -c \"from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())\" || ~/.local/bin/uv run python -c \"from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())\")" true true) if [[ -n "$secret_key" ]]; then # Update SECRET_KEY in ***REMOVED*** file remote_exec "cd '$project_path' && sed -i 's/SECRET_KEY=.*/SECRET_KEY=$secret_key/' ***REMOVED***" false true deploy_success "SECRET_KEY generated and configured" else deploy_warning "Failed to generate SECRET_KEY, using placeholder" fi # Configure environment based on deployment preset case "$preset" in "prod") deploy_info "Configuring for production deployment" remote_exec "cd '$project_path' && sed -i 's/DEBUG=.*/DEBUG=False/' ***REMOVED***" false true remote_exec "cd '$project_path' && sed -i 's/SECURE_SSL_REDIRECT=.*/SECURE_SSL_REDIRECT=True/' ***REMOVED***" false true remote_exec "cd '$project_path' && sed -i 's/SESSION_COOKIE_SECURE=.*/SESSION_COOKIE_SECURE=True/' ***REMOVED***" false true remote_exec "cd '$project_path' && sed -i 's/CSRF_COOKIE_SECURE=.*/CSRF_COOKIE_SECURE=True/' ***REMOVED***" false true ;; "demo"|"testing") deploy_info "Configuring for $preset deployment" remote_exec "cd '$project_path' && sed -i 's/DEBUG=.*/DEBUG=False/' ***REMOVED***" false true ;; "dev"|*) deploy_info "Configuring for development deployment" remote_exec "cd '$project_path' && sed -i 's/DEBUG=.*/DEBUG=True/' ***REMOVED***" false true ;; esac # Configure database for SQLite (simpler for automated deployment) deploy_info "Configuring database for SQLite" remote_exec "cd '$project_path' && sed -i 's|DATABASE_URL=.*|DATABASE_URL=spatialite:///'"$project_path"'/db.sqlite3|' ***REMOVED***" false true # Set GeoDjango library paths for Linux deploy_info "Configuring GeoDjango library paths for Linux" remote_exec "cd '$project_path' && sed -i 's|GDAL_LIBRARY_PATH=.*|GDAL_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgdal.so|' ***REMOVED***" false true remote_exec "cd '$project_path' && sed -i 's|GEOS_LIBRARY_PATH=.*|GEOS_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgeos_c.so|' ***REMOVED***" false true deploy_success "Django environment configured successfully" return 0 } # Set up Django database and run migrations with ThrillWiki-specific configuration setup_django_database() { deploy_info "Setting up Django database and running migrations" local project_path="$REMOTE_PATH" local preset="${DEPLOYMENT_PRESET:-dev}" # Clean up any existing database lock files deploy_info "Cleaning up database lock files" remote_exec "cd '$project_path' && rm -f db.sqlite3-wal db.sqlite3-shm" false true # Clean up Python cache files following .clinerules pattern deploy_info "Cleaning up Python cache files" remote_exec "cd '$project_path' && find . -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true" false true # Check for existing migrations and create initial ones if needed deploy_info "Checking Django migration status" if ! remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py showmigrations --plan" true true; then deploy_info "Creating initial migrations for ThrillWiki apps" # Create migrations for ThrillWiki-specific apps local thrillwiki_apps=("accounts" "parks" "rides" "core" "media" "moderation" "location") for app in "${thrillwiki_apps[@]}"; do deploy_info "Creating migrations for $app app" remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py makemigrations $app" false true done fi # Run Django migrations using proper UV syntax deploy_info "Running Django database migrations" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py migrate"; then deploy_success "Database migrations completed successfully" else deploy_error "Database migrations failed" return 1 fi # Setup ThrillWiki-specific database configuration setup_thrillwiki_database_config "$project_path" "$preset" # Create superuser based on deployment preset if [[ "$preset" == "dev" || "$preset" == "demo" ]]; then deploy_info "Creating Django superuser for $preset environment" # Create superuser non-interactively with default credentials if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && echo \"from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.filter(username='admin').exists() or User.objects.create_superuser('admin', 'admin@thrillwiki.com', 'admin123')\" | uv run manage.py shell" false true; then deploy_success "Superuser created successfully (admin/admin123)" else deploy_warning "Failed to create superuser, you can create one manually later" fi fi # Load initial data if available load_initial_data "$project_path" "$preset" deploy_success "Django database setup completed" return 0 } # Setup ThrillWiki-specific database configuration setup_thrillwiki_database_config() { local project_path="$1" local preset="$2" deploy_info "Configuring ThrillWiki-specific database settings" # Create media directories for ThrillWiki deploy_info "Creating ThrillWiki media directories" remote_exec "cd '$project_path' && mkdir -p media/park media/ride media/avatars media/submissions" false true # Set proper permissions for media directories remote_exec "cd '$project_path' && chmod -R 755 media/" false true # Configure uploads directory structure remote_exec "cd '$project_path' && mkdir -p uploads/park uploads/ride uploads/avatars" false true remote_exec "cd '$project_path' && chmod -R 755 uploads/" false true deploy_success "ThrillWiki database configuration completed" } # Load initial data for ThrillWiki load_initial_data() { local project_path="$1" local preset="$2" deploy_info "Loading ThrillWiki initial data" # Check for and load fixtures if they exist if remote_exec "cd '$project_path' && test -d fixtures/" true true; then deploy_info "Loading initial fixtures" # Load initial data based on preset case "$preset" in "dev"|"demo") # Load demo data for development and demo environments if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && find fixtures/ -name '*.json' -exec uv run manage.py loaddata {} \;" false true; then deploy_success "Demo fixtures loaded successfully" else deploy_warning "Some fixtures failed to load" fi ;; "prod"|"testing") # Only load essential data for production and testing if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && find fixtures/ -name '*initial*.json' -exec uv run manage.py loaddata {} \;" false true; then deploy_success "Initial fixtures loaded successfully" else deploy_warning "Some initial fixtures failed to load" fi ;; esac else deploy_info "No fixtures directory found, skipping initial data loading" fi } # Set up Tailwind CSS setup_tailwind_css() { deploy_info "Setting up Tailwind CSS" local project_path="$REMOTE_PATH" # Install Tailwind CSS using Django's Tailwind CLI deploy_info "Installing Tailwind CSS dependencies" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && (uv run manage.py tailwind install || ~/.local/bin/uv run manage.py tailwind install)"; then deploy_success "Tailwind CSS installed successfully" else deploy_warning "Failed to install Tailwind CSS, continuing without it" return 0 # Don't fail deployment for Tailwind issues fi # Build Tailwind CSS deploy_info "Building Tailwind CSS" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && (uv run manage.py tailwind build || ~/.local/bin/uv run manage.py tailwind build)"; then deploy_success "Tailwind CSS built successfully" else deploy_warning "Failed to build Tailwind CSS, continuing without it" fi return 0 } # Collect Django static files with ThrillWiki-specific optimizations collect_static_files() { deploy_info "Collecting Django static files for ThrillWiki" local project_path="$REMOTE_PATH" local preset="${DEPLOYMENT_PRESET:-dev}" # Create necessary static directories deploy_info "Creating static file directories" remote_exec "cd '$project_path' && mkdir -p staticfiles static/css static/js static/images" false true # Set proper permissions for static directories remote_exec "cd '$project_path' && chmod -R 755 static/ staticfiles/" false true # Clean existing static files for clean collection if [[ "$preset" == "prod" ]]; then deploy_info "Cleaning existing static files for production" remote_exec "cd '$project_path' && rm -rf staticfiles/* 2>/dev/null || true" false true fi # Collect static files using proper UV syntax deploy_info "Collecting static files using Django collectstatic" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py collectstatic --noinput --clear"; then deploy_success "Static files collected successfully" # Additional static file optimizations for production if [[ "$preset" == "prod" ]]; then deploy_info "Applying production static file optimizations" # Compress CSS and JS files if available if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py compress" true true; then deploy_success "Static files compressed for production" else deploy_info "Django-compressor not available, skipping compression" fi # Set proper cache headers for static files deploy_info "Configuring static file caching for production" remote_exec "cd '$project_path' && find staticfiles/ -type f \( -name '*.css' -o -name '*.js' -o -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o -name '*.svg' \) -exec chmod 644 {} \;" false true fi else deploy_warning "Failed to collect static files, trying without --clear" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && uv run manage.py collectstatic --noinput"; then deploy_success "Static files collected successfully (without clear)" else deploy_warning "Failed to collect static files, continuing anyway" fi fi # Verify static files were collected deploy_info "Verifying static file collection" if remote_exec "cd '$project_path' && test -d staticfiles/ && find staticfiles/ -type f | wc -l" true true; then local file_count file_count=$(remote_exec "cd '$project_path' && find staticfiles/ -type f | wc -l" true true || echo "unknown") deploy_success "Static files verification: $file_count files collected" else deploy_warning "Static files directory is empty or missing" fi return 0 } # Validate Django project setup validate_django_setup() { deploy_info "Validating Django project setup" local project_path="$REMOTE_PATH" local validation_errors=0 # Check if Django can start without errors deploy_info "Checking Django configuration" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && timeout 10 (uv run manage.py check || ~/.local/bin/uv run manage.py check)" true true; then deploy_success "βœ“ Django configuration is valid" else deploy_error "βœ— Django configuration check failed" ((validation_errors++)) fi # Check database connectivity deploy_info "Checking database connectivity" if remote_exec "cd '$project_path' && export PATH=\"\$HOME/.local/bin:\$PATH\" && (uv run manage.py showmigrations --plan || ~/.local/bin/uv run manage.py showmigrations --plan)" true true; then deploy_success "βœ“ Database is accessible" else deploy_error "βœ— Database connectivity failed" ((validation_errors++)) fi # Check static files deploy_info "Checking static files" if remote_exec "cd '$project_path' && test -d staticfiles && ls staticfiles/ | grep -q ." true true; then deploy_success "βœ“ Static files collected" else deploy_warning "⚠ Static files not found" fi # Check essential directories local essential_dirs=("logs" "media" "staticfiles") for dir in "${essential_dirs[@]}"; do if remote_exec "cd '$project_path' && test -d $dir" true true; then deploy_success "βœ“ Directory exists: $dir" else deploy_warning "⚠ Directory missing: $dir" remote_exec "cd '$project_path' && mkdir -p $dir" false true fi done if [[ $validation_errors -eq 0 ]]; then deploy_success "Django project validation completed successfully" return 0 else deploy_warning "Django project validation completed with $validation_errors errors" return 1 fi } # Configure and start automation service setup_automation_service() { if [[ "${SKIP_SERVICE_SETUP:-false}" == "true" ]]; then deploy_info "Skipping systemd service setup" return 0 fi deploy_progress "Setting up automation service with automatic pull scheduling" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would configure systemd service for automatic pull scheduling" return 0 fi # Check if systemd is available if ! remote_exec "command -v systemctl" true true; then deploy_warning "systemd not available on remote host, skipping service setup" return 0 fi # Configure automation service environment variables if ! configure_automation_environment; then deploy_error "Failed to configure automation environment" return 1 fi # Run the setup automation script with proper environment deploy_info "Running automation setup script with environment configuration" local setup_env="" # Pass deployment preset and GitHub token if available if [[ -n "${DEPLOYMENT_PRESET:-}" ]]; then setup_env+="DEPLOYMENT_PRESET='${DEPLOYMENT_PRESET}' " fi if [[ -n "${GITHUB_TOKEN:-}" ]]; then setup_env+="GITHUB_TOKEN='${GITHUB_TOKEN}' " fi # Export NON_INTERACTIVE for automated setup setup_env+="NON_INTERACTIVE=true " if remote_exec "cd '$REMOTE_PATH' && export PATH=\"\$HOME/.local/bin:\$PATH\" && $setup_env bash scripts/vm/setup-automation.sh setup --non-interactive"; then deploy_success "Automation service configured successfully" # Validate service configuration if ! validate_automation_service; then deploy_warning "Service validation failed, but installation may still be functional" fi # Start the service deploy_info "Starting automation service" if remote_exec "sudo systemctl start thrillwiki-automation"; then deploy_success "Automation service started successfully" # Enable service for auto-start deploy_info "Enabling service for auto-start on boot" if remote_exec "sudo systemctl enable thrillwiki-automation"; then deploy_success "Service enabled for auto-start" else deploy_warning "Failed to enable service for auto-start" fi # Wait a moment for service to stabilize sleep 3 # Check service status and health deploy_info "Checking service status and health" if check_automation_service_health; then deploy_success "Automation service is running and healthy" else deploy_warning "Service health check failed" fi else deploy_warning "Failed to start automation service" deploy_info "You can start it manually with: sudo systemctl start thrillwiki-automation" # Show service logs for debugging deploy_info "Checking service logs for troubleshooting" remote_exec "sudo journalctl -u thrillwiki-automation --no-pager -l | tail -20" false true fi else deploy_warning "Automation service setup failed" deploy_info "You can set it up manually using: scripts/vm/setup-automation.sh" # Show setup logs for debugging deploy_info "Checking setup logs for troubleshooting" remote_exec "cat '$REMOTE_PATH/logs/setup-automation.log' | tail -20" false true fi return 0 } # Configure automation service environment variables configure_automation_environment() { deploy_info "Configuring automation service environment" local project_path="$REMOTE_PATH" local preset="${DEPLOYMENT_PRESET:-dev}" # Ensure environment configuration directory exists deploy_debug "Creating systemd environment configuration" remote_exec "mkdir -p '$project_path/scripts/systemd'" false true # Create or update environment configuration for the service local env_config="$project_path/scripts/systemd/thrillwiki-automation***REMOVED***" # Generate environment configuration based on deployment preset deploy_info "Generating environment configuration for preset: $preset" local env_content="" env_content+="# ThrillWiki Automation Service Environment Configuration\n" env_content+="# Generated during deployment - $(date)\n" env_content+="\n" env_content+="# Project Configuration\n" env_content+="PROJECT_DIR=$project_path\n" env_content+="DEPLOYMENT_PRESET=$preset\n" env_content+="\n" env_content+="# Automation Settings\n" # Configure intervals based on deployment preset case "$preset" in "prod") env_content+="PULL_INTERVAL=900\n" # 15 minutes for production env_content+="HEALTH_CHECK_INTERVAL=300\n" # 5 minutes ;; "demo"|"testing") env_content+="PULL_INTERVAL=600\n" # 10 minutes for demo/testing env_content+="HEALTH_CHECK_INTERVAL=180\n" # 3 minutes ;; "dev"|*) env_content+="PULL_INTERVAL=300\n" # 5 minutes for development env_content+="HEALTH_CHECK_INTERVAL=60\n" # 1 minute ;; esac env_content+="\n" env_content+="# Logging Configuration\n" env_content+="LOG_LEVEL=INFO\n" env_content+="LOG_FILE=$project_path/logs/bulletproof-automation.log\n" env_content+="\n" env_content+="# GitHub Configuration\n" if [[ -n "${GITHUB_TOKEN:-}" ]]; then env_content+="GITHUB_PAT_FILE=$project_path/.github-pat\n" fi if [[ -n "${GITHUB_REPO_URL:-}" ]]; then env_content+="GITHUB_REPO_URL=${GITHUB_REPO_URL}\n" fi if [[ -n "${GITHUB_REPO_BRANCH:-}" ]]; then env_content+="GITHUB_REPO_BRANCH=${GITHUB_REPO_BRANCH}\n" fi # Write environment configuration to remote host if remote_exec "cat > '$env_config' << 'EOF' $(echo -e "$env_content") EOF"; then deploy_success "Environment configuration created: $env_config" # Set proper permissions remote_exec "chmod 600 '$env_config'" false true remote_exec "chown $REMOTE_USER:$REMOTE_USER '$env_config'" false true else deploy_error "Failed to create environment configuration" return 1 fi return 0 } # Validate automation service configuration validate_automation_service() { deploy_info "Validating automation service configuration" local validation_errors=0 # Check if service file exists if remote_exec "test -f /etc/systemd/system/thrillwiki-automation.service" true true; then deploy_success "βœ“ Systemd service file installed" else deploy_error "βœ— Systemd service file not found" ((validation_errors++)) fi # Check if environment configuration exists if remote_exec "test -f '$REMOTE_PATH/scripts/systemd/thrillwiki-automation***REMOVED***'" true true; then deploy_success "βœ“ Environment configuration file exists" else deploy_warning "⚠ Environment configuration file not found" fi # Check if service is enabled if remote_exec "systemctl is-enabled thrillwiki-automation" true true; then deploy_success "βœ“ Service is enabled for auto-start" else deploy_info "β„Ή Service is not enabled for auto-start" fi # Check if automation script is executable if remote_exec "test -x '$REMOTE_PATH/scripts/vm/bulletproof-automation.sh'" true true; then deploy_success "βœ“ Automation script is executable" else deploy_error "βœ— Automation script is not executable" ((validation_errors++)) fi # Check GitHub authentication if configured if [[ -n "${GITHUB_TOKEN:-}" ]]; then if remote_exec "test -f '$REMOTE_PATH/.github-pat'" true true; then deploy_success "βœ“ GitHub token file exists" else deploy_warning "⚠ GitHub token file not found" fi fi if [[ $validation_errors -eq 0 ]]; then deploy_success "Service configuration validation completed successfully" return 0 else deploy_warning "Service configuration validation completed with $validation_errors errors" return 1 fi } # Check automation service health check_automation_service_health() { deploy_info "Performing automation service health check" local health_errors=0 # Check if service is active if remote_exec "systemctl is-active thrillwiki-automation" true true; then deploy_success "βœ“ Service is active and running" else deploy_error "βœ— Service is not active" ((health_errors++)) fi # Check service status local service_status service_status=$(remote_exec "systemctl show thrillwiki-automation --property=ActiveState --value" true true 2>/dev/null || echo "unknown") case "$service_status" in "active") deploy_success "βœ“ Service status: active" ;; "failed") deploy_error "βœ— Service status: failed" ((health_errors++)) ;; "inactive") deploy_warning "⚠ Service status: inactive" ;; *) deploy_info "β„Ή Service status: $service_status" ;; esac # Check recent service logs for errors deploy_info "Checking recent service logs for errors" local recent_errors recent_errors=$(remote_exec "sudo journalctl -u thrillwiki-automation --since='5 minutes ago' --grep='ERROR\\|CRITICAL\\|FATAL' --no-pager | wc -l" true true 2>/dev/null || echo "0") if [[ "$recent_errors" -gt 0 ]]; then deploy_warning "⚠ Found $recent_errors recent error(s) in service logs" deploy_info "Recent errors:" remote_exec "sudo journalctl -u thrillwiki-automation --since='5 minutes ago' --grep='ERROR\\|CRITICAL\\|FATAL' --no-pager | tail -5" false true else deploy_success "βœ“ No recent errors in service logs" fi # Check if automation script can validate deploy_info "Testing automation script validation" if remote_exec "cd '$REMOTE_PATH' && timeout 15 bash scripts/vm/bulletproof-automation.sh --validate-only" true true; then deploy_success "βœ“ Automation script validation passed" else deploy_warning "⚠ Automation script validation failed or timed out" fi # Check if project directory is accessible if remote_exec "cd '$REMOTE_PATH' && test -f manage.py" true true; then deploy_success "βœ“ Project directory is accessible" else deploy_error "βœ— Project directory is not accessible" ((health_errors++)) fi if [[ $health_errors -eq 0 ]]; then deploy_success "Service health check completed successfully" return 0 else deploy_warning "Service health check completed with $health_errors issues" return 1 fi } # [AWS-SECRET-REMOVED]==================================== # HEALTH VALIDATION # [AWS-SECRET-REMOVED]==================================== validate_deployment() { deploy_progress "Validating deployment" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_success "Dry run completed successfully" return 0 fi local validation_errors=0 # Check project directory deploy_info "Checking project directory structure" if remote_exec "test -d '$REMOTE_PATH' && test -f '$REMOTE_PATH/scripts/vm/bulletproof-automation.sh'" true true; then deploy_success "βœ“ Project files deployed correctly" else deploy_error "βœ— Project files missing or incomplete" ((validation_errors++)) fi # Check scripts are executable deploy_info "Checking script permissions" if remote_exec "test -x '$REMOTE_PATH/scripts/vm/bulletproof-automation.sh'" true true; then deploy_success "βœ“ Scripts are executable" else deploy_error "βœ— Scripts are not executable" ((validation_errors++)) fi # Check repository if configured if [[ "${SKIP_REPO_CONFIG:-false}" != "true" ]] && [[ -n "${GITHUB_REPO_URL:-}" ]]; then deploy_info "Checking repository setup" if remote_exec "cd '$REMOTE_PATH' && git status" true true; then deploy_success "βœ“ Repository cloned and configured" # Check repository branch local current_branch current_branch=$(remote_exec "cd '$REMOTE_PATH' && git branch --show-current" true true) deploy_info "Repository branch: $current_branch" else deploy_error "βœ— Repository not properly configured" ((validation_errors++)) fi fi # Check GitHub authentication if configured if [[ "${SKIP_GITHUB_SETUP:-false}" != "true" ]]; then deploy_info "Checking GitHub authentication" if remote_exec "cd '$REMOTE_PATH' && python3 scripts/vm/github-setup.py validate" true true; then deploy_success "βœ“ GitHub authentication configured" else deploy_warning "⚠ GitHub authentication not configured" fi fi # Check systemd service if configured if [[ "${SKIP_SERVICE_SETUP:-false}" != "true" ]] && remote_exec "command -v systemctl" true true; then deploy_info "Checking systemd service configuration" if remote_exec "systemctl is-enabled thrillwiki-automation" true true; then deploy_success "βœ“ Systemd service enabled" # Check if service is running if remote_exec "systemctl is-active thrillwiki-automation" true true; then deploy_success "βœ“ Automation service is running" # Perform comprehensive service health check deploy_info "Performing comprehensive service health check" if remote_exec "cd '$REMOTE_PATH' && timeout 10 bash scripts/vm/bulletproof-automation.sh --validate-only" true true; then deploy_success "βœ“ Service health check passed" else deploy_warning "⚠ Service health check failed" fi else deploy_warning "⚠ Automation service is not running" # Show service status for debugging deploy_info "Service status details:" remote_exec "sudo systemctl status thrillwiki-automation --no-pager -l | head -10" false true fi else deploy_warning "⚠ Systemd service not enabled" # Check if service file exists if remote_exec "test -f /etc/systemd/system/thrillwiki-automation.service" true true; then deploy_info "β„Ή Service file exists but is not enabled" else deploy_error "βœ— Service file not found" ((validation_errors++)) fi fi # Check automation service environment configuration deploy_info "Checking automation service environment" if remote_exec "test -f '$REMOTE_PATH/scripts/systemd/thrillwiki-automation***REMOVED***'" true true; then deploy_success "βœ“ Service environment configuration exists" else deploy_warning "⚠ Service environment configuration missing" fi fi # Test automation script functionality deploy_info "Testing automation script" if remote_exec "cd '$REMOTE_PATH' && timeout 30 bash scripts/vm/bulletproof-automation.sh test" true true; then deploy_success "βœ“ Automation script test passed" else deploy_warning "⚠ Automation script test failed or timed out" fi # Summary if [[ $validation_errors -eq 0 ]]; then deploy_success "Deployment validation completed successfully" return 0 else deploy_warning "Deployment validation completed with $validation_errors errors" return 1 fi } # [AWS-SECRET-REMOVED]==================================== # ROLLBACK FUNCTIONALITY # [AWS-SECRET-REMOVED]==================================== rollback_deployment() { deploy_warning "Rolling back deployment" if [[ "${DRY_RUN:-false}" == "true" ]]; then deploy_info "Dry run: would rollback deployment" return 0 fi echo "Rollback started at $(date)" >> "$ROLLBACK_LOG" # Stop automation service if running if remote_exec "command -v systemctl" true true; then deploy_info "Stopping automation service" remote_exec "sudo systemctl stop thrillwiki-automation" false true remote_exec "sudo systemctl disable thrillwiki-automation" false true remote_exec "sudo rm -f /etc/systemd/system/thrillwiki-automation.service" false true remote_exec "sudo systemctl daemon-reload" false true fi # Clean up git credentials if they were configured deploy_info "Cleaning up git credentials" remote_exec "rm -f ~/.git-credentials" false true # Remove deployed files and repository deploy_info "Removing deployed files and repository" if remote_exec "rm -rf '$REMOTE_PATH'" false true; then deploy_success "Deployed files and repository removed" else deploy_error "Failed to remove deployed files" fi deploy_info "Rollback completed" echo "Rollback completed at $(date)" >> "$ROLLBACK_LOG" } # [AWS-SECRET-REMOVED]==================================== # STATUS REPORTING # [AWS-SECRET-REMOVED]==================================== show_deployment_status() { echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🎯 ThrillWiki Remote Deployment Status" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" if [[ "${DRY_RUN:-false}" == "true" ]]; then echo "πŸ” DRY RUN COMPLETED" echo "" echo "The following would be deployed to $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH:" echo "β€’ Complete ThrillWiki automation system" echo "β€’ Django project setup with UV package manager" echo "β€’ Database migrations and environment configuration" echo "β€’ Tailwind CSS compilation and static file collection" echo "β€’ GitHub authentication setup" echo "β€’ Automatic pull scheduling (5-minute intervals)" echo "β€’ Systemd service for auto-start" echo "β€’ Health monitoring and logging" echo "" echo "To execute the actual deployment, run without --dry-run" return 0 fi echo "πŸ“Š Deployment Summary:" echo "β€’ Target: $REMOTE_USER@$REMOTE_HOST:$REMOTE_PORT" echo "β€’ Project Path: $REMOTE_PATH" echo "β€’ Deployment Preset: ${DEPLOYMENT_PRESET:-dev}" echo "β€’ Django Setup: ${DJANGO_PROJECT_SETUP:-true}" echo "β€’ GitHub Auth: ${SKIP_GITHUB_SETUP:-false}" echo "β€’ Service Setup: ${SKIP_SERVICE_SETUP:-false}" echo "β€’ Repository: ${GITHUB_REPO_URL:-}" echo "β€’ Branch: ${GITHUB_REPO_BRANCH:-main}" echo "" # Show automation service status echo "πŸ”§ Automation Service Status:" if [[ "${SKIP_SERVICE_SETUP:-false}" != "true" ]] && remote_exec "command -v systemctl" true true; then local service_status service_status=$(remote_exec "systemctl is-active thrillwiki-automation 2>/dev/null || echo 'inactive'" true true) case "$service_status" in "active") echo "β€’ Service Status: βœ… Running" ;; "inactive") echo "β€’ Service Status: ⏸️ Stopped" ;; "failed") echo "β€’ Service Status: ❌ Failed" ;; *) echo "β€’ Service Status: ❓ $service_status" ;; esac # Show service configuration if remote_exec "test -f '$REMOTE_PATH/scripts/systemd/thrillwiki-automation***REMOVED***'" true true; then echo "β€’ Environment Config: βœ… Configured" local pull_interval pull_interval=$(remote_exec "grep '^PULL_INTERVAL=' '$REMOTE_PATH/scripts/systemd/thrillwiki-automation***REMOVED***' | cut -d'=' -f2" true true 2>/dev/null || echo "300") echo "β€’ Pull Interval: ${pull_interval}s ($((pull_interval / 60)) minutes)" else echo "β€’ Environment Config: ❌ Missing" fi # Show service enablement status if remote_exec "systemctl is-enabled thrillwiki-automation" true true; then echo "β€’ Auto-start: βœ… Enabled" else echo "β€’ Auto-start: ❌ Disabled" fi else echo "β€’ Service Status: ⏭️ Skipped" fi echo "" echo "πŸš€ Next Steps:" echo "" if [[ "${SKIP_GITHUB_SETUP:-false}" == "true" ]]; then echo "1. Set up GitHub authentication:" echo " ssh $REMOTE_USER@$REMOTE_HOST 'cd $REMOTE_PATH && python3 scripts/vm/github-setup.py setup'" echo "" fi if [[ "${SKIP_SERVICE_SETUP:-false}" == "true" ]]; then echo "2. Set up systemd service:" echo " ssh $REMOTE_USER@$REMOTE_HOST 'cd $REMOTE_PATH && bash scripts/vm/setup-automation.sh'" echo "" fi echo "3. Monitor automation:" echo " ssh $REMOTE_USER@$REMOTE_HOST 'sudo journalctl -u thrillwiki-automation -f'" echo "" echo "4. Check status:" echo " ssh $REMOTE_USER@$REMOTE_HOST 'sudo systemctl status thrillwiki-automation'" echo "" echo "5. View logs:" echo " ssh $REMOTE_USER@$REMOTE_HOST 'tail -f $REMOTE_PATH/logs/bulletproof-automation.log'" echo "" echo "πŸ“š Documentation:" echo "β€’ Automation script: $REMOTE_PATH/scripts/vm/bulletproof-automation.sh" echo "β€’ Setup guide: $REMOTE_PATH/scripts/vm/setup-automation.sh --help" echo "β€’ GitHub setup: $REMOTE_PATH/scripts/vm/github-setup.py --help" echo "" deploy_success "Remote deployment completed successfully!" } # [AWS-SECRET-REMOVED]==================================== # MAIN DEPLOYMENT WORKFLOW # [AWS-SECRET-REMOVED]==================================== main() { echo "" echo "πŸš€ ThrillWiki Remote Deployment" echo "===============================" echo "" # Parse command line arguments parse_arguments "$@" # Validate local dependencies deploy_info "Validating local dependencies" local missing_deps=() for cmd in ssh scp rsync git; do if ! command_exists "$cmd"; then missing_deps+=("$cmd") fi done if [[ ${#missing_deps[@]} -gt 0 ]]; then deploy_error "Missing required local dependencies: ${missing_deps[*]}" exit 1 fi # Show configuration echo "πŸ“‹ Deployment Configuration:" echo "β€’ Remote Host: $REMOTE_HOST:$REMOTE_PORT" echo "β€’ Remote User: $REMOTE_USER" echo "β€’ Remote Path: $REMOTE_PATH" echo "β€’ SSH Key: ${SSH_KEY:-}" echo "β€’ Repository: ${GITHUB_REPO_URL:-}" echo "β€’ Branch: ${GITHUB_REPO_BRANCH:-main}" echo "β€’ Deployment Preset: ${DEPLOYMENT_PRESET:-dev}" echo "β€’ Django Setup: ${DJANGO_PROJECT_SETUP:-true}" echo "β€’ Timeout: ${DEPLOYMENT_TIMEOUT}s" echo "" if [[ "${DRY_RUN:-false}" == "true" ]]; then echo "πŸ” DRY RUN MODE - No changes will be made" echo "" fi # Set up trap for cleanup on error trap 'deploy_error "Deployment interrupted"; rollback_deployment; exit 4' INT TERM local start_time start_time=$(date +%s) # Main deployment steps echo "πŸ”§ Starting deployment process..." echo "" # Step 1: Test connection if ! test_ssh_connection; then deploy_error "Cannot establish SSH connection" exit 2 fi # Step 2: Validate remote environment if ! validate_remote_environment; then deploy_error "Remote environment validation failed" exit 5 fi # Step 3: Check target directory if ! check_target_directory; then deploy_error "Target directory check failed" exit 4 fi # Step 4: Deploy project files if ! deploy_project_files; then deploy_error "Project file deployment failed" rollback_deployment exit 4 fi # Step 5: Clone project repository if ! clone_repository_on_remote; then deploy_error "Repository cloning failed" rollback_deployment exit 4 fi # Step 6: Set up dependencies if ! setup_remote_dependencies; then deploy_error "Remote dependency setup failed" rollback_deployment exit 4 fi # Step 7: Set up Django project if ! setup_django_project; then deploy_error "Django project setup failed" rollback_deployment exit 4 fi # Step 8: Configure GitHub authentication if ! setup_remote_github_auth; then deploy_warning "GitHub authentication setup failed, continuing without it" fi # Step 9: Set up automation service if ! setup_automation_service; then deploy_warning "Automation service setup failed, continuing without it" fi # Step 10: Validate deployment if ! validate_deployment; then deploy_warning "Deployment validation had issues, but deployment may still be functional" fi # Step 11: Validate Django setup if ! validate_django_setup; then deploy_warning "Django setup validation had issues, but may still be functional" fi # Calculate deployment time local end_time end_time=$(date +%s) local duration=$((end_time - start_time)) echo "" deploy_success "Remote deployment completed in ${duration}s" # Show final status show_deployment_status } # Run main function if script is executed directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi