From 2c7f55bfe86c786f6aa9ea56e34e6311c9e99cd2 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 30 Sep 2026 14:29:39 +0000 Subject: [PATCH] Document svc.sh comprehensively and layer its help system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: svc.sh grew into a three-kind service manager with database operations, but its only documentation was a flat --help wall of text; the user asked for a complete usage document and a better help experience. Evidence: - docs/svc.md: 14-section manual (prerequisites, quick start, concepts and directory layout, per-command reference for service/global/db commands, deploy sources, database operations handbook, configuration layering, registry fields and event types, safety boundaries, exit codes, troubleshooting table, recipe index) — every claim cross-checked against the implementation from the previous two rounds. - Help layering: no-args and -h print a one-screen overview; --help renders the header comment reorganized into 7 labeled sections; the new "help [topic]" command deep-dives eight topics (model, deploy, lifecycle, db, dbops, config, examples, safety); usage errors now show the short overview plus a pointer to --help / help / docs/svc.md. All topics verified to render; unknown topic exits 2. - No runtime behavior changed: end-to-end db lifecycle regression (create → start → backup → status → stop → rm --purge) passes after the refactor. - Makefile help and README's documentation section point to docs/svc.md. Impact: Documentation-only plus display-layer changes; command semantics, exit codes and file formats are untouched. Maintenance note: behavior changes must now update both --help and docs/svc.md. Refs: .task/feature-svc-docs-help.md(任务卡=PR 描述) --- .task/feature-svc-docs-help.md | 25 +- Makefile | 2 +- README.md | 1 + docs/svc.md | 578 +++++++++++++++++++++++++++++++++ scripts/svc.sh | 440 +++++++++++++++++++++---- 5 files changed, 982 insertions(+), 64 deletions(-) create mode 100644 docs/svc.md diff --git a/.task/feature-svc-docs-help.md b/.task/feature-svc-docs-help.md index bd746d0e..71f9fdb5 100644 --- a/.task/feature-svc-docs-help.md +++ b/.task/feature-svc-docs-help.md @@ -6,11 +6,21 @@ - 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 22d14db80c31e4bcfa24411f847018f527b16a01 - 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-svc-docs-help(worktree);分支:feature/svc-docs-help -- 授权与交付终点:<本次允许的动作与终点;未授权事项写明> +- 授权与交付终点:用户要求"详细完整使用文档 + 完善 --help";终点 = 实现合入 main 并回收工作区。 + 仅新增 docs/svc.md,修改 scripts/svc.sh 帮助层、Makefile 帮助行、README 文档指引。 ## 目标与非目标 -<预期结果、受影响入口/模块/契约;明确不做什么。> +目标: +1. docs/svc.md:完整使用文档(前提/上手/概念布局/四段命令参考/部署来源/配置系统/登记与 + 事件/安全边界/退出码/故障排查/配方索引)。 +2. svc.sh 帮助分层:无参数/-h → 一屏短帮助;--help → 分节完整帮助(重排头部注释); + help [主题] → 8 个主题详解(model deploy lifecycle db dbops config examples safety); + 用法错误改走短帮助并指路。 +3. Makefile help 加文档指引;README「文档」节加 svc.md 条目。 + +非目标:不改任何运行行为/命令语义(帮助与文档纯属展示层);不翻译英文错误信息; +不生成 man page。 ## 同步记录 @@ -20,6 +30,11 @@ ## 结果与验证(完成后填写) -- 结果: -- 验证: -- 影响与剩余项: +- 结果:docs/svc.md(14 节,与实现逐条对齐);svc.sh 头部注释重排为 7 个分节(--help 渲染), + 新增 usage_short() 与 cmd_help()(8 主题 heredoc),main 接线 -h/--help/help 三层, + parse_args 未知参数改走短帮助;Makefile/README 指引。无运行行为改动。 +- 验证:bash -n 通过;无参数/-h 短帮助、--help 完整(7 分节 + 文档指路)、help 主题清单、 + 8 个主题全部渲染、help bogus 退出码 2 并列出可选主题;db 用法错误指向分主题; + 功能回归(db create→start→backup→status→stop→rm --purge 全链路)通过,runtime 清理干净。 +- 影响与剩余项:帮助文本与实现的双源维护成本(后续改行为须同步 --help 与 docs/svc.md); + 未做 man page / shell 补全(可作后续项)。 diff --git a/Makefile b/Makefile index 810cf1c2..25b33130 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ help: @echo "make svc 服务级操作:make svc ENV= [ACTION=deploy|start|stop|restart|status|logs] [ARGS='--from dev@ --tag ...']" @echo "make svc-list 服务总览:make svc-list [ARGS='--tag core'];数据库运维:scripts/svc.sh db create|dsn|backup|backups|restore|clone|copy|ls|set" @echo "make svc-commits 列出可部署提交:make svc-commits SRC=dev|release [N=20]" - @echo "make svc-history 事件历史:make svc-history [ENV] [N=20]" + @echo "make svc-history 事件历史:make svc-history [ENV] [N=20](完整文档:docs/svc.md)" @echo "make dev 开发模式说明(前后端分离热更新)" @echo "make pin 推进 submodule 指针到各仓远端 main(提交前须 make test 通过)" @echo "make status submodule 状态" diff --git a/README.md b/README.md index c8467423..a9874688 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,4 @@ make dev # 开发模式说明(前后端分离热更新) ## 文档 设计方案:`modules/documents/docs/proposals/2026-09-29-system-skeleton-design-and-plan.md`(含 D1–D16 决策记录)。 +本机服务管理(后端/前端/数据库部署、生命周期、备份克隆):`docs/svc.md`(`scripts/svc.sh`)。 diff --git a/docs/svc.md b/docs/svc.md new file mode 100644 index 00000000..ed058965 --- /dev/null +++ b/docs/svc.md @@ -0,0 +1,578 @@ +# svc.sh 使用说明 + +`scripts/svc.sh` 是本仓的**本机服务管理器**:把 后端(agent)、前端静态服务(static)、 +PostgreSQL 数据库(postgres)三类服务统一为"服务"模型——按提交/版本部署、生命周期管理、 +配置分层持久化、标签、事件追溯,以及一套完整的数据库运维能力(换端口 / 备份 / 恢复 / +克隆 / 跨实例搬库)。 + +设计原则(也是安全边界,详见 [§11 安全与边界](#11-安全与边界)): + +- **服务自包含**:每个服务的产物、数据、日志、登记都在 `runtime//` 一个目录里, + 服务之间互不串版本;删目录 = 完全移除。 +- **登记持久化**:所有状态落盘(`svc.env` 当前态 + `history.jsonl` 事件流),`list` / + `history` 全部从文件重建,脚本无内存状态、跨会话可追溯。 +- **不碰现场**:不改主检出 git 状态;只停"自己 pidfile + cmdline 双校验"通过的进程; + 端口冲突报错不抢;除 `--from local` 外的构建全部在隔离 worktree。 + +分主题速查:`scripts/svc.sh help [model|deploy|lifecycle|db|dbops|config|examples|safety]`; +本文档与 `--help` 内容同源,以实现为准。 + +--- + +## 目录 + +1. [前提条件](#1-前提条件) +2. [90 秒上手](#2-90-秒上手) +3. [概念与目录布局](#3-概念与目录布局) +4. [命令参考:服务级](#4-命令参考服务级) +5. [命令参考:部署来源](#5-命令参考部署来源) +6. [命令参考:全局命令](#6-命令参考全局命令) +7. [命令参考:数据库](#7-命令参考数据库) +8. [数据库运维手册](#8-数据库运维手册) +9. [配置系统](#9-配置系统) +10. [登记文件与事件流](#10-登记文件与事件流) +11. [安全与边界](#11-安全与边界) +12. [退出码](#12-退出码) +13. [故障排查](#13-故障排查) +14. [配方索引](#14-配方索引) + +--- + +## 1. 前提条件 + +| 依赖 | 用途 | 必需性 | +|---|---|---| +| bash ≥ 4、git | 脚本本体与提交物化 | 必需 | +| curl | 健康检查、设置播种 | 必需 | +| python3 | static 服务(http.server)、播种 payload | static/播种需要 | +| ss | 端口占用检测(`iproute2`) | 建议(缺失时跳过检测) | +| PostgreSQL 工具链(initdb/pg_ctl/psql/pg_dump/pg_restore/pg_isready) | 数据库功能 | 数据库功能需要 | + +PostgreSQL 工具链定位顺序:`SVC_PG_BIN` 环境变量 → `pg_config --bindir` → +`/usr/lib/postgresql/*/bin` 等常见路径。都没有时 agent/static 功能不受影响。 + +## 2. 90 秒上手 + +```bash +# 一套演示后端(构建当前工作区 → 启动 → 健康检查) +scripts/svc.sh dev deploy --mock + +# 一个数据库 + 让后端连上它 +scripts/svc.sh db create pg1 --port 8890 # initdb,只登记不启动 +scripts/svc.sh pg1 start # pg_ctl 启动 + 自动建库 + pg_isready +scripts/svc.sh prod deploy --from dev@ \ + --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" # DSN 直接组合进部署 + +# 日常操作 +scripts/svc.sh list # 全服务总览 +scripts/svc.sh pg1 restart # 重启(复用上次配置) +scripts/svc.sh prod logs 100 # 跟日志 +scripts/svc.sh history # 事件追溯 + +# 备份 / 恢复 / 克隆 +scripts/svc.sh db backup pg1 # pg_dump -Fc,自动自检 +scripts/svc.sh db restore pg1 --file --force # 覆盖恢复需 --force +scripts/svc.sh db clone pg1 pg1test --port 8896 # 克隆一套到新端口 +``` + +## 3. 概念与目录布局 + +### 3.1 服务名 + +`^[a-z][a-z0-9-]{0,15}$`(小写字母开头,可含数字与连字符,≤16 位)。保留字不可用作 +服务名:`status list add set db rm create dsn commits history deploy stop start restart +logs help version`。 + +三个名字有默认端口:`dev` = `127.0.0.1:8788`,`prod` = `127.0.0.1:8787`;其余名字按 +名称哈希在 **8800–9099** 确定性派生端口,与已登记端口或正在监听的端口冲突时自动向后 +扫描;`SVC__ADDR` 可显式覆盖。 + +### 3.2 目录布局 + +每个服务自包含于一个目录(`--purge` 删目录即完全移除): + +``` +runtime// +├── bin/agent-system agent 产物(deploy 复制就位;start 只跑这份) +├── htdocs/ static 前端产物(--dir 模式登记外部目录,不复制) +├── data/ agent 数据(SQLite 与工件;--data 可改) +├── pgdata/ postgres 数据(initdb 产物,700) +├── plugins/ agent 插件(随 deploy 复制) +├── backups/ pg_dump 备份(目录 700,文件 600) +├── svc.pid pidfile +├── svc.log | pg.log 日志(postgres 用 pg.log) +├── svc.env 登记 + 持久化配置(dotenv,600)——见 §10 +└── history.jsonl 事件流(append-only JSON Lines)——见 §10 +``` + +因为产物在环境目录内,`start`/`restart` 不依赖主检出;**旧版脚本部署过的环境没有环境 +产物,升级后首次需重新 deploy 一次**(脚本会明确提示)。 + +### 3.3 三类服务(kind) + +| kind | 产物 | 数据 | 生命周期实现 | +|---|---|---|---| +| `agent` | 单二进制(内嵌前端) | `data/`(SQLite)或外部 DSN | nohup + TERM→KILL | +| `static` | `htdocs/` | 无 | `python3 http.server` | +| `postgres` | 系统工具链 | `pgdata/` | `pg_ctl`(fast stop,绝不 KILL) | + +登记里没有 `kind=` 的旧条目自动视为 `agent`。同名服务不能换类型,需先 `rm`。 + +## 4. 命令参考:服务级 + +以下 `` 均指服务名;`action` 可省略的只有 `deploy`。 + +### 4.1 deploy(部署并启动) + +``` +scripts/svc.sh deploy [--kind agent|static] [--from 源] [--tag T]... [参数...] +``` + +流程:解析来源 →(dry-run 则只打印计划)→ 停旧实例 → 物化/构建/校验 → 产物复制到 +`runtime//` → 启动 → 健康等待(agent 20s / static 10s)→ 播种设置(agent)→ +登记。任何一步失败都会写 `deploy fail` 事件并保留现场。 + +**agent**:来源见 [§5](#5-命令参考部署来源);启动参数组装见 [§9 配置系统](#9-配置系统); +健康后按 `--model-*` / `--light-*` / `--tavily-keys` 播种(幂等,设置存在 agent 数据目录)。 + +**static**: + +```bash +scripts/svc.sh ui deploy --kind static --from dev@ # 构建该提交的 frontend dist → htdocs/ +scripts/svc.sh ui deploy --kind static --dir ../dist # 直接服务既有目录(不构建不复制) +``` + +- `--dir` 模式登记原始路径(`source=dir@<路径>`),目录内容由你自己管理; +- `release` 源对 static 无意义(制品内嵌前端),明确拒绝。 + +**通用参数**: + +| 参数 | 说明 | +|---|---| +| `--tag T` | 标签,可重复;登记合并去重,`list --tag` 过滤 | +| `--addr host:port` / `--host H` / `--port P` | 监听地址 | +| `--data DIR` | agent 数据目录(默认 `runtime//data`) | +| `--plugins DIR` | agent 插件源(默认随 deploy 复制环境目录的 `plugins/`) | +| `--dsn DSN` / `--auth on\|off\|auto` / `--mock` | agent 运行参数(非 dev 环境默认 `--auth auto`) | +| `--model-base/--model-key/--model-name`、`--light-*`、`--tavily-keys` | 启动健康后播种 | +| `--dry-run` | 只解析并打印部署计划(来源、物化路径、产物去向、启动参数) | +| `--pick [dev\|release]` | 交互式编号选提交(需 TTY) | +| `--config FILE` / `--env FILE` | 配置文件(见 §9) | +| `-- <剩余参数>` | 原样透传给 `agent-system serve` | + +### 4.2 start / stop / restart / logs / status + +```bash +scripts/svc.sh start # 已在运行则提示并返回 0 +scripts/svc.sh stop # agent/static: TERM→10s→KILL;postgres: pg_ctl -m fast +scripts/svc.sh restart # stop + start +scripts/svc.sh logs [N] # tail -f,从最近 N 行起(默认 50) +scripts/svc.sh status # 单服务详情 +``` + +- **裸 `restart` 复用上次启动配置**(svc.env `exec=`),换过端口/参数不用重敲; +- 显式给任何运行参数(或 `--config` 文件、`SVC_*` 变量)时**整体重建**参数; +- agent/static 换端口:`restart --port N`(进程与登记同步更新); +- postgres 换端口:`set --apply`(见 §7.4)或 `restart --port N` 同样有效。 + +`status` 输出示例(postgres): + +``` + [pg1] postgres 运行中 pid=3115294 + 地址 127.0.0.1:8890 数据 /…/runtime/pg1/pgdata + 来源 postgres@local 版本 18.6 + 标签 core,local + 库 app 用户 agent + DSN postgresql://agent:***@127.0.0.1:8890/app(db dsn pg1 --reveal 看密码) +``` + +### 4.3 set(变更登记) + +```bash +scripts/svc.sh set --tag T... # 三类服务通用:改标签(不动进程) +scripts/svc.sh set --port N [--host H] # 未运行:直接改登记 +scripts/svc.sh set --port N --apply # 运行中:停止 → 改登记 → 重启 +scripts/svc.sh set --host 0.0.0.0 --apply # 换绑定主机同理 +``` + +- 运行中的 postgres **必须** `--apply`,否则拒绝(防止登记地址与实际监听不一致); +- 新端口先做双重校验:未被其他服务登记占用、且当前无人监听; +- `--apply` 的重启走 `pg_ctl` 优雅停机,数据安全; +- agent/static 的地址变更用 `restart --port N`(set 只改登记,会提示)。 + +### 4.4 rm(注销) + +```bash +scripts/svc.sh rm # 停止 + 移除登记(svc.env/svc.pid);目录数据历史保留 +scripts/svc.sh rm --purge # 连 runtime// 整个目录一起删除(含备份!) +``` + +只管理本脚本登记过的服务;未登记名字会明确报错。 + +## 5. 命令参考:部署来源 + +`deploy --from` 支持三种来源(缺省 `local`),agent 与 static 共用: + +### 5.1 local —— 当前工作区 + +``` +--from local +``` + +在主检出跑 `make build`(agent)或 `make frontend`(static),产物复制到服务目录。 +**这是唯一会写主检出的路径**,写的都是 gitignore 目录(`bin/`、`node_modules/`、 +`modules/frontend/dist/`),不产生任何 git 状态变化。适合"把我现在的工作区跑起来"。 + +### 5.2 dev@\ —— Dev 仓提交 + +``` +--from dev # 当前 HEAD +--from dev@ # 指定提交(短/长 sha 均可) +``` + +流程:`git worktree add --detach runtime/.worktrees/ ` → 递归 +`git submodule update --init --recursive`(子模块精确落位该提交的 gitlink 组合)→ +`make build`/`make frontend` → 产物复制 → worktree 回收。全程不触碰当前工作区。 + +- 可用提交列表:`scripts/svc.sh commits dev [N]`; +- 只列本地已有对象;远端新提交先 `git -C <仓库> fetch`; +- 需要 npm/go 网络(构建在 worktree 内进行); +- worktree 回收失败会提示手动命令,不阻塞部署结果。 + +### 5.3 release@\<版本\> —— Releases 仓制品 + +``` +--from release # Releases 仓最新(HEAD) +--from release@0.1.0 # 指定版本号 +--from release@43ac28d # 指定 Releases 仓提交也可 +``` + +制品取 `/build//agent-system`(+ `plugins/`),**sha256 对 +`versions//manifest.json` 校验,不符即拒绝部署**;并回显该版本对应的 Dev 提交。 +仓库定位:`SVC_RELEASES_DIR` > `../Agent-System-Releases`。制品缺失时提示先在 +Releases 仓跑 `scripts/build-release.sh `。 + +### 5.4 辅助 + +```bash +scripts/svc.sh commits [dev|release] [N] # 可部署提交表(默认 20 条) +scripts/svc.sh deploy --pick # 交互式编号选择 +scripts/svc.sh deploy --dry-run # 只打印计划 +``` + +`commits` 表格:dev 源标注 `[HEAD]` 与 `[已部署:<服务名>]`(从各服务 `svc.env source=` +汇总);release 源额外给出版本号与对应 Dev 提交。 + +## 6. 命令参考:全局命令 + +### 6.1 list / status(总览) + +```bash +scripts/svc.sh list [--tag T] # status 等价 +``` + +扫描 `runtime/*/svc.env` 枚举全部已登记服务: + +``` + NAME KIND STATE PID ADDR SOURCE TAGS + pg1 postgres 运行中 3115294 127.0.0.1:8890 postgres@local core,local + prod agent 运行中 3117059 127.0.0.1:8791 release@0.1.0 stable + ui-qa static 未运行 - 127.0.0.1:9054 dir@/tmp/static-demo ui +``` + +STATE 的判定:pidfile 存在且 `/proc//cmdline` 匹配登记的 `pidcheck` 标记 +(agent=二进制路径、postgres=pgdata 路径、static=`--directory <目录>`)。进程被人手动 +杀掉后 STATE 自动落回"未运行",不会误报。 + +### 6.2 history(事件追溯) + +```bash +scripts/svc.sh history [name] [N] # 默认全部服务、最近 20 条;首参为纯数字时视为条数 +``` + +合并各服务 `history.jsonl`(按时间排序),字段:时间 / 服务 / kind / 事件 / 结果 / +来源 / pid / 详情。事件类型见 §10.2。 + +### 6.3 help + +```bash +scripts/svc.sh # 一屏速览(短帮助) +scripts/svc.sh -h # 同上 +scripts/svc.sh --help # 完整帮助(全部命令/参数/约束) +scripts/svc.sh help # 分主题清单 +scripts/svc.sh help <主题> # 主题详解:model deploy lifecycle db dbops config examples safety +``` + +## 7. 命令参考:数据库 + +### 7.1 db create(创建实例) + +```bash +scripts/svc.sh db create pg1 [--port 8890] [--host 127.0.0.1] \ + [--user agent] [--password ] [--db app] \ + [--auth-mode scram-sha-256|md5] [--tag T]... +``` + +- `initdb` 到 `runtime/pg1/pgdata`(目录 700;密码经 pwfile 传递,即用即删); +- 默认用户 `agent`、默认库 `agent`、认证 `scram-sha-256`(pg_hba 全线); +- `--password` 缺省时自动生成(18 字节十六进制随机); +- **只登记不启动**——`scripts/svc.sh pg1 start` 启动;端口占用预检在 create 时已做。 + +### 7.2 start / stop / restart + +- `start`:`pg_ctl -w -t 30 -l pg.log -o "-p -k <环境目录> -c listen_addresses=…"` + ——socket 目录指向环境目录(不落 pgdata,克隆时不随数据目录传播); + 首次启动自动创建登记库(已存在则跳过);就绪判定 `pg_isready`; +- `stop`:`pg_ctl -m fast`(回滚事务、断连接、优雅退出);失败退 SIGTERM; + **SIGKILL 永远不用于 postmaster**; +- `restart`:stop + start。 + +### 7.3 db dsn / db ls + +```bash +scripts/svc.sh db dsn pg1 # postgresql://agent:***@127.0.0.1:8890/agent(掩码) +scripts/svc.sh db dsn pg1 --reveal # 显示真实密码 +scripts/svc.sh db dsn pg1 --db other # 指定库名 +scripts/svc.sh db ls pg1 # 实例内数据库列表(须运行中) +``` + +DSN 走 stdout(提示走 stderr),可直接组合:`--dsn "$(scripts/svc.sh db dsn pg1 --reveal)"`。 + +### 7.4 换端口 / 换主机 + +见 §4.3 `set`。要点:运行中必须 `--apply`;登记、DSN、`pg_ctl` 启动参数联动更新; +数据不动。换端口 = 同机内"迁移"。 + +## 8. 数据库运维手册 + +### 8.1 备份 + +```bash +scripts/svc.sh db backup pg1 # 备份登记库 +scripts/svc.sh db backup pg1 --db app # 指定库 +scripts/svc.sh db backup pg1 --out /tmp/x.dump # 自定义输出路径 +scripts/svc.sh db backups pg1 # 列表(时间 / 大小 / 文件名) +``` + +- 实现:`pg_dump -Fc` 走 TCP(实例须运行中),默认落到 + `runtime/pg1/backups/-<库>.dump`; +- 完成后 `pg_restore --list` 自检归档有效性,失败即删除该文件并记 `backup fail`; +- 备份**只增不删**;随 `rm --purge` 一并删除。 + +### 8.2 恢复 + +```bash +scripts/svc.sh db restore pg1 --file # 恢复到登记库 +scripts/svc.sh db restore pg1 --file --db app2 # 恢复到指定库 +scripts/svc.sh db restore pg1 --file --db app --force # 覆盖非空库 +``` + +目标语义: + +| 目标库状态 | 行为 | +|---|---| +| 不存在 | 建库 → 恢复 | +| 存在且无用户表 | 直接恢复 | +| 存在且有用户表 | **拒绝**;`--force` 才 `DROP DATABASE … WITH (FORCE)` → 重建 → 恢复 | + +恢复前先校验文件是合法 pg_dump 归档;完成后回显恢复出的表数;全程事件入 history。 +这是脚本里唯一可能删除数据的操作,必须显式 `--force` 解锁。 + +### 8.3 克隆实例(绑新端口) + +```bash +scripts/svc.sh db clone pg1 pg1test --port 8896 # 物理克隆(默认) +scripts/svc.sh db clone pg1 pg1test --password newpw --tag test # 换密码/加标签 +scripts/svc.sh db clone pg1 pg1test --online # 在线克隆(不停源) +``` + +**物理克隆(默认)**:源在运行则短暂 `pg_ctl -m fast` 停止(一致性要求)→ +`cp -a pgdata`(剔除 `postmaster.pid` / `postmaster.opts`;socket 本就不在 pgdata)→ +登记目标(端口 = `--port` 或派生;用户/库/认证继承,密码可 `--password` 覆盖;标签 +继承 + `--tag` 追加;`source=clone@<源名>`)→ 自动复启源。**目标不自动启动**——按需 +`scripts/svc.sh pg1test start`。 + +**`--online`**:不停源,把源的登记库 dump/restore 到目标——要求目标**已** +`db create` + `start`。适合源不能停的场景。 + +### 8.4 跨实例搬库 + +```bash +scripts/svc.sh db copy pgdev pgprod --db app # pgdev.app → pgprod.app +scripts/svc.sh db copy pgdev pgprod --db app --force # 目标非空时覆盖 +``` + +- 双方实例都须运行中(走 TCP dump/restore); +- `--db X` 同时命名源库与目标库;缺省取源的登记库; +- 目标库非空须 `--force`(语义同 restore);成功后两侧 history 各记一条 `copy`。 + +### 8.5 端口规划建议 + +- 默认派生区 8800–9099 按服务名哈希,天然分散;多实例并存时建议显式 `--port` 分配 + 并记入标签(如 `--tag port-8896`); +- 换端口用 `set --apply`;克隆用 `--port` 指定;两者都会做占用双重校验。 + +## 9. 配置系统 + +### 9.1 优先级 + +``` +命令行参数 + > --config FILE(--env FILE 别名) + > svc.env 持久化(addr=、exec=) + > SVC__* 环境变量(.svc.env 自动加载,同层) + > 默认值 +``` + +### 9.2 配置文件 + +dotenv 格式:`KEY=VALUE`,支持 `#` 注释、成对引号、`export` 前缀;只识别合法变量名, +**不 source 执行文件内容**(防注入)。建议 `.env.dev` / `.env.prod` 等加入 gitignore。 + +```bash +scripts/svc.sh prod deploy --config .env.prod +scripts/svc.sh prod deploy --env .env.prod # 等价 +``` + +### 9.3 环境变量 + +| 变量 | 说明 | +|---|---| +| `SVC__ADDR/DATA/DSN/AUTH/MOCK` | 按服务覆盖(NAME 大写、`-`→`_`,如 `staging` → `SVC_STAGING_ADDR`) | +| `SVC_RELEASES_DIR` | Releases 仓路径(默认 `../Agent-System-Releases`) | +| `SVC_PG_BIN` | PostgreSQL 工具链 bindir | +| `SVC_MODEL_BASE/KEY/NAME` | 主模型(播种 `settings.models.chat`) | +| `SVC_LIGHT_BASE/KEY/NAME` | 轻量模型 | +| `SVC_TAVILY_KEYS` | Tavily 密钥池(逗号分隔) | + +### 9.4 持久化语义 + +- `deploy`/`start` 成功后把当前态写入 `svc.env`(600):`addr`、`exec`(agent 上次 + 完整启动参数)等; +- 裸 `start`/`restart` 复用 `exec=`;任何显式参数(CLI、`--config`、`SVC_*`)出现即 + **整体重建**启动参数; +- 标签合并去重、跨重启保留;postgres 的用户/密码/库名/认证模式持久化,重启不丢。 + +## 10. 登记文件与事件流 + +### 10.1 svc.env 字段 + +| 键 | 说明 | +|---|---| +| `kind` | `agent` / `static` / `postgres`(缺省视为 agent) | +| `env` | 服务名 | +| `pid` | 最近启动 pid | +| `addr` | 监听地址(持久化,下次 start 复用) | +| `tags` | 标签(逗号分隔) | +| `source` | 部署来源:`local@` / `dev@` / `release@<版本>` / `dir@<路径>` / `postgres@local` / `clone@<名>` | +| `version` | 产物版本串(`agent-system version` 输出) | +| `started` | 最近启动时间 | +| `pidcheck` | 进程判定标记(见 §6.1) | +| `exec` | agent 上次完整启动参数 | +| `user` / `password` / `dbname` / `authmode` / `pgdata` | postgres 专属 | +| `htdocs` | static 专属 | + +### 10.2 history.jsonl 事件 + +一行一个 JSON:`ts / event / env / kind / source / result / pid / addr / detail`。 + +| event | result | 触发 | +|---|---|---| +| `create` | ok | `db create` / 物理克隆登记 | +| `deploy` | begin / ok / fail | agent/static 部署全程 | +| `start` | ok / fail / warn | 启动(fail 含"进程即退/健康检查超时";warn 为进程起但探活未过) | +| `stop` | ok / fail | 停止(含 pg_ctl fast / SIGTERM 兜底说明) | +| `set` | ok | 登记变更(`addr 旧 → 新`) | +| `backup` | ok / fail | 备份(文件、大小) | +| `restore` | ok / fail | 恢复(库、文件、表数) | +| `clone` | ok / fail | 克隆(物理/在线) | +| `copy` | ok / fail | 搬库 | +| `rm` | ok | 注销(是否 purge) | + +## 11. 安全与边界 + +- **写入边界**:只写 `runtime//` 与 `runtime/.worktrees/`;不改主检出 git 状态; + `--from local` 是唯一写主检出构建产物的路径(均 gitignore 目录);`rm --purge` 只删 + 已登记服务的 `runtime//`。 +- **进程边界**:只停"pidfile + cmdline 标记"双校验通过的进程;postgres 走 `pg_ctl -m + fast`,失败退 SIGTERM,SIGKILL 不用于 postmaster;agent/static TERM 10s 后才 KILL。 +- **端口边界**:被他人占用报错不抢;显式端口双重校验(登记 + 监听);派生端口避让。 +- **秘密与数据**:`svc.env`(含 DB 密码)与备份 600;环境/数据目录 700;DSN 默认掩码; + dotenv 不执行文件内容;`restore`/`copy` 覆盖非空库必须显式 `--force`;备份/克隆只增不删。 +- **兼容承诺**:`kind=` 缺省视为 agent;旧格式 `svc.env`(仅 addr/data)在 `list`/ + `status` 正常显示;旧产物二进制不识别的 `--dsn/--auth` 自动省略并提示。 + +## 12. 退出码 + +| 码 | 含义 | +|---|---| +| 0 | 成功(含"已在运行,无需操作") | +| 1 | 运行失败(构建失败、启动失败、校验不过、拒绝覆盖等) | +| 2 | 用法错误(未知参数/服务名/来源、缺参数、服务未登记等) | + +## 13. 故障排查 + +| 症状 | 处置 | +|---|---| +| `端口被占用(非本服务)` | 按提示的占用者处理,或换端口(`--port` / `set --port --apply`) | +| `进程启动后即退出` | `scripts/svc.sh logs 200` / postgres 看 `runtime//pg.log`;history 里有 fail 记录 | +| `20s 健康检查未通过` | 进程活着但未响应:查日志与端口;agent 常见原因是 DSN 指向未启动的库 | +| `该产物不支持 --dsn/--auth` | 提示而非错误:旧版本制品自动省略了不支持的 flag;需要该能力请部署新提交 | +| `环境产物缺失` | 该环境没有部署过(或旧版脚本部署):先 `deploy` | +| `pg_ctl start 失败` | 看 pg.log 常见:端口被占、pgdata 权限(须 700)、postmaster 残留 | +| `pidfile 指向的进程已不存在` | 脚本自动清理 pidfile;直接重新 start 即可 | +| `sha256 校验失败` | 制品与 manifest 不符:不要部署;在 Releases 仓重新 build | +| `Dev 仓无此提交` | 本地无该对象:`git -C <仓库> fetch` 后重试 | +| `worktree 回收失败` | 按提示手动 `git worktree remove --force runtime/.worktrees/` | +| `子模块物化失败` | 子模块对象缺失:先 fetch;或网络问题重试 | +| `目标库已有 N 张表——拒绝覆盖` | 确认后加 `--force`(会 drop 重建) | +| `list 里服务显示未运行但进程在` | 手工起的进程不满足 pidcheck 标记:脚本不会动它;纳入管理请走 svc 部署 | + +## 14. 配方索引 + +```bash +# ① 演示后端 +scripts/svc.sh dev deploy --mock + +# ② 建库 + 后端连库 +scripts/svc.sh db create pg1 --port 8890 +scripts/svc.sh pg1 start +scripts/svc.sh prod deploy --from dev@ --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" + +# ③ 按提交部署 + 回退 +scripts/svc.sh commits dev +scripts/svc.sh v1 deploy --from dev@73b2802 --tag test +scripts/svc.sh v1 deploy --from release@0.1.0 + +# ④ 双版本并行对比 +scripts/svc.sh a deploy --from dev@ --port 8801 +scripts/svc.sh b deploy --from dev@ --port 8802 + +# ⑤ 前端静态服务 +scripts/svc.sh ui deploy --kind static --from dev@ +scripts/svc.sh ui2 deploy --kind static --dir ../frontend/dist + +# ⑥ 备份 → 改坏 → 恢复 +scripts/svc.sh db backup pg1 +scripts/svc.sh db restore pg1 --file runtime/pg1/backups/<最新>.dump --force + +# ⑦ 克隆隔离环境 +scripts/svc.sh db clone pg1 pg1test --port 8896 +scripts/svc.sh pg1test start +scripts/svc.sh qa deploy --from dev@ --dsn "$(scripts/svc.sh db dsn pg1test --reveal)" + +# ⑧ 开发库搬到 prod 实例 +scripts/svc.sh db copy pgdev pgprod --db app --force + +# ⑨ 换端口 +scripts/svc.sh set pg1 --port 8895 --apply + +# ⑩ 总览与追溯 +scripts/svc.sh list --tag test +scripts/svc.sh history pg1 50 +``` + +--- + +*本文档对应 `scripts/svc.sh`(Agent-System-Dev 仓)。命令行为以实现为准;发现文档与 +实现不一致时,以任务卡记录的方式提出修正。* diff --git a/scripts/svc.sh b/scripts/svc.sh index 76d1e348..f06acf9c 100755 --- a/scripts/svc.sh +++ b/scripts/svc.sh @@ -1,81 +1,89 @@ #!/usr/bin/env bash -# svc.sh —— 本机服务管理(服务模型 v2):按提交/版本部署并启动 后端(agent) / 前端(static) -# / 数据库(postgres) 三类服务,统一登记(svc.env + history.jsonl)、标签、配置分层、 -# 生命周期(start/stop/restart/rm)与数据库运维(换端口/备份/恢复/克隆/搬库), -# 全程不触碰主检出与其他已启动服务。 +# svc.sh —— 本机服务管理:后端(agent) / 前端(static) / 数据库(postgres) 三类服务的 +# 部署、生命周期与数据库运维。每个服务自包含于 runtime//,登记与配置持久化 +# (svc.env + history.jsonl),全程不触碰主检出 git 状态与其他已启动服务。 # -# 用法: -# scripts/svc.sh [action] [参数...] # 服务级操作(name 见命名规则) -# action:deploy(默认,agent/static)| start | stop | restart | status | logs [行数] -# scripts/svc.sh list [--tag T] # 全服务总览(status 等价) -# scripts/svc.sh db create [参数] # 创建 PostgreSQL 服务(initdb) -# scripts/svc.sh db dsn [--reveal] [--db X] # 打印数据库连接串(默认掩码密码) -# scripts/svc.sh db backup [--db X] [--out F] # pg_dump 备份(自动自检归档) -# scripts/svc.sh db backups # 列出已有备份 -# scripts/svc.sh db restore --file F [--db X] [--force] # 恢复(覆盖非空库须 --force) -# scripts/svc.sh db clone [--port P] [--password PW] [--online] # 克隆实例 -# scripts/svc.sh db copy [--db X] [--force] # 跨实例搬库(双方须运行) -# scripts/svc.sh db ls # 列出实例内数据库 -# scripts/svc.sh set [--port N --apply] [--tag T] # 变更登记(换端口/标签) -# scripts/svc.sh rm [--purge] # 注销服务(--purge 连数据一起删) -# scripts/svc.sh commits [dev|release] [N] # 列出可部署提交(默认 dev,N=20) -# scripts/svc.sh history [name] [N] # 事件历史(默认全部,最近 20 条) +# ════════════════════ 命令一览 ════════════════════ +# scripts/svc.sh [action] [参数...] 服务级:deploy(默认)|start|stop|restart|status|logs [N] +# scripts/svc.sh list [--tag T] 服务总览(status 等价) +# scripts/svc.sh set [--port N --apply] [--tag T] 变更登记(换端口/标签) +# scripts/svc.sh rm [--purge] 注销服务(--purge 连数据目录删除) +# scripts/svc.sh db create [参数] 创建 PostgreSQL 实例(initdb) +# scripts/svc.sh db dsn [--reveal] [--db X] 连接串(默认掩码密码) +# scripts/svc.sh db backup [--db X] [--out F] 备份(pg_dump -Fc + 自检) +# scripts/svc.sh db backups 备份列表 +# scripts/svc.sh db restore --file F [--db X] [--force] 恢复(覆盖非空库须 --force) +# scripts/svc.sh db clone [--port P] [--password PW] [--online] 克隆实例 +# scripts/svc.sh db copy [--db X] [--force] 跨实例搬库(双方须运行) +# scripts/svc.sh db ls 实例内数据库列表 +# scripts/svc.sh db set [...] 等价于 set +# scripts/svc.sh commits [dev|release] [N] 可部署提交列表(默认 dev,N=20) +# scripts/svc.sh history [name] [N] 事件历史(默认全部,最近 20 条) +# scripts/svc.sh help [主题] 分主题详细帮助 # -# 服务模型: -# - name:^[a-z][a-z0-9-]{0,15}$(保留字不可用);dev/prod 有默认端口,其余按名字 -# 确定性派生(8800-9099,避让已登记/监听端口),SVC__ADDR 可覆盖 -# - 每个服务自包含于 runtime//:产物(bin/htdocs)/数据(data/pgdata)/svc.pid/ -# svc.log|pg.log/svc.env(登记+持久化配置,600)/history.jsonl(追加事件流) -# - kind(svc.env kind=,缺省 agent,兼容旧登记): -# agent 后端单二进制(内嵌前端);deploy 三源:local | dev[@] | release[@] -# static 前端静态服务;deploy --from dev[@sha]|local 构建 frontend dist 复制到 -# htdocs/,或 deploy --dir PATH 直接服务既有目录 -# postgres 数据库;db create 后 start/stop/restart(pg_ctl 生命周期,绝不裸杀) +# ════════════════════ 部署来源(agent/static 的 deploy)════════════════════ +# --from local 构建当前工作区(唯一写主检出构建产物的路径,均 gitignore) +# --from dev[@] 隔离 worktree(runtime/.worktrees/)物化该提交的 +# 子模块组合并构建,不触碰当前工作区 +# --from release[@<版本|sha>] Releases 仓制品(sha256 对 versions//manifest.json 校验) +# --pick [dev|release] 交互式编号选提交;--dry-run 只打印部署计划 # -# 配置优先级(三类服务一致):命令行 > --config/--env 文件 > svc.env 持久化的上次配置 -# > SVC__* 系统环境变量(.svc.env 同)> 默认值。裸 start/restart 复用上次持久化 -# 配置(svc.env exec= 等),显式参数出现即覆盖。 -# -# 常用参数(服务级): -# --config FILE / --env FILE dotenv 配置文件(KEY=VALUE;SVC__ADDR/DSN/...) +# ════════════════════ 常用参数(服务级)════════════════════ +# --config FILE / --env FILE dotenv 配置文件(KEY=VALUE;--env 为兼容别名) # --addr host:port / --host H / --port P 监听地址(pg=TCP,static=HTTP) -# --tag T 标签(可重复;登记于 svc.env,list --tag 过滤) -# --kind agent|static deploy 时指定类型(postgres 用 db create 建) -# --from SOURCE 部署来源(见上);--dir PATH(static)直接服务目录 -# --dry-run 只打印部署计划;--pick [dev|release] 交互式选提交 -# --plugins/--dsn/--auth/--mock/--data 与 --model-* / --light-* / --tavily-keys -# (agent 播种/运行参数)同上一版 -# -- <剩余参数> 原样透传给 agent-system serve(escape hatch) -# db create 专属:--user U(默认 agent)--password P(缺省自动生成,db dsn --reveal 查看) -# --db NAME(默认 agent)--auth-mode scram-sha-256|md5(默认 scram) -# rm 专属:--purge 删除 runtime// 整个目录(含数据与历史) +# --tag T 标签(可重复;登记于 svc.env,list --tag 过滤) +# --kind agent|static deploy 时指定类型(postgres 用 db create 建) +# --dir PATH static 直接服务既有目录(不构建) +# --plugins/--dsn/--auth/--mock/--data agent 运行参数 +# --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 +# db backup:--db X --out F db restore:--file F --db X --force +# db clone:--port P --host H --password PW --tag T --online +# db copy:--db X --force set:--port N --host H --apply --tag T # -# 示例: +# ════════════════════ 配置优先级 ════════════════════ +# 命令行 > --config/--env 文件 > svc.env 持久化(exec=/addr=)> SVC__* 环境变量 +# (.svc.env 自动加载,同层)> 默认值。裸 start/restart 复用上次启动配置;显式参数 +# 出现即覆盖。任意环境 X 的变量名:SVC__ADDR/DATA/DSN/AUTH/MOCK。 +# +# ════════════════════ 服务模型与登记 ════════════════════ +# - 服务名 ^[a-z][a-z0-9-]{0,15}$(保留字不可用);dev=8788 / prod=8787 默认端口, +# 其余按名字确定性派生(8800-9099,避让已登记/监听端口),SVC__ADDR 可覆盖 +# - runtime//:bin|htdocs|data|pgdata|plugins|backups + svc.pid + +# svc.log|pg.log + svc.env(登记+持久化配置,600)+ history.jsonl(事件流) +# - kind:agent(后端单二进制,内嵌前端)/ static(前端静态服务)/ postgres(数据库); +# 无 kind= 的旧登记自动视为 agent;start/restart 使用环境内产物 +# +# ════════════════════ 示例 ════════════════════ # scripts/svc.sh db create pg1 --port 8890 --tag core # 建库 # scripts/svc.sh pg1 start && scripts/svc.sh db dsn pg1 # 起库 + 看 DSN # scripts/svc.sh prod deploy --from release@0.1.0 --tag stable # scripts/svc.sh prod deploy --from dev@ --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" # scripts/svc.sh ui-r4 deploy --kind static --from dev@ --tag ui # scripts/svc.sh ui-qa deploy --kind static --dir ../some/dist +# scripts/svc.sh set pg1 --port 8895 --apply # 换端口并重启生效 +# scripts/svc.sh db backup pg1 && scripts/svc.sh db backups pg1 +# scripts/svc.sh db clone pg1 pg2 --port 8896 # 物理克隆一套到新端口 +# 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 # scripts/svc.sh pg1 restart && scripts/svc.sh rm tmp1 --purge -# scripts/svc.sh set pg1 --port 8895 --apply # 换端口并重启生效(登记/DSN 联动) -# scripts/svc.sh db backup pg1 && scripts/svc.sh db backups pg1 -# scripts/svc.sh db clone pg1 pg2 --port 8896 # 物理克隆一套到新端口 -# scripts/svc.sh db copy pg1 pg2 --db app # 把 pg1 的 app 库搬到 pg2 -# scripts/svc.sh db restore pg2 --file --db app --force +# make svc ENV=dev ARGS="--env .env.dev --port 9000" # -# 安全约束: +# ════════════════════ 安全约束 ════════════════════ # - 只写 runtime// 与 runtime/.worktrees/;不改主检出 git 状态、不动他人现场; # --from local 是唯一写主检出构建产物(bin/、node_modules/、dist/,均 gitignore) # 的路径,dev@sha/release/static 构建全部在隔离 worktree; # - 只停自己启动的进程:pidfile + /proc//cmdline 标记双校验;postgres 走 # pg_ctl -m fast 优雅停机,SIGKILL 不用于 postmaster; # - 端口被非本脚本进程占用时报错并给出占用者,不抢不杀; -# - svc.env 含密码时权限 600;DSN 默认掩码密码(--reveal 才显示); +# - svc.env/备份文件权限 600;DSN 默认掩码密码(--reveal 才显示); # - 备份/克隆只增不删;restore/copy 覆盖非空库必须显式 --force(drop 重建再恢复); -# 备份文件在 runtime//backups/(600),随 rm --purge 一并删除; +# 备份文件在 runtime//backups/,随 rm --purge 一并删除; # - 物理克隆期间源实例短暂停止(一致性要求),完成后自动复启;--online 不停源。 +# +# 完整文档:docs/svc.md # <> set -euo pipefail @@ -94,6 +102,320 @@ usage() { sed -n '2,/^# <>$/p' "${BASH_SOURCE[0]}" | sed '$d;s/^# \{0,1\}//' } +# usage_short:一屏速览(无参数 / -h / 用法错误时显示;完整帮助走 --help / help <主题>)。 +usage_short() { + cat >&2 <<'EOF' +svc.sh —— 本机服务管理:后端(agent) / 前端(static) / 数据库(postgres) 统一部署、生命周期与运维 + +常用: + scripts/svc.sh list [--tag T] 服务总览 + scripts/svc.sh deploy --from dev@ 按提交部署并启动(agent/static) + scripts/svc.sh start|stop|restart|logs [N] 生命周期与日志 + scripts/svc.sh status 服务详情(版本/来源/DSN 等) + 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 改标签) + scripts/svc.sh rm pg1 [--purge] 注销(--purge 连数据删除) + scripts/svc.sh commits [dev|release] [N] 可部署提交 + scripts/svc.sh history [name] [N] 事件历史 + +帮助:--help 完整帮助 | help [主题] 分主题(model deploy lifecycle db dbops config examples safety) +文档:docs/svc.md +EOF +} + +# HELP_TOPICS:help 的主题清单(cmd_help 与提示共用)。 +HELP_TOPICS="model deploy lifecycle db dbops config examples safety" + +cmd_help() { + local topic="${1:-}" + case "$topic" in + ""|topics|list) + cat >&2 <;完整 --help;文档 docs/svc.md) + + model 服务模型:命名、目录布局、svc.env 字段、history 事件、pidcheck + deploy 部署:三来源(local/dev@sha/release@v)、--pick、--dry-run、static --dir + lifecycle 生命周期:start/stop/restart/logs/status、set 变更、rm 注销 + db 数据库服务:create/start/dsn/ls/set(换端口) + dbops 数据库运维:backup/backups/restore/clone/copy + config 配置分层:优先级、--config 文件、SVC_* 环境变量、播种参数 + examples 配方:常见场景的完整命令序列 + safety 安全约束与边界 +EOF + return 0 ;; + model) + cat >&2 <<'EOF' +【model】服务模型与登记 + +服务名:^[a-z][a-z0-9-]{0,15}$(小写字母开头,可含数字与连字符,≤16 位)。 +保留字不可用作服务名:status list add set db rm create dsn commits history +deploy stop start restart logs help version。 + +目录布局(每个服务自包含,互不串版本): + runtime// + bin/agent-system agent 产物(deploy 复制就位) + htdocs/ static 前端产物(--dir 模式则登记外部目录) + data/ agent 数据目录(SQLite 与工件;--data 可改) + pgdata/ postgres 数据目录(initdb 产物) + plugins/ agent 插件(随 deploy 复制) + backups/ pg_dump 备份(0600) + svc.pid pidfile(只停 cmdline 校验通过的进程) + svc.log | pg.log 日志(postgres 用 pg.log) + svc.env 登记 + 持久化配置(dotenv,600) + history.jsonl 事件流(append-only,一行一个 JSON) + +svc.env 字段: + kind agent|static|postgres(旧登记无此键视为 agent) + env 服务名 + pid 最近启动的 pid + addr 监听地址 host:port(持久化——下次 start 复用) + tags 标签(逗号分隔) + source 部署来源(local@ | dev@ | release@<版本> | dir@<路径> | + postgres@local | clone@<名>) + version 产物版本串(agent-system version 输出) + started 最近启动时间 + pidcheck 进程判定标记(agent=二进制路径、postgres=pgdata 路径、static=--directory) + exec agent 上次完整启动参数(裸 start/restart 复用;显式参数出现即重建) + user/password/dbname/authmode/pgdata postgres 专属(密码 600 保存) + htdocs static 专属 + +history.jsonl 事件(scripts/svc.sh history 查看): + create db create / 物理克隆登记 + deploy begin|ok|fail(含来源) + start ok|fail|warn(fail 含原因:进程即退/健康检查超时) + stop ok|fail + set 登记变更(addr 旧 → 新) + backup ok|fail(文件与大小) + restore ok|fail(库、文件、表数) + clone ok|fail(物理/在线) + copy ok|fail + rm 注销(purge 与否) + +端口规则:dev 默认 127.0.0.1:8788、prod 127.0.0.1:8787;其余按名字确定性派生 +(8800-9099,与已登记端口/监听端口冲突时递增扫描);SVC__ADDR 显式覆盖。 +EOF + return 0 ;; + deploy) + cat >&2 <<'EOF' +【deploy】部署与启动(agent / static;省略 action 即 deploy) + +语法:scripts/svc.sh deploy [--kind agent|static] [--from 源] [参数...] + +部署来源(--from,默认 local): + local 构建当前工作区(make build / make frontend),产物复制到 + 服务目录。注意:这是唯一会写主检出构建产物(bin/、 + node_modules/、dist/——均 gitignore)的路径。 + dev[@] 在隔离 worktree(runtime/.worktrees/)检出该提交, + 递归物化子模块组合(精确落位 gitlink),make build/frontend, + 产物复制后回收 worktree。子模块对象缺失时需先 git fetch。 + release[@<版本|sha>] 取 Releases 仓 build//agent-system(+plugins), + sha256 对 versions//manifest.json 校验,不符即拒绝。 + --pick [dev|release] 交互式编号选择(需 TTY;列表与提示走 stderr) + --dry-run 只打印部署计划不执行 + +行为细节: + - deploy 前自动停止旧实例(只停自己 pidfile+cmdline 校验的进程) + - agent 启动后等健康检查(20s),随后按 --model-*/--tavily-keys 播种设置(幂等) + - 旧版本产物不认识 --dsn/--auth 时自动省略(探测 serve 用法)并提示 + - static 的 release 源不支持(制品内嵌前端);--dir PATH 可直接登记并服务 + 既有目录(不构建、不复制,source=dir@<路径>) + - deploy 全程事件入 history.jsonl(begin/ok/fail) + +示例: + scripts/svc.sh prod deploy --from release@0.1.0 --tag stable + scripts/svc.sh prod deploy --from dev@73b2802 --dry-run + scripts/svc.sh ui-r4 deploy --kind static --from dev@73b2802 + scripts/svc.sh ui-qa deploy --kind static --dir ../frontend/dist +EOF + return 0 ;; + lifecycle) + cat >&2 <<'EOF' +【lifecycle】生命周期与登记变更 + + scripts/svc.sh start 启动(agent/static/postgres 分别分派;已在运行则提示) + scripts/svc.sh stop 停止(agent/static: TERM→KILL;postgres: pg_ctl -m fast) + scripts/svc.sh restart 停止 + 启动(裸 restart 复用上次配置) + 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 + scripts/svc.sh history [name] [N] 事件历史(首参为纯数字时视为条数) + +变更登记(不动进程,除 --apply): + scripts/svc.sh set --tag T... 三类服务通用,合并去重 + scripts/svc.sh set --port N [--host H] 未运行:直接改登记 + scripts/svc.sh set --port N --apply 运行中:停止→改登记→重启 + - 运行中的 postgres 改端口必须 --apply(防止登记与实际监听不一致) + - agent/static 换端口用 restart --port N(进程与登记同步更新) + - 新端口会先校验:未被其他服务登记占用且未在监听 + +注销: + scripts/svc.sh rm 停止并移除登记(目录/数据/历史保留) + scripts/svc.sh rm --purge 连 runtime// 整个目录一起删除 +EOF + return 0 ;; + db) + cat >&2 <<'EOF' +【db】数据库服务:创建 / 连接 / 换端口 + + scripts/svc.sh db create [--port P] [--host H] [--user U] [--password P] + [--db NAME] [--auth-mode scram-sha-256|md5] [--tag T...] + initdb 到 runtime//pgdata(目录 700,密码文件即用即删); + 默认用户/库均为 agent,缺省密码自动生成(db dsn --reveal 查看); + 只登记不启动——scripts/svc.sh start 启动。 + scripts/svc.sh start|stop|restart + pg_ctl 生命周期:start 带 -p -k <环境目录>(socket 不落 pgdata)、 + listen_addresses 随 --host;首次启动自动创建登记库(已存在则跳过); + 就绪判定 pg_isready;stop 用 -m fast 优雅回写,失败退 SIGTERM,绝不 KILL。 + scripts/svc.sh db dsn [--reveal] [--db X] + 连接串 postgresql://user:pass@host:port/db;默认掩码密码(stdout 纯净可组合)。 + scripts/svc.sh db ls + 实例内数据库列表(须运行中)。 + scripts/svc.sh db set [...] / scripts/svc.sh set [...] + 换端口/主机/标签:见 help lifecycle(运行中改端口需 --apply)。 + +连接其他服务:DSN 可直接组合进 agent 部署—— + scripts/svc.sh prod deploy --from dev@ --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" + (旧版本产物不识别 --dsn 时会自动省略并提示。) + +密码管理:密码明文存 runtime//svc.env(600);--password 未给时自动生成 +(head -c 18 /dev/urandom)。auth-mode 默认 scram-sha-256(pg_hba 全线)。 +EOF + return 0 ;; + dbops) + cat >&2 <<'EOF' +【dbops】数据库运维:备份 / 恢复 / 克隆 / 搬库 + +备份: + scripts/svc.sh db backup [--db X] [--out FILE] + pg_dump -Fc 走 TCP(实例须运行中)→ 默认 runtime//backups/-<库>.dump + (目录 700、文件 600);完成后 pg_restore --list 自检归档,失败即删。 + scripts/svc.sh db backups + 列出备份(时间 / 大小 / 文件名)。 + +恢复(唯一可能覆盖数据的操作,非空库必须 --force): + scripts/svc.sh db restore --file F [--db X] [--force] + 目标实例须运行中;F 须为合法 pg_dump 归档(先自检); + 目标库不存在→建库后恢复;存在但空→直接恢复;存在且非空→拒绝(--force 才 + DROP WITH (FORCE) + 重建 + 恢复);完成后回显表数。 + +克隆(整实例,绑定新端口): + scripts/svc.sh db clone [--port P] [--host H] [--password PW] [--tag T] [--online] + 默认物理克隆:短暂停止源(一致性)→ cp -a pgdata(剔除 postmaster.pid/.opts)→ + 登记目标(新端口:--port 或派生;用户/库/认证继承,密码可换;标签继承+可加; + source=clone@)→ 自动复启源。目标不自动启动(提示 start 命令)。 + --online:不停源,dump/restore 逻辑复制源登记库到目标——要求目标已 + db create + start。 + +搬库(单库、跨实例): + scripts/svc.sh db copy [--db X] [--force] + 双方都须运行中;--db X 同时命名源与目标库(默认取源登记库); + 目标库非空须 --force(语义同 restore)。 + +注意:备份/克隆只增不删;备份随 rm --purge 一并删除;所有操作入 history.jsonl。 +EOF + return 0 ;; + config) + cat >&2 <<'EOF' +【config】配置分层与持久化 + +优先级(高 → 低): + 1. 命令行参数(--port/--dsn/--tag/...) + 2. --config FILE(或 --env FILE,兼容别名;=FILE 写法亦可) + 3. svc.env 持久化(addr=、exec=——上次启动的实际参数) + 4. SVC__* 环境变量与 .svc.env(自动加载,同层) + 5. 默认值(dev=8788 / prod=8787 / 其余派生端口) + +dotenv 格式:KEY=VALUE,支持 # 注释、成对引号、export 前缀;只识别合法变量名, +不 source 执行文件内容。建议 .env.dev/.env.prod 等加入 gitignore。 + +环境变量(X 为服务名大写,- → _): + SVC_X_ADDR / SVC_X_DATA / SVC_X_DSN / SVC_X_AUTH / SVC_X_MOCK + SVC_RELEASES_DIR Releases 仓路径(默认 ../Agent-System-Releases) + SVC_PG_BIN postgres 工具链 bindir(默认 pg_config --bindir 探测) + SVC_MODEL_BASE/KEY/NAME 主模型(agent 启动健康后播种) + SVC_LIGHT_BASE/KEY/NAME 轻量模型 + SVC_TAVILY_KEYS Tavily 密钥池 +播种幂等:设置持久化在 agent 数据目录,播过一次后续启动无需再传。 + +持久化语义: + - deploy/start 成功后把当前态写入 svc.env(600) + - 裸 start/restart 复用 exec=(上次完整参数);任何显式参数(或 --config 文件、 + SVC_* 变量)出现即整体重建参数 + - tags 随每次登记合并去重,跨重启保留 +EOF + return 0 ;; + examples) + cat >&2 <<'EOF' +【examples】常见配方 + +① 起一套演示后端(当前工作区构建): + scripts/svc.sh dev deploy --mock +② 建库 + 后端连库: + scripts/svc.sh db create pg1 --port 8890 + scripts/svc.sh pg1 start + scripts/svc.sh prod deploy --from dev@ \ + --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" +③ 按提交部署 + 回退: + scripts/svc.sh commits dev # 看可部署提交 + scripts/svc.sh v1 deploy --from dev@73b2802 --tag test + scripts/svc.sh v1 deploy --from release@0.1.0 # 回退到发布版本 +④ 双版本对比(同机并行): + scripts/svc.sh a deploy --from dev@ --port 8801 + scripts/svc.sh b deploy --from dev@ --port 8802 +⑤ 前端静态服务: + scripts/svc.sh ui deploy --kind static --from dev@ # 构建并服务 dist + scripts/svc.sh ui2 deploy --kind static --dir ../frontend/dist # 直接服务目录 +⑥ 备份 → 改坏数据 → 恢复: + scripts/svc.sh db backup pg1 + ... (随意操作) + scripts/svc.sh db restore pg1 --file runtime/pg1/backups/<最新>.dump --force +⑦ 克隆一套隔离环境(不动原库): + scripts/svc.sh db clone pg1 pg1test --port 8896 + scripts/svc.sh pg1test start + scripts/svc.sh qa deploy --from dev@ --dsn "$(scripts/svc.sh db dsn pg1test --reveal)" +⑧ 把开发库搬到 prod 实例: + scripts/svc.sh db copy pgdev pgprod --db app --force +⑨ 换端口: + scripts/svc.sh set pg1 --port 8895 --apply +⑩ 总览与追溯: + scripts/svc.sh list --tag test + scripts/svc.sh history pg1 50 +EOF + return 0 ;; + safety) + cat >&2 <<'EOF' +【safety】安全约束与边界 + +写入边界: + - 只写 runtime// 与 runtime/.worktrees/(构建 worktree,用后即收); + - 不改主检出 git 状态;--from local 是唯一写主检出构建产物(bin/、node_modules/、 + dist/——均 gitignore)的路径; + - rm --purge 只删 runtime//,且要求服务已登记。 + +进程边界: + - 只停"自己 pidfile + /proc cmdline 标记双校验"都通过的进程; + - postgres 走 pg_ctl -m fast,失败退 SIGTERM,SIGKILL 不用于 postmaster; + - agent/static:TERM 优雅退出,10s 未退才 KILL。 + +端口边界: + - 被非本脚本进程占用:报错并给出占用者,不抢不杀; + - 显式选端口(--port / set --port / clone --port)会同时查登记与监听; + - 派生端口在 8800-9099 内避让已登记与监听端口。 + +秘密与数据: + - svc.env(含 DB 密码)与备份文件权限 600;数据/环境目录 700; + - DSN 默认掩码密码,--reveal 才显示; + - restore/copy 覆盖非空库必须显式 --force;备份/克隆只增不删; + - dotenv 解析不执行文件内容(防注入)。 +EOF + return 0 ;; + *) + c_err "未知帮助主题:$topic(可选:$HELP_TOPICS;总览:scripts/svc.sh help)" + return 2 ;; + esac +} + # load_env_file FILE:dotenv 安全解析——逐行 KEY=VALUE 赋值(剥 export 前缀与成对 # 引号,忽略注释/空行/非法行),不 source 执行文件内容。 load_env_file() { @@ -165,7 +487,7 @@ parse_args() { --auth-mode) need_value "$@"; CLI_DB_AUTH="$2"; shift 2 ;; --) shift; CLI_EXTRA="$*"; break ;; -h|--help) usage; exit 0 ;; - *) c_err "未知参数:$1"; usage; exit 2 ;; + *) c_err "未知参数:$1"; usage_short; exit 2 ;; esac done } @@ -1710,7 +2032,7 @@ cmd_history() { # ---- main ---- main() { - [ $# -ge 1 ] || { usage; exit 2; } + [ $# -ge 1 ] || { usage_short; exit 2; } # 预扫 --config/--env(FILE 与 =FILE 两种写法,可出现在任意位置)。 local svc_envfile="" pre=() while [ $# -gt 0 ]; do @@ -1725,7 +2047,9 @@ main() { [ -f "$ROOT/.svc.env" ] && load_env_file "$ROOT/.svc.env" [ -n "$svc_envfile" ] && load_env_file "$svc_envfile" case "$1" in - -h|--help|help) usage; exit 0 ;; + -h) usage_short; exit 0 ;; + --help) usage; exit 0 ;; + help) shift; cmd_help "$@"; exit $? ;; esac case "$1" in list|status)