#!/bin/bash # Tool_OCR - Unified Server Startup Script # Usage: # ./start.sh Start all services (backend + frontend) # ./start.sh backend Start only backend # ./start.sh frontend Start only frontend # ./start.sh --prod Start in production mode (no hot-reload) # ./start.sh --stop Stop all services # ./start.sh --status Show service status # ./start.sh --help Show help # Note: We don't use 'set -e' here because stop commands may fail gracefully # Colors GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PID_DIR="$SCRIPT_DIR/.pid" BACKEND_PID_FILE="$PID_DIR/backend.pid" FRONTEND_PID_FILE="$PID_DIR/frontend.pid" BACKEND_HOST=${BACKEND_HOST:-0.0.0.0} BACKEND_PORT=${BACKEND_PORT:-8000} FRONTEND_HOST=${FRONTEND_HOST:-0.0.0.0} FRONTEND_PORT=${FRONTEND_PORT:-5173} # Production settings PROD_MODE=false # Translation status uses filesystem lock files for multi-worker support UVICORN_WORKERS=${UVICORN_WORKERS:-4} # Create PID directory mkdir -p "$PID_DIR" # Functions print_header() { echo "" echo -e "${BLUE}================================${NC}" if [ "$PROD_MODE" = true ]; then echo -e "${BLUE} Tool_OCR Server (Production)${NC}" else echo -e "${BLUE} Tool_OCR Server (Development)${NC}" fi echo -e "${BLUE}================================${NC}" echo "" } show_help() { echo "Tool_OCR - Server Manager" echo "" echo "Usage: ./start.sh [options] [command]" echo "" echo "Commands:" echo " (none) Start all services (backend + frontend)" echo " backend Start only backend service" echo " frontend Start only frontend service" echo " --stop Stop all running services" echo " --status Show status of services" echo " --help Show this help message" echo "" echo "Options:" echo " --prod Run in production mode (no hot-reload, multiple workers)" echo "" echo "Environment Variables:" echo " BACKEND_PORT Backend port (default: 8000)" echo " FRONTEND_PORT Frontend port (default: 5173)" echo " UVICORN_WORKERS Number of workers in prod mode (default: 4)" echo "" echo "Examples:" echo " ./start.sh # Start in dev mode" echo " ./start.sh --prod # Start in production mode" echo " BACKEND_PORT=8088 ./start.sh # Use custom port" echo " ./start.sh --stop # Stop all services" echo "" } check_requirements() { local missing=0 # Check virtual environment if [ ! -d "$SCRIPT_DIR/venv" ]; then echo -e "${RED}Error: Python virtual environment not found${NC}" echo "Please run: ./setup_dev_env.sh" missing=1 fi # Check node_modules if [ ! -d "$SCRIPT_DIR/frontend/node_modules" ]; then echo -e "${RED}Error: Frontend dependencies not found${NC}" echo "Please run: ./setup_dev_env.sh" missing=1 fi # Check .env if [ ! -f "$SCRIPT_DIR/.env" ]; then echo -e "${RED}Error: .env not found${NC}" echo "Please copy .env.example to .env and configure" missing=1 fi return $missing } load_env() { # Load environment variables from root .env (if present). # This keeps backend/frontend config in sync without hardcoding ports/URLs in scripts. if [ -f "$SCRIPT_DIR/.env" ]; then set -a # shellcheck disable=SC1090 source "$SCRIPT_DIR/.env" set +a fi } is_running() { local pid_file=$1 if [ -f "$pid_file" ]; then local pid=$(cat "$pid_file") if ps -p "$pid" > /dev/null 2>&1; then return 0 else # PID file exists but process is not running, clean up rm -f "$pid_file" fi fi return 1 } get_pid() { local pid_file=$1 if [ -f "$pid_file" ]; then cat "$pid_file" fi } start_backend() { if is_running "$BACKEND_PID_FILE"; then echo -e "${YELLOW}Backend already running (PID: $(get_pid $BACKEND_PID_FILE))${NC}" return 0 fi if [ "$PROD_MODE" = true ]; then echo -e "${GREEN}Starting backend server (production mode)...${NC}" else echo -e "${GREEN}Starting backend server (development mode)...${NC}" fi # Activate virtual environment source "$SCRIPT_DIR/venv/bin/activate" # Load environment variables load_env # Start backend in background cd "$SCRIPT_DIR/backend" # Create necessary directories mkdir -p uploads/{temp,processed,images} mkdir -p storage/{markdown,json,exports,results} mkdir -p models/paddleocr mkdir -p logs # Start uvicorn (different modes) if [ "$PROD_MODE" = true ]; then nohup uvicorn app.main:app \ --host "$BACKEND_HOST" \ --port "$BACKEND_PORT" \ --workers "$UVICORN_WORKERS" \ --access-log \ --log-level info \ > "$SCRIPT_DIR/.pid/backend.log" 2>&1 & else nohup uvicorn app.main:app --reload --host "$BACKEND_HOST" --port "$BACKEND_PORT" > "$SCRIPT_DIR/.pid/backend.log" 2>&1 & fi local pid=$! echo $pid > "$BACKEND_PID_FILE" cd "$SCRIPT_DIR" # Wait a moment and verify sleep 2 if is_running "$BACKEND_PID_FILE"; then if [ "$PROD_MODE" = true ]; then echo -e "${GREEN}Backend started (PID: $pid, Workers: $UVICORN_WORKERS)${NC}" else echo -e "${GREEN}Backend started (PID: $pid)${NC}" fi echo -e " API Docs: http://localhost:$BACKEND_PORT/docs" echo -e " Health: http://localhost:$BACKEND_PORT/health" else echo -e "${RED}Backend failed to start. Check .pid/backend.log${NC}" return 1 fi } start_frontend() { if is_running "$FRONTEND_PID_FILE"; then echo -e "${YELLOW}Frontend already running (PID: $(get_pid $FRONTEND_PID_FILE))${NC}" return 0 fi if [ "$PROD_MODE" = true ]; then echo -e "${GREEN}Starting frontend server (production mode)...${NC}" else echo -e "${GREEN}Starting frontend server (development mode)...${NC}" fi # Load environment variables so Vite config can use FRONTEND_PORT/FRONTEND_HOST/etc. load_env # Load nvm export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" cd "$SCRIPT_DIR/frontend" # Start frontend (different modes) if [ "$PROD_MODE" = true ]; then # Build if needed if [ ! -d "dist" ] || [ "$(find src -newer dist -type f 2>/dev/null | head -1)" ]; then echo -e "${YELLOW}Building frontend...${NC}" npm run build fi # Start production preview server nohup npm run preview -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT" > "$SCRIPT_DIR/.pid/frontend.log" 2>&1 & else # Start vite dev server nohup npm run dev > "$SCRIPT_DIR/.pid/frontend.log" 2>&1 & fi local pid=$! echo $pid > "$FRONTEND_PID_FILE" cd "$SCRIPT_DIR" # Wait a moment and verify sleep 3 if is_running "$FRONTEND_PID_FILE"; then echo -e "${GREEN}Frontend started (PID: $pid)${NC}" echo -e " URL: http://localhost:$FRONTEND_PORT" else echo -e "${RED}Frontend failed to start. Check .pid/frontend.log${NC}" return 1 fi } kill_process_tree() { local pid=$1 # Kill all child processes first pkill -TERM -P "$pid" 2>/dev/null || true # Then kill the parent kill -TERM "$pid" 2>/dev/null || true } force_kill_process_tree() { local pid=$1 # Force kill all child processes pkill -9 -P "$pid" 2>/dev/null || true # Force kill the parent kill -9 "$pid" 2>/dev/null || true } kill_by_port() { local port=$1 local pids=$(lsof -ti :$port 2>/dev/null) if [ -n "$pids" ]; then echo "$pids" | xargs kill -TERM 2>/dev/null || true sleep 1 # Force kill if still running pids=$(lsof -ti :$port 2>/dev/null) if [ -n "$pids" ]; then echo "$pids" | xargs kill -9 2>/dev/null || true fi fi } stop_service() { local name=$1 local pid_file=$2 local port=$3 if is_running "$pid_file"; then local pid=$(get_pid "$pid_file") echo -e "${YELLOW}Stopping $name (PID: $pid)...${NC}" # Kill the entire process tree kill_process_tree "$pid" # Wait up to 5 seconds local count=0 while [ $count -lt 5 ] && is_running "$pid_file"; do sleep 1 count=$((count + 1)) done # Force kill if still running if is_running "$pid_file"; then force_kill_process_tree "$pid" fi rm -f "$pid_file" fi # Also kill any orphaned processes by port (fallback) if [ -n "$port" ]; then local port_pids=$(lsof -ti :$port 2>/dev/null) if [ -n "$port_pids" ]; then echo -e "${YELLOW}Cleaning up orphaned processes on port $port...${NC}" kill_by_port "$port" fi fi echo -e "${GREEN}$name stopped${NC}" } stop_all() { echo -e "${YELLOW}Stopping all services...${NC}" stop_service "Backend" "$BACKEND_PID_FILE" "$BACKEND_PORT" stop_service "Frontend" "$FRONTEND_PID_FILE" "$FRONTEND_PORT" echo -e "${GREEN}All services stopped${NC}" } show_status() { echo -e "${BLUE}Service Status:${NC}" echo "" if is_running "$BACKEND_PID_FILE"; then local pid=$(get_pid "$BACKEND_PID_FILE") echo -e " Backend: ${GREEN}Running${NC} (PID: $pid)" echo -e " http://localhost:$BACKEND_PORT" else echo -e " Backend: ${RED}Stopped${NC}" fi if is_running "$FRONTEND_PID_FILE"; then local pid=$(get_pid "$FRONTEND_PID_FILE") echo -e " Frontend: ${GREEN}Running${NC} (PID: $pid)" echo -e " http://localhost:$FRONTEND_PORT" else echo -e " Frontend: ${RED}Stopped${NC}" fi echo "" } # Parse arguments COMMAND="" while [[ $# -gt 0 ]]; do case $1 in --prod) PROD_MODE=true shift ;; --help|-h|--stop|--status|backend|frontend|all) COMMAND=$1 shift ;; *) echo -e "${RED}Unknown option: $1${NC}" show_help exit 1 ;; esac done # Default command COMMAND=${COMMAND:-all} # Main case "$COMMAND" in --help|-h) show_help ;; --stop) stop_all ;; --status) show_status ;; backend) print_header check_requirements || exit 1 start_backend echo "" echo -e "${YELLOW}Press Ctrl+C to stop (or use ./start.sh --stop)${NC}" echo -e "${YELLOW}Logs: tail -f .pid/backend.log${NC}" ;; frontend) print_header check_requirements || exit 1 start_frontend echo "" echo -e "${YELLOW}Press Ctrl+C to stop (or use ./start.sh --stop)${NC}" echo -e "${YELLOW}Logs: tail -f .pid/frontend.log${NC}" ;; all) print_header check_requirements || exit 1 start_backend start_frontend echo "" echo -e "${GREEN}================================${NC}" echo -e "${GREEN}All services started!${NC}" echo -e "${GREEN}================================${NC}" echo "" echo "Access the application:" echo -e " Frontend: ${BLUE}http://localhost:$FRONTEND_PORT${NC}" echo -e " API Docs: ${BLUE}http://localhost:$BACKEND_PORT/docs${NC}" echo "" echo -e "${YELLOW}Use ./start.sh --stop to stop all services${NC}" echo -e "${YELLOW}Use ./start.sh --status to check status${NC}" echo "" echo "Logs:" echo " Backend: tail -f .pid/backend.log" echo " Frontend: tail -f .pid/frontend.log" ;; *) echo -e "${RED}Unknown command: $COMMAND${NC}" show_help exit 1 ;; esac