#!/bin/bash # ============================================================================ # Production Server Startup Script for Task Reporter # Starts all services: MinIO, Backend (FastAPI with Gunicorn/Uvicorn workers) # Frontend is served as static files (build first with npm run build) # ============================================================================ set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Script directory (scripts are in project root) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$SCRIPT_DIR" # PID file locations PID_DIR="$PROJECT_ROOT/.pids" BACKEND_PID_FILE="$PID_DIR/backend-prod.pid" FRONTEND_PID_FILE="$PID_DIR/frontend-prod.pid" # Log file locations LOG_DIR="$PROJECT_ROOT/logs" BACKEND_LOG="$LOG_DIR/backend-prod.log" FRONTEND_LOG="$LOG_DIR/frontend-prod.log" # Default configuration BACKEND_PORT="${BACKEND_PORT:-8000}" FRONTEND_PORT="${FRONTEND_PORT:-3000}" WORKERS="${WORKERS:-4}" # Helper functions print_header() { echo -e "\n${BLUE}=== $1 ===${NC}" } print_ok() { echo -e "${GREEN}[OK]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } print_info() { echo -e "${CYAN}[INFO]${NC} $1" } # Show help show_help() { echo "Usage: $0 [OPTIONS]" echo "" echo "Starts all production services for Task Reporter." echo "" echo "Options:" echo " -h, --help Show this help message" echo " --skip-build Skip frontend build step" echo " --no-frontend Don't start static file server (use external nginx/caddy)" echo " --no-minio Don't start MinIO (use external storage)" echo " --workers N Number of uvicorn workers (default: 4)" echo " --backend-port N Backend port (default: 8000)" echo " --frontend-port N Frontend static server port (default: 3000)" echo "" echo "Environment variables:" echo " WORKERS Number of uvicorn workers (default: 4)" echo " BACKEND_PORT Backend API port (default: 8000)" echo " FRONTEND_PORT Frontend static server port (default: 3000)" echo "" echo "Services started:" echo " - MinIO (Object Storage) - http://localhost:9000 (API), http://localhost:9001 (Console)" echo " - Backend (FastAPI) - http://localhost:\$BACKEND_PORT" echo " - Frontend (Static) - http://localhost:\$FRONTEND_PORT" echo "" echo "Prerequisites:" echo " - Python virtual environment at ./venv" echo " - Frontend built (npm run build in ./frontend)" echo " - Docker for MinIO" } # Cleanup function for graceful shutdown cleanup() { echo -e "\n${YELLOW}Shutting down services...${NC}" "$PROJECT_ROOT/stop-prod.sh" exit 0 } # Trap Ctrl+C trap cleanup SIGINT SIGTERM # Parse arguments SKIP_BUILD=false NO_FRONTEND=false NO_MINIO=false while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; --skip-build) SKIP_BUILD=true shift ;; --no-frontend) NO_FRONTEND=true shift ;; --no-minio) NO_MINIO=true shift ;; --workers) WORKERS="$2" shift 2 ;; --backend-port) BACKEND_PORT="$2" shift 2 ;; --frontend-port) FRONTEND_PORT="$2" shift 2 ;; *) echo "Unknown option: $1" show_help exit 1 ;; esac done echo -e "${BLUE}============================================${NC}" echo -e "${BLUE} Task Reporter - Production Server${NC}" echo -e "${BLUE}============================================${NC}" # Create directories mkdir -p "$PID_DIR" mkdir -p "$LOG_DIR" # ============================================================================ # Pre-flight Check # ============================================================================ print_header "Pre-flight Check" # Check .env file exists if [[ ! -f "$PROJECT_ROOT/.env" ]]; then print_error ".env file not found. Copy from .env.example and configure." exit 1 fi print_ok ".env file exists" # Check virtual environment if [[ ! -d "$PROJECT_ROOT/venv" ]]; then print_error "Python virtual environment not found at ./venv" exit 1 fi print_ok "Python virtual environment found" # Check Docker if ! command -v docker &> /dev/null; then print_warn "Docker not found. MinIO will not be started." NO_MINIO=true else print_ok "Docker available" fi # ============================================================================ # Build Frontend (if needed) # ============================================================================ if [[ "$NO_FRONTEND" == "false" ]] && [[ "$SKIP_BUILD" == "false" ]]; then print_header "Building Frontend" cd "$PROJECT_ROOT/frontend" if [[ ! -d "node_modules" ]]; then print_info "Installing frontend dependencies..." npm install fi print_info "Building frontend for production..." npm run build if [[ -d "dist" ]]; then print_ok "Frontend built successfully" else print_error "Frontend build failed" exit 1 fi fi # ============================================================================ # Start MinIO # ============================================================================ if [[ "$NO_MINIO" == "false" ]]; then print_header "Starting MinIO" cd "$PROJECT_ROOT" # Check if MinIO container is already running if docker ps --format '{{.Names}}' | grep -q "task-reporter-minio"; then print_ok "MinIO is already running" else # Start MinIO if command -v docker-compose &> /dev/null; then docker-compose -f docker-compose.minio.yml up -d else docker compose -f docker-compose.minio.yml up -d fi # Wait for MinIO to be healthy print_info "Waiting for MinIO to be healthy..." MINIO_READY=false for i in {1..30}; do if curl -s http://localhost:9000/minio/health/live > /dev/null 2>&1; then MINIO_READY=true break fi sleep 1 echo -n "." done echo "" if [[ "$MINIO_READY" == "true" ]]; then print_ok "MinIO is healthy" else print_error "MinIO failed to start within 30 seconds" exit 1 fi fi print_info "MinIO Console: http://localhost:9001" fi # ============================================================================ # Start Backend (Production Mode) # ============================================================================ print_header "Starting Backend (Production)" cd "$PROJECT_ROOT" # Check if backend is already running if [[ -f "$BACKEND_PID_FILE" ]] && kill -0 "$(cat "$BACKEND_PID_FILE")" 2>/dev/null; then print_ok "Backend is already running (PID: $(cat "$BACKEND_PID_FILE"))" else print_info "Starting uvicorn with $WORKERS workers..." # Source the virtual environment and run uvicorn in production mode ( source "$PROJECT_ROOT/venv/bin/activate" cd "$PROJECT_ROOT" # Production mode: no reload, multiple workers uvicorn app.main:app \ --host 0.0.0.0 \ --port "$BACKEND_PORT" \ --workers "$WORKERS" \ --log-level info \ --access-log \ > "$BACKEND_LOG" 2>&1 & echo $! > "$BACKEND_PID_FILE" ) # Wait for backend to be ready print_info "Waiting for backend to be ready..." BACKEND_READY=false for i in {1..30}; do if curl -s "http://localhost:$BACKEND_PORT/api/health" > /dev/null 2>&1 || \ curl -s "http://localhost:$BACKEND_PORT/docs" > /dev/null 2>&1; then BACKEND_READY=true break fi sleep 1 echo -n "." done echo "" if [[ "$BACKEND_READY" == "true" ]]; then print_ok "Backend is ready (PID: $(cat "$BACKEND_PID_FILE"))" else print_warn "Backend may still be starting. Check logs: $BACKEND_LOG" fi fi print_info "Backend API: http://localhost:$BACKEND_PORT" print_info "API Docs: http://localhost:$BACKEND_PORT/docs" # ============================================================================ # Start Frontend Static Server # ============================================================================ if [[ "$NO_FRONTEND" == "false" ]]; then print_header "Starting Frontend Static Server" cd "$PROJECT_ROOT/frontend" # Check if dist folder exists if [[ ! -d "dist" ]]; then print_error "Frontend dist folder not found. Run 'npm run build' first or remove --skip-build" exit 1 fi # Check if frontend is already running if [[ -f "$FRONTEND_PID_FILE" ]] && kill -0 "$(cat "$FRONTEND_PID_FILE")" 2>/dev/null; then print_ok "Frontend server is already running (PID: $(cat "$FRONTEND_PID_FILE"))" else # Use Python's http.server as a simple static file server # For production, consider using nginx, caddy, or serve print_info "Starting static file server on port $FRONTEND_PORT..." # Check if 'serve' is available (better for SPA) if command -v npx &> /dev/null; then # Use serve for proper SPA support (handles client-side routing) cd "$PROJECT_ROOT/frontend" npx serve -s dist -l "$FRONTEND_PORT" > "$FRONTEND_LOG" 2>&1 & echo $! > "$FRONTEND_PID_FILE" else # Fallback to Python http.server cd "$PROJECT_ROOT/frontend/dist" python3 -m http.server "$FRONTEND_PORT" --bind 0.0.0.0 > "$FRONTEND_LOG" 2>&1 & echo $! > "$FRONTEND_PID_FILE" print_warn "Using Python http.server (SPA routing may not work). Install 'serve' for better support." fi # Wait for frontend to be ready sleep 2 if kill -0 "$(cat "$FRONTEND_PID_FILE")" 2>/dev/null; then print_ok "Frontend server started (PID: $(cat "$FRONTEND_PID_FILE"))" else print_error "Frontend server failed to start. Check logs: $FRONTEND_LOG" fi fi print_info "Frontend: http://localhost:$FRONTEND_PORT" fi # ============================================================================ # Summary # ============================================================================ print_header "Production Services Running" echo -e "${GREEN}============================================${NC}" echo -e "${GREEN} All production services are running!${NC}" echo -e "${GREEN}============================================${NC}" echo "" if [[ "$NO_FRONTEND" == "false" ]]; then echo -e " ${CYAN}Frontend:${NC} http://localhost:$FRONTEND_PORT" fi echo -e " ${CYAN}Backend API:${NC} http://localhost:$BACKEND_PORT" echo -e " ${CYAN}API Docs:${NC} http://localhost:$BACKEND_PORT/docs" if [[ "$NO_MINIO" == "false" ]]; then echo -e " ${CYAN}MinIO Console:${NC} http://localhost:9001" fi echo "" echo -e " ${CYAN}Workers:${NC} $WORKERS" echo "" echo -e " ${YELLOW}Logs:${NC}" echo -e " Backend: $BACKEND_LOG" if [[ "$NO_FRONTEND" == "false" ]]; then echo -e " Frontend: $FRONTEND_LOG" fi echo "" echo -e " ${YELLOW}Press Ctrl+C to stop all services${NC}" echo "" # Keep script running to catch Ctrl+C while true; do sleep 1 done