#!/usr/bin/env bash # # serve.sh — Unified DeerFlow service launcher # # Usage: # ./scripts/serve.sh [--dev|--prod] [--daemon] [--stop|--restart] # # Modes: # --dev Development mode with hot-reload (default) # --prod Production mode, pre-built frontend, no hot-reload # --daemon Run all services in background (nohup), exit after startup # # Actions: # --skip-install Skip dependency installation (faster restart) # --stop Stop all running services and exit # --restart Stop all services, then start with the given mode flags # # Examples: # ./scripts/serve.sh --dev # Gateway dev, hot reload # ./scripts/serve.sh --prod # Gateway prod # ./scripts/serve.sh --dev --daemon # Gateway dev, background # ./scripts/serve.sh --stop # Stop all services # ./scripts/serve.sh --restart --dev # Restart dev services # # Must be run from the repo root directory. set -e REPO_ROOT="$(builtin cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 && pwd -P)" cd "$REPO_ROOT" # ── Load .env ──────────────────────────────────────────────────────────────── if [ -f "$REPO_ROOT/.env" ]; then set -a source "$REPO_ROOT/.env" set +a fi # ── Argument parsing ───────────────────────────────────────────────────────── DEV_MODE=true DAEMON_MODE=false SKIP_INSTALL=false ACTION="start" # start | stop | restart for arg in "$@"; do case "$arg" in --dev) DEV_MODE=true ;; --prod) DEV_MODE=false ;; --daemon) DAEMON_MODE=true ;; --skip-install) SKIP_INSTALL=true ;; --stop) ACTION="stop" ;; --restart) ACTION="restart" ;; *) echo "Unknown argument: $arg" echo "Usage: $0 [--dev|--prod] [--daemon] [--skip-install] [--stop|--restart]" exit 1 ;; esac done # ── Stop helper ────────────────────────────────────────────────────────────── _kill_port() { local port=$1 local pid pid=$(lsof -ti :"$port" 2>/dev/null) || true if [ -n "$pid" ]; then kill -9 $pid 2>/dev/null || true fi } stop_all() { echo "Stopping all services..." pkill -f "uvicorn app.gateway.app:app" 2>/dev/null || true pkill -f "next dev" 2>/dev/null || true pkill -f "next start" 2>/dev/null || true pkill -f "next-server" 2>/dev/null || true nginx -c "$REPO_ROOT/docker/nginx/nginx.local.conf" -p "$REPO_ROOT" -s quit 2>/dev/null || true sleep 1 pkill -9 nginx 2>/dev/null || true # Force-kill any survivors still holding the service ports _kill_port 8001 _kill_port 3000 ./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true echo "✓ All services stopped" } # ── Action routing ─────────────────────────────────────────────────────────── if [ "$ACTION" = "stop" ]; then stop_all exit 0 fi ALREADY_STOPPED=false if [ "$ACTION" = "restart" ]; then stop_all sleep 1 ALREADY_STOPPED=true fi # Mode label for banner if $DEV_MODE; then MODE_LABEL="DEV (Gateway runtime, hot-reload enabled)" else MODE_LABEL="PROD (Gateway runtime, optimized)" fi if $DAEMON_MODE; then MODE_LABEL="$MODE_LABEL [daemon]" fi # Frontend command if $DEV_MODE; then FRONTEND_CMD="pnpm run dev" else if command -v python3 >/dev/null 2>&1; then PYTHON_BIN="python3" elif command -v python >/dev/null 2>&1; then PYTHON_BIN="python" else echo "Python is required to generate BETTER_AUTH_SECRET." exit 1 fi FRONTEND_CMD="env BETTER_AUTH_SECRET=$($PYTHON_BIN -c 'import secrets; print(secrets.token_hex(16))') pnpm run preview" fi # Extra flags for uvicorn if $DEV_MODE && ! $DAEMON_MODE; then GATEWAY_EXTRA_FLAGS="--reload --reload-include='*.yaml' --reload-include='.env' --reload-exclude='*.pyc' --reload-exclude='__pycache__' --reload-exclude='sandbox/' --reload-exclude='.deer-flow/'" else GATEWAY_EXTRA_FLAGS="" fi # ── Stop existing services (skip if restart already did it) ────────────────── if ! $ALREADY_STOPPED; then stop_all sleep 1 fi # ── Config check ───────────────────────────────────────────────────────────── if ! { \ [ -n "$DEER_FLOW_CONFIG_PATH" ] && [ -f "$DEER_FLOW_CONFIG_PATH" ] || \ [ -f backend/config.yaml ] || \ [ -f config.yaml ]; \ }; then echo "✗ No DeerFlow config file found." echo " Run 'make setup' (recommended) or 'make config' to generate config.yaml." exit 1 fi "$REPO_ROOT/scripts/config-upgrade.sh" # ── Install dependencies ──────────────────────────────────────────────────── if ! $SKIP_INSTALL; then echo "Syncing dependencies..." (cd backend && uv sync --quiet) || { echo "✗ Backend dependency install failed"; exit 1; } (cd frontend && pnpm install --silent) || { echo "✗ Frontend dependency install failed"; exit 1; } echo "✓ Dependencies synced" else echo "⏩ Skipping dependency install (--skip-install)" fi # ── Banner ─────────────────────────────────────────────────────────────────── echo "" echo "==========================================" echo " Starting DeerFlow" echo "==========================================" echo "" echo " Mode: $MODE_LABEL" echo "" echo " Services:" echo " Gateway → localhost:8001 (REST API + agent runtime)" echo " Frontend → localhost:3000 (Next.js)" echo " Nginx → localhost:2026 (reverse proxy)" echo "" # ── Cleanup handler ────────────────────────────────────────────────────────── cleanup() { trap - INT TERM echo "" stop_all exit 0 } trap cleanup INT TERM # ── Helper: start a service ────────────────────────────────────────────────── # run_service NAME COMMAND PORT TIMEOUT # In daemon mode, wraps with nohup. Waits for port to be ready. run_service() { local name="$1" cmd="$2" port="$3" timeout="$4" echo "Starting $name..." if $DAEMON_MODE; then nohup sh -c "$cmd" > /dev/null 2>&1 & else sh -c "$cmd" & fi ./scripts/wait-for-port.sh "$port" "$timeout" "$name" || { local logfile="logs/$(echo "$name" | tr '[:upper:]' '[:lower:]' | tr ' ' '-').log" echo "✗ $name failed to start." [ -f "$logfile" ] && tail -20 "$logfile" cleanup } echo "✓ $name started on localhost:$port" } # ── Start services ─────────────────────────────────────────────────────────── mkdir -p logs mkdir -p temp/client_body_temp temp/proxy_temp temp/fastcgi_temp temp/uwsgi_temp temp/scgi_temp # 1. Gateway API run_service "Gateway" \ "cd backend && PYTHONPATH=. uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 $GATEWAY_EXTRA_FLAGS > ../logs/gateway.log 2>&1" \ 8001 30 # 2. Frontend run_service "Frontend" \ "cd frontend && $FRONTEND_CMD > ../logs/frontend.log 2>&1" \ 3000 120 # 3. Nginx run_service "Nginx" \ "nginx -g 'daemon off;' -c '$REPO_ROOT/docker/nginx/nginx.local.conf' -p '$REPO_ROOT' > logs/nginx.log 2>&1" \ 2026 10 # ── Ready ──────────────────────────────────────────────────────────────────── echo "" echo "==========================================" echo " ✓ DeerFlow is running! [$MODE_LABEL]" echo "==========================================" echo "" echo " 🌐 http://localhost:2026" echo "" echo " Routing: Frontend → Nginx → Gateway" echo " API: /api/langgraph/* → Gateway agent runtime" echo " /api/* → Gateway REST API (8001)" echo "" echo " 📋 Logs: logs/{gateway,frontend,nginx}.log" echo "" if $DAEMON_MODE; then echo " 🛑 Stop: make stop" # Detach — trap is no longer needed trap - INT TERM else echo " Press Ctrl+C to stop all services" wait fi