Files
ZY-Agent/scripts/dev-gateway.sh
T
1445043649 dd1d40368d feat(scripts): 新增本地调试与示例启动脚本,并登记进 apps/README
新增 4 个脚本,统一子命令风格(start/stop/restart/status/logs/run):

- scripts/dev-gateway.sh:方式 B,只起 Gateway(:8001),用 backend/.venv 虚拟环境,
  自动加载 .env、释放端口、等待就绪;带 PID/日志文件,可查状态与跟随日志。
- scripts/dev-full.sh:方式 A,复用 serve.sh 守护模式起全量栈(Gateway+前端+nginx),
  补齐 serve.sh 缺失的 status 与 logs;restart 默认跳过依赖安装,统一入口 :2026。
- apps/examples/http-chat/run.sh:自动探测网关(:2026 优先,回退 :8001),
  优先 uv 临时环境带 requests(--no-project,不污染系统),无 uv 时回退 venv+pip。
- apps/examples/embedded-chat/run.sh:自动定位 backend、加载 .env 后用 uv run 运行,
  内嵌 SDK 模式无需起服务。

apps/README.md 增加「本地调试脚本」与「运行示例」两节,说明上述脚本用法。

全部脚本均已本地实跑验证:全量栈三服务 HTTP 200,两个示例多轮对话正常。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 22:47:30 +08:00

