Files
thrillwiki_django_no_react/scripts/vm/remote-deploy.sh
pacnpal f4f8ec8f9b Configure PostgreSQL with PostGIS support
- Updated database settings to use dj_database_url for environment-based configuration
- Added dj-database-url dependency
- Configured PostGIS backend for spatial data support
- Set default DATABASE_URL for production PostgreSQL connection
2025-08-19 18:51:33 -04:00

2685 lines
100 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#
# 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] <remote_host>
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 <remote_host>"
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:-<none>}"
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:-<default>}"
echo "• Repository: ${GITHUB_REPO_URL:-<none>}"
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