diff --git a/.task/feature-svc-db-bind.md b/.task/feature-svc-db-bind.md index c76af9e3..5d245896 100644 --- a/.task/feature-svc-db-bind.md +++ b/.task/feature-svc-db-bind.md @@ -6,25 +6,33 @@ - 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 3836ea88e3954878f257b7708ae5ccef947ccd79 - 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-svc-db-bind(worktree);分支:feature/svc-db-bind -- 授权与交付终点:实现"服务↔数据库绑定"(svc.sh 服务级 db 子命令 + info/list 显示绑定)并修复 - adjust_args_for_bin 单横线误判;终点 = PR 合入 main(附 precheck / task-sync / 测试证据) +- 授权与交付终点:实现"服务↔数据库绑定"(svc.sh 服务级 db 子命令 + info/list 显示绑定)与 + "服务配置文件更新/即时生效"(config 子命令),并修复 adjust_args_for_bin 单横线误判; + 终点 = PR 合入 main(用户指示完成后一并合入;附 precheck / task-sync / 测试证据) ## 目标与非目标 目标: -- agent 服务可绑定/切换/解除数据库:`svc.sh db [pg名 [--db X] | --clear] [--no-restart]`; - 缺省查看当前绑定与可用实例。绑定 = DSN 写入持久化 exec= + dbref=/<库> 登记,实例运行中 - 缺库自动建,服务运行中自动重启生效;--clear 回内嵌 SQLite 档。 -- 显示层全链路:list 表新增 DB 列;status 详情新增"数据库"行;info 数据库节新增"绑定服务" - 反向映射;无 dbref 的旧服务按 DSN host:port 反查匹配实例(外部 DSN 显示 外部@host:port)。 -- 修复既有缺陷:adjust_args_for_bin 以 grep '--dsn' 检测旗标支持,Go flag 帮助打印单横线 +- **数据库绑定**:agent 服务可绑定/切换/解除数据库:`svc.sh db [pg名 [--db X] | --clear] + [--no-restart]`;缺省查看当前绑定与可用实例。绑定 = DSN 写入持久化 exec= + dbref=/<库> + 登记,实例运行中缺库自动建,服务运行中自动重启生效;--clear 回内嵌 SQLite 档。 +- **配置文件更新**:`svc.sh config [file.json |--clear] [--no-restart]`;缺省查看当前 + 配置文件(掩码内容 + `agent-system config explain -config` 视角的生效值)。切换前经二进制 + schema 强校验(未知键/非法值拒绝,不把服务重启进坏配置),通过后把 --config <绝对路径> + 写入 exec= + conffile= 登记,运行中自动重启即时生效(agent 无 SIGHUP 热重载,重启秒级); + --clear 移除回命令行参数形态。绑定后改文件内容 + restart(或重跑 config 同文件)即生效。 +- **显示层全链路**:list 表新增 DB 列(含配置文件 dsn 反查);status 详情新增"数据库""配置 + 文件"行;info 数据库节新增"绑定服务"反向映射、配置节附配置文件内容快照(敏感值掩码, + --reveal 明文);无 dbref 的旧服务按 exec= --dsn 或配置文件 dsn 的 host:port 反查匹配实例 + (外部 DSN 显示 外部@host:port)。 +- **修复既有缺陷**:adjust_args_for_bin 以 grep '--dsn' 检测旗标支持,Go flag 帮助打印单横线 `-dsn`,导致现役二进制的 --dsn/--auth 在 deploy 重建参数时被误删——改为"去横线旗标名 + 1-2 横线"匹配。 -- record_state 跨重启保留 dbref;svc_del_key 辅助;db create 尾部提示绑定命令; - usage/usage_short/help(lifecycle/db)/docs/svc.md 同步。 +- record_state 跨重启保留 dbref/conffile;svc_del_key 辅助;db create 尾部提示绑定命令; + usage/usage_short/help(lifecycle/config/db)/docs/svc.md(§2/§4.5/§4.6/§6/§10.1)同步。 非目标:不改 agent 二进制与部署/生命周期语义;不做 static/postgres 的绑定;不做运行中热切换 -(切换经重启生效)。 +(切换经重启生效);不做配置文件的编辑器(内容仍由用户维护,svc.sh 负责校验/切换/生效)。 ## 同步记录 @@ -34,19 +42,26 @@ ## 结果与验证(完成后填写) -- 结果:已实现(上列目标全部落地;` db` 服务级动作与顶层 `db` 命令并存无冲突)。 +- 结果:已实现(数据库绑定与配置文件更新两块目标全部落地;` db`/` config` + 服务级动作与顶层 `db` 命令、`--config` 旗标并存无冲突)。 - 验证(worktree runtime 实测,真实 agent 二进制 + PostgreSQL 18,2026-10-04): - `bash -n` 通过; - - 绑定:`t-app db bpg` → exec= 写入 --dsn、dbref=bpg/agent、自动重启生效;**record_state - 重写 svc.env 后 dbref 保留**;PG 的 agent 库出现 **21 张表**(服务真实落库,非仅配置); - - 切换:`t-app db bpg --db t2` → 自动建库 t2、重启、t2 库 21 张表; - - 解绑:`db --clear` → dbref/--dsn 移除、重启回内嵌档; - - `--no-restart` 只改登记;错误路径(未登记实例/多余参数/postgres 服务)均 exit 2 且提示正确; - - 显示层:list DB 列(t-app→bpg/agent)、`t-app db` 查看(掩码 DSN+实例列表)、info 数据库节 - "绑定服务 t-app(agent)";手删 dbref 模拟旧部署 → list 按 DSN host:port 反查仍显示 bpg/agent; + - 数据库绑定:`t-app db bpg` → exec= 写入 --dsn、dbref=bpg/agent、自动重启生效; + **record_state 重写 svc.env 后 dbref 保留**;PG 的 agent 库出现 **21 张表**(服务真实 + 落库,非仅配置);`--db t2` 切换自动建库(t2 库 21 张表);`--clear` 干净回内嵌档; + `--no-restart` 只改登记;错误路径(未登记实例/多余参数/postgres 服务)均 exit 2; + - 配置文件更新:坏文件(未知键 bogus)被 schema 校验拒绝且不动 exec、不重启;合法文件 + 绑定后 cmdline 含 `--config `、conffile= 跨 record_state 保留;**配置文件内的 dsn + 真实生效**(错误密码版本在日志中报 PG 认证失败=配置确已应用;正确密码版连上 PG、 + agent 库 21 张表);内容修改后重跑 `config <同一文件>` 再校验并重启(pid 更新); + `--clear` 移除 --config/conffile 并重启;错误路径(文件不存在/双文件)exit 2; + - db + config 共存:exec= 同时带 --dsn 与 --config,重启健康; + - 显示层:list DB 列(含"仅配置文件带 dsn"场景的反查 bpg/agent)、`config` 查看视图 + (JSON 掩码 `"dsn": "***"` + explain 生效值 dsn 掩码)、status"数据库/配置文件"行、 + info 配置节文件内容快照、info 数据库节"绑定服务";手删 dbref 模拟旧部署 → 反查仍显示; - adjust 修复:正例(帮助含 `-dsn` 判支持)与负例(无 dsn 判不支持)均正确; - - 回归:db dsn(掩码)/ db ls / info / history(新增 `set ok db bind → bpg/agent` 事件)/ - --help 正常;测试现场(t-app/bpg)已 rm --purge 清除; + - 回归:db dsn(掩码)/ db ls / info / history(新增 `set ok db bind →` / `config →` / + `config unbind` 事件)/ --help 正常;测试现场(t-app/bpg、临时 JSON)已清除。 - 正式验收:`make test` exit 0(test-boundaries/pins/libs/core/backend/frontend/ui/agentcmd 全过)。 - 影响与剩余项:list 表加宽一列;老脚本无兼容性问题(dbref 缺省视为未绑定); 发现并修复的 adjust_args_for_bin 缺陷影响既有 `deploy --dsn/--auth` 路径(此前被静默丢弃)。 diff --git a/docs/svc.md b/docs/svc.md index c6f4394d..aabfe710 100644 --- a/docs/svc.md +++ b/docs/svc.md @@ -65,6 +65,7 @@ scripts/svc.sh pg1 start # pg_ctl 启动 + 自动建库 scripts/svc.sh prod deploy --from dev@ \ --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" # DSN 直接组合进部署 scripts/svc.sh prod db pg1 --db prod # 或事后绑定/切换(缺库自动建,重启生效) +scripts/svc.sh prod config .svc/prod.json # 指定/更换配置文件(校验+重启即时生效) # 日常操作 scripts/svc.sh list # 全服务总览(含 DB 绑定列) @@ -240,6 +241,26 @@ scripts/svc.sh db --clear # 解除绑定(回内嵌 SQL 登记实例(显示 `/<库>`;匹配不上显示 `外部@host:port`); - `--clear` 后服务回到内嵌 SQLite 档(数据在原 `--data` 目录,切回即见旧数据)。 +### 4.6 config(agent 服务的配置文件更新/生效) + +```bash +scripts/svc.sh config # 查看当前配置文件(掩码内容 + agent 视角生效值) +scripts/svc.sh config # 更换使用的配置文件(校验通过才切换) +scripts/svc.sh config --no-restart # 只改登记,下次 start 生效 +scripts/svc.sh config --clear # 移除(回命令行参数形态) +``` + +配置文件是 agent 二进制的 `--config`(JSON,schema 严格校验;可用 +`agent-system config explain -config ` 预览每个键的来源与生效值)。 + +- **切换前强校验**:文件必须存在且通过二进制的 schema 校验(未知键/非法值直接拒绝, + 不会把服务重启进坏配置);旧产物不识别 `--config` 时警告; +- **即时生效**:运行中的服务自动重启应用新配置(agent 无 SIGHUP 热重载,重启秒级); + 绑定后日常改配置 = 编辑文件 + `restart`(或重跑 `config <同一文件>` 再校验一遍); +- 持久化:`exec=` 的 `--config <绝对路径>` + `conffile=` 键(跨重启保留,600); +- 展示:`status`/`info ` 的"配置文件"行、`info` 配置节的文件内容快照 + (敏感值掩码,`info --reveal` 显示明文);文件丢失时明确告警并提示恢复或 `--clear`。 + ## 5. 命令参考:部署来源 `deploy --from` 支持三种来源(**缺省 = `dev` 远端最新**),agent 与 static 共用: @@ -578,6 +599,7 @@ scripts/svc.sh prod deploy --env .env.prod # 等价 | `pidcheck` | 进程判定标记(见 §6.1) | | `exec` | agent 上次完整启动参数(含 `--dsn`,密码在其中) | | `dbref` | agent 数据库绑定 `/<库名>`(` db` 命令写入,跨重启保留;见 §4.5) | +| `conffile` | agent 配置文件绝对路径(` config` 命令写入,跨重启保留;见 §4.6) | | `user` / `password` / `dbname` / `authmode` / `pgdata` | postgres 专属 | | `htdocs` | static 专属 | diff --git a/scripts/svc.sh b/scripts/svc.sh index ce78c600..574bbf99 100755 --- a/scripts/svc.sh +++ b/scripts/svc.sh @@ -6,6 +6,7 @@ # ════════════════════ 命令一览 ════════════════════ # scripts/svc.sh [action] [参数...] 服务级:deploy(默认)|start|stop|restart|status|logs [N] # |db [pg名|--clear](agent 数据库绑定/切换) +# |config [file|--clear](agent 配置文件更新/生效) # scripts/svc.sh list [--tag T] 服务总览(status 等价) # scripts/svc.sh info [name] [--tag T] [--reveal] 综合报告(只读):服务/数据库/配置快照/最近事件 # scripts/svc.sh set [--port N --apply] [--tag T] 变更登记(换端口/标签) @@ -42,6 +43,8 @@ # --plugins/--dsn/--auth/--mock/--data agent 运行参数 # db [--db X] [--no-restart] agent 绑定/切换数据库(实例运行中缺库自动建; # --clear 解绑回内嵌 SQLite;无参查看当前绑定与可用实例) +# config [--no-restart] agent 更换使用的配置文件(schema 校验通过才切换, +# 运行中自动重启即时生效;--clear 移除;无参查看当前配置与生效值) # --model-base/--model-key/--model-name 与 --light-*/--tavily-keys 启动后播种设置 # -- <剩余参数> 原样透传给 agent-system serve(escape hatch) # db create:--user U(默认 agent)--password P(缺省自动生成)--db NAME --auth-mode scram-sha-256|md5 @@ -75,6 +78,8 @@ # scripts/svc.sh dev db pg1 --db dev # 把 dev 服务绑定到 pg1/dev 库(自动重启) # scripts/svc.sh dev db pg2 --no-restart # 切换但不重启(下次 start 生效) # scripts/svc.sh dev db --clear # 解绑回内嵌 SQLite 档 +# scripts/svc.sh dev config .svc/dev.json # 更换配置文件(校验+重启即时生效) +# scripts/svc.sh dev config # 查看当前配置文件与生效值 # scripts/svc.sh db copy pg1 pg2 --db app # 把 pg1 的 app 库搬到 pg2 # scripts/svc.sh db restore pg2 --file --db app --force # scripts/svc.sh list --tag core && scripts/svc.sh history @@ -130,6 +135,7 @@ svc.sh —— 本机服务管理:后端(agent) / 前端(static) / 数据库(po scripts/svc.sh start|stop|restart|logs [N] 生命周期与日志 scripts/svc.sh status 服务详情(版本/来源/DSN 等) scripts/svc.sh db [pg名|--clear] agent 数据库绑定/切换(缺省查看) + scripts/svc.sh config [file|--clear] agent 配置文件更新/即时生效(缺省查看) scripts/svc.sh db create pg1 --port 8890 建数据库 scripts/svc.sh db dsn|backup|backups|restore|clone|copy|ls|set pg1 ... 数据库运维 scripts/svc.sh set pg1 --port 8895 --apply 换端口(--tag 改标签) @@ -264,6 +270,10 @@ EOF 绑定与可用实例;给 pg名=绑定/切换(DSN 写入 exec=,dbref= 登记, 实例运行中缺库自动建,服务运行中自动重启生效);--clear 解绑回内嵌 SQLite 档;--no-restart 只改登记 + scripts/svc.sh config [file|--clear] [--no-restart] agent 配置文件:缺省查看当前 + 文件与生效值;给 file=更换(agent 二进制 --config,JSON schema 校验 + 通过才切换,运行中自动重启即时生效);--clear 移除回命令行参数形态。 + 绑定后改文件内容 + restart 即生效(agent 无热重载,重启秒级) scripts/svc.sh logs [N] 跟随日志(默认 50 行起;如 svc.sh pg1 logs 200) scripts/svc.sh status 单服务详情(kind/状态/地址/来源/版本/标签/DSN/目录) scripts/svc.sh list [--tag T] 全服务表格:NAME KIND STATE PID ADDR SOURCE TAGS @@ -394,6 +404,11 @@ dotenv 格式:KEY=VALUE,支持 # 注释、成对引号、export 前缀;只 - 裸 start/restart 复用 exec=(上次完整参数);任何显式参数(或 --config 文件、 SVC_* 变量)出现即整体重建参数 - tags 随每次登记合并去重,跨重启保留 + +服务级配置文件(与上述 svc.sh dotenv 层不同——这是 agent 二进制自己的 JSON 配置): + scripts/svc.sh config [file|--clear] 更换/移除/查看(见 help lifecycle); + 切换前 schema 校验(agent-system config explain -config ),运行中自动重启 + 即时生效;登记于 svc.env conffile=,info/status 可见。 EOF return 0 ;; examples) @@ -644,15 +659,17 @@ svc_merge_tags() { } # record_state EXTRA_KEY=VALUE...:把当前态+持久化配置写入 svc.env(600)。 -# 基本键:kind/env/pid/addr/tags/source/version/started/pidcheck/exec + dbref(跨重启 -# 保留的数据库绑定);kind 特有键(user/password/dbname/authmode/pgdata/htdocs...)经参数追加。 +# 基本键:kind/env/pid/addr/tags/source/version/started/pidcheck/exec + dbref/ +# conffile(跨重启保留的数据库绑定与配置文件);kind 特有键(user/password/dbname/ +# authmode/pgdata/htdocs...)经参数追加。 record_state() { # 先读完旧文件再开重定向(块内 {} > svc.env 会先截断文件,读旧值必须在其之前)。 - local pid prev_src prev_dbref merged_tags + local pid prev_src prev_dbref prev_conffile merged_tags pid="$(read_pid)" prev_src="" [ -f "$ENV_DIR/svc.env" ] && prev_src="$(svc_get source)" prev_dbref="$(svc_get dbref)" + prev_conffile="$(svc_get conffile)" SVC_SOURCE="${SRC_LABEL:-${prev_src:-}}" merged_tags="$(svc_merge_tags)" mkdir -p "$ENV_DIR" @@ -668,6 +685,7 @@ record_state() { printf 'pidcheck=%s\n' "${SVC_PIDCHECK:-}" printf 'exec=%s\n' "${SVC_EXEC:-}" [ -n "$prev_dbref" ] && printf 'dbref=%s\n' "$prev_dbref" + [ -n "$prev_conffile" ] && printf 'conffile=%s\n' "$prev_conffile" [ $# -gt 0 ] && printf '%s\n' "$@" } > "$ENV_DIR/svc.env" chmod 600 "$ENV_DIR/svc.env" @@ -1895,6 +1913,127 @@ db_ls() { return 0 } +# mask_json TEXT:JSON 文本里键名含 key/pass/secret/token/dsn(不区分大小写)的 +# 字符串值掩为 ***(配置文件内容快照用)。输出带尾换行。 +mask_json() { + printf '%s\n' "$1" | sed -E 's/("[^"]*(key|pass|secret|token|dsn)[^"]*"[[:space:]]*:[[:space:]]*")[^"]*"/\1***"/Ig' +} + +# ---- agent 服务的配置文件( config ...)---- + +# svc_config [FILE|--clear] [--no-restart]:查看 / 更新 / 移除 agent 服务使用的 +# 配置文件(agent 二进制的 --config,JSON,schema 严格校验)。更新 = 校验通过后 +# 把 --config <绝对路径> 写入持久化 exec= 并登记 conffile=,运行中自动重启生效 +# (agent 无 SIGHUP 热重载,重启即"及时生效",秒级);--clear 移除回命令行参数形态。 +svc_config() { + [ "$SVC_KIND" = agent ] || { c_err "[$ENV_NAME] kind=$SVC_KIND 无配置文件绑定(仅 agent 服务)"; return 2; } + [ -f "$ENV_DIR/svc.env" ] || { c_err "[$ENV_NAME] 无登记(先 deploy)"; return 2; } + local target="" clear=0 restart=1 + while [ $# -gt 0 ]; do + case "$1" in + --clear) clear=1; shift ;; + --no-restart) restart=0; shift ;; + -h|--help) + cat >&2 < [--no-restart] 更新使用的配置文件 + (schema 校验通过才切换;运行中自动重启生效) + scripts/svc.sh $ENV_NAME config --clear [--no-restart] 移除(回命令行参数形态) +提示:配置文件绑定后,改文件内容 + restart 即生效;config <同一文件> 也会校验并重启。 +EOF + return 0 ;; + -*) c_err "[$ENV_NAME] config 未知参数:$1(可用:<文件> | --clear,--no-restart)"; return 2 ;; + *) [ -z "$target" ] || { c_err "[$ENV_NAME] config 只接受一个文件路径"; return 2; } + target="$1"; shift ;; + esac + done + + # 当前配置文件(conffile= 优先;旧服务回退 exec= 的 --config)。 + local cur="" + cur="$(svc_get conffile)" + [ -z "$cur" ] && cur="$(printf '%s' "$(svc_get exec)" | sed -n 's/.*--config \([^ ]*\).*/\1/p')" + + # 无参:查看(文件状态 + 掩码内容 + 二进制视角的生效值)。 + if [ -z "$target" ] && [ "$clear" = 0 ]; then + if [ -n "$cur" ]; then + printf ' [%s] 配置文件:%s\n' "$ENV_NAME" "$cur" + if [ -f "$cur" ]; then + printf ' 权限 %s;内容(敏感值掩码):\n' "$(info_file_mode "$cur")" + mask_json "$(cat "$cur")" | sed 's/^/ /' + if [ -x "$ENV_BIN" ] && "$ENV_BIN" config explain -config "$cur" >/dev/null 2>&1; then + printf ' 生效值(agent 视角,敏感值掩码):\n' + "$ENV_BIN" config explain -config "$cur" 2>/dev/null \ + | sed -E 's#(postgres(ql)?://[^:/@[:space:]]+:)[^@[:space:]]+@#\1***@#g; s/^/ /' + else + printf ' (当前产物无法校验该文件:config explain 不可用或文件不合法)\n' + fi + else + printf ' 文件不存在!重启将失败——请恢复文件或 config --clear\n' + fi + else + printf ' [%s] 配置文件:无(命令行参数形态;绑定:config )\n' "$ENV_NAME" + fi + return 0 + fi + + # 移除。 + if [ "$clear" = 1 ]; then + [ -n "$target" ] && { c_err "--clear 不接受文件参数"; return 2; } + local exec_old new_exec + exec_old="$(svc_get exec)" + if printf '%s' "$exec_old" | grep -q -- '--config '; then + new_exec="$(printf '%s' "$exec_old" | sed -E 's/(^| )--config [^ ]+//; s/ +/ /g; s/^ //; s/ $//')" + svc_set_key exec "$new_exec" + fi + svc_del_key conffile + hist_event set ok "$(read_pid)" "config unbind" + c_ok "[$ENV_NAME] 已移除配置文件(回命令行参数形态)" + if [ "$restart" = 1 ] && [ -n "$(read_pid)" ] && pid_alive "$(read_pid)"; then + do_stop; do_start || { c_err "[$ENV_NAME] 重启失败——移除已登记,排查后 start"; return 1; } + else + c_info "[$ENV_NAME] 未运行或 --no-restart——下次 start 生效" + fi + return 0 + fi + + # 更新。 + [ -f "$target" ] || { c_err "配置文件不存在:$target"; return 2; } + local abs + abs="$(cd "$(dirname "$target")" && pwd)/$(basename "$target")" + if [ -x "$ENV_BIN" ]; then + if ! "$ENV_BIN" config explain -config "$abs" >/dev/null 2>&1; then + c_err "[$ENV_NAME] 配置文件未通过 schema 校验:$abs" + "$ENV_BIN" config explain -config "$abs" >&2 || true + c_err "(键与取值以 config explain 为准;先修正文件再切换)" + return 2 + fi + if ! "$ENV_BIN" serve -h 2>&1 | grep -Eq -- "(^|[[:space:]])-{1,2}config([[:space:]]|=|$)"; then + c_warn "[$ENV_NAME] 当前产物不识别 --config(旧版本二进制)——绑定已登记,但重启将忽略该参数" + fi + fi + local exec_old new_exec + exec_old="$(svc_get exec)" + if printf '%s' "$exec_old" | grep -q -- '--config '; then + new_exec="$(printf '%s' "$exec_old" | sed -E "s|--config [^ ]+|--config $abs|")" + else + new_exec="${exec_old:+$exec_old }--config $abs" + fi + svc_set_key exec "$new_exec" + svc_set_key conffile "$abs" + hist_event set ok "$(read_pid)" "config → $abs" + c_ok "[$ENV_NAME] 配置文件已更新:$abs(schema 校验通过;svc.env 600 保存)" + if [ "$restart" = 1 ] && [ -n "$(read_pid)" ] && pid_alive "$(read_pid)"; then + c_info "[$ENV_NAME] 重启以应用新配置……" + do_stop + do_start || { c_err "[$ENV_NAME] 重启失败——绑定已登记,排查日志后 scripts/svc.sh $ENV_NAME start"; return 1; } + c_ok "[$ENV_NAME] 新配置已生效:$abs" + else + c_info "[$ENV_NAME] 未运行或 --no-restart——下次 start 生效" + fi + return 0 +} + # ---- agent 服务的数据库绑定( db ...)---- # svc_db_bind [pg名] [--db X] [--clear] [--no-restart]:查看 / 绑定切换 / 解除 @@ -2183,13 +2322,21 @@ registered_names() { } # db_ref_of NAME:agent 服务的数据库绑定标识(list/status/info 共用)。 -# dbref= 优先;否则从 exec= 的 --dsn 提取 host:port 匹配已登记 postgres 实例 -# (兼容直接 --dsn 部署、尚未登记 dbref 的服务);都匹配不上则回 外部@host:port。 +# dbref= 优先;否则从 exec= 的 --dsn 或配置文件(conffile=)的 dsn 键提取 host:port +# 匹配已登记 postgres 实例(兼容直接 --dsn/--config 部署、尚未登记 dbref 的服务); +# 都匹配不上则回 外部@host:port。 db_ref_of() { - local name="$1" ref dsn hostport pg addr + local name="$1" ref dsn hostport pg addr cf ref="$(env_kv_of "$name" dbref)" if [ -n "$ref" ]; then printf '%s' "$ref"; return 0; fi dsn="$(printf '%s' "$(env_kv_of "$name" exec)" | sed -n 's/.*--dsn \([^ ]*\).*/\1/p')" + if [ -z "$dsn" ]; then + cf="$(env_kv_of "$name" conffile)" + [ -z "$cf" ] && cf="$(printf '%s' "$(env_kv_of "$name" exec)" | sed -n 's/.*--config \([^ ]*\).*/\1/p')" + if [ -n "$cf" ] && [ -f "$cf" ]; then + dsn="$(grep -oE '"dsn"[[:space:]]*:[[:space:]]*"[^"]*"' "$cf" 2>/dev/null | head -1 | sed -E 's/.*"([^"]*)"$/\1/')" + fi + fi [ -n "$dsn" ] || return 0 hostport="$(printf '%s' "$dsn" | sed -n 's#.*@\([^/?]*\).*#\1#p')" while IFS= read -r pg; do @@ -2250,8 +2397,12 @@ env_status_detail() { [ -n "$tags" ] && printf ' 标签 %s\n' "$tags" case "$kind" in agent) - local dbref; dbref="$(db_ref_of "$ENV_NAME")" - printf ' 数据库 %s\n' "${dbref:-无(内嵌 SQLite 档;绑定: db )}" ;; + local dbref cfile + dbref="$(db_ref_of "$ENV_NAME")" + printf ' 数据库 %s\n' "${dbref:-无(内嵌 SQLite 档;绑定: db )}" + cfile="$(svc_get conffile)" + [ -z "$cfile" ] && cfile="$(printf '%s' "$(svc_get exec)" | sed -n 's/.*--config \([^ ]*\).*/\1/p')" + printf ' 配置文件 %s\n' "${cfile:-无(命令行参数形态; config 绑定)}" ;; postgres) printf ' 库 %s 用户 %s\n' "$(svc_get dbname)" "$(svc_get user)" printf ' DSN postgresql://%s:***@%s:%s/%s(db dsn %s --reveal 看密码)\n' \ @@ -2419,11 +2570,27 @@ info_pg_block() { return 0 } -# info_config_block NAME:单服务 svc.env 内容快照(文件路径+权限+逐行内容)。 +# info_config_block NAME:单服务 svc.env 内容快照(文件路径+权限+逐行内容), +# agent 服务另附其配置文件(conffile)的掩码内容快照。 info_config_block() { local name="$1" f="$RUNTIME/$1/svc.env" printf ' [%s] %s(权限 %s,kind=%s)\n' "$name" "$f" "$(info_file_mode "$f")" "$(svc_kind "$name")" info_snapshot_lines "$f" + local cf + cf="$(env_kv_of "$name" conffile)" + [ -z "$cf" ] && cf="$(printf '%s' "$(env_kv_of "$name" exec)" | sed -n 's/.*--config \([^ ]*\).*/\1/p')" + if [ -n "$cf" ]; then + if [ -f "$cf" ]; then + printf ' ── 配置文件 %s(权限 %s)──\n' "$cf" "$(info_file_mode "$cf")" + if [ "$CLI_REVEAL" = 1 ]; then + cat "$cf" | sed 's/^/ /' + else + mask_json "$(cat "$cf")" | sed 's/^/ /' + fi + else + printf ' ── 配置文件 %s:文件不存在(重启将失败,恢复或 %s config --clear)──\n' "$cf" "$name" + fi + fi return 0 } @@ -2769,15 +2936,18 @@ main() { # 动作词可省略(省略 = deploy);flags 可直接跟随服务名。 local action="deploy" case "${1:-}" in - deploy|stop|start|restart|status|logs|db) action="$1"; shift ;; + deploy|stop|start|restart|status|logs|db|config) action="$1"; shift ;; esac if [ "$action" = logs ] && [ "${1:-}" != "" ] && [[ "$1" =~ ^[0-9]+$ ]]; then CLI_LOGS_N="$1"; shift fi - # db 绑定子命令:自带参数解析(位置参数 不走 parse_args;未知 flags 由其自行拒绝)。 - if [ "$action" = db ]; then + # db / config 子命令:自带参数解析(位置参数不走 parse_args;未知 flags 由其自行拒绝)。 + if [ "$action" = db ] || [ "$action" = config ]; then env_config "$ENV_NAME" - svc_db_bind "$@" || exit $? + case "$action" in + db) svc_db_bind "$@" || exit $? ;; + config) svc_config "$@" || exit $? ;; + esac exit 0 fi parse_args "$@"