175 lines
6.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# 本地调试 Gateway 管理脚本(方式 B:只起 Gateway,端口 8001
# ------------------------------------------------------------------
# - 用 backend/.venv 虚拟环境运行(由 uv 管理)
# - 自动加载仓库根 .env(数据库、模型 key 等)
# - 支持 start / stop / restart / status / logs / run 子命令
#
# 用法:
# ./scripts/dev-gateway.sh start # 后台启动(写 PID + 日志)
# ./scripts/dev-gateway.sh stop # 关闭
# ./scripts/dev-gateway.sh restart # 重启
# ./scripts/dev-gateway.sh status # 查看状态(PID / 端口 / 健康检查)
# ./scripts/dev-gateway.sh logs # 实时跟随日志(Ctrl-C 退出,不影响服务)
# ./scripts/dev-gateway.sh run # 前台运行(断点调试,Ctrl-C 退出)
#
# 环境变量:
# PORT=8002 换端口(默认 8001)
# NO_RELOAD=1 关掉热重载(断点调试更稳)
#
set -euo pipefail
# ── 定位仓库根 ────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PORT="${PORT:-8001}"
PID_FILE="$REPO_ROOT/logs/gateway-dev.pid"
LOG_FILE="$REPO_ROOT/logs/gateway-dev.log"
# ── 工具函数 ──────────────────────────────────────────────────────
_running_pid() {
# 打印存活的服务 PID(优先 PID 文件,回退到端口探测),否则空
if [ -f "$PID_FILE" ]; then
local pid
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
echo "$pid"; return 0
fi
fi
# lsof 在无监听时返回 1,配合 pipefail+set -e 会误终止脚本 → 用 || true 兜底
{ lsof -ti tcp:"$PORT" 2>/dev/null || true; } | head -1
}
_load_env() {
if [ -f "$REPO_ROOT/.env" ]; then
set -a
# shellcheck disable=SC1091
source "$REPO_ROOT/.env"
set +a
else
echo "⚠ 未找到 $REPO_ROOT/.env(数据库/模型 key 可能缺失)" >&2
fi
}
_uvicorn_flags() {
if [ "${NO_RELOAD:-0}" != "1" ]; then
echo "--reload --reload-include=*.yaml --reload-include=.env --reload-exclude=*.pyc --reload-exclude=__pycache__/* --reload-exclude=sandbox/* --reload-exclude=.deer-flow/*"
fi
}
_preflight() {
command -v uv >/dev/null 2>&1 || { echo "✗ 未找到 uv。安装:curl -LsSf https://astral.sh/uv/install.sh | sh" >&2; exit 1; }
mkdir -p "$REPO_ROOT/logs"
if [ ! -d "$REPO_ROOT/backend/.venv" ]; then
echo "→ 未发现 backend/.venv,执行 uv sync 创建虚拟环境"
(cd "$REPO_ROOT/backend" && uv sync)
fi
}
_wait_ready() {
# 探测 setup-status,最多 30s;就绪返回 0
for _ in $(seq 1 30); do
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT/api/v1/auth/setup-status" 2>/dev/null | grep -qE "200|429"; then
return 0
fi
sleep 1
done
return 1
}
# ── 子命令 ────────────────────────────────────────────────────────
cmd_start() {
local pid; pid="$(_running_pid)"
if [ -n "$pid" ]; then
echo "已在运行 (PID $pid, 端口 $PORT) —— 如需重启用:$0 restart"
return 0
fi
_preflight
_load_env
echo "→ 后台启动 Gateway @localhost:${PORT}venv: backend/.venv, 热重载: $([ "${NO_RELOAD:-0}" = "1" ] && echo off || echo on)"
# shellcheck disable=SC2086
( cd "$REPO_ROOT/backend" && exec env PYTHONPATH=. uv run uvicorn app.gateway.app:app \
--host 0.0.0.0 --port "$PORT" $(_uvicorn_flags) ) > "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
if _wait_ready; then
echo "✓ 启动成功 (PID $(cat "$PID_FILE"))"
echo " 日志: $0 logs 状态: $0 status 关闭: $0 stop"
else
echo "✗ 30s 内未就绪,最后 20 行日志:" >&2
tail -n 20 "$LOG_FILE" >&2
return 1
fi
}
cmd_stop() {
local pid; pid="$(_running_pid)"
if [ -z "$pid" ]; then
echo "未在运行"
rm -f "$PID_FILE"
return 0
fi
echo "→ 关闭 Gateway (PID $pid)"
# 优雅终止整组进程(uv → uvicorn → reloader 子进程)
kill "$pid" 2>/dev/null || true
for _ in $(seq 1 10); do kill -0 "$pid" 2>/dev/null || break; sleep 0.5; done
# 兜底:按端口清残留(reload worker 偶尔不随父进程退出)
lsof -ti tcp:"$PORT" 2>/dev/null | xargs kill -9 2>/dev/null || true
rm -f "$PID_FILE"
echo "✓ 已停止,端口 $PORT 释放"
}
cmd_status() {
local pid; pid="$(_running_pid)"
if [ -z "$pid" ]; then
echo "● Gateway: 已停止 (端口 $PORT 空闲)"
return 1
fi
echo "● Gateway: 运行中"
echo " PID: $pid"
echo " 端口: $PORT"
local code
code="$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT/api/v1/auth/setup-status" 2>/dev/null || echo "000")"
case "$code" in
200|429) echo " 健康: ✓ HTTP $code (REST API 响应中)";;
000) echo " 健康: ⚠ 端口占用但 HTTP 无响应(可能仍在启动)";;
*) echo " 健康: ⚠ HTTP $code";;
esac
echo " 日志: $LOG_FILE"
}
cmd_logs() {
[ -f "$LOG_FILE" ] || { echo "暂无日志文件:$LOG_FILE"; return 1; }
echo "→ 跟随日志(Ctrl-C 退出,不影响服务):$LOG_FILE"
tail -n 50 -f "$LOG_FILE"
}
cmd_run() {
# 前台运行:日志直出终端,适合 IDE 断点 / 看实时堆栈
local pid; pid="$(_running_pid)"
[ -n "$pid" ] && { echo "已有后台实例在运行 (PID $pid),先 $0 stop" >&2; exit 1; }
_preflight
_load_env
echo "→ 前台运行 @localhost:${PORT}Ctrl-C 退出)"
cd "$REPO_ROOT/backend"
# shellcheck disable=SC2046,SC2086
exec env PYTHONPATH=. uv run uvicorn app.gateway.app:app \
--host 0.0.0.0 --port "$PORT" $(_uvicorn_flags)
}
# ── 分发 ──────────────────────────────────────────────────────────
case "${1:-status}" in
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_stop; echo; cmd_start ;;
status|"") cmd_status ;;
logs) cmd_logs ;;
run) cmd_run ;;
-h|--help|help)
sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//' ;;
*)
echo "未知命令: $1" >&2
echo "可用: start | stop | restart | status | logs | run" >&2
exit 1 ;;
esac