Document svc.sh comprehensively and layer its help system

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 描述)
This commit is contained in:
2026-09-30 14:29:39 +00:00
parent 96dfac6bc4
commit 2c7f55bfe8
5 changed files with 982 additions and 64 deletions
+20 -5
View File
@@ -6,11 +6,21 @@
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 22d14db80c31e4bcfa24411f847018f527b16a01 - 权威仓: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 - 工作区:/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 补全(可作后续项)。
+1 -1
View File
@@ -48,7 +48,7 @@ help:
@echo "make svc 服务级操作:make svc ENV=<name> [ACTION=deploy|start|stop|restart|status|logs] [ARGS='--from dev@<sha> --tag ...']" @echo "make svc 服务级操作:make svc ENV=<name> [ACTION=deploy|start|stop|restart|status|logs] [ARGS='--from dev@<sha> --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-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-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 dev 开发模式说明(前后端分离热更新)"
@echo "make pin 推进 submodule 指针到各仓远端 main(提交前须 make test 通过)" @echo "make pin 推进 submodule 指针到各仓远端 main(提交前须 make test 通过)"
@echo "make status submodule 状态" @echo "make status submodule 状态"
+1
View File
@@ -37,3 +37,4 @@ make dev # 开发模式说明(前后端分离热更新)
## 文档 ## 文档
设计方案:`modules/documents/docs/proposals/2026-09-29-system-skeleton-design-and-plan.md`(含 D1–D16 决策记录)。 设计方案:`modules/documents/docs/proposals/2026-09-29-system-skeleton-design-and-plan.md`(含 D1–D16 决策记录)。
本机服务管理(后端/前端/数据库部署、生命周期、备份克隆):`docs/svc.md`(`scripts/svc.sh`)。
+578
View File
@@ -0,0 +1,578 @@
# svc.sh 使用说明
`scripts/svc.sh` 是本仓的**本机服务管理器**:把 后端(agent)、前端静态服务(static)、
PostgreSQL 数据库(postgres)三类服务统一为"服务"模型——按提交/版本部署、生命周期管理、
配置分层持久化、标签、事件追溯,以及一套完整的数据库运维能力(换端口 / 备份 / 恢复 /
克隆 / 跨实例搬库)。
设计原则(也是安全边界,详见 [§11 安全与边界](#11-安全与边界)):
- **服务自包含**:每个服务的产物、数据、日志、登记都在 `runtime/<name>/` 一个目录里,
服务之间互不串版本;删目录 = 完全移除。
- **登记持久化**:所有状态落盘(`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@<sha> \
--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 <dump> --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_<NAME>_ADDR` 可显式覆盖。
### 3.2 目录布局
每个服务自包含于一个目录(`--purge` 删目录即完全移除):
```
runtime/<name>/
├── 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. 命令参考:服务级
以下 `<name>` 均指服务名;`action` 可省略的只有 `deploy`。
### 4.1 deploy(部署并启动)
```
scripts/svc.sh <name> deploy [--kind agent|static] [--from 源] [--tag T]... [参数...]
```
流程:解析来源 →(dry-run 则只打印计划)→ 停旧实例 → 物化/构建/校验 → 产物复制到
`runtime/<name>/` → 启动 → 健康等待(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@<sha> # 构建该提交的 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/<name>/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 <name> start # 已在运行则提示并返回 0
scripts/svc.sh <name> stop # agent/static: TERM→10s→KILL;postgres: pg_ctl -m fast
scripts/svc.sh <name> restart # stop + start
scripts/svc.sh <name> logs [N] # tail -f,从最近 N 行起(默认 50)
scripts/svc.sh <name> 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 <name> --tag T... # 三类服务通用:改标签(不动进程)
scripts/svc.sh set <pg名> --port N [--host H] # 未运行:直接改登记
scripts/svc.sh set <pg名> --port N --apply # 运行中:停止 → 改登记 → 重启
scripts/svc.sh set <pg名> --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 <name> # 停止 + 移除登记(svc.env/svc.pid);目录数据历史保留
scripts/svc.sh rm <name> --purge # 连 runtime/<name>/ 整个目录一起删除(含备份!)
```
只管理本脚本登记过的服务;未登记名字会明确报错。
## 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@\<sha\> —— Dev 仓提交
```
--from dev # 当前 HEAD
--from dev@<sha> # 指定提交(短/长 sha 均可)
```
流程:`git worktree add --detach runtime/.worktrees/<sha> <sha>` → 递归
`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 仓提交也可
```
制品取 `<Releases>/build/<v>/agent-system`(+ `plugins/`),**sha256 对
`versions/<v>/manifest.json` 校验,不符即拒绝部署**;并回显该版本对应的 Dev 提交。
仓库定位:`SVC_RELEASES_DIR` > `../Agent-System-Releases`。制品缺失时提示先在
Releases 仓跑 `scripts/build-release.sh <v>`。
### 5.4 辅助
```bash
scripts/svc.sh commits [dev|release] [N] # 可部署提交表(默认 20 条)
scripts/svc.sh <name> deploy --pick # 交互式编号选择
scripts/svc.sh <name> 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/<pid>/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 <pw>] [--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 <port> -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/<UTC时间戳>-<库>.dump`;
- 完成后 `pg_restore --list` 自检归档有效性,失败即删除该文件并记 `backup fail`;
- 备份**只增不删**;随 `rm --purge` 一并删除。
### 8.2 恢复
```bash
scripts/svc.sh db restore pg1 --file <dump> # 恢复到登记库
scripts/svc.sh db restore pg1 --file <dump> --db app2 # 恢复到指定库
scripts/svc.sh db restore pg1 --file <dump> --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_<NAME>_* 环境变量(.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_<NAME>_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@<sha>` / `dev@<sha>` / `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/<name>/` 与 `runtime/.worktrees/`;不改主检出 git 状态;
`--from local` 是唯一写主检出构建产物的路径(均 gitignore 目录);`rm --purge` 只删
已登记服务的 `runtime/<name>/`。
- **进程边界**:只停"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 <name> logs 200` / postgres 看 `runtime/<name>/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/<sha>` |
| `子模块物化失败` | 子模块对象缺失:先 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@<sha> --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@<shaA> --port 8801
scripts/svc.sh b deploy --from dev@<shaB> --port 8802
# ⑤ 前端静态服务
scripts/svc.sh ui deploy --kind static --from dev@<sha>
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@<sha> --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 仓)。命令行为以实现为准;发现文档与
实现不一致时,以任务卡记录的方式提出修正。*
+382 -58
View File
@@ -1,81 +1,89 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# svc.sh —— 本机服务管理(服务模型 v2):按提交/版本部署并启动 后端(agent) / 前端(static) # svc.sh —— 本机服务管理:后端(agent) / 前端(static) / 数据库(postgres) 三类服务的
# / 数据库(postgres) 三类服务,统一登记(svc.env + history.jsonl)、标签、配置分层、 # 部署、生命周期与数据库运维。每个服务自包含于 runtime/<name>/,登记与配置持久化
# 生命周期(start/stop/restart/rm)与数据库运维(换端口/备份/恢复/克隆/搬库), # (svc.env + history.jsonl),全程不触碰主检出 git 状态与其他已启动服务。
# 全程不触碰主检出与其他已启动服务。
# #
# 用法: # ════════════════════ 命令一览 ════════════════════
# scripts/svc.sh <name> [action] [参数...] # 服务级操作(name 见命名规则) # scripts/svc.sh <name> [action] [参数...] 服务级:deploy(默认)|start|stop|restart|status|logs [N]
# action:deploy(默认,agent/static)| start | stop | restart | status | logs [行数] # scripts/svc.sh list [--tag T] 服务总览(status 等价)
# scripts/svc.sh list [--tag T] # 全服务总览(status 等价) # scripts/svc.sh set <name> [--port N --apply] [--tag T] 变更登记(换端口/标签)
# scripts/svc.sh db create <name> [参数] # 创建 PostgreSQL 服务(initdb) # scripts/svc.sh rm <name> [--purge] 注销服务(--purge 连数据目录删除)
# scripts/svc.sh db dsn <name> [--reveal] [--db X] # 打印数据库连接串(默认掩码密码) # scripts/svc.sh db create <name> [参数] 创建 PostgreSQL 实例(initdb)
# scripts/svc.sh db backup <name> [--db X] [--out F] # pg_dump 备份(自动自检归档) # scripts/svc.sh db dsn <name> [--reveal] [--db X] 连接串(默认掩码密码)
# scripts/svc.sh db backups <name> # 列出已有备份 # scripts/svc.sh db backup <name> [--db X] [--out F] 备份(pg_dump -Fc + 自检)
# scripts/svc.sh db restore <name> --file F [--db X] [--force] # 恢复(覆盖非空库须 --force) # scripts/svc.sh db backups <name> 备份列表
# scripts/svc.sh db clone <src> <dst> [--port P] [--password PW] [--online] # 克隆实例 # scripts/svc.sh db restore <name> --file F [--db X] [--force] 恢复(覆盖非空库须 --force)
# scripts/svc.sh db copy <src> <dst> [--db X] [--force] # 跨实例搬库(双方须运行) # scripts/svc.sh db clone <src> <dst> [--port P] [--password PW] [--online] 克隆实例
# scripts/svc.sh db ls <name> # 列出实例内数据库 # scripts/svc.sh db copy <src> <dst> [--db X] [--force] 跨实例搬库(双方须运行)
# scripts/svc.sh set <name> [--port N --apply] [--tag T] # 变更登记(换端口/标签) # scripts/svc.sh db ls <name> 实例内数据库列表
# scripts/svc.sh rm <name> [--purge] # 注销服务(--purge 连数据一起删) # scripts/svc.sh db set <name> [...] 等价于 set
# scripts/svc.sh commits [dev|release] [N] # 列出可部署提交(默认 dev,N=20) # scripts/svc.sh commits [dev|release] [N] 可部署提交列表(默认 dev,N=20)
# scripts/svc.sh history [name] [N] # 事件历史(默认全部,最近 20 条) # scripts/svc.sh history [name] [N] 事件历史(默认全部,最近 20 条)
# scripts/svc.sh help [主题] 分主题详细帮助
# #
# 服务模型: # ════════════════════ 部署来源(agent/static 的 deploy)════════════════════
# - name:^[a-z][a-z0-9-]{0,15}$(保留字不可用);dev/prod 有默认端口,其余按名字 # --from local 构建当前工作区(唯一写主检出构建产物的路径,均 gitignore)
# 确定性派生(8800-9099,避让已登记/监听端口),SVC_<NAME 大写>_ADDR 可覆盖 # --from dev[@<sha>] 隔离 worktree(runtime/.worktrees/<sha>)物化该提交的
# - 每个服务自包含于 runtime/<name>/:产物(bin/htdocs)/数据(data/pgdata)/svc.pid/ # 子模块组合并构建,不触碰当前工作区
# svc.log|pg.log/svc.env(登记+持久化配置,600)/history.jsonl(追加事件流) # --from release[@<版本|sha>] Releases 仓制品(sha256 对 versions/<v>/manifest.json 校验)
# - kind(svc.env kind=,缺省 agent,兼容旧登记): # --pick [dev|release] 交互式编号选提交;--dry-run 只打印部署计划
# agent 后端单二进制(内嵌前端);deploy 三源:local | dev[@<sha>] | release[@<v>]
# static 前端静态服务;deploy --from dev[@sha]|local 构建 frontend dist 复制到
# htdocs/,或 deploy --dir PATH 直接服务既有目录
# postgres 数据库;db create 后 start/stop/restart(pg_ctl 生命周期,绝不裸杀)
# #
# 配置优先级(三类服务一致):命令行 > --config/--env 文件 > svc.env 持久化的上次配置 # ════════════════════ 常用参数(服务级)════════════════════
# > SVC_<NAME>_* 系统环境变量(.svc.env 同)> 默认值。裸 start/restart 复用上次持久化 # --config FILE / --env FILE dotenv 配置文件(KEY=VALUE;--env 为兼容别名)
# 配置(svc.env exec= 等),显式参数出现即覆盖。
#
# 常用参数(服务级):
# --config FILE / --env FILE dotenv 配置文件(KEY=VALUE;SVC_<NAME>_ADDR/DSN/...)
# --addr host:port / --host H / --port P 监听地址(pg=TCP,static=HTTP) # --addr host:port / --host H / --port P 监听地址(pg=TCP,static=HTTP)
# --tag T 标签(可重复;登记于 svc.env,list --tag 过滤) # --tag T 标签(可重复;登记于 svc.env,list --tag 过滤)
# --kind agent|static deploy 时指定类型(postgres 用 db create 建) # --kind agent|static deploy 时指定类型(postgres 用 db create 建)
# --from SOURCE 部署来源(见上);--dir PATH(static)直接服务目录 # --dir PATH static 直接服务既有目录(不构建)
# --dry-run 只打印部署计划;--pick [dev|release] 交互式选提交 # --plugins/--dsn/--auth/--mock/--data agent 运行参数
# --plugins/--dsn/--auth/--mock/--data 与 --model-* / --light-* / --tavily-keys # --model-base/--model-key/--model-name 与 --light-*/--tavily-keys 启动后播种设置
# (agent 播种/运行参数)同上一版 # -- <剩余参数> 原样透传给 agent-system serve(escape hatch)
# -- <剩余参数> 原样透传给 agent-system serve(escape hatch) # db create:--user U(默认 agent)--password P(缺省自动生成)--db NAME --auth-mode scram-sha-256|md5
# db create 专属:--user U(默认 agent)--password P(缺省自动生成,db dsn --reveal 查看) # db backup:--db X --out F db restore:--file F --db X --force
# --db NAME(默认 agent)--auth-mode scram-sha-256|md5(默认 scram) # db clone:--port P --host H --password PW --tag T --online
# rm 专属:--purge 删除 runtime/<name>/ 整个目录(含数据与历史) # db copy:--db X --force set:--port N --host H --apply --tag T
# #
# 示例: # ════════════════════ 配置优先级 ════════════════════
# 命令行 > --config/--env 文件 > svc.env 持久化(exec=/addr=)> SVC_<NAME>_* 环境变量
# (.svc.env 自动加载,同层)> 默认值。裸 start/restart 复用上次启动配置;显式参数
# 出现即覆盖。任意环境 X 的变量名:SVC_<X 大写,- → _>_ADDR/DATA/DSN/AUTH/MOCK。
#
# ════════════════════ 服务模型与登记 ════════════════════
# - 服务名 ^[a-z][a-z0-9-]{0,15}$(保留字不可用);dev=8788 / prod=8787 默认端口,
# 其余按名字确定性派生(8800-9099,避让已登记/监听端口),SVC_<NAME>_ADDR 可覆盖
# - runtime/<name>/: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 db create pg1 --port 8890 --tag core # 建库
# scripts/svc.sh pg1 start && scripts/svc.sh db dsn pg1 # 起库 + 看 DSN # 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 release@0.1.0 --tag stable
# scripts/svc.sh prod deploy --from dev@<sha> --dsn "$(scripts/svc.sh db dsn pg1 --reveal)" # scripts/svc.sh prod deploy --from dev@<sha> --dsn "$(scripts/svc.sh db dsn pg1 --reveal)"
# scripts/svc.sh ui-r4 deploy --kind static --from dev@<sha> --tag ui # scripts/svc.sh ui-r4 deploy --kind static --from dev@<sha> --tag ui
# scripts/svc.sh ui-qa deploy --kind static --dir ../some/dist # 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 <dump> --db app --force
# scripts/svc.sh list --tag core && scripts/svc.sh history # scripts/svc.sh list --tag core && scripts/svc.sh history
# scripts/svc.sh pg1 restart && scripts/svc.sh rm tmp1 --purge # scripts/svc.sh pg1 restart && scripts/svc.sh rm tmp1 --purge
# scripts/svc.sh set pg1 --port 8895 --apply # 换端口并重启生效(登记/DSN 联动) # make svc ENV=dev ARGS="--env .env.dev --port 9000"
# 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 <dump> --db app --force
# #
# 安全约束: # ════════════════════ 安全约束 ════════════════════
# - 只写 runtime/<name>/ 与 runtime/.worktrees/;不改主检出 git 状态、不动他人现场; # - 只写 runtime/<name>/ 与 runtime/.worktrees/;不改主检出 git 状态、不动他人现场;
# --from local 是唯一写主检出构建产物(bin/、node_modules/、dist/,均 gitignore) # --from local 是唯一写主检出构建产物(bin/、node_modules/、dist/,均 gitignore)
# 的路径,dev@sha/release/static 构建全部在隔离 worktree; # 的路径,dev@sha/release/static 构建全部在隔离 worktree;
# - 只停自己启动的进程:pidfile + /proc/<pid>/cmdline 标记双校验;postgres 走 # - 只停自己启动的进程:pidfile + /proc/<pid>/cmdline 标记双校验;postgres 走
# pg_ctl -m fast 优雅停机,SIGKILL 不用于 postmaster; # pg_ctl -m fast 优雅停机,SIGKILL 不用于 postmaster;
# - 端口被非本脚本进程占用时报错并给出占用者,不抢不杀; # - 端口被非本脚本进程占用时报错并给出占用者,不抢不杀;
# - svc.env 含密码时权限 600;DSN 默认掩码密码(--reveal 才显示); # - svc.env/备份文件权限 600;DSN 默认掩码密码(--reveal 才显示);
# - 备份/克隆只增不删;restore/copy 覆盖非空库必须显式 --force(drop 重建再恢复); # - 备份/克隆只增不删;restore/copy 覆盖非空库必须显式 --force(drop 重建再恢复);
# 备份文件在 runtime/<name>/backups/(600),随 rm --purge 一并删除; # 备份文件在 runtime/<name>/backups/,随 rm --purge 一并删除;
# - 物理克隆期间源实例短暂停止(一致性要求),完成后自动复启;--online 不停源。 # - 物理克隆期间源实例短暂停止(一致性要求),完成后自动复启;--online 不停源。
#
# 完整文档:docs/svc.md
# <<usage-end>> # <<usage-end>>
set -euo pipefail set -euo pipefail
@@ -94,6 +102,320 @@ usage() {
sed -n '2,/^# <<usage-end>>$/p' "${BASH_SOURCE[0]}" | sed '$d;s/^# \{0,1\}//' sed -n '2,/^# <<usage-end>>$/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 <name> deploy --from dev@<sha> 按提交部署并启动(agent/static)
scripts/svc.sh <name> start|stop|restart|logs [N] 生命周期与日志
scripts/svc.sh <name> 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 <<EOF
svc.sh 分主题帮助(scripts/svc.sh help <主题>;完整 --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/<name>/
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@<sha> | dev@<sha> | 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_<NAME>_ADDR 显式覆盖。
EOF
return 0 ;;
deploy)
cat >&2 <<'EOF'
【deploy】部署与启动(agent / static;省略 action 即 deploy)
语法:scripts/svc.sh <name> deploy [--kind agent|static] [--from 源] [参数...]
部署来源(--from,默认 local):
local 构建当前工作区(make build / make frontend),产物复制到
服务目录。注意:这是唯一会写主检出构建产物(bin/、
node_modules/、dist/——均 gitignore)的路径。
dev[@<sha>] 在隔离 worktree(runtime/.worktrees/<sha>)检出该提交,
递归物化子模块组合(精确落位 gitlink),make build/frontend,
产物复制后回收 worktree。子模块对象缺失时需先 git fetch。
release[@<版本|sha>] 取 Releases 仓 build/<v>/agent-system(+plugins),
sha256 对 versions/<v>/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 <name> start 启动(agent/static/postgres 分别分派;已在运行则提示)
scripts/svc.sh <name> stop 停止(agent/static: TERM→KILL;postgres: pg_ctl -m fast)
scripts/svc.sh <name> restart 停止 + 启动(裸 restart 复用上次配置)
scripts/svc.sh <name> logs [N] 跟随日志(默认 50 行起;如 svc.sh pg1 logs 200)
scripts/svc.sh <name> 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 <name> --tag T... 三类服务通用,合并去重
scripts/svc.sh set <pg名> --port N [--host H] 未运行:直接改登记
scripts/svc.sh set <pg名> --port N --apply 运行中:停止→改登记→重启
- 运行中的 postgres 改端口必须 --apply(防止登记与实际监听不一致)
- agent/static 换端口用 restart --port N(进程与登记同步更新)
- 新端口会先校验:未被其他服务登记占用且未在监听
注销:
scripts/svc.sh rm <name> 停止并移除登记(目录/数据/历史保留)
scripts/svc.sh rm <name> --purge 连 runtime/<name>/ 整个目录一起删除
EOF
return 0 ;;
db)
cat >&2 <<'EOF'
【db】数据库服务:创建 / 连接 / 换端口
scripts/svc.sh db create <name> [--port P] [--host H] [--user U] [--password P]
[--db NAME] [--auth-mode scram-sha-256|md5] [--tag T...]
initdb 到 runtime/<name>/pgdata(目录 700,密码文件即用即删);
默认用户/库均为 agent,缺省密码自动生成(db dsn --reveal 查看);
只登记不启动——scripts/svc.sh <name> start 启动。
scripts/svc.sh <name> start|stop|restart
pg_ctl 生命周期:start 带 -p <port> -k <环境目录>(socket 不落 pgdata)、
listen_addresses 随 --host;首次启动自动创建登记库(已存在则跳过);
就绪判定 pg_isready;stop 用 -m fast 优雅回写,失败退 SIGTERM,绝不 KILL。
scripts/svc.sh db dsn <name> [--reveal] [--db X]
连接串 postgresql://user:pass@host:port/db;默认掩码密码(stdout 纯净可组合)。
scripts/svc.sh db ls <name>
实例内数据库列表(须运行中)。
scripts/svc.sh db set <name> [...] / scripts/svc.sh set <name> [...]
换端口/主机/标签:见 help lifecycle(运行中改端口需 --apply)。
连接其他服务:DSN 可直接组合进 agent 部署——
scripts/svc.sh prod deploy --from dev@<sha> --dsn "$(scripts/svc.sh db dsn pg1 --reveal)"
(旧版本产物不识别 --dsn 时会自动省略并提示。)
密码管理:密码明文存 runtime/<name>/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 <name> [--db X] [--out FILE]
pg_dump -Fc 走 TCP(实例须运行中)→ 默认 runtime/<name>/backups/<UTC时间戳>-<库>.dump
(目录 700、文件 600);完成后 pg_restore --list 自检归档,失败即删。
scripts/svc.sh db backups <name>
列出备份(时间 / 大小 / 文件名)。
恢复(唯一可能覆盖数据的操作,非空库必须 --force):
scripts/svc.sh db restore <name> --file F [--db X] [--force]
目标实例须运行中;F 须为合法 pg_dump 归档(先自检);
目标库不存在→建库后恢复;存在但空→直接恢复;存在且非空→拒绝(--force 才
DROP WITH (FORCE) + 重建 + 恢复);完成后回显表数。
克隆(整实例,绑定新端口):
scripts/svc.sh db clone <src> <dst> [--port P] [--host H] [--password PW] [--tag T] [--online]
默认物理克隆:短暂停止源(一致性)→ cp -a pgdata(剔除 postmaster.pid/.opts)→
登记目标(新端口:--port 或派生;用户/库/认证继承,密码可换;标签继承+可加;
source=clone@<src>)→ 自动复启源。目标不自动启动(提示 start 命令)。
--online:不停源,dump/restore 逻辑复制源登记库到目标——要求目标已
db create + start。
搬库(单库、跨实例):
scripts/svc.sh db copy <src> <dst> [--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_<NAME>_* 环境变量与 .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@<sha> \
--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@<shaA> --port 8801
scripts/svc.sh b deploy --from dev@<shaB> --port 8802
⑤ 前端静态服务:
scripts/svc.sh ui deploy --kind static --from dev@<sha> # 构建并服务 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@<sha> --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/<name>/ 与 runtime/.worktrees/(构建 worktree,用后即收);
- 不改主检出 git 状态;--from local 是唯一写主检出构建产物(bin/、node_modules/、
dist/——均 gitignore)的路径;
- rm --purge 只删 runtime/<name>/,且要求服务已登记。
进程边界:
- 只停"自己 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 前缀与成对 # load_env_file FILE:dotenv 安全解析——逐行 KEY=VALUE 赋值(剥 export 前缀与成对
# 引号,忽略注释/空行/非法行),不 source 执行文件内容。 # 引号,忽略注释/空行/非法行),不 source 执行文件内容。
load_env_file() { load_env_file() {
@@ -165,7 +487,7 @@ parse_args() {
--auth-mode) need_value "$@"; CLI_DB_AUTH="$2"; shift 2 ;; --auth-mode) need_value "$@"; CLI_DB_AUTH="$2"; shift 2 ;;
--) shift; CLI_EXTRA="$*"; break ;; --) shift; CLI_EXTRA="$*"; break ;;
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
*) c_err "未知参数:$1"; usage; exit 2 ;; *) c_err "未知参数:$1"; usage_short; exit 2 ;;
esac esac
done done
} }
@@ -1710,7 +2032,7 @@ cmd_history() {
# ---- main ---- # ---- main ----
main() { main() {
[ $# -ge 1 ] || { usage; exit 2; } [ $# -ge 1 ] || { usage_short; exit 2; }
# 预扫 --config/--env(FILE 与 =FILE 两种写法,可出现在任意位置)。 # 预扫 --config/--env(FILE 与 =FILE 两种写法,可出现在任意位置)。
local svc_envfile="" pre=() local svc_envfile="" pre=()
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@@ -1725,7 +2047,9 @@ main() {
[ -f "$ROOT/.svc.env" ] && load_env_file "$ROOT/.svc.env" [ -f "$ROOT/.svc.env" ] && load_env_file "$ROOT/.svc.env"
[ -n "$svc_envfile" ] && load_env_file "$svc_envfile" [ -n "$svc_envfile" ] && load_env_file "$svc_envfile"
case "$1" in case "$1" in
-h|--help|help) usage; exit 0 ;; -h) usage_short; exit 0 ;;
--help) usage; exit 0 ;;
help) shift; cmd_help "$@"; exit $? ;;
esac esac
case "$1" in case "$1" in
list|status) list|status)