Add CLI flags to svc.sh (addr/host/port/data/dsn/auth/mock/passthrough) #47

Merged
weichen merged 4 commits from feature/svc-cli-args into main 2026-09-30 11:50:33 +00:00
4 changed files with 126 additions and 38 deletions
@@ -33,3 +33,4 @@
| 同步前验收 | 81e4a3447a1daf743d88a7e7bc70e97cf9cac0c7 | 0d84b1d68f160999eedf315ea98156b850b76008 | 2026-09-30T10:44:54Z |
| 同步前验收 | b71ef3e9a857646aec7e46839914f6cc0af1cc9f | 1634b42df094257fec61d475a9c0fac65bad6ab5 | 2026-09-30T11:00:17Z |
| 同步前验收 | fcddfee5be45870071c18d921173f58bfab9e851 | f0e7831ed1cddc67621f873c2e49cba02f81fc3c | 2026-09-30T11:38:44Z |
| 同步前验收 | 1174431ee6953ddc6e319f8fdbd8ecbf18576dbf | e3ba86aa16ceaa052bd5f9c2568e9a6c9cdd90d0 | 2026-09-30T11:50:25Z |
+20
View File
@@ -0,0 +1,20 @@
# feature/svc-cli-args 任务卡
任务主记录:<PR 链接,task-finish 时填写>;状态:已完成;owner:weichen
## 基线与授权
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0 = 1174431ee6953ddc6e319f8fdbd8ecbf18576dbf
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-svc-cli-args;分支:feature/svc-cli-args
- 授权与交付终点:用户要求 svc.sh 支持命令行参数(监听地址/端口/数据库端口等)→ 实现并实测后自主闭环合入。
## 目标与非目标
- 目标:CLI 参数面 --addr/--host/--port/--data/--dsn/--auth/--mock/--plugins/-- 透传;优先级 CLI > SVC_* 环境变量 > 默认;健康探测跟随绑定主机(0.0.0.0→127.0.0.1);make svc ARGS 透传。
- 非目标:数据库端口单独拆参(端口在 DSN 内整体传递,已在 help 说明);不改服务本身。
## 结果与验证
- 结果:scripts/svc.sh 参数化 + Makefile ARGS + help 重写。
- 验证(全实测):--port 8801 ✓;--host 0.0.0.0(ss 验证 *:8812 绑定、健康探测自动映射)✓;--addr 整体 ✓;CLI 覆盖 SVC_DEV_ADDR ✓;--mock 进 cmdline ✓;--dsn 假 DSN → PG 连接尝试日志证明透传 + 优雅失败路径 ✓;`--` 透传 --artifacts ✓;make svc ARGS ✓。实测抓出并修复 set -e 静默退出陷阱(条件语句作为函数末语句)。端口守卫实战:主检出并行启动的 dev 实例被正确拒接并列名,未受影响。
- 影响与剩余项:DSN 含密码建议走 SVC_PROD_DSN 环境变量(避免进 shell 历史),已在 help 标注。
+2 -2
View File
@@ -94,8 +94,8 @@ test: test-core test-backend test-frontend test-ui test-agentcmd
# 运行演示(mock 模式):单二进制 + 示例插件 + 嵌入式存储,浏览器 http://127.0.0.1:8787
# 环境服务管理(scripts/svc.sh):deploy = 停旧 → 构建 → 启动;dev/prod 相互隔离。
svc:
@test -n "$(ENV)" || (echo "用法:make svc ENV=dev|prod [ACTION=deploy|stop|start|restart|status|logs]"; exit 2)
scripts/svc.sh $(ENV) $(ACTION)
@test -n "$(ENV)" || (echo "用法:make svc ENV=dev|prod [ACTION=deploy|stop|start|restart|status|logs] [ARGS='--port 9000 --mock ...']"; exit 2)
scripts/svc.sh $(ENV) $(ACTION) $(ARGS)
demo: build
$(BIN) serve --addr 127.0.0.1:8787 --data ./data --plugins $(PLUGINS) --mock
+103 -36
View File
@@ -2,17 +2,32 @@
# svc.sh —— 多环境服务管理:按环境隔离地 停止旧服务 → 构建最新 → 启动。
#
# 用法:
# scripts/svc.sh dev # 部署 dev(停止旧 dev → make build → 启动)
# scripts/svc.sh prod # 部署 prod(dev 不受影响;反之亦然)
# scripts/svc.sh dev stop|start|restart|status|logs
# scripts/svc.sh status # 全环境总览
# scripts/svc.sh <dev|prod> [action] [参数...]
# action:deploy(默认)| stop | start | restart | status | logs [行数]
# scripts/svc.sh status # 全环境总览
#
# 环境隔离(默认值,可用环境变量覆盖):
# dev: 127.0.0.1:8788 数据 runtime/dev/data 日志 runtime/dev/svc.log
# prod: 127.0.0.1:8787 数据 runtime/prod/data 日志 runtime/prod/svc.log
# 覆盖:SVC_DEV_ADDR / SVC_PROD_ADDR、SVC_DEV_DATA / SVC_PROD_DATA、
# SVC_DEV_MOCK=1(dev 加 --mock)、SVC_PROD_DSN(PG 档)、
# SVC_PROD_AUTH(on|off|auto,默认 auto)。
# 参数(优先级:命令行 > 环境变量 > 默认值):
# --addr host:port 监听地址(整体覆盖)
# --host host 只改主机部分(如 0.0.0.0 对外开放)
# --port port 只改端口部分
# --data dir 数据目录(SQLite 与工件)
# --dsn DSN PostgreSQL 档 DSN(数据库地址/端口/库名都在 DSN 内,如
# postgres://user:pass@dbhost:5432/agentdb)——含密码时建议
# 改用环境变量 SVC_PROD_DSN 传递,避免进 shell 历史
# --auth on|off|auto API key 认证(默认 auto:PG 档开、嵌入式关)
# --mock 演示模式(dev 常用)
# --plugins dir 插件目录(默认 <repo>/plugins)
# -- <剩余参数> 原样透传给 agent-system serve(escape hatch)
#
# 环境变量默认值:
# dev: 127.0.0.1:8788 数据 runtime/dev/data;SVC_DEV_ADDR/DATA/MOCK=1 可覆盖
# prod: 127.0.0.1:8787 数据 runtime/prod/data;SVC_PROD_ADDR/DATA/DSN/AUTH 可覆盖
#
# 示例:
# scripts/svc.sh dev --port 9000 --mock
# scripts/svc.sh prod --host 0.0.0.0 --dsn postgres://u:p@127.0.0.1:5432/as
# scripts/svc.sh dev deploy --addr 0.0.0.0:8788
# make svc ENV=dev ARGS="--port 9000"
#
# 安全约束:
# - 只停自己启动过的进程(pidfile + /proc/<pid>/cmdline 双重校验),绝不动他人现场;
@@ -28,31 +43,73 @@ c_ok() { printf '\033[1;32m[svc]\033[0m %s\n' "$*"; }
c_warn() { printf '\033[1;33m[svc]\033[0m %s\n' "$*"; }
c_err() { printf '\033[1;31m[svc]\033[0m %s\n' "$*" >&2; }
usage() {
sed -n '2,36p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}
# ---- 命令行参数(覆盖层)----
CLI_ADDR=""; CLI_HOST=""; CLI_PORT=""; CLI_DATA=""; CLI_DSN=""
CLI_AUTH=""; CLI_MOCK=0; CLI_PLUGINS=""; CLI_EXTRA=""; CLI_LOGS_N=""
need_value() { [ $# -ge 2 ] || { c_err "参数 $1 需要值"; exit 2; }; }
parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
--addr) need_value "$@"; CLI_ADDR="$2"; shift 2 ;;
--host) need_value "$@"; CLI_HOST="$2"; shift 2 ;;
--port) need_value "$@"; CLI_PORT="$2"; shift 2 ;;
--data) need_value "$@"; CLI_DATA="$2"; shift 2 ;;
--dsn) need_value "$@"; CLI_DSN="$2"; shift 2 ;;
--auth) need_value "$@"; CLI_AUTH="$2"; shift 2 ;;
--plugins) need_value "$@"; CLI_PLUGINS="$2"; shift 2 ;;
--mock) CLI_MOCK=1; shift ;;
--) shift; CLI_EXTRA="$*"; break ;;
-h|--help) usage; exit 0 ;;
*) c_err "未知参数:$1"; usage; exit 2 ;;
esac
done
}
# addr_compose 基础值(host:port)按 CLI 的 --addr/--host/--port 组合出最终地址。
addr_compose() {
local base="$1" host port
host="${base%:*}"; port="${base##*:}"
[ -n "$CLI_ADDR" ] && { echo "$CLI_ADDR"; return 0; }
[ -n "$CLI_HOST" ] && host="$CLI_HOST"
[ -n "$CLI_PORT" ] && port="$CLI_PORT"
echo "$host:$port"
}
env_config() {
case "$1" in
dev)
ENV_ADDR="${SVC_DEV_ADDR:-127.0.0.1:8788}"
ENV_DATA="${SVC_DEV_DATA:-$RUNTIME/dev/data}"
ENV_ADDR="$(addr_compose "${SVC_DEV_ADDR:-127.0.0.1:8788}")"
ENV_DATA="${CLI_DATA:-${SVC_DEV_DATA:-$RUNTIME/dev/data}}"
ENV_DIR="$RUNTIME/dev"
ENV_ARGS=""
[ "${SVC_DEV_MOCK:-0}" = "1" ] && ENV_ARGS="--mock"
;;
prod)
ENV_ADDR="${SVC_PROD_ADDR:-127.0.0.1:8787}"
ENV_DATA="${SVC_PROD_DATA:-$RUNTIME/prod/data}"
ENV_ADDR="$(addr_compose "${SVC_PROD_ADDR:-127.0.0.1:8787}")"
ENV_DATA="${CLI_DATA:-${SVC_PROD_DATA:-$RUNTIME/prod/data}}"
ENV_DIR="$RUNTIME/prod"
ENV_ARGS=""
[ -n "${SVC_PROD_DSN:-}" ] && ENV_ARGS="$ENV_ARGS --dsn $SVC_PROD_DSN"
ENV_ARGS="$ENV_ARGS --auth ${SVC_PROD_AUTH:-auto}"
;;
*) c_err "未知环境:$1(可用:dev | prod)"; usage; exit 2 ;;
esac
ENV_PID="$ENV_DIR/svc.pid"
ENV_LOG="$ENV_DIR/svc.log"
}
ENV_PLUGINS="${CLI_PLUGINS:-$ROOT/plugins}"
usage() {
sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
# 运行参数:mock/DSN/auth(CLI 与环境变量取并集,CLI 优先)。
ENV_ARGS=""
if [ "$ENV_NAME" = dev ]; then
if [ "${SVC_DEV_MOCK:-0}" = "1" ] || [ "$CLI_MOCK" = 1 ]; then
ENV_ARGS="--mock"
fi
else
local dsn="${CLI_DSN:-${SVC_PROD_DSN:-}}"
if [ -n "$dsn" ]; then ENV_ARGS="$ENV_ARGS --dsn $dsn"; fi
ENV_ARGS="$ENV_ARGS --auth ${CLI_AUTH:-${SVC_PROD_AUTH:-auto}}"
fi
}
# pid_alive <pid>:进程存在且 cmdline 是我们的 serve(防 PID 复用误杀)。
@@ -60,7 +117,7 @@ pid_alive() {
local pid="$1"
[ -n "$pid" ] && [ -r "/proc/$pid/cmdline" ] || return 1
local cmd
cmd="$(tr '\0' ' ' < "/proc/$pid/cmdline")" || return 1
cmd="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)" || return 1
case "$cmd" in
*agent-system*serve*) return 0 ;;
*) return 1 ;;
@@ -109,7 +166,7 @@ do_build() {
do_start() {
if pid_alive "$(read_pid)"; then
c_warn "[$ENV_NAME] 已在运行 pid=$(read_pid)(如需换新版本请用 deploy)"
c_warn "[$ENV_NAME] 已在运行 pid=$(read_pid)(如需换参数/新版本请用 deploy/restart)"
return 0
fi
mkdir -p "$ENV_DATA" "$ENV_DIR"
@@ -117,21 +174,22 @@ do_start() {
local owner; owner="$(port_owner)"
if [ -n "$owner" ]; then
c_err "[$ENV_NAME] 端口被占用(非本脚本管理):$owner"
c_err "处理:停掉该进程,或用环境变量换端口(如 SVC_${ENV_NAME^^}_ADDR=127.0.0.1:8799)"
c_err "处理:停掉该进程,或换端口(--port 8799 / --addr host:port)"
exit 1
fi
[ -x "$BIN" ] || { c_err "[$ENV_NAME] 缺少二进制 $BIN——先构建(deploy 会自动构建)"; exit 1; }
c_info "[$ENV_NAME] 启动:$BIN serve --addr $ENV_ADDR --data $ENV_DATA --plugins $ROOT/plugins $ENV_ARGS"
c_info "[$ENV_NAME] 启动:$BIN serve --addr $ENV_ADDR --data $ENV_DATA --plugins $ENV_PLUGINS $ENV_ARGS $CLI_EXTRA"
# 直接 nohup 简单命令(不经 cd 复合后台作业):$! 即服务进程本身,pidfile 语义才成立。
nohup "$BIN" serve --addr "$ENV_ADDR" --data "$ENV_DATA" --plugins "$ROOT/plugins" $ENV_ARGS \
nohup "$BIN" serve --addr "$ENV_ADDR" --data "$ENV_DATA" --plugins "$ENV_PLUGINS" $ENV_ARGS $CLI_EXTRA \
>> "$ENV_LOG" 2>&1 &
echo $! > "$ENV_PID"
printf 'addr=%s\ndata=%s\n' "$ENV_ADDR" "$ENV_DATA" > "$ENV_DIR/svc.env"
# 健康等待:最多 20s。
local port="${ENV_ADDR##*:}"
# 健康等待:最多 20s;探测主机取绑定主机(0.0.0.0/:: → 127.0.0.1)。
local host="${ENV_ADDR%:*}" port="${ENV_ADDR##*:}"
if [ "$host" = "0.0.0.0" ] || [ "$host" = "::" ]; then host="127.0.0.1"; fi
for _ in $(seq 1 100); do
if curl -s -o /dev/null -m 2 "http://127.0.0.1:$port/" 2>/dev/null; then
c_ok "[$ENV_NAME] 已启动 pid=$(cat "$ENV_PID") → http://127.0.0.1:$port(日志:$ENV_LOG)"
if curl -s -o /dev/null -m 2 "http://$host:$port/" 2>/dev/null; then
c_ok "[$ENV_NAME] 已启动 pid=$(cat "$ENV_PID") → http://$host:$port(日志:$ENV_LOG)"
return 0
fi
pid_alive "$(read_pid)" || { c_err "[$ENV_NAME] 进程启动后即退出,查看日志:tail -40 $ENV_LOG"; exit 1; }
@@ -160,19 +218,28 @@ main() {
[ $# -ge 1 ] || { usage; exit 2; }
if [ "$1" = "status" ] && [ $# -eq 1 ]; then
c_info "服务总览(root: $ROOT)"
for e in dev prod; do env_config "$e"; ENV_NAME="$e"; env_status_line; done
for e in dev prod; do ENV_NAME="$e"; env_config "$e"; env_status_line; done
exit 0
fi
ENV_NAME="$1"; env_config "$ENV_NAME"
local action="${2:-deploy}"
ENV_NAME="$1"; shift
# 动作词可省略(省略 = deploy);flags 可直接跟随环境名。
local action="deploy"
case "${1:-}" in
deploy|stop|start|restart|status|logs) action="$1"; shift ;;
esac
# logs 的行数(兼容 svc.sh dev logs 100)。
if [ "$action" = "logs" ] && [ "${1:-}" != "" ] && [[ "$1" =~ ^[0-9]+$ ]]; then
CLI_LOGS_N="$1"; shift
fi
parse_args "$@"
env_config "$ENV_NAME"
case "$action" in
deploy) do_stop; do_build; do_start ;;
stop) do_stop ;;
start) do_start ;;
restart) do_stop; do_start ;;
status) env_status_line ;;
logs) [ -f "$ENV_LOG" ] && tail -n "${3:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;;
*) c_err "未知动作:$action(可用:deploy|stop|start|restart|status|logs)"; usage; exit 2 ;;
logs) [ -f "$ENV_LOG" ] && tail -n "${CLI_LOGS_N:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;;
esac
}