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

1156 lines
35 KiB
Bash
Executable File

#!/bin/bash
#
# ThrillWiki Bulletproof Development Automation Script
# Enhanced automation for VM startup, GitHub repository pulls, and server management
# Designed for development environments with automatic migrations
#
# Features:
# - Automated VM startup and server management
# - GitHub repository pulls every 5 minutes (configurable)
# - Automatic Django migrations on code changes
# - Enhanced dependency updates with uv sync -U and uv lock -U
# - Easy GitHub PAT (Personal Access Token) configuration
# - Enhanced error handling and recovery
# - Comprehensive logging and health monitoring
# - Signal handling for graceful shutdown
# - File locking to prevent multiple instances
#
set -e
# [AWS-SECRET-REMOVED]====================================
# CONFIGURATION SECTION
# [AWS-SECRET-REMOVED]====================================
# Customize these variables for your environment
# Project Configuration
PROJECT_DIR="${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
SERVICE_NAME="${SERVICE_NAME:-thrillwiki}"
GITHUB_REPO="${GITHUB_REPO:-origin}"
GITHUB_BRANCH="${GITHUB_BRANCH:-main}"
# Timing Configuration (in seconds)
PULL_INTERVAL="${PULL_INTERVAL:-300}" # 5 minutes default
HEALTH_CHECK_INTERVAL="${HEALTH_CHECK_INTERVAL:-60}" # 1 minute
STARTUP_TIMEOUT="${STARTUP_TIMEOUT:-120}" # 2 minutes
RESTART_DELAY="${RESTART_DELAY:-10}" # 10 seconds
# Logging Configuration
LOG_DIR="${LOG_DIR:-$PROJECT_DIR/logs}"
LOG_FILE="${LOG_FILE:-$LOG_DIR/bulletproof-automation.log}"
LOCK_FILE="${LOCK_FILE:-/tmp/thrillwiki-bulletproof.lock}"
MAX_LOG_SIZE="${MAX_LOG_SIZE:-10485760}" # 10MB
# GitHub Authentication Configuration
GITHUB_AUTH_SCRIPT="${GITHUB_AUTH_SCRIPT:-$PROJECT_DIR/scripts/github-auth.py}"
GITHUB_TOKEN_FILE="${GITHUB_TOKEN_FILE:-$PROJECT_DIR/.github-pat}"
# Development Server Configuration
SERVER_HOST="${SERVER_HOST:-0.0.0.0}"
SERVER_PORT="${SERVER_PORT:-8000}"
HEALTH_ENDPOINT="${HEALTH_ENDPOINT:-http://localhost:$SERVER_PORT}"
# Auto-recovery Configuration
MAX_RESTART_ATTEMPTS="${MAX_RESTART_ATTEMPTS:-3}"
RESTART_COOLDOWN="${RESTART_COOLDOWN:-300}" # 5 minutes
# [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'
NC='\033[0m' # No Color
# [AWS-SECRET-REMOVED]====================================
# GLOBAL VARIABLES
# [AWS-SECRET-REMOVED]====================================
SCRIPT_PID=$$
START_TIME=$(date +%s)
LAST_SUCCESSFUL_PULL=0
RESTART_ATTEMPTS=0
LAST_RESTART_TIME=0
SERVER_PID=""
SHUTDOWN_REQUESTED=false
# [AWS-SECRET-REMOVED]====================================
# LOGGING FUNCTIONS
# [AWS-SECRET-REMOVED]====================================
# Main logging function with timestamp and color
log() {
local level="$1"
local color="$2"
local message="$3"
local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
# Log to file (without colors)
echo "[$timestamp] [$level] [PID:$SCRIPT_PID] $message" >> "$LOG_FILE"
# Log to console (with colors)
echo -e "${color}[$timestamp] [$level]${NC} $message"
}
log_info() {
log "INFO" "$BLUE" "$1"
}
log_success() {
log "SUCCESS" "$GREEN" "$1"
}
log_warning() {
log "WARNING" "$YELLOW" "⚠️ $1"
}
log_error() {
log "ERROR" "$RED" "$1"
}
log_debug() {
if [[ "${DEBUG:-false}" == "true" ]]; then
log "DEBUG" "$PURPLE" "🔍 $1"
fi
}
log_automation() {
log "AUTOMATION" "$CYAN" "🤖 $1"
}
# [AWS-SECRET-REMOVED]====================================
# UTILITY FUNCTIONS
# [AWS-SECRET-REMOVED]====================================
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Get current timestamp
timestamp() {
date +%s
}
# Calculate time difference in human readable format
time_diff() {
local start="$1"
local end="$2"
local diff=$((end - start))
if [[ $diff -lt 60 ]]; then
echo "${diff}s"
elif [[ $diff -lt 3600 ]]; then
echo "$((diff / 60))m $((diff % 60))s"
else
echo "$((diff / 3600))h $(((diff % 3600) / 60))m"
fi
}
# Rotate log file if it exceeds max size
rotate_log() {
if [[ -f "$LOG_FILE" ]]; then
local size
size=$(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)
if [[ $size -gt $MAX_LOG_SIZE ]]; then
mv "$LOG_FILE" "${LOG_FILE}.old"
log_info "Log file rotated due to size limit ($MAX_LOG_SIZE bytes)"
fi
fi
}
# [AWS-SECRET-REMOVED]====================================
# LOCK FILE MANAGEMENT
# [AWS-SECRET-REMOVED]====================================
# Acquire lock to prevent multiple instances
acquire_lock() {
log_debug "Attempting to acquire lock file: $LOCK_FILE"
if [[ -f "$LOCK_FILE" ]]; then
local lock_pid
lock_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then
log_error "Bulletproof automation already running (PID: $lock_pid)"
log_error "If you're sure no other instance is running, remove: $LOCK_FILE"
exit 1
else
log_warning "Removing stale lock file (PID: $lock_pid)"
rm -f "$LOCK_FILE"
fi
fi
echo $SCRIPT_PID > "$LOCK_FILE"
log_debug "Lock acquired successfully"
# Set up trap to remove lock on exit
trap 'cleanup_on_exit' EXIT INT TERM HUP
}
# Release lock and cleanup
cleanup_on_exit() {
log_info "Cleaning up on exit..."
SHUTDOWN_REQUESTED=true
# Stop server if we started it
if [[ -n "$SERVER_PID" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then
log_info "Stopping server (PID: $SERVER_PID)..."
kill -TERM "$SERVER_PID" 2>/dev/null || true
sleep 5
if kill -0 "$SERVER_PID" 2>/dev/null; then
log_warning "Force killing server..."
kill -KILL "$SERVER_PID" 2>/dev/null || true
fi
fi
# Remove lock file
if [[ -f "$LOCK_FILE" ]]; then
rm -f "$LOCK_FILE"
log_debug "Lock file removed"
fi
local end_time
end_time=$(timestamp)
local runtime
runtime=$(time_diff "$START_TIME" "$end_time")
log_success "Bulletproof automation stopped after running for $runtime"
}
# [AWS-SECRET-REMOVED]====================================
# DEPENDENCY VALIDATION
# [AWS-SECRET-REMOVED]====================================
validate_dependencies() {
log_info "Validating dependencies..."
local missing_deps=()
# Check required commands (except uv which needs special handling)
for cmd in git curl lsof; do
if ! command_exists "$cmd"; then
missing_deps+=("$cmd")
fi
done
# Special check for UV with fallback to ~/.local/bin/uv
if ! command_exists "uv" && ! [[ -x "$HOME/.local/bin/uv" ]]; then
missing_deps+=("uv")
fi
# Check if we're in a Git repository
if [[ ! -d "$PROJECT_DIR/.git" ]]; then
log_error "Not a Git repository: $PROJECT_DIR"
return 1
fi
# Check GitHub authentication script
if [[ ! -f "$GITHUB_AUTH_SCRIPT" ]]; then
log_warning "GitHub authentication script not found: $GITHUB_AUTH_SCRIPT"
log_warning "Will attempt public repository access"
elif [[ ! -x "$GITHUB_AUTH_SCRIPT" ]]; then
log_warning "GitHub authentication script not executable: $GITHUB_AUTH_SCRIPT"
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log_error "Missing required dependencies: ${missing_deps[*]}"
log_error "Please install missing dependencies and try again"
return 1
fi
log_success "All dependencies validated"
return 0
}
# [AWS-SECRET-REMOVED]====================================
# GITHUB PAT (PERSONAL ACCESS TOKEN) MANAGEMENT
# [AWS-SECRET-REMOVED]====================================
# Set GitHub PAT interactively
set_github_pat() {
echo "Setting up GitHub Personal Access Token (PAT)"
echo "[AWS-SECRET-REMOVED]======"
echo ""
echo "To use this automation script with private repositories or to avoid"
echo "rate limits, you need to provide a GitHub Personal Access Token."
echo ""
echo "To create a PAT:"
echo "1. Go to https://github.com/settings/tokens"
echo "2. Click 'Generate new token (classic)'"
echo "3. Select scopes: 'repo' (for private repos) or 'public_repo' (for public repos)"
echo "4. Click 'Generate token'"
echo "5. Copy the token (it won't be shown again)"
echo ""
# Prompt for token
read -r -s -p "Enter your GitHub PAT (input will be hidden): " github_token
echo ""
if [[ -z "$github_token" ]]; then
log_warning "No token provided, automation will use public access"
return 1
fi
# Validate token by making a test API call
log_info "Validating GitHub token..."
if curl -s -H "Authorization: Bearer $github_token" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/user" >/dev/null 2>&1; then
# Save token to file with secure permissions
echo "$github_token" > "$GITHUB_TOKEN_FILE"
chmod 600 "$GITHUB_TOKEN_FILE"
log_success "GitHub PAT saved and validated successfully"
log_info "Token saved to: $GITHUB_TOKEN_FILE"
# Export for current session
export GITHUB_TOKEN="$github_token"
return 0
else
log_error "Invalid GitHub token provided"
return 1
fi
}
# Load GitHub PAT from various sources
load_github_pat() {
log_debug "Loading GitHub PAT..."
# Priority order:
# 1. Command line argument (--token)
# 2. Environment variable (GITHUB_TOKEN)
# 3. Token file (.github-pat)
# 4. ***REMOVED*** files
# 5. GitHub auth script
# Check if already set via command line or environment
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
log_debug "Using GitHub token from environment/command line"
return 0
fi
# Try loading from token file
if [[ -f "$GITHUB_TOKEN_FILE" ]]; then
log_debug "Loading GitHub token from file: $GITHUB_TOKEN_FILE"
if GITHUB_TOKEN=$(cat "$GITHUB_TOKEN_FILE" 2>/dev/null | tr -d '\n\r') && [[ -n "$GITHUB_TOKEN" ]]; then
export GITHUB_TOKEN
log_debug "GitHub token loaded from token file"
return 0
fi
fi
# Try loading from ***REMOVED*** files
for env_file in "$PROJECT_DIR/***REMOVED***" "$PROJECT_DIR/../***REMOVED***.unraid" "$PROJECT_DIR/../../***REMOVED***.unraid"; do
if [[ -f "$env_file" ]]; then
log_debug "Loading environment from: $env_file"
# shellcheck source=/dev/null
source "$env_file"
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
log_debug "GitHub token loaded from $env_file"
return 0
fi
fi
done
# Try using GitHub authentication script
if [[ -x "$GITHUB_AUTH_SCRIPT" ]]; then
log_debug "Attempting to get token from authentication script..."
if GITHUB_TOKEN=$(python3 "$GITHUB_AUTH_SCRIPT" token 2>/dev/null) && [[ -n "$GITHUB_TOKEN" ]]; then
export GITHUB_TOKEN
log_debug "GitHub token obtained from authentication script"
return 0
fi
fi
log_debug "No GitHub authentication available"
return 1
}
# Remove stored GitHub PAT
remove_github_pat() {
if [[ -f "$GITHUB_TOKEN_FILE" ]]; then
rm -f "$GITHUB_TOKEN_FILE"
log_success "GitHub PAT removed from: $GITHUB_TOKEN_FILE"
else
log_info "No stored GitHub PAT found"
fi
# Clear from environment
unset GITHUB_TOKEN
log_info "GitHub PAT cleared from environment"
}
# [AWS-SECRET-REMOVED]====================================
# GITHUB AUTHENTICATION
# [AWS-SECRET-REMOVED]====================================
setup_github_auth() {
log_debug "Setting up GitHub authentication..."
# Load GitHub PAT
if load_github_pat; then
log_debug "GitHub authentication configured successfully"
return 0
else
log_warning "No GitHub authentication available, will use public access"
return 1
fi
}
configure_git_auth() {
local repo_url
repo_url=$(git remote get-url "$GITHUB_REPO" 2>/dev/null || echo "")
if [[ -n "${GITHUB_TOKEN:-}" ]] && [[ "$repo_url" =~ github\.com ]]; then
log_debug "Configuring Git with token authentication..."
# Extract repository path from URL
local repo_path
repo_path=$(echo "$repo_url" | sed -E 's|.*github\.com[:/]([^/]+/[^/]+).*|\1|' | sed 's|\.git$||')
if [[ -n "$repo_path" ]]; then
local auth_url="https://oauth2:${GITHUB_TOKEN}@github.com/${repo_path}.git"
git remote set-url "$GITHUB_REPO" "$auth_url"
log_debug "Git authentication configured successfully"
return 0
fi
fi
log_debug "Using existing Git configuration"
return 0
}
# [AWS-SECRET-REMOVED]====================================
# REPOSITORY MANAGEMENT
# [AWS-SECRET-REMOVED]====================================
# Check for remote changes using GitHub API or Git
check_remote_changes() {
log_debug "Checking for remote changes..."
# Setup authentication first
setup_github_auth
configure_git_auth
# Fetch latest changes
if ! git fetch "$GITHUB_REPO" "$GITHUB_BRANCH" --quiet 2>/dev/null; then
log_warning "Failed to fetch from remote repository"
return 1
fi
# Compare local and remote commits
local local_commit
local remote_commit
local_commit=$(git rev-parse HEAD 2>/dev/null || echo "")
remote_commit=$(git rev-parse "$GITHUB_REPO/$GITHUB_BRANCH" 2>/dev/null || echo "")
if [[ -z "$local_commit" ]] || [[ -z "$remote_commit" ]]; then
log_warning "Unable to compare commits"
return 1
fi
log_debug "Local commit: ${local_commit:0:8}"
log_debug "Remote commit: ${remote_commit:0:8}"
if [[ "$local_commit" != "$remote_commit" ]]; then
log_automation "New changes detected on remote branch"
return 0 # Changes available
else
log_debug "Repository is up to date"
return 1 # No changes
fi
}
# Pull latest changes from repository
pull_repository_changes() {
log_automation "Pulling latest changes from repository..."
local pull_output
if pull_output=$(git pull "$GITHUB_REPO" "$GITHUB_BRANCH" 2>&1); then
log_success "Successfully pulled latest changes"
# Log changes summary
echo "$pull_output" | grep -E "^( |Updating|Fast-forward)" | head -10 | while IFS= read -r line; do
log_info " $line"
done
LAST_SUCCESSFUL_PULL=$(timestamp)
return 0
else
log_error "Failed to pull changes:"
echo "$pull_output" | head -5 | while IFS= read -r line; do
log_error " $line"
done
return 1
fi
}
# [AWS-SECRET-REMOVED]====================================
# SERVER MANAGEMENT
# [AWS-SECRET-REMOVED]====================================
# Stop Django server and clean up processes
stop_server() {
log_info "Stopping Django server..."
# Kill processes on port 8000
if lsof -ti :"$SERVER_PORT" >/dev/null 2>&1; then
log_info "Stopping processes on port $SERVER_PORT..."
lsof -ti :"$SERVER_PORT" | xargs kill -9 2>/dev/null || true
sleep 2
fi
# Clean up Python cache
log_debug "Cleaning Python cache..."
find "$PROJECT_DIR" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
log_success "Server stopped and cleaned up"
}
# Start Django development server following project requirements
start_server() {
log_info "Starting Django development server..."
# Change to project directory
cd "$PROJECT_DIR"
# Ensure UV path is available - add both cargo and local bin paths
export PATH="$HOME/.local/bin:/home/$(whoami)/.cargo/bin:$PATH"
# Verify UV is available (check command in PATH or explicit path)
if ! command_exists uv && ! [[ -x "$HOME/.local/bin/uv" ]]; then
log_error "UV is not installed or not accessible"
log_error "Checked: command 'uv' in PATH and ~/.local/bin/uv"
return 1
fi
# Set UV command path for consistency
if command_exists uv; then
UV_CMD="uv"
else
UV_CMD="$HOME/.local/bin/uv"
fi
log_debug "Using UV command: $UV_CMD"
# Execute the exact startup sequence from .clinerules
log_info "Executing startup sequence: lsof -ti :$SERVER_PORT | xargs kill -9; find . -type d -name '__pycache__' -exec rm -r {} +; uv run manage.py tailwind runserver"
# Start server in background and capture PID
lsof -ti :"$SERVER_PORT" | xargs kill -9 2>/dev/null || true
find . -type d -name "__pycache__" -exec rm -r {} + 2>/dev/null || true
# Start server using the determined UV command
"$UV_CMD" run manage.py tailwind runserver "$SERVER_HOST:$SERVER_PORT" > "$LOG_DIR/django-server.log" 2>&1 &
SERVER_PID=$!
# Wait for server to start
log_info "Waiting for server to start (PID: $SERVER_PID)..."
local attempts=0
local max_attempts=$((STARTUP_TIMEOUT / 5))
while [[ $attempts -lt $max_attempts ]]; do
if kill -0 "$SERVER_PID" 2>/dev/null; then
sleep 5
if perform_health_check silent; then
log_success "Django server started successfully on $SERVER_HOST:$SERVER_PORT"
return 0
fi
else
log_error "Server process died unexpectedly"
return 1
fi
attempts=$((attempts + 1))
log_debug "Startup attempt $attempts/$max_attempts..."
done
log_error "Server failed to start within timeout period"
return 1
}
# Restart server with proper cleanup and recovery
restart_server() {
log_automation "Restarting Django server..."
# Check restart cooldown
local current_time
current_time=$(timestamp)
if [[ $LAST_RESTART_TIME -gt 0 ]] && [[ $((current_time - LAST_RESTART_TIME)) -lt $RESTART_COOLDOWN ]]; then
local wait_time=$((RESTART_COOLDOWN - (current_time - LAST_RESTART_TIME)))
log_warning "Restart cooldown active, waiting ${wait_time}s..."
return 1
fi
# Increment restart attempts
RESTART_ATTEMPTS=$((RESTART_ATTEMPTS + 1))
LAST_RESTART_TIME=$current_time
if [[ $RESTART_ATTEMPTS -gt $MAX_RESTART_ATTEMPTS ]]; then
log_error "Maximum restart attempts ($MAX_RESTART_ATTEMPTS) exceeded"
return 1
fi
# Stop current server
stop_server
# Wait before restart
log_info "Waiting ${RESTART_DELAY}s before restart..."
sleep "$RESTART_DELAY"
# Start server
if start_server; then
RESTART_ATTEMPTS=0 # Reset counter on successful restart
log_success "Server restarted successfully"
return 0
else
log_error "Server restart failed (attempt $RESTART_ATTEMPTS/$MAX_RESTART_ATTEMPTS)"
return 1
fi
}
# [AWS-SECRET-REMOVED]====================================
# DJANGO OPERATIONS
# [AWS-SECRET-REMOVED]====================================
# Update dependencies using UV with latest versions
update_dependencies() {
log_info "Updating dependencies with UV..."
cd "$PROJECT_DIR"
# Ensure UV is available and set command
if command_exists uv; then
UV_CMD="uv"
elif [[ -x "$HOME/.local/bin/uv" ]]; then
UV_CMD="$HOME/.local/bin/uv"
else
log_error "UV not found for dependency update"
return 1
fi
# Update lock file first to get latest versions
log_debug "Updating lock file with latest versions ($UV_CMD lock -U)..."
if ! "$UV_CMD" lock -U --quiet 2>/dev/null; then
log_warning "Failed to update lock file, continuing with sync..."
else
log_debug "Lock file updated successfully"
fi
# Sync dependencies with upgrade flag
log_debug "Syncing dependencies with upgrades ($UV_CMD sync -U)..."
if "$UV_CMD" sync -U --quiet 2>/dev/null; then
log_success "Dependencies updated and synced successfully"
return 0
else
log_warning "Dependency update failed"
return 1
fi
}
# Run Django migrations
run_migrations() {
log_info "Running Django migrations..."
cd "$PROJECT_DIR"
# Ensure UV is available and set command
if command_exists uv; then
UV_CMD="uv"
elif [[ -x "$HOME/.local/bin/uv" ]]; then
UV_CMD="$HOME/.local/bin/uv"
else
log_error "UV not found for migrations"
return 1
fi
# Check for pending migrations first
local pending_migrations
if pending_migrations=$("$UV_CMD" run manage.py showmigrations --plan 2>/dev/null | grep -c "^\\[ \\]" || echo "0"); then
if [[ "$pending_migrations" -gt 0 ]]; then
log_automation "Found $pending_migrations pending migration(s), applying..."
if "$UV_CMD" run manage.py migrate --quiet 2>/dev/null; then
log_success "Django migrations completed successfully"
return 0
else
log_error "Django migrations failed"
return 1
fi
else
log_debug "No pending migrations found"
return 0
fi
else
log_warning "Could not check migration status"
return 1
fi
}
# Collect static files
collect_static_files() {
log_info "Collecting static files..."
cd "$PROJECT_DIR"
# Ensure UV is available and set command
if command_exists uv; then
UV_CMD="uv"
elif [[ -x "$HOME/.local/bin/uv" ]]; then
UV_CMD="$HOME/.local/bin/uv"
else
log_error "UV not found for static file collection"
return 1
fi
if "$UV_CMD" run manage.py collectstatic --noinput --quiet 2>/dev/null; then
log_success "Static files collected successfully"
return 0
else
log_warning "Static file collection failed"
return 1
fi
}
# [AWS-SECRET-REMOVED]====================================
# HEALTH MONITORING
# [AWS-SECRET-REMOVED]====================================
# Perform health check on the running server
perform_health_check() {
local silent="${1:-false}"
if [[ "$silent" != "true" ]]; then
log_debug "Performing health check..."
fi
# Check if server process is running
if [[ -n "$SERVER_PID" ]] && ! kill -0 "$SERVER_PID" 2>/dev/null; then
if [[ "$silent" != "true" ]]; then
log_warning "Server process is not running"
fi
return 1
fi
# Check HTTP endpoint
if curl -f -s "$HEALTH_ENDPOINT" >/dev/null 2>&1; then
if [[ "$silent" != "true" ]]; then
log_debug "Health check passed"
fi
return 0
else
# Try root endpoint if health endpoint fails
if curl -f -s "$HEALTH_ENDPOINT/" >/dev/null 2>&1; then
if [[ "$silent" != "true" ]]; then
log_debug "Health check passed (root endpoint)"
fi
return 0
fi
if [[ "$silent" != "true" ]]; then
log_warning "Health check failed - server not responding"
fi
return 1
fi
}
# [AWS-SECRET-REMOVED]====================================
# AUTOMATION LOOPS
# [AWS-SECRET-REMOVED]====================================
# Process code changes after pulling updates
process_code_changes() {
local pull_output="$1"
local needs_restart=false
log_automation "Processing code changes..."
# Check if dependencies changed
if echo "$pull_output" | grep -qE "(pyproject\.toml|requirements.*\.txt|uv\.lock)"; then
log_automation "Dependencies changed, updating with latest versions..."
if update_dependencies; then
needs_restart=true
fi
fi
# Always run migrations on code changes (development best practice)
log_automation "Running migrations (development mode)..."
if run_migrations; then
needs_restart=true
fi
# Check if static files changed
if echo "$pull_output" | grep -qE "(static/|templates/|\.css|\.js|\.scss)"; then
log_automation "Static files changed, collecting..."
collect_static_files
# Static files don't require restart in development
fi
# Check if Python code changed
if echo "$pull_output" | grep -qE "\.py$"; then
log_automation "Python code changed, restart required"
needs_restart=true
fi
if [[ "$needs_restart" == "true" ]]; then
log_automation "Restarting server due to code changes..."
restart_server
else
log_info "No restart required for these changes"
fi
}
# Main automation loop for repository pulling
repository_pull_loop() {
log_automation "Starting repository pull loop (interval: ${PULL_INTERVAL}s)"
while [[ "$SHUTDOWN_REQUESTED" != "true" ]]; do
if check_remote_changes; then
local pull_output
if pull_output=$(git pull "$GITHUB_REPO" "$GITHUB_BRANCH" 2>&1); then
log_success "Repository updated successfully"
process_code_changes "$pull_output"
else
log_error "Failed to pull repository changes"
fi
fi
# Sleep in small increments to allow for responsive shutdown
local sleep_remaining="$PULL_INTERVAL"
while [[ $sleep_remaining -gt 0 ]] && [[ "$SHUTDOWN_REQUESTED" != "true" ]]; do
local sleep_time
sleep_time=$([[ $sleep_remaining -gt 10 ]] && echo 10 || echo $sleep_remaining)
sleep "$sleep_time"
sleep_remaining=$((sleep_remaining - sleep_time))
done
done
log_automation "Repository pull loop stopped"
}
# Health monitoring loop
health_monitoring_loop() {
log_automation "Starting health monitoring loop (interval: ${HEALTH_CHECK_INTERVAL}s)"
while [[ "$SHUTDOWN_REQUESTED" != "true" ]]; do
if ! perform_health_check silent; then
log_warning "Health check failed, attempting server recovery..."
if ! restart_server; then
log_error "Server recovery failed, will try again next cycle"
fi
fi
# Sleep in small increments for responsive shutdown
local sleep_remaining="$HEALTH_CHECK_INTERVAL"
while [[ $sleep_remaining -gt 0 ]] && [[ "$SHUTDOWN_REQUESTED" != "true" ]]; do
local sleep_time
sleep_time=$([[ $sleep_remaining -gt 10 ]] && echo 10 || echo $sleep_remaining)
sleep "$sleep_time"
sleep_remaining=$((sleep_remaining - sleep_time))
done
done
log_automation "Health monitoring loop stopped"
}
# [AWS-SECRET-REMOVED]====================================
# MAIN FUNCTIONS
# [AWS-SECRET-REMOVED]====================================
# Initialize automation environment
initialize_automation() {
log_info "Initializing ThrillWiki Bulletproof Automation..."
log_info "Project Directory: $PROJECT_DIR"
log_info "Pull Interval: ${PULL_INTERVAL}s"
log_info "Health Check Interval: ${HEALTH_CHECK_INTERVAL}s"
# Create necessary directories
mkdir -p "$LOG_DIR"
# Change to project directory
cd "$PROJECT_DIR"
# Rotate log if needed
rotate_log
# Acquire lock
acquire_lock
# Validate dependencies
if ! validate_dependencies; then
log_error "Dependency validation failed"
exit 1
fi
# Setup GitHub authentication
setup_github_auth
log_success "Automation environment initialized"
}
# Start the automation system
start_automation() {
log_automation "Starting bulletproof automation system..."
# Initial server start
if ! start_server; then
log_error "Failed to start initial server"
exit 1
fi
# Start background loops
repository_pull_loop &
local pull_loop_pid=$!
health_monitoring_loop &
local health_loop_pid=$!
log_success "Automation system started successfully"
log_info "Repository pull loop PID: $pull_loop_pid"
log_info "Health monitoring loop PID: $health_loop_pid"
log_info "Server PID: $SERVER_PID"
log_info "Server available at: $HEALTH_ENDPOINT"
# Wait for background processes
wait $pull_loop_pid $health_loop_pid
}
# Display status information
show_status() {
echo "ThrillWiki Bulletproof Automation Status"
echo "[AWS-SECRET-REMOVED]"
# Check if automation is running
if [[ -f "$LOCK_FILE" ]]; then
local lock_pid
lock_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then
echo "✅ Automation is running (PID: $lock_pid)"
else
echo "❌ Stale lock file found (PID: $lock_pid)"
fi
else
echo "❌ Automation is not running"
fi
# Check server status
if lsof -ti :"$SERVER_PORT" >/dev/null 2>&1; then
echo "✅ Server is running on port $SERVER_PORT"
else
echo "❌ Server is not running on port $SERVER_PORT"
fi
# Check GitHub PAT status
if [[ -f "$GITHUB_TOKEN_FILE" ]]; then
echo "✅ GitHub PAT is configured"
elif [[ -n "${GITHUB_TOKEN:-}" ]]; then
echo "✅ GitHub PAT is available (environment)"
else
echo "⚠️ No GitHub PAT configured (public access only)"
fi
# Check repository status
if [[ -d "$PROJECT_DIR/.git" ]]; then
cd "$PROJECT_DIR"
local current_branch
current_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
local last_commit
last_commit=$(git log -1 --format="%h %s" 2>/dev/null || echo "unknown")
echo "📂 Repository: $current_branch branch"
echo "📝 Last commit: $last_commit"
else
echo "❌ Not a Git repository"
fi
# Show recent logs
if [[ -f "$LOG_FILE" ]]; then
echo ""
echo "Recent logs:"
tail -10 "$LOG_FILE"
else
echo "❌ No log file found"
fi
}
# Display help information
show_help() {
cat << EOF
ThrillWiki Bulletproof Development Automation Script
USAGE:
$0 [COMMAND] [OPTIONS]
COMMANDS:
start Start the automation system (default)
stop Stop the automation system
restart Restart the automation system
status Show current system status
logs Show recent log entries
test Test configuration and dependencies
set-token Set GitHub Personal Access Token (PAT)
clear-token Clear stored GitHub PAT
help Show this help message
OPTIONS:
--debug Enable debug logging
--interval Set pull interval in seconds (default: 300)
--token Set GitHub PAT for this session
ENVIRONMENT VARIABLES:
PROJECT_DIR Project root directory
PULL_INTERVAL Repository pull interval in seconds
HEALTH_CHECK_INTERVAL Health check interval in seconds
GITHUB_TOKEN GitHub Personal Access Token
DEBUG Enable debug logging (true/false)
EXAMPLES:
$0 # Start automation with default settings
$0 start --debug # Start with debug logging
$0 --interval 120 # Start with 2-minute pull interval
$0 --token ghp_xxxx # Start with GitHub PAT
$0 set-token # Set GitHub PAT interactively
$0 status # Check system status
$0 logs # View recent logs
GITHUB PAT SETUP:
For private repositories or to avoid rate limits, set up a GitHub PAT:
1. Interactive setup:
$0 set-token
2. Command line:
$0 --token YOUR_GITHUB_PAT start
3. Environment variable:
export GITHUB_TOKEN=YOUR_GITHUB_PAT
$0 start
4. Save to file:
echo "YOUR_GITHUB_PAT" > .github-pat
chmod 600 .github-pat
FEATURES:
✅ Automated VM startup and server management
✅ GitHub repository pulls every 5 minutes (configurable)
✅ Automatic Django migrations on code changes
✅ Enhanced dependency updates with uv sync -U and uv lock -U
✅ Easy GitHub PAT (Personal Access Token) configuration
✅ Enhanced error handling and recovery
✅ Comprehensive logging and health monitoring
✅ Signal handling for graceful shutdown
✅ File locking to prevent multiple instances
For more information, visit: https://github.com/your-repo/thrillwiki
EOF
}
# [AWS-SECRET-REMOVED]====================================
# COMMAND LINE INTERFACE
# [AWS-SECRET-REMOVED]====================================
# Parse command line arguments
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
--debug)
export DEBUG=true
log_debug "Debug logging enabled"
shift
;;
--interval)
PULL_INTERVAL="$2"
log_info "Pull interval set to: ${PULL_INTERVAL}s"
shift 2
;;
--token)
export GITHUB_TOKEN="$2"
log_info "GitHub PAT set from command line"
shift 2
;;
--help|-h)
show_help
exit 0
;;
*)
# Store command for later processing
COMMAND="$1"
shift
;;
esac
done
}
# Main entry point
main() {
local command="${COMMAND:-start}"
case "$command" in
start)
initialize_automation
start_automation
;;
stop)
if [[ -f "$LOCK_FILE" ]]; then
local lock_pid
lock_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then
log_info "Stopping automation (PID: $lock_pid)..."
kill -TERM "$lock_pid"
echo "Automation stop signal sent"
else
echo "No running automation found"
fi
else
echo "Automation is not running"
fi
;;
restart)
$0 stop
sleep 3
$0 start
;;
status)
show_status
;;
logs)
if [[ -f "$LOG_FILE" ]]; then
tail -50 "$LOG_FILE"
else
echo "No log file found at: $LOG_FILE"
fi
;;
test)
initialize_automation
log_success "Configuration and dependencies test completed"
;;
set-token)
set_github_pat
;;
clear-token)
remove_github_pat
;;
help)
show_help
;;
*)
echo "Unknown command: $command"
echo "Use '$0 help' for usage information"
exit 1
;;
esac
}
# [AWS-SECRET-REMOVED]====================================
# SCRIPT EXECUTION
# [AWS-SECRET-REMOVED]====================================
# Parse arguments and run main function
parse_arguments "$@"
main
# End of script