#!/bin/bash # # ThrillWiki Auto-Pull Script # Automatically pulls latest changes from Git repository every 10 minutes # Designed to run as a cron job on the VM # set -e # Configuration PROJECT_DIR="/home/thrillwiki/thrillwiki" LOG_FILE="/home/thrillwiki/logs/auto-pull.log" LOCK_FILE="/tmp/thrillwiki-auto-pull.lock" SERVICE_NAME="thrillwiki" MAX_LOG_SIZE=10485760 # 10MB # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging function log() { echo -e "$(date '+%Y-%m-%d %H:%M:%S') [AUTO-PULL] $1" | tee -a "$LOG_FILE" } log_error() { echo -e "$(date '+%Y-%m-%d %H:%M:%S') ${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" } log_success() { echo -e "$(date '+%Y-%m-%d %H:%M:%S') ${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" } log_warning() { echo -e "$(date '+%Y-%m-%d %H:%M:%S') ${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" } # Function to rotate log file if it gets too large rotate_log() { if [ -f "$LOG_FILE" ] && [ $(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt $MAX_LOG_SIZE ]; then mv "$LOG_FILE" "${LOG_FILE}.old" log "Log file rotated due to size limit" fi } # Function to acquire lock acquire_lock() { if [ -f "$LOCK_FILE" ]; then local lock_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "") if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then log_warning "Auto-pull already running (PID: $lock_pid), skipping this run" exit 0 else log "Removing stale lock file" rm -f "$LOCK_FILE" fi fi echo $$ > "$LOCK_FILE" trap 'rm -f "$LOCK_FILE"' EXIT } # Function to setup GitHub authentication setup_git_auth() { log "🔐 Setting up GitHub authentication..." # Check if GITHUB_TOKEN is available if [ -z "${GITHUB_TOKEN:-}" ]; then # Try loading from ***REMOVED*** file in project directory if [ -f "$PROJECT_DIR/***REMOVED***" ]; then source "$PROJECT_DIR/***REMOVED***" fi # Try loading from global ***REMOVED***.unraid if [ -z "${GITHUB_TOKEN:-}" ] && [ -f "$PROJECT_DIR/../../***REMOVED***.unraid" ]; then source "$PROJECT_DIR/../../***REMOVED***.unraid" fi # Try loading from parent directory ***REMOVED***.unraid if [ -z "${GITHUB_TOKEN:-}" ] && [ -f "$PROJECT_DIR/../***REMOVED***.unraid" ]; then source "$PROJECT_DIR/../***REMOVED***.unraid" fi fi # Verify we have the token if [ -z "${GITHUB_TOKEN:-}" ]; then log_warning "⚠️ GITHUB_TOKEN not found, trying public access..." return 1 fi # Configure git to use token authentication local repo_url="https://github.com/pacnpal/thrillwiki_django_no_react.git" local auth_url="https://pacnpal:${GITHUB_TOKEN}@github.com/pacnpal/thrillwiki_django_no_react.git" # Update remote URL to use token if git remote get-url origin | grep -q "github.com/pacnpal/thrillwiki_django_no_react"; then git remote set-url origin "$auth_url" log_success "✅ GitHub authentication configured with token" return 0 else log_warning "⚠️ Repository origin URL doesn't match expected GitHub repo" return 1 fi } # Function to check if Git repository has changes has_remote_changes() { # Setup authentication first if ! setup_git_auth; then log_warning "⚠️ GitHub authentication failed, skipping remote check" return 1 # Assume no changes if we can't authenticate fi # Fetch latest changes without merging log "📡 Fetching latest changes from remote..." if ! git fetch origin main --quiet 2>/dev/null; then log_error "❌ Failed to fetch from remote repository - authentication or network issue" log_warning "⚠️ Auto-pull will skip this cycle due to fetch failure" return 1 fi # Compare local and remote local local_commit=$(git rev-parse HEAD) local remote_commit=$(git rev-parse origin/main) log "📊 Local commit: ${local_commit:0:8}" log "📊 Remote commit: ${remote_commit:0:8}" if [ "$local_commit" != "$remote_commit" ]; then log "📥 New changes detected!" return 0 # Has changes else log "✅ Repository is up to date" return 1 # No changes fi } # Function to check service status is_service_running() { systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null } # Function to restart service safely restart_service() { log "Restarting ThrillWiki service..." if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then if sudo systemctl restart "$SERVICE_NAME"; then log_success "Service restarted successfully" return 0 else log_error "Failed to restart service" return 1 fi else log_warning "Service not enabled, attempting manual restart..." # Try to start it anyway if sudo systemctl start "$SERVICE_NAME" 2>/dev/null; then log_success "Service started successfully" return 0 else log_warning "Service restart failed, may need manual intervention" return 1 fi fi } # Function to update Python dependencies update_dependencies() { log "Checking for dependency updates..." # Check if UV is available export PATH="/home/thrillwiki/.cargo/bin:$PATH" if command -v uv > /dev/null 2>&1; then log "Updating dependencies with UV..." if uv sync --quiet; then log_success "Dependencies updated with UV" return 0 else log_warning "UV sync failed, trying pip..." fi fi # Fallback to pip if UV fails or isn't available if [ -d ".venv" ]; then log "Activating virtual environment and updating with pip..." source .venv/bin/activate if pip install -e . --quiet; then log_success "Dependencies updated with pip" return 0 else log_warning "Pip install failed" return 1 fi else log_warning "No virtual environment found, skipping dependency update" return 1 fi } # Function to run Django migrations run_migrations() { log "Running Django migrations..." export PATH="/home/thrillwiki/.cargo/bin:$PATH" # Try with UV first if command -v uv > /dev/null 2>&1; then if uv run python manage.py migrate --quiet; then log_success "Migrations completed with UV" return 0 else log_warning "UV migrations failed, trying direct Python..." fi fi # Fallback to direct Python if [ -d ".venv" ]; then source .venv/bin/activate if python manage.py migrate --quiet; then log_success "Migrations completed with Python" return 0 else log_warning "Django migrations failed" return 1 fi else if python3 manage.py migrate --quiet; then log_success "Migrations completed" return 0 else log_warning "Django migrations failed" return 1 fi fi } # Function to collect static files collect_static() { log "Collecting static files..." export PATH="/home/thrillwiki/.cargo/bin:$PATH" # Try with UV first if command -v uv > /dev/null 2>&1; then if uv run python manage.py collectstatic --noinput --quiet; then log_success "Static files collected with UV" return 0 else log_warning "UV collectstatic failed, trying direct Python..." fi fi # Fallback to direct Python if [ -d ".venv" ]; then source .venv/bin/activate if python manage.py collectstatic --noinput --quiet; then log_success "Static files collected with Python" return 0 else log_warning "Static file collection failed" return 1 fi else if python3 manage.py collectstatic --noinput --quiet; then log_success "Static files collected" return 0 else log_warning "Static file collection failed" return 1 fi fi } # Main auto-pull function main() { # Setup rotate_log acquire_lock log "🔄 Starting auto-pull check..." # Ensure logs directory exists mkdir -p "$(dirname "$LOG_FILE")" # Change to project directory if ! cd "$PROJECT_DIR"; then log_error "Failed to change to project directory: $PROJECT_DIR" exit 1 fi # Check if this is a Git repository if [ ! -d ".git" ]; then log_error "Not a Git repository: $PROJECT_DIR" exit 1 fi # Check for remote changes log "📡 Checking for remote changes..." if ! has_remote_changes; then log "✅ Repository is up to date, no changes to pull" exit 0 fi log "📥 New changes detected, pulling updates..." # Record current service status local service_was_running=false if is_service_running; then service_was_running=true log "📊 Service is currently running" else log "📊 Service is not running" fi # Pull the latest changes local pull_output if pull_output=$(git pull origin main 2>&1); then log_success "✅ Git pull completed successfully" log "📋 Changes:" echo "$pull_output" | grep -E "^\s*(create|modify|delete|rename)" | head -10 | while read line; do log " $line" done else log_error "❌ Git pull failed:" echo "$pull_output" | head -10 | while read line; do log_error " $line" done exit 1 fi # Update dependencies if requirements files changed if echo "$pull_output" | grep -qE "(pyproject\.toml|requirements.*\.txt|setup\.py)"; then log "📦 Dependencies file changed, updating..." update_dependencies else log "📦 No dependency changes detected, skipping update" fi # Run migrations if models changed if echo "$pull_output" | grep -qE "(models\.py|migrations/)"; then log "🗄️ Model changes detected, running migrations..." run_migrations else log "🗄️ No model changes detected, skipping migrations" fi # Collect static files if they changed if echo "$pull_output" | grep -qE "(static/|templates/|\.css|\.js)"; then log "🎨 Static files changed, collecting..." collect_static else log "🎨 No static file changes detected, skipping collection" fi # Restart service if it was running if $service_was_running; then log "🔄 Restarting service due to code changes..." if restart_service; then # Wait a moment for service to fully start sleep 3 # Verify service is running if is_service_running; then log_success "🎉 Auto-pull completed successfully! Service is running." else log_error "⚠️ Service failed to start after restart" exit 1 fi else log_error "⚠️ Service restart failed" exit 1 fi else log_success "🎉 Auto-pull completed successfully! (Service was not running)" fi # Health check log "🔍 Performing health check..." if curl -f http://localhost:8000 > /dev/null 2>&1; then log_success "✅ Application health check passed" else log_warning "⚠️ Application health check failed (may still be starting up)" fi log "✨ Auto-pull cycle completed at $(date)" } # Handle script arguments case "${1:-}" in --help|-h) echo "ThrillWiki Auto-Pull Script" echo "" echo "Usage:" echo " $0 Run auto-pull check (default)" echo " $0 --force Force pull even if no changes detected" echo " $0 --status Check auto-pull service status" echo " $0 --logs Show recent auto-pull logs" echo " $0 --help Show this help" exit 0 ;; --force) log "🚨 Force mode: Pulling regardless of changes" # Skip the has_remote_changes check cd "$PROJECT_DIR" # Setup authentication and pull setup_git_auth if git pull origin main; then log_success "✅ Force pull completed" # Run standard update procedures update_dependencies run_migrations collect_static # Restart service if it was running if is_service_running; then restart_service fi log_success "🎉 Force update completed successfully!" else log_error "❌ Force pull failed" exit 1 fi ;; --status) if systemctl is-active --quiet crond 2>/dev/null; then echo "✅ Cron daemon is running" else echo "❌ Cron daemon is not running" fi if crontab -l 2>/dev/null | grep -q "auto-pull.sh"; then echo "✅ Auto-pull cron job is installed" echo "📋 Current cron jobs:" crontab -l 2>/dev/null | grep -E "(auto-pull|thrillwiki)" else echo "❌ Auto-pull cron job is not installed" fi if [ -f "$LOG_FILE" ]; then echo "📄 Last auto-pull log entries:" tail -5 "$LOG_FILE" else echo "📄 No auto-pull logs found" fi ;; --logs) if [ -f "$LOG_FILE" ]; then tail -50 "$LOG_FILE" else echo "No auto-pull logs found at $LOG_FILE" fi ;; *) # Default: run main auto-pull main ;; esac