mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 16:11: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
1156 lines
35 KiB
Bash
Executable File
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 |