#!/bin/bash # ThrillWiki Server Start Script # Stops any running servers, clears caches, runs migrations, and starts both servers # Works whether servers are currently running or not # Usage: ./start-servers.sh set -e # Exit on any error # Global variables for process management BACKEND_PID="" FRONTEND_PID="" CLEANUP_PERFORMED=false # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Script directory and project root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" BACKEND_DIR="$PROJECT_ROOT/backend" FRONTEND_DIR="$PROJECT_ROOT/frontend" # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Function for graceful shutdown graceful_shutdown() { if [ "$CLEANUP_PERFORMED" = true ]; then return 0 fi CLEANUP_PERFORMED=true print_warning "Received shutdown signal - performing graceful shutdown..." # Disable further signal handling to prevent recursive calls trap - INT TERM # Kill backend server if running if [ -n "$BACKEND_PID" ] && kill -0 "$BACKEND_PID" 2>/dev/null; then print_status "Stopping backend server (PID: $BACKEND_PID)..." kill -TERM "$BACKEND_PID" 2>/dev/null || true # Wait up to 10 seconds for graceful shutdown local count=0 while [ $count -lt 10 ] && kill -0 "$BACKEND_PID" 2>/dev/null; do sleep 1 count=$((count + 1)) done # Force kill if still running if kill -0 "$BACKEND_PID" 2>/dev/null; then print_warning "Force killing backend server..." kill -KILL "$BACKEND_PID" 2>/dev/null || true fi print_success "Backend server stopped" else print_status "Backend server not running or already stopped" fi # Kill frontend server if running if [ -n "$FRONTEND_PID" ] && kill -0 "$FRONTEND_PID" 2>/dev/null; then print_status "Stopping frontend server (PID: $FRONTEND_PID)..." kill -TERM "$FRONTEND_PID" 2>/dev/null || true # Wait up to 10 seconds for graceful shutdown local count=0 while [ $count -lt 10 ] && kill -0 "$FRONTEND_PID" 2>/dev/null; do sleep 1 count=$((count + 1)) done # Force kill if still running if kill -0 "$FRONTEND_PID" 2>/dev/null; then print_warning "Force killing frontend server..." kill -KILL "$FRONTEND_PID" 2>/dev/null || true fi print_success "Frontend server stopped" else print_status "Frontend server not running or already stopped" fi # Clear PID files if they exist if [ -f "$PROJECT_ROOT/shared/logs/backend.pid" ]; then rm -f "$PROJECT_ROOT/shared/logs/backend.pid" fi if [ -f "$PROJECT_ROOT/shared/logs/frontend.pid" ]; then rm -f "$PROJECT_ROOT/shared/logs/frontend.pid" fi print_success "Graceful shutdown completed" exit 0 } # Function to kill processes by pattern kill_processes() { local pattern="$1" local description="$2" print_status "Checking for $description processes..." # Find and kill processes local pids=$(pgrep -f "$pattern" 2>/dev/null || true) if [ -n "$pids" ]; then print_status "Found $description processes, stopping them..." echo "$pids" | xargs kill -TERM 2>/dev/null || true sleep 2 # Force kill if still running local remaining_pids=$(pgrep -f "$pattern" 2>/dev/null || true) if [ -n "$remaining_pids" ]; then print_warning "Force killing remaining $description processes..." echo "$remaining_pids" | xargs kill -KILL 2>/dev/null || true fi print_success "$description processes stopped" else print_status "No $description processes found (this is fine)" fi } # Function to clear Django cache clear_django_cache() { print_status "Clearing Django cache..." cd "$BACKEND_DIR" # Clear Django cache if command -v uv >/dev/null 2>&1; then if ! uv run manage.py clear_cache 2>clear_cache_error.log; then print_error "Django clear_cache command failed:" cat clear_cache_error.log rm -f clear_cache_error.log exit 1 else rm -f clear_cache_error.log fi else print_error "uv not found! Please install uv first." exit 1 fi # Remove Python cache files find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true find . -name "*.pyc" -delete 2>/dev/null || true find . -name "*.pyo" -delete 2>/dev/null || true print_success "Django cache cleared" } # Function to clear frontend cache clear_frontend_cache() { print_status "Clearing frontend cache..." cd "$FRONTEND_DIR" # Remove node_modules/.cache if it exists if [ -d "node_modules/.cache" ]; then rm -rf node_modules/.cache print_status "Removed node_modules/.cache" fi # Remove .nuxt cache if it exists (for Nuxt projects) if [ -d ".nuxt" ]; then rm -rf .nuxt print_status "Removed .nuxt cache" fi # Remove dist/build directories if [ -d "dist" ]; then rm -rf dist print_status "Removed dist directory" fi if [ -d "build" ]; then rm -rf build print_status "Removed build directory" fi # Clear pnpm cache if command -v pnpm >/dev/null 2>&1; then pnpm store prune 2>/dev/null || print_warning "Could not prune pnpm store" else print_error "pnpm not found! Please install pnpm first." exit 1 fi print_success "Frontend cache cleared" } # Function to run Django migrations run_migrations() { print_status "Running Django migrations..." cd "$BACKEND_DIR" # Check for pending migrations if uv run python manage.py showmigrations --plan | grep -q "\[ \]"; then print_status "Pending migrations found, applying..." uv run python manage.py migrate print_success "Migrations applied successfully" else print_status "No pending migrations found" fi # Run any custom management commands if needed # uv run python manage.py collectstatic --noinput --clear 2>/dev/null || print_warning "collectstatic failed or not needed" } # Function to start backend server start_backend() { print_status "Starting Django backend server with runserver_plus (verbose output)..." cd "$BACKEND_DIR" # Start Django development server with runserver_plus for enhanced features and verbose output print_status "Running: uv run python manage.py runserver_plus 8000 --verbosity=2" uv run python manage.py runserver_plus 8000 --verbosity=2 & BACKEND_PID=$! # Make sure the background process can receive signals disown -h "$BACKEND_PID" 2>/dev/null || true # Wait a moment and check if it started successfully sleep 3 if kill -0 $BACKEND_PID 2>/dev/null; then print_success "Backend server started (PID: $BACKEND_PID)" echo $BACKEND_PID > ../shared/logs/backend.pid else print_error "Failed to start backend server" return 1 fi } # Function to start frontend server start_frontend() { print_status "Starting frontend server with verbose output..." cd "$FRONTEND_DIR" # Install dependencies if node_modules doesn't exist or package.json is newer if [ ! -d "node_modules" ] || [ "package.json" -nt "node_modules" ]; then print_status "Installing/updating frontend dependencies..." pnpm install fi # Start frontend development server using Vite with explicit port, auto-open, and verbose output # --port 5173: Use standard Vite port # --open: Automatically open browser when ready # --host localhost: Ensure it binds to localhost # --debug: Enable debug logging print_status "Starting Vite development server with verbose output and auto-browser opening..." print_status "Running: pnpm vite --port 5173 --open --host localhost --debug" pnpm vite --port 5173 --open --host localhost --debug & FRONTEND_PID=$! # Make sure the background process can receive signals disown -h "$FRONTEND_PID" 2>/dev/null || true # Wait a moment and check if it started successfully sleep 3 if kill -0 $FRONTEND_PID 2>/dev/null; then print_success "Frontend server started (PID: $FRONTEND_PID) - browser should open automatically" echo $FRONTEND_PID > ../shared/logs/frontend.pid else print_error "Failed to start frontend server" return 1 fi } # Function to detect operating system detect_os() { case "$(uname -s)" in Darwin*) echo "macos";; Linux*) echo "linux";; *) echo "unknown";; esac } # Function to open browser on the appropriate OS open_browser() { local url="$1" local os=$(detect_os) print_status "Opening browser to $url..." case "$os" in "macos") if command -v open >/dev/null 2>&1; then open "$url" 2>/dev/null || print_warning "Failed to open browser automatically" else print_warning "Cannot open browser: 'open' command not available" fi ;; "linux") if command -v xdg-open >/dev/null 2>&1; then xdg-open "$url" 2>/dev/null || print_warning "Failed to open browser automatically" else print_warning "Cannot open browser: 'xdg-open' command not available" fi ;; *) print_warning "Cannot open browser automatically: Unsupported operating system" ;; esac } # Function to verify frontend is responding (simplified since port is known) verify_frontend_ready() { local frontend_url="http://localhost:5173" local max_checks=15 local check=0 print_status "Verifying frontend server is responding at $frontend_url..." while [ $check -lt $max_checks ]; do local response_code=$(curl -s -o /dev/null -w "%{http_code}" "$frontend_url" 2>/dev/null) if [ "$response_code" = "200" ] || [ "$response_code" = "301" ] || [ "$response_code" = "302" ] || [ "$response_code" = "404" ]; then print_success "Frontend server is responding (HTTP $response_code)" return 0 fi if [ $((check % 3)) -eq 0 ]; then print_status "Waiting for frontend to respond... (attempt $((check + 1))/$max_checks)" fi sleep 2 check=$((check + 1)) done print_warning "Frontend may still be starting up" return 1 } # Function to verify servers are responding verify_servers_ready() { print_status "Verifying both servers are responding..." # Check backend local backend_ready=false local frontend_ready=false local max_checks=10 local check=0 while [ $check -lt $max_checks ]; do # Check backend if [ "$backend_ready" = false ]; then local backend_response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8000" 2>/dev/null) if [ "$backend_response" = "200" ] || [ "$backend_response" = "301" ] || [ "$backend_response" = "302" ] || [ "$backend_response" = "404" ]; then print_success "Backend server is responding (HTTP $backend_response)" backend_ready=true fi fi # Check frontend if [ "$frontend_ready" = false ]; then local frontend_response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:5173" 2>/dev/null) if [ "$frontend_response" = "200" ] || [ "$frontend_response" = "301" ] || [ "$frontend_response" = "302" ] || [ "$frontend_response" = "404" ]; then print_success "Frontend server is responding (HTTP $frontend_response)" frontend_ready=true fi fi # Both ready? if [ "$backend_ready" = true ] && [ "$frontend_ready" = true ]; then print_success "Both servers are responding!" return 0 fi sleep 2 check=$((check + 1)) done # Show status of what's working if [ "$backend_ready" = true ]; then print_success "Backend is ready at http://localhost:8000" else print_warning "Backend may still be starting up" fi if [ "$frontend_ready" = true ]; then print_success "Frontend is ready at http://localhost:5173" else print_warning "Frontend may still be starting up" fi } # Function to create logs directory if it doesn't exist ensure_logs_dir() { local logs_dir="$PROJECT_ROOT/shared/logs" if [ ! -d "$logs_dir" ]; then mkdir -p "$logs_dir" print_status "Created logs directory: $logs_dir" fi } # Function to validate project structure validate_project() { if [ ! -d "$BACKEND_DIR" ]; then print_error "Backend directory not found: $BACKEND_DIR" exit 1 fi if [ ! -d "$FRONTEND_DIR" ]; then print_error "Frontend directory not found: $FRONTEND_DIR" exit 1 fi if [ ! -f "$BACKEND_DIR/manage.py" ]; then print_error "Django manage.py not found in: $BACKEND_DIR" exit 1 fi if [ ! -f "$FRONTEND_DIR/package.json" ]; then print_error "Frontend package.json not found in: $FRONTEND_DIR" exit 1 fi } # Function to kill processes using specific ports kill_port_processes() { local port="$1" local description="$2" print_status "Checking for processes using port $port ($description)..." # Find processes using the specific port local pids=$(lsof -ti :$port 2>/dev/null || true) if [ -n "$pids" ]; then print_warning "Found processes using port $port, killing them..." echo "$pids" | xargs kill -TERM 2>/dev/null || true sleep 2 # Force kill if still running local remaining_pids=$(lsof -ti :$port 2>/dev/null || true) if [ -n "$remaining_pids" ]; then print_warning "Force killing remaining processes on port $port..." echo "$remaining_pids" | xargs kill -KILL 2>/dev/null || true fi print_success "Port $port cleared" else print_status "Port $port is available" fi } # Function to check and clear required ports check_and_clear_ports() { print_status "Checking and clearing required ports..." # Kill processes using our specific ports kill_port_processes 8000 "Django backend" kill_port_processes 5173 "Frontend Vite" } # Main execution function main() { print_status "ThrillWiki Server Start Script Starting..." print_status "This script works whether servers are currently running or not." print_status "Project root: $PROJECT_ROOT" # Set up signal traps EARLY - before any long-running operations print_status "Setting up signal handlers for graceful shutdown..." trap 'graceful_shutdown' INT TERM # Validate project structure validate_project # Ensure logs directory exists ensure_logs_dir # Check and clear ports check_and_clear_ports # Kill existing server processes (if any) print_status "=== Stopping Any Running Servers ===" print_status "Note: It's perfectly fine if no servers are currently running" kill_processes "manage.py runserver" "Django backend" kill_processes "pnpm.*dev\|npm.*dev\|yarn.*dev\|node.*dev" "Frontend development" kill_processes "uvicorn\|gunicorn" "Python web servers" # Clear caches print_status "=== Clearing Caches ===" clear_django_cache clear_frontend_cache # Run migrations print_status "=== Running Migrations ===" run_migrations # Start servers print_status "=== Starting Servers ===" # Start backend first if start_backend; then print_success "Backend server is running" else print_error "Failed to start backend server" exit 1 fi # Start frontend if start_frontend; then print_success "Frontend server is running" else print_error "Failed to start frontend server" print_status "Backend server is still running" exit 1 fi # Verify servers are responding print_status "=== Verifying Servers ===" verify_servers_ready # Final status print_status "=== Server Status ===" print_success "✅ Backend server: http://localhost:8000 (Django with runserver_plus)" print_success "✅ Frontend server: http://localhost:5173 (Vite with verbose output)" print_status "🌐 Browser should have opened automatically via Vite --open" print_status "🔧 To stop servers, use: kill \$(cat $PROJECT_ROOT/shared/logs/backend.pid) \$(cat $PROJECT_ROOT/shared/logs/frontend.pid)" print_status "📋 Both servers are running with verbose output directly in your terminal" print_success "🚀 All servers started successfully with full verbose output!" # Keep the script running and wait for signals wait_for_servers } # Wait for servers function to keep script running and handle signals wait_for_servers() { print_status "🚀 Servers are running! Press Ctrl+C for graceful shutdown." print_status "📋 Backend: http://localhost:8000 | Frontend: http://localhost:5173" # Keep the script alive and wait for signals while [ "$CLEANUP_PERFORMED" != true ]; do # Check if both servers are still running if [ -n "$BACKEND_PID" ] && ! kill -0 "$BACKEND_PID" 2>/dev/null; then print_error "Backend server has stopped unexpectedly" graceful_shutdown break fi if [ -n "$FRONTEND_PID" ] && ! kill -0 "$FRONTEND_PID" 2>/dev/null; then print_error "Frontend server has stopped unexpectedly" graceful_shutdown break fi # Use shorter sleep and check for signals more frequently sleep 1 done } # Run main function (no traps set up initially) main "$@"