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