#!/usr/bin/env bash # ============================================================ # 大时代 单机启动脚本 # # 用法: # ./start.sh # 构建前端 + 后台启动全部服务 (单机模式) # ./start.sh --no-build # 跳过前端构建 # ./start.sh --no-daemon # 前台运行 (不使用 nohup) # ./start.sh stop # 停止所有后台服务 # ./start.sh status # 查看服务状态 # # 环境变量: # WORKERS=2 # uvicorn worker 数 (默认: 2) # GATEWAY_HOST=0.0.0.0 # Gateway 绑定地址 # GATEWAY_PORT=8765 # Gateway 端口 # FRONTEND_PORT=8080 # 前端服务端口 (默认: 8080) # ============================================================ set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" WORKERS="${WORKERS:-2}" GATEWAY_HOST="${GATEWAY_HOST:-0.0.0.0}" GATEWAY_PORT="${GATEWAY_PORT:-8765}" FRONTEND_PORT="${FRONTEND_PORT:-8080}" PID_DIR="${SCRIPT_DIR}/.pids" LOG_DIR="${SCRIPT_DIR}/logs" FRONTEND_DIST="${SCRIPT_DIR}/frontend/dist" DAEMON=true BUILD_FRONTEND=true ACTION="start" for arg in "$@"; do case "$arg" in --no-daemon) DAEMON=false ;; --no-build) BUILD_FRONTEND=false ;; stop) ACTION="stop" ;; status) ACTION="status" ;; *) echo -e "${YELLOW}忽略未知参数: ${arg}${NC}" ;; esac done ensure_dirs() { mkdir -p "${PID_DIR}" "${LOG_DIR}" } save_pid() { local name="$1" pid="$2" echo "${pid}" > "${PID_DIR}/${name}.pid" } read_pid() { local name="$1" local pidfile="${PID_DIR}/${name}.pid" if [ -f "${pidfile}" ]; then cat "${pidfile}" fi } is_running() { local pid="$1" [ -n "${pid}" ] && kill -0 "${pid}" 2>/dev/null } stop_service() { local name="$1" local pid pid="$(read_pid "${name}")" if is_running "${pid}"; then echo -e " ${YELLOW}停止${NC} ${name} (PID: ${pid})" kill "${pid}" 2>/dev/null || true local count=0 while is_running "${pid}" && [ "${count}" -lt 20 ]; do sleep 0.5 count=$((count + 1)) done if is_running "${pid}"; then echo -e " ${RED}强制终止${NC} ${name}" kill -9 "${pid}" 2>/dev/null || true fi fi rm -f "${PID_DIR}/${name}.pid" } print_status() { local name="$1" port="$2" local pid pid="$(read_pid "${name}")" if is_running "${pid}"; then echo -e " ${GREEN}●${NC} ${name} (PID: ${pid}, 端口: ${port})" else echo -e " ${RED}○${NC} ${name} (未运行)" fi } load_env() { if [ -f .env ]; then set -a # shellcheck disable=SC1091 source .env set +a else echo -e "${YELLOW}警告: 未检测到 .env,将使用环境变量或默认值${NC}" fi } check_prereqs() { # Resolve the Python interpreter to use. Prefer the project venv. if [ -x "${SCRIPT_DIR}/.venv/bin/python" ]; then PYTHON="${SCRIPT_DIR}/.venv/bin/python" else PYTHON="$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)" fi if [ -z "${PYTHON}" ]; then echo -e "${RED}未找到 python${NC}" exit 1 fi echo -e "${GREEN}使用 Python: ${PYTHON}${NC}" command -v lsof >/dev/null 2>&1 || { echo -e "${RED}未找到 lsof${NC}" exit 1 } } kill_port() { local port="$1" local pids pids="$(lsof -ti :"${port}" 2>/dev/null || true)" if [ -n "${pids}" ]; then echo -e "${YELLOW}端口 ${port} 已被占用,清理 PID:${NC} ${pids}" echo "${pids}" | xargs kill -9 2>/dev/null || true sleep 0.5 fi } do_stop() { echo -e "${CYAN}停止所有服务...${NC}" for svc in frontend agent_service trading_service news_service runtime_service; do stop_service "${svc}" done echo -e "${GREEN}已停止${NC}" } do_status() { echo "" echo -e "${CYAN}服务状态${NC}" print_status "agent_service" 8000 print_status "trading_service" 8001 print_status "news_service" 8002 print_status "runtime_service" 8003 print_status "frontend" "${FRONTEND_PORT}" echo "" echo -e " ${CYAN}ℹ${NC} Gateway 由 runtime_service 管理,运行日志写入 runs//logs/gateway.log" echo "" if [ -d "${FRONTEND_DIST}" ]; then echo -e " ${GREEN}✔${NC} 前端已构建: ${FRONTEND_DIST}" else echo -e " ${YELLOW}⚠${NC} 前端未构建,运行: cd frontend && npm run build" fi echo "" } build_frontend() { if ! ${BUILD_FRONTEND}; then return fi echo "" echo -e "${CYAN}构建前端...${NC}" if [ -d "frontend" ] && command -v npm >/dev/null 2>&1; then if [ -f "frontend/package-lock.json" ]; then (cd frontend && npm ci && npm run build) else (cd frontend && npm install && npm run build) fi echo -e "${GREEN}前端构建完成: ${FRONTEND_DIST}${NC}" else echo -e "${RED}前端构建失败: 需要 npm 和 frontend 目录${NC}" exit 1 fi } start_single_daemon() { local name="$1" app_path="$2" port="$3" echo -e " ${GREEN}▶${NC} ${name} → :${port} (${WORKERS} workers)" nohup env SERVICE_NAME="${name}" "${PYTHON}" -m uvicorn "${app_path}" \ --host 0.0.0.0 \ --port "${port}" \ --workers "${WORKERS}" \ --log-level warning \ --no-access-log \ >> "${LOG_DIR}/${name}.log" 2>&1 & save_pid "${name}" $! } start_daemon() { start_single_daemon "agent_service" "backend.apps.agent_service:app" 8000 start_single_daemon "trading_service" "backend.apps.trading_service:app" 8001 start_single_daemon "news_service" "backend.apps.news_service:app" 8002 start_single_daemon "runtime_service" "backend.apps.runtime_service:app" 8003 echo -e " ${GREEN}▶${NC} frontend → http://0.0.0.0:${FRONTEND_PORT}" nohup env SERVICE_NAME="frontend" "${PYTHON}" -m uvicorn "backend.apps.frontend_service:app" \ --host 0.0.0.0 \ --port "${FRONTEND_PORT}" \ --workers "${WORKERS}" \ --log-level warning \ --no-access-log \ >> "${LOG_DIR}/frontend.log" 2>&1 & save_pid "frontend" $! echo "" echo -e "${GREEN}所有服务已在后台启动${NC}" echo " 日志目录: ${LOG_DIR}/" echo " PID 目录: ${PID_DIR}/" echo "" echo " 查看状态: ./start.sh status" echo " 查看服务日志: tail -f ${LOG_DIR}/runtime_service.log" echo " 查看运行日志: tail -f runs//logs/gateway.log" echo " 停止服务: ./start.sh stop" echo "" } PIDS=() cleanup_foreground() { echo "" echo -e "${YELLOW}正在停止所有服务...${NC}" if [ "${#PIDS[@]}" -gt 0 ]; then kill "${PIDS[@]}" 2>/dev/null || true wait "${PIDS[@]}" 2>/dev/null || true fi } start_single_foreground() { local name="$1" app_path="$2" port="$3" echo -e " ${GREEN}▶${NC} ${name} → :${port}" env SERVICE_NAME="${name}" "${PYTHON}" -m uvicorn "${app_path}" \ --host 0.0.0.0 \ --port "${port}" \ --log-level warning \ --no-access-log & PIDS+=($!) } start_foreground() { trap cleanup_foreground EXIT INT TERM start_single_foreground "agent_service" "backend.apps.agent_service:app" 8000 start_single_foreground "trading_service" "backend.apps.trading_service:app" 8001 start_single_foreground "news_service" "backend.apps.news_service:app" 8002 start_single_foreground "runtime_service" "backend.apps.runtime_service:app" 8003 echo -e " ${GREEN}▶${NC} frontend → http://0.0.0.0:${FRONTEND_PORT}" env SERVICE_NAME="frontend" "${PYTHON}" -m uvicorn "backend.apps.frontend_service:app" \ --host 0.0.0.0 \ --port "${FRONTEND_PORT}" \ --log-level warning \ --no-access-log & PIDS+=($!) echo "" echo -e "${GREEN}服务以前台模式运行。按 Ctrl+C 停止。${NC}" wait } do_start() { ensure_dirs check_prereqs load_env export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:8001}" export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:8002}" export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:8003}" build_frontend echo "" echo -e "${CYAN}停止已有服务...${NC}" for svc in frontend agent_service trading_service news_service runtime_service; do stop_service "${svc}" done echo "" echo -e "${CYAN}══════════════════════════════════════════${NC}" echo -e "${CYAN} 大时代 · 单机启动${NC}" echo -e "${CYAN}══════════════════════════════════════════${NC}" echo "" echo -e "${YELLOW}说明:${NC} 当前脚本适合单机运行或演示环境。" echo -e "${YELLOW}正式生产部署请优先使用 deploy/systemd + nginx 静态前端方案。${NC}" echo "" if ${DAEMON}; then start_daemon else start_foreground fi } case "${ACTION}" in start) do_start ;; stop) do_stop ;; status) do_status ;; *) echo -e "${RED}未知动作: ${ACTION}${NC}" exit 1 ;; esac