Merge pull request 'Keep the DB binding and fail closed on svc.sh start rebuilds' (#188) from feature/svc-start-keep-db-binding into main
ci / test (push) Successful in 1m50s
ci / test (push) Successful in 1m50s
This commit was merged in pull request #188.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# feature/svc-start-keep-db-binding 任务卡
|
||||
|
||||
任务主记录:<PR 链接,task-finish 时填写>;状态:进行中;owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 4d59610fd17fb1a05cf55ff266f393cc007995e2
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-svc-start-keep-db-binding(worktree);分支:feature/svc-start-keep-db-binding
|
||||
- 授权与交付终点:用户明确要求修复 svc.sh 此类缺陷(2026-10-05 dev 服务事故后续)。终点 = PR 合入 main + 主检出回归验证。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
背景(2026-10-05 01:23 dev 事故):后端 8eda35f 引入 listen guard 后,`svc.sh dev restart --addr 0.0.0.0:8788` 走"显式参数重建"分支,dev 分支刻意不拼 `--dsn/--auth`,持久化的 pg1 绑定被静默丢弃 → 二进制 `--auth auto` 无 DSN 判认证关 → 守卫拒启("进程即退")。且 `record_state` 在健康探测前写盘,坏 `exec=` 持久化,裸 start 复用导致二次失败。
|
||||
|
||||
修复项:
|
||||
1. 重建分支 dbref 兜底:`dsn_from_dbref` 由 dbref 从 pg 实例登记重建 `--dsn`(优先级 CLI_DSN > SVC_*_DSN > dbref;mock 除外);dev 同样尊重显式 `--dsn/--auth`(此前传入即被静默丢弃)。
|
||||
2. 失败不落坏配置:agent/static 启动的 `record_state` 后移至健康探测后——探测通过才写 svc.env;进程即退时保留上次配置并明示;超时(进程存活)按实际运行参数登记。pg 路径原本就在 pg_ctl 成功后落盘,不动。
|
||||
3. 预检拦截:非回环 + 认证判定关(按二进制 AuthEnabled 口径:--auth on;未显式 off 且带 --dsn)时 svc.sh 层提前拒绝并给可操作报错(回环/绑定/--auth on/INSECURE_LISTEN 四条出路);二进制 listen guard 仍是权威。
|
||||
4. 日志泄漏对齐:启动/复用/旧产物过滤三处日志对 DSN 掩码(此前裸打密码,违背脚本自述"DSN 默认掩码"原则)。
|
||||
|
||||
同类排查结论(不改):`set --addr` 对 agent 拒绝并指向 restart;`db --clear`/`config --clear` 均同步改写 exec=,无漂移;非 dev 环境重建分支原本就带 --dsn/--auth,行为保持。
|
||||
|
||||
非目标:不改后端 listen guard 语义;不改 deploy 物化/构建链路。
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 4d59610fd17fb1a05cf55ff266f393cc007995e2 | 4d59610fd17fb1a05cf55ff266f393cc007995e2 | 2026-10-05T01:51:21Z |
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:
|
||||
- 验证:
|
||||
- 影响与剩余项:
|
||||
| 同步前验收 | 4d59610fd17fb1a05cf55ff266f393cc007995e2 | 42151071263aa133520fe346b6b1929e1ecda008 | 2026-10-05T02:16:28Z |
|
||||
+80
-13
@@ -401,9 +401,11 @@ dotenv 格式:KEY=VALUE,支持 # 注释、成对引号、export 前缀;只
|
||||
播种幂等:设置持久化在 agent 数据目录,播过一次后续启动无需再传。
|
||||
|
||||
持久化语义:
|
||||
- deploy/start 成功后把当前态写入 svc.env(600)
|
||||
- deploy/start 成功(健康探测通过)后把当前态写入 svc.env(600);启动失败不覆盖
|
||||
上次配置——裸 start 仍按上次可用参数重试
|
||||
- 裸 start/restart 复用 exec=(上次完整参数);任何显式参数(或 --config 文件、
|
||||
SVC_* 变量)出现即整体重建参数
|
||||
SVC_* 变量)出现即整体重建参数——重建时 dbref 绑定的 --dsn 自动从实例登记
|
||||
重建保留(mock 档或显式 --dsn 除外)
|
||||
- tags 随每次登记合并去重,跨重启保留
|
||||
|
||||
服务级配置文件(与上述 svc.sh dotenv 层不同——这是 agent 二进制自己的 JSON 配置):
|
||||
@@ -1172,6 +1174,41 @@ PY
|
||||
fi
|
||||
}
|
||||
|
||||
# dsn_from_dbref DBREF:由 dbref(pg实例/库名)从该实例登记重建连接串。
|
||||
# 实例未登记 / 非 postgres / 缺 addr·user·password 时返回非零且不输出。
|
||||
dsn_from_dbref() {
|
||||
local dbref="$1" inst tdb taddr tuser tpass
|
||||
inst="${dbref%%/*}"; tdb="${dbref#*/}"
|
||||
{ [ -n "$inst" ] && [ -n "$tdb" ] && [ "$inst" != "$dbref" ]; } || return 1
|
||||
[ -f "$RUNTIME/$inst/svc.env" ] || return 1
|
||||
[ "$(svc_kind "$inst")" = postgres ] || return 1
|
||||
taddr="$(env_kv_of "$inst" addr)"
|
||||
tuser="$(env_kv_of "$inst" user)"
|
||||
tpass="$(env_kv_of "$inst" password)"
|
||||
{ [ -n "$taddr" ] && [ -n "$tuser" ] && [ -n "$tpass" ]; } || return 1
|
||||
printf 'postgresql://%s:%s@%s:%s/%s' "$tuser" "$tpass" "${taddr%:*}" "${taddr##*:}" "$tdb"
|
||||
}
|
||||
|
||||
# addr_is_loopback HOST:回环写法白名单判定(127.x / ::1 / localhost)。
|
||||
# 只服务启动预检的快速拦截:其余形态一律按非回环处理,权威判定在二进制 listen guard。
|
||||
addr_is_loopback() {
|
||||
case "$1" in
|
||||
localhost|127.*|::1|[::1]) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# agent_args_authed ARGS:按二进制 AuthEnabled 口径判断启动参数是否开启认证
|
||||
# (--auth on;或未显式 off 且带 --dsn——auto+PG 即开)。--mock 只影响存储档不影响认证。
|
||||
agent_args_authed() {
|
||||
case "$1" in
|
||||
*--auth[=\ ]on*) return 0 ;;
|
||||
*--auth[=\ ]off*) return 1 ;;
|
||||
*--dsn\ *) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
agent_do_start() {
|
||||
mkdir -p "$ENV_DATA" "$ENV_DIR"
|
||||
check_port_free || return 1
|
||||
@@ -1185,21 +1222,34 @@ agent_do_start() {
|
||||
local exec_args
|
||||
if ! agent_has_explicit_cfg && [ -n "$(svc_get exec)" ]; then
|
||||
exec_args="$(svc_get exec)"
|
||||
c_info "[$ENV_NAME] 复用上次配置(svc.env 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
|
||||
args="--mock"
|
||||
elif [ "$ENV_NAME" != dev ]; then
|
||||
else
|
||||
local dsn="${CLI_DSN:-$(env_var "$ENV_NAME" DSN)}"
|
||||
[ -n "$dsn" ] && args="$args --dsn $dsn"
|
||||
local auth="${CLI_AUTH:-$(env_var "$ENV_NAME" AUTH)}"
|
||||
[ -z "$auth" ] && auth=auto
|
||||
args="$args --auth $auth"
|
||||
# dbref 兜底:显式参数重建启动参数时,持久化的数据库绑定不再静默丢失
|
||||
#(2026-10-05 dev 事故:restart --addr 走重建分支丢 --dsn → auto 判认证关 → 守卫拒启)。
|
||||
if [ -z "$dsn" ] && [ -n "$(svc_get dbref)" ]; then
|
||||
local bound_dsn
|
||||
if bound_dsn="$(dsn_from_dbref "$(svc_get dbref)")"; then
|
||||
dsn="$bound_dsn"
|
||||
c_info "[$ENV_NAME] 数据库绑定 $(svc_get dbref) 随参数重建保留"
|
||||
else
|
||||
c_warn "[$ENV_NAME] dbref=$(svc_get dbref) 无法重建 DSN(实例缺失或登记不全),按无数据库绑定启动"
|
||||
fi
|
||||
fi
|
||||
[ -n "$dsn" ] && args="$args --dsn $dsn"
|
||||
if [ -n "$dsn" ] || [ -n "$auth" ] || [ "$ENV_NAME" != dev ]; then
|
||||
[ -z "$auth" ] && auth=auto
|
||||
args="$args --auth $auth"
|
||||
fi
|
||||
fi
|
||||
local adjusted; adjusted="$(adjust_args_for_bin "$ENV_BIN" "$args")"
|
||||
if [ "$adjusted" != "$args" ]; then
|
||||
c_warn "[$ENV_NAME] 该产物不支持 --dsn/--auth(旧版本二进制),已自动省略:${args:-<无>} → ${adjusted:-<无>}"
|
||||
c_warn "[$ENV_NAME] 该产物不支持 --dsn/--auth(旧版本二进制),已自动省略:$(mask_dsn "${args:-<无>}") → $(mask_dsn "${adjusted:-<无>}")"
|
||||
fi
|
||||
args="$adjusted"
|
||||
exec_args="--addr $ENV_ADDR --data $ENV_DATA --plugins ${CLI_PLUGINS:-$ENV_DIR/plugins} $args $CLI_EXTRA"
|
||||
@@ -1212,7 +1262,19 @@ agent_do_start() {
|
||||
hosts="$(env_var "$ENV_NAME" ALLOWED_HOSTS)"
|
||||
[ -n "$insecure" ] && guard_env+=("AGENT_SYSTEM_INSECURE_LISTEN=$insecure")
|
||||
[ -n "$hosts" ] && guard_env+=("AGENT_SYSTEM_ALLOWED_HOSTS=$hosts")
|
||||
c_info "[$ENV_NAME] 启动:$ENV_BIN serve $exec_args"
|
||||
# 预检(快速拦截):非回环地址且认证判定为关时提前拒绝,报错可操作——
|
||||
# 不等二进制 listen guard 以"进程即退"暴露;二进制守卫仍是权威。
|
||||
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
|
||||
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 显式放行。"
|
||||
c_err " 登记未变更(svc.env 保留上次配置);二进制侧 listen guard 同样会拒绝。"
|
||||
hist_event start fail "" "预检拒绝:非回环且未开认证"
|
||||
return 1
|
||||
fi
|
||||
c_info "[$ENV_NAME] 启动:$ENV_BIN serve $(mask_dsn "$exec_args")"
|
||||
if [ -n "$insecure" ] || [ -n "$hosts" ]; then
|
||||
c_info "[$ENV_NAME] 监听守卫:insecureListen=${insecure:-0} allowedHosts=${hosts:-无}"
|
||||
fi
|
||||
@@ -1224,7 +1286,6 @@ agent_do_start() {
|
||||
nohup "$ENV_BIN" serve $exec_args >> "$ENV_DIR/svc.log" 2>&1 &
|
||||
fi
|
||||
echo $! > "$ENV_PID"
|
||||
record_state
|
||||
# 健康探测:取 exec 里的 --addr(复用持久化配置时可能与当前派生值不同)。
|
||||
local probe="${ENV_ADDR}"
|
||||
case "$exec_args" in *--addr\ *)
|
||||
@@ -1234,18 +1295,23 @@ agent_do_start() {
|
||||
[ "$phost" = "0.0.0.0" ] || [ "$phost" = "::" ] && phost="127.0.0.1"
|
||||
for _ in $(seq 1 100); do
|
||||
if curl -s -o /dev/null -m 2 "http://$phost:${probe##*:}/" 2>/dev/null; then
|
||||
# 健康通过才写 svc.env——启动失败不再用新参数覆盖上次的可用配置
|
||||
#(2026-10-05 dev 事故:失败后坏 exec= 持久化,裸 start 复用导致二次失败)。
|
||||
record_state
|
||||
c_ok "[$ENV_NAME] 已启动 pid=$(read_pid) → http://$phost:${probe##*:}(来源 ${SVC_SOURCE:-?},日志:$ENV_DIR/svc.log)"
|
||||
hist_event start ok "$(read_pid)" "http://$phost:${probe##*:}"
|
||||
seed_settings "$phost" "${probe##*:}"
|
||||
return 0
|
||||
fi
|
||||
pid_alive "$(read_pid)" || {
|
||||
c_err "[$ENV_NAME] 进程启动后即退出,查看日志:tail -40 $ENV_DIR/svc.log"
|
||||
c_err "[$ENV_NAME] 进程启动后即退出(登记未变更,svc.env 保留上次 exec=),查看日志:tail -40 $ENV_DIR/svc.log"
|
||||
hist_event start fail "$(read_pid)" "进程即退(日志 svc.log)"
|
||||
return 1
|
||||
}
|
||||
sleep 0.2
|
||||
done
|
||||
# 进程存活但未响应:按实际运行参数登记(进程确以新参数在跑,登记与现实一致)。
|
||||
record_state
|
||||
c_err "[$ENV_NAME] 20s 健康检查未通过(进程存活但未响应),查看日志:tail -40 $ENV_DIR/svc.log"
|
||||
hist_event start fail "$(read_pid)" "健康检查超时"
|
||||
return 1
|
||||
@@ -1356,22 +1422,23 @@ static_do_start() {
|
||||
c_info "[$ENV_NAME] 启动静态服务:python3 http.server $port --bind $host --directory $htdocs"
|
||||
nohup python3 -m http.server "$port" --bind "$host" --directory "$htdocs" >> "$ENV_DIR/svc.log" 2>&1 &
|
||||
echo $! > "$ENV_PID"
|
||||
record_state "htdocs=$htdocs"
|
||||
local probe_host="$host"
|
||||
[ "$probe_host" = "0.0.0.0" ] || [ "$probe_host" = "::" ] && probe_host="127.0.0.1"
|
||||
for _ in $(seq 1 50); do
|
||||
if curl -s -o /dev/null -m 2 "http://$probe_host:$port/" 2>/dev/null; then
|
||||
record_state "htdocs=$htdocs" # 健康通过才写 svc.env(与 agent 路径同规)
|
||||
c_ok "[$ENV_NAME] 已启动 pid=$(read_pid) → http://$probe_host:$port/(目录 $htdocs)"
|
||||
hist_event start ok "$(read_pid)" "http://$probe_host:$port/"
|
||||
return 0
|
||||
fi
|
||||
pid_alive "$(read_pid)" || {
|
||||
c_err "[$ENV_NAME] 进程启动后即退出,查看日志:tail -40 $ENV_DIR/svc.log"
|
||||
c_err "[$ENV_NAME] 进程启动后即退出(登记未变更,svc.env 保留上次配置),查看日志:tail -40 $ENV_DIR/svc.log"
|
||||
hist_event start fail "$(read_pid)" "进程即退"
|
||||
return 1
|
||||
}
|
||||
sleep 0.2
|
||||
done
|
||||
record_state "htdocs=$htdocs"
|
||||
c_err "[$ENV_NAME] 10s 健康检查未通过"
|
||||
hist_event start fail "$(read_pid)" "健康检查超时"
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user