#!/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