321 lines
9.2 KiB
Bash
321 lines
9.2 KiB
Bash
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# 大时代 生产环境启动脚本
|
||
#
|
||
# 用法:
|
||
# ./start.sh # 构建前端 + 后台启动全部服务 (默认)
|
||
# ./start.sh --no-build # 跳过前端构建
|
||
# ./start.sh --no-daemon # 前台运行 (不使用 nohup)
|
||
# ./start.sh --gateway-only # 仅启动 Gateway (配合 nginx)
|
||
# ./start.sh stop # 停止所有后台服务
|
||
# ./start.sh status # 查看服务状态
|
||
#
|
||
# 环境变量:
|
||
# WORKERS=2 # uvicorn worker 数 (默认: 2)
|
||
# GATEWAY_HOST=0.0.0.0 # Gateway 绑定地址
|
||
# GATEWAY_PORT=8765 # Gateway 端口
|
||
# ============================================================
|
||
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}"
|
||
PID_DIR="${SCRIPT_DIR}/.pids"
|
||
LOG_DIR="${SCRIPT_DIR}/logs"
|
||
FRONTEND_DIST="${SCRIPT_DIR}/frontend/dist"
|
||
|
||
DAEMON=true
|
||
BUILD_FRONTEND=true
|
||
GATEWAY_ONLY=false
|
||
ACTION="start"
|
||
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--no-daemon) DAEMON=false ;;
|
||
--no-build) BUILD_FRONTEND=false ;;
|
||
--gateway-only) GATEWAY_ONLY=true ;;
|
||
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() {
|
||
if [ -z "${VIRTUAL_ENV:-}" ] && [ -f ".venv/bin/activate" ]; then
|
||
# shellcheck disable=SC1091
|
||
source .venv/bin/activate
|
||
fi
|
||
|
||
command -v python >/dev/null 2>&1 || {
|
||
echo -e "${RED}未找到 python${NC}"
|
||
exit 1
|
||
}
|
||
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 gateway agent_service trading_service news_service runtime_service openclaw_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 "openclaw_service" 8004
|
||
print_status "gateway" "${GATEWAY_PORT}"
|
||
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() {
|
||
if ! ${GATEWAY_ONLY}; then
|
||
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
|
||
start_single_daemon "openclaw_service" "backend.apps.openclaw_service:app" 8004
|
||
fi
|
||
|
||
echo -e " ${GREEN}▶${NC} gateway → ws://${GATEWAY_HOST}:${GATEWAY_PORT}"
|
||
nohup env SERVICE_NAME="gateway" python -m backend.main \
|
||
--mode live \
|
||
--host "${GATEWAY_HOST}" \
|
||
--port "${GATEWAY_PORT}" \
|
||
>> "${LOG_DIR}/gateway.log" 2>&1 &
|
||
save_pid "gateway" $!
|
||
|
||
echo ""
|
||
echo -e "${GREEN}所有服务已在后台启动${NC}"
|
||
echo " 日志目录: ${LOG_DIR}/"
|
||
echo " PID 目录: ${PID_DIR}/"
|
||
echo ""
|
||
echo " 查看状态: ./start.sh status"
|
||
echo " 查看日志: tail -f ${LOG_DIR}/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
|
||
|
||
if ! ${GATEWAY_ONLY}; then
|
||
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
|
||
start_single_foreground "openclaw_service" "backend.apps.openclaw_service:app" 8004
|
||
fi
|
||
|
||
echo -e " ${GREEN}▶${NC} gateway → ws://${GATEWAY_HOST}:${GATEWAY_PORT}"
|
||
env SERVICE_NAME="gateway" python -m backend.main \
|
||
--mode live \
|
||
--host "${GATEWAY_HOST}" \
|
||
--port "${GATEWAY_PORT}" &
|
||
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}"
|
||
export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:8004}"
|
||
|
||
build_frontend
|
||
|
||
echo ""
|
||
echo -e "${CYAN}停止已有服务...${NC}"
|
||
for svc in gateway agent_service trading_service news_service runtime_service openclaw_service; do
|
||
stop_service "${svc}"
|
||
done
|
||
|
||
echo ""
|
||
echo -e "${CYAN}══════════════════════════════════════════${NC}"
|
||
echo -e "${CYAN} 大时代 · 生产环境启动${NC}"
|
||
echo -e "${CYAN}══════════════════════════════════════════${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
|