Align the svc.sh startup precheck with the binary auth semantics and keep the reused DSN
Why: G1-7 moved the DSN into AGENT_SYSTEM_DSN, but the precheck only read the arg string, so a non-loopback restart with auth auto was wrongly rejected (2026-10-10 dev incident); the reuse path no longer restored the dbref-bound DSN and would silently fall back to SQLite; and DSN_ENV leaked across same-process starts (update --all) into other services' env. Evidence: tests/test_svc_precheck.py 19 cases green (auth matrix vs config.go AuthEnabled, reuse migration/dbref restore, scope isolation); full governance suite 123 green; incident-scenario replay allowed + DSN carried. Recorded via make evidence. Impact: scripts/svc.sh precheck and reuse-start DSN handling only; no binary change, no persistence-format change. Refs: .task/fix-svc-dsn-reuse-dbref.md; config.go AuthEnabled (on || (not off && DSN non-empty), env DSN counts).
This commit is contained in:
+63
-17
@@ -1198,15 +1198,61 @@ addr_is_loopback() {
|
||||
esac
|
||||
}
|
||||
|
||||
# agent_args_authed ARGS:按二进制 AuthEnabled 口径判断启动参数是否开启认证
|
||||
# (--auth on;或未显式 off 且带 --dsn——auto+PG 即开)。--mock 只影响存储档不影响认证。
|
||||
# agent_args_authed ARGS [DSN_ENV]:按二进制 AuthEnabled 口径(config.go:
|
||||
# 显式 on;或非显式 off 且 StorageDSN 非空)判断认证是否开启。DSN 两个来源等价:
|
||||
# 命令行 --dsn(历史兼容)与 AGENT_SYSTEM_DSN 环境变量(G1-7 起的标准通道,
|
||||
# 第二参数传其值)——只看参数串会把「非回环 + auto + env DSN」误判为认证关
|
||||
#(2026-10-10 dev 实测误拒)。--mock 只影响存储档不影响认证。
|
||||
agent_args_authed() {
|
||||
case "$1" in
|
||||
*--auth[=\ ]on*) return 0 ;;
|
||||
*--auth[=\ ]off*) return 1 ;;
|
||||
*--dsn\ *) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
[ -n "${2:-}" ] && return 0
|
||||
case "$1" in *--dsn\ *) return 0 ;; esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# agent_listen_rejected EXEC_ARGS DEFAULT_ADDR INSECURE DSN_ENV:启动预检判定(0=拒绝)。
|
||||
# 与二进制 listen guard 同口径:回环或显式放行(insecure)不拦;认证开不拦;
|
||||
# 仅非回环 + 未放行 + 认证关才拦。
|
||||
agent_listen_rejected() {
|
||||
local addr; addr="$(printf '%s' "$1" | sed -n 's/.*--addr \([^ ]*\).*/\1/p')"
|
||||
addr="${addr:-$2}"
|
||||
addr_is_loopback "${addr%:*}" && return 1
|
||||
[ -n "$3" ] && return 1
|
||||
agent_args_authed "$1" "$4" && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# agent_reuse_exec EXEC_ARGS OUT_VAR:复用启动(无显式参数,复用 svc.env exec=)
|
||||
# 时的 exec 参数与 DSN 解析。处理后的 exec 参数经 nameref OUT_VAR 回写;DSN 经
|
||||
# 全局 DSN_ENV 传递(agent_do_start 内已 local 化)。必须直接调用而非放进命令
|
||||
# 替换——$() 子壳里的 DSN_ENV 赋值回不到调用方。两步:先做 G1-7 迁移(存量
|
||||
# --dsn 剥到环境变量);exec= 不含 DSN 且 dbref= 登记在案时从实例登记重建——
|
||||
# 否则 G1-7 后 exec= 永不含 DSN,纯 start/restart 复用会静默回落 SQLite,数据库
|
||||
# 绑定丢失。
|
||||
agent_reuse_exec() {
|
||||
local -n out_var="$2"
|
||||
# 内部变量不得叫 exec_args:与 nameref 目标同名会在函数作用域自我遮蔽,
|
||||
# 回写落到局部、调用方拿不到值。
|
||||
local cur="$1" bound_dsn
|
||||
case "$cur" in
|
||||
*--dsn\ *)
|
||||
DSN_ENV="$(printf '%s' "$cur" | sed -n 's/.*--dsn \([^ ]*\).*/\1/p')"
|
||||
cur="$(printf '%s' "$cur" | sed 's/--dsn [^ ]*//' | sed 's/[[:space:]]\{2,\}/ /g; s/^ //; s/ $//')"
|
||||
c_info "[$ENV_NAME] DSN 已从命令行迁移到环境变量(G1-7)"
|
||||
;;
|
||||
esac
|
||||
if [ -z "${DSN_ENV:-}" ] && [ -n "$(svc_get dbref)" ]; then
|
||||
if bound_dsn="$(dsn_from_dbref "$(svc_get dbref)")"; then
|
||||
DSN_ENV="$bound_dsn"
|
||||
c_info "[$ENV_NAME] 数据库绑定 $(svc_get dbref) 随复用启动保留(AGENT_SYSTEM_DSN)"
|
||||
else
|
||||
c_warn "[$ENV_NAME] dbref=$(svc_get dbref) 无法重建 DSN(实例缺失或登记不全),按无数据库绑定启动"
|
||||
fi
|
||||
fi
|
||||
out_var="$cur"
|
||||
}
|
||||
|
||||
agent_do_start() {
|
||||
@@ -1219,18 +1265,12 @@ agent_do_start() {
|
||||
fi
|
||||
SVC_VERSION="$("$ENV_BIN" version 2>/dev/null || true)"
|
||||
# 组装启动参数:显式配置 → 重建;否则复用上次持久化 exec=。
|
||||
local exec_args
|
||||
# DSN_ENV 必须 local 并清零:G1-7 起 DSN 经它传给启动守卫环境,全局残留会让
|
||||
# update --all 同进程连续启动时前一个服务的 DSN 泄漏进后一个的 AGENT_SYSTEM_DSN。
|
||||
local exec_args DSN_ENV=""
|
||||
if ! agent_has_explicit_cfg && [ -n "$(svc_get exec)" ]; then
|
||||
exec_args="$(svc_get exec)"
|
||||
# G1-7:存量 exec= 里的 --dsn 剥离到环境变量(迁移;命令行不再可见明文)。
|
||||
case "$exec_args" in
|
||||
*--dsn\ *)
|
||||
DSN_ENV="$(printf '%s' "$exec_args" | sed -n 's/.*--dsn \([^ ]*\).*/\1/p')"
|
||||
exec_args="$(printf '%s' "$exec_args" | sed 's/--dsn [^ ]*//' | sed 's/[[:space:]]\{2,\}/ /g; s/^ //; s/ $//')"
|
||||
c_info "[$ENV_NAME] DSN 已从命令行迁移到环境变量(G1-7)"
|
||||
;;
|
||||
esac
|
||||
c_info "[$ENV_NAME] 复用上次配置(svc.env exec=;显式参数可覆盖):$(mask_dsn "$exec_args")"
|
||||
agent_reuse_exec "$(svc_get exec)" exec_args
|
||||
c_info "[$ENV_NAME] 复用上次配置(svc.env exec=;显式参数可覆盖):$(mask_dsn "$exec_args")"
|
||||
else
|
||||
local args=""
|
||||
if [ "$CLI_MOCK" = 1 ] || [ "$(env_var "$ENV_NAME" MOCK)" = "1" ]; then
|
||||
@@ -1278,9 +1318,12 @@ agent_do_start() {
|
||||
[ -n "$hosts" ] && guard_env+=("AGENT_SYSTEM_ALLOWED_HOSTS=$hosts")
|
||||
# 预检(快速拦截):非回环地址且认证判定为关时提前拒绝,报错可操作——
|
||||
# 不等二进制 listen guard 以"进程即退"暴露;二进制守卫仍是权威。
|
||||
# 认证判定与二进制同口径(agent_listen_rejected → agent_args_authed):
|
||||
# env DSN(AGENT_SYSTEM_DSN)同样算「已配置 DSN」,不再因命令行不见 --dsn
|
||||
# 而把非回环+auto 误判为认证关(2026-10-10 dev 实测误拒)。
|
||||
local pf_addr; pf_addr="$(printf '%s' "$exec_args" | sed -n 's/.*--addr \([^ ]*\).*/\1/p')"
|
||||
pf_addr="${pf_addr:-$ENV_ADDR}"
|
||||
if ! addr_is_loopback "${pf_addr%:*}" && [ -z "$insecure" ] && ! agent_args_authed "$exec_args"; then
|
||||
if agent_listen_rejected "$exec_args" "$ENV_ADDR" "$insecure" "${DSN_ENV:-}"; then
|
||||
c_err "[$ENV_NAME] 拒绝启动:非回环地址 $pf_addr 且认证判定为关(auto 无 DSN 即关)。"
|
||||
c_err " 任选其一:--addr 127.0.0.1:${pf_addr##*:};数据库绑定 scripts/svc.sh $ENV_NAME db <pg实例>;"
|
||||
c_err " 显式 --auth on;或环境 SVC_$(printf '%s' "$ENV_NAME" | tr 'a-z-' 'A-Z_')_INSECURE_LISTEN=1 显式放行。"
|
||||
@@ -3072,4 +3115,7 @@ main() {
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
# 仅作为脚本直接执行时进入主流程;被 source(治理单测 fixtures)时只定义函数。
|
||||
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
||||
main "$@"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user