Files
Agent-System-Dev/docs/svc.md
T
weichen c514bc4f4d Merge authoritative main into the task branch
Why: include the current integration baseline before verification.

Evidence: fetched origin/main=3de5791e0841e9c7c40c3dc236be6d95b3969827.

Impact: integrate upstream changes without rewriting task history.

Refs: task-sync
2026-10-11 21:56:13 +00:00

841 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
# 一套演示后端(缺省部署远端 main 最新:独立部署源仓物化构建 → 启动 → 健康检查;
# 与当前工作区状态无关。要构建当前工作区:--from local)
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 prod db pg1 --db prod # 或事后绑定/切换(缺库自动建,重启生效)
scripts/svc.sh prod config .svc/prod.json # 指定/更换配置文件(校验+重启即时生效)
# 日常操作
scripts/svc.sh list # 全服务总览(含 DB 绑定列)
scripts/svc.sh info # 综合报告:运行服务/数据库/配置快照
scripts/svc.sh prod db # 查看 prod 的数据库绑定与可用实例
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 # 克隆一套到新端口
# 把服务刷到远端最新(不依赖当前工作区状态:主检出脏树、任意分支均可)
scripts/svc.sh update --check # 先看哪些服务落后
scripts/svc.sh update # 自动更新过时且运行中的服务
```
## 3. 概念与目录布局
### 3.1 服务名
`^[a-z][a-z0-9-]{0,15}$`(小写字母开头,可含数字与连字符,≤16 位)。保留字不可用作
服务名:`status list add set db rm create dsn commits history update deploy stop start
restart logs help info 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`) |
| `SVC_<NAME>_INSECURE_LISTEN` | 设为 `1` 时允许该服务在非回环地址上不开启认证。值传给进程的 `AGENT_SYSTEM_INSECURE_LISTEN`,不写进 `exec=`。旧二进制会忽略 |
| `SVC_<NAME>_ALLOWED_HOSTS` | 逗号分隔的额外主机名,传给 `AGENT_SYSTEM_ALLOWED_HOSTS`,供反向代理的 Host / Origin 校验 |
| `--model-base/--model-key/--model-name`、`--light-*`、`--tavily-keys` | 启动健康后播种 |
| `--dry-run` | 只解析并打印部署计划(来源、物化路径、产物去向、启动参数) |
| `--pick [dev\|release\|rdm]` | 交互式编号选提交(需 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>/ 整个目录一起删除(含备份!)
```
只管理本脚本登记过的服务;未登记名字会明确报错。
### 4.5 db(agent 服务的数据库绑定/切换)
```bash
scripts/svc.sh <agent名> db # 查看当前绑定 + 可用实例
scripts/svc.sh <agent名> db <pg名> [--db X] # 绑定/切换数据库
scripts/svc.sh <agent名> db <pg名> --no-restart # 只改登记,下次 start 生效
scripts/svc.sh <agent名> db --clear # 解除绑定(回内嵌 SQLite 档)
```
每个 agent 服务都可以对应一个数据库实例中的库;绑定登记在 `svc.env`
(`dbref=<pg名>/<库名>` + `db=pg`,权限 600),跨重启保留——**DSN 不落 `exec=`**,
启动时由实例登记重建并经 `AGENT_SYSTEM_DSN` 环境变量交付(实例端口/密码变更自动跟随)。
- **缺库自动建**:实例运行中且目标库不存在时自动 `CREATE DATABASE`(幂等);
实例未运行且目标库非其默认库 → 提示先 `start` 实例;
- **运行中自动重启生效**(按新登记重建 DSN);`--no-restart` 只写登记;
- **产物能力预检**:当前二进制不消费环境变量 DSN 时拒绝绑定(先 deploy 新版本);
mock 档与绑定共存同样拒绝(先 `--clear` 或去 mock);
- 展示:`list` 的 **DB 列**、`status` 的"数据库"行、`info` 数据库节的**绑定服务**行
(反向映射:哪些服务指向该实例)都能看到服务↔数据库对应关系;
- 未用本命令、直接 `deploy --dsn` 的服务:命中本机实例登记的会升级为托管绑定
(`dbref=`),否则持久化为 `dsn=` 字段并在显示层标注 `外部@host:port`;
- `--clear` 显式解绑:记录 `db=sqlite`、清除 `dbref`/`dsn`(数据在原 `--data` 目录,
切回即见旧数据);此后残留 `SVC_*_DSN` 不再悄悄重新选库。
### 4.6 config(agent 服务的配置文件更新/生效)
```bash
scripts/svc.sh <agent名> config # 查看当前配置文件(掩码内容 + agent 视角生效值)
scripts/svc.sh <agent名> config <file.json> # 更换使用的配置文件(校验通过才切换)
scripts/svc.sh <agent名> config <file> --no-restart # 只改登记,下次 start 生效
scripts/svc.sh <agent名> config --clear # 移除(回命令行参数形态)
```
配置文件是 agent 二进制的 `--config`(JSON,schema 严格校验;可用
`agent-system config explain -config <file>` 预览每个键的来源与生效值)。
- **切换前强校验**:文件必须存在且通过二进制的 schema 校验(未知键/非法值直接拒绝,
不会把服务重启进坏配置);旧产物不识别 `--config` 时警告;
- **即时生效**:运行中的服务自动重启应用新配置(agent 无 SIGHUP 热重载,重启秒级);
绑定后日常改配置 = 编辑文件 + `restart`(或重跑 `config <同一文件>` 再校验一遍);
- 持久化:`exec=` 的 `--config <绝对路径>` + `conffile=` 键(跨重启保留,600);
- 展示:`status`/`info <name>` 的"配置文件"行、`info` 配置节的文件内容快照
(敏感值掩码,`info --reveal` 显示明文);文件丢失时明确告警并提示恢复或 `--clear`。
## 5. 命令参考:部署来源
`deploy --from` 支持四种来源(**缺省 = `dev` 远端最新**):dev/local/release 通用于
agent 与 static,rdm 仅限 agent。
### 5.1 dev(缺省)—— 远端最新 / 指定提交
```
scripts/svc.sh <name> deploy # 缺省 = dev 远端最新(origin/main)
--from dev # 同缺省
--from dev@<sha> # 指定提交(短/长 sha 均可)
```
部署源来自**独立部署源仓**:脚本自有的干净克隆,默认 `~/.cache/agent-system-svc/src`
(`SVC_SRC_DIR` 覆盖;首次使用自动从本仓 origin 克隆,可随时删除、下次自动重建)。
dev 源的提交解析、`commits` 列举与构建 worktree 物化全部基于它——**更新与部署因此
不依赖当前工作区状态**:主检出脏树、任意分支、甚至未 fetch 都不影响。
流程:源仓自动 fetch → `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]`(列源仓 origin/main,自动确保+fetch);
- 需要 npm/go 网络(构建在 worktree 内进行);
- worktree 回收失败会提示手动命令,不阻塞部署结果。
### 5.2 local —— 当前工作区(显式)
```
--from local
```
在主检出跑 `make build`(agent)或 `make frontend`(static),产物复制到服务目录。
**这是唯一会写主检出的路径**,写的都是 gitignore 目录(`bin/`、`node_modules/`、
`modules/frontend/dist/`),不产生任何 git 状态变化;也是**唯一依赖当前工作区状态**
的来源。适合"把我现在的工作区跑起来"。
### 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 rdm —— 下游组合仓 Agent-System-RDM(agent 专用)
```
--from rdm # RDM 源仓 origin/main 最新
--from rdm@<sha> # 指定提交
```
部署**下游组合二进制**(基座 + RDM 领域插件:两树一网与 `rdm.*` 追溯审计命令面)。
与 dev 源同构的独立源仓机制:默认 `~/.cache/agent-system-svc/rdm-src`
(`SVC_RDM_SRC_DIR` 覆盖路径;克隆来源 `SVC_RDM_ORIGIN` 覆盖,缺省为 RDM 远端 ssh
地址),流程 = fetch → `worktree add --detach runtime/.worktrees/rdm-<sha>` →
`git submodule update --init --recursive`(物化 `pins/` 基座钉入组合)→ `make build`
(纯 Go,无需 npm)→ `bin/agent-system-rdm` 复制为环境二进制 → worktree 回收。
与基座部署的差异:
- **插件目录按空集安装**:RDM 组合插件编译进二进制(`extension.RegisterCompiled`),
无声明式插件资产;管理页「插件管理」列表只反映目录插件,组合插件经
`/api/registry` 与命令面可见;
- **web/ 工作台**:RDM 自带零构建静态工作台(直连后端 API),deploy 时复制到
`runtime/<name>/htdocs/`,用伴生 static 服务伺服
(`scripts/svc.sh <name>web deploy --kind static --dir runtime/<name>/htdocs`;
注意该目录随本环境 `rm --purge` 一并删除);
- **启动兼容**:组合二进制容忍基座风格的首位 `serve` 子命令并提供 `version`
子命令;svc 的 `version`/`serve -h` 探测带 timeout 护栏;
- **不支持** `--mock` 与 `--config`(组合 CLI 无此旗标);模型设置播种
(`--model-*`/`--tavily-keys`)仅基座产物支持,rdm 源跳过(登记态
`source=rdm@*` 即识别形态,跨裸 start/restart 稳定);
- **PG 档**:`db <pg实例>` 绑定同样走 `AGENT_SYSTEM_DSN` 环境交付;RDM CLI 在
`-dsn` 为空时回退读该环境变量(无 `config explain` 子命令时,svc 以 ELF 内静态
引用作能力证据)。
### 5.5 辅助
```bash
scripts/svc.sh commits [dev|release|rdm] [N] # 可部署提交表(默认 20 条)
scripts/svc.sh <name> deploy --pick # 交互式编号选择
scripts/svc.sh <name> deploy --dry-run # 只打印计划
```
`commits` 表格:dev 源标注 `[最新]`(源仓 origin/main 顶)与 `[已部署:<服务名>]`
(从各服务 `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 DB SOURCE TAGS
pg1 postgres 运行中 3115294 127.0.0.1:8890 - postgres@local core,local
prod agent 运行中 3117059 127.0.0.1:8791 pg1/prod release@0.1.0 stable
ui-qa static 未运行 - 127.0.0.1:9054 - dir@/tmp/static-demo ui
```
DB 列:agent 服务当前绑定的 `<pg名>/<库名>`(见 §4.5;外部 DSN 显示 `外部@host:port`,
未绑定显示 `-`)。
STATE 的判定:pidfile 存在且 `/proc/<pid>/cmdline` 匹配登记的 `pidcheck` 标记
(agent=二进制路径、postgres=pgdata 路径、static=`--directory <目录>`)。进程被人手动
杀掉后 STATE 自动落回"未运行",不会误报。
### 6.2 info(综合信息报告,只读)
```bash
scripts/svc.sh info [name] [--tag T] [--reveal]
```
一条命令看全本机现场:**正在运行哪些服务、有哪些数据库(含实例内部库)、每个服务
的配置文件与内容快照**。全程只读,不改任何登记、进程与文件。
| 用法 | 行为 |
|---|---|
| `info` | 全景报告(下述五节全量) |
| `info pg1` | 单服务深度报告:状态详情 +(postgres)实例与内部库 + 配置快照 + 最近事件 |
| `info --tag core` | 服务名/数据库/配置/事件均按标签过滤(与 `list --tag` 同语义) |
| `info --reveal` | 显示默认掩码的敏感值(DB 密码、API key、DSN 密码) |
报告分节(整体走 stdout,可直接 `> snapshot.txt` 整页保存):
1. **服务**——`list` 同款表格(含 **DB 绑定列**)+ 汇总行(`N 已登记 / M 运行中(agent a、postgres p、static s)`);
2. **数据库**——每个 postgres 实例一屏:运行状态/地址、版本/用户/认证/默认库/来源、
**绑定服务**(哪些 agent 服务指向该实例,见 §4.5)、数据目录与体量、备份份数与
最近一份;**运行中的实例直接列出内部数据库及大小**(未运行则注明内部库不可见);
3. **配置**——逐服务 `runtime/<name>/svc.env` 的文件路径、权限与逐行内容快照,加上
全局层:`.svc.env`(自动加载层)、本次 `--config/--env` 文件、shell 已导出的
`SVC_*` 变量;
4. **最近事件**——各服务 history 最近 5 条(完整历史走 `history`)。
敏感值默认掩码:`password=` 键、键名含 `KEY/PASS/SECRET/TOKEN/DSN` 的值整段掩为
`***`,`exec=` 里 DSN 的密码段同样掩码;`--reveal` 才显示明文。掩码规则与
`db dsn`(默认掩密码)一致,便于把报告贴进工单/日志而不泄密。
### 6.3 history(事件追溯)
```bash
scripts/svc.sh history [name] [N] # 默认全部服务、最近 20 条;首参为纯数字时视为条数
```
合并各服务 `history.jsonl`(按时间排序),字段:时间 / 服务 / kind / 事件 / 结果 /
来源 / pid / 详情。事件类型见 §10.2。
### 6.4 update(自动检索并更新)
```bash
scripts/svc.sh update [name...] [--check] [--all] [--no-fetch]
```
把服务刷到远端最新:fetch 独立部署源仓 → 逐服务比较登记来源与最新 → 报告并(可选)
更新。**全程不依赖当前工作区状态**(主检出脏树、任意分支均可更新)。
| 用法 | 行为 |
|---|---|
| `update --check` | 只输出检测表,不改任何状态 |
| `update` | 自动更新所有"过时且**运行中**"的服务 |
| `update dev pg1` | 点名更新(已停止的过时服务也会更新并启动) |
| `update --all` | 范围扩大到已停止服务 |
| `update --no-fetch` | 跳过网络 fetch,用本地已有引用比较(报告注明) |
检测表输出:服务 / 当前来源 / 最新 / 状态。状态含义:
- `✓ 最新`——登记来源与最新一致,不动作;
- `可更新`——本地来源是远端最新的祖先,将被更新;
- `领先远端 main(不动作)` / `与远端 main 分叉(不自动更新)` /
`提交不在远端(不自动更新)`——不盲目降级或强更;
- `不适用`——postgres(无代码版本)与 static `--dir`(外部目录)不参与。
各来源的更新动作(复用 deploy 全链路:停旧 → 构建/校验 → 启动 → 健康检查;持久化配置
与标签保留;写 `update` 事件入 history):
| 来源 | 更新方式 |
|---|---|
| `local@<sha>` / `dev@<sha>` | 一律 `deploy --from dev@<远端 main 最新>`(独立部署源仓 + 隔离 worktree 构建;不 pull 主检出、不看工作树状态) |
| `release@<版本>` | `deploy --from release@<Releases 最新版本>`(sha256 校验) |
| `rdm@<sha>` | `deploy --from rdm@<RDM 远端 main 最新>`(组合二进制;见 §5.4) |
任一服务失败不中断其余服务;最终汇总 `成功/失败/跳过`。退出码:0 全部成功或最新;
1 有失败;2 用法错误。
### 6.5 recover(自恢复:拉回死亡服务)
```bash
scripts/svc.sh recover # 扫描全部已登记服务
scripts/svc.sh recover dev pg1 # 点名恢复
scripts/svc.sh recover --quiet # 静默(watchdog timer 用)
```
svc 服务是裸后台进程(nohup):进程崩溃、被 OOM killer 杀掉或机器重启后,
**没有人把它拉回来**——recover 补上这一环。
判定依据是 svc.env 的 **autostart 期望态键**:
| 动作 | autostart |
|---|---|
| start / restart / deploy / update 健康检查通过 | 置 `1`(期望在运行) |
| 显式 `stop`(含 deploy/update 的前置停机) | 移除(保持停止是明确意图) |
| 进程自行死亡(崩溃 / OOM 被杀 / 重启后) | 保留 → recover 据此拉回 |
| `rm` 注销 | 键随 svc.env 消失,不再触碰 |
行为细节:
- 扫描顺序 postgres 在前、agent/static 在后(绑定实例先起,agent 重建 DSN 才有效);
- 只对 `autostart=1` 且 pidfile+cmdline 校验已死的服务执行**裸 start**(复用持久化
`exec=` 与数据库绑定,不改任何配置、不重建产物);
- 逐服务持 `runtime/<name>/.start.lock`(非阻塞):deploy(含构建窗口)/手动 start
持锁期间跳过本轮,不抢跑;
- 每次尝试写 `recover ok|fail` 事件(`history` 可追溯);进程反复即退时自然限频
约 1 次/分钟(健康探测 20s + timer 周期);
- deploy/update **失败**(构建失败、预检拒绝)→ 服务保持停止等人工介入,watchdog
不盲目复活(前置停机已清 autostart);
- 端口被外部进程占用时 start 报错放弃(不抢不杀),下轮重试。
### 6.6 watchdog(自恢复看护 timer)
```bash
scripts/svc.sh watchdog install # 安装并启用 systemd 用户 timer(每分钟 recover)
scripts/svc.sh watchdog install --print # 只打印单元文件与计划
scripts/svc.sh watchdog remove # 停用并删除 timer
scripts/svc.sh watchdog status # timer 状态 + autostart=1 的服务清单
```
- 单元写入 `~/.config/systemd/user/agent-system-svc-recover.{service,timer}`
(`OnBootSec=1min` + `OnUnitActiveSec=60s`),`ExecStart=<本仓>/scripts/svc.sh recover --quiet`;
- 开机自启依赖 linger(`sudo loginctl enable-linger <user>`);未开时安装会提示,
登出不受影响(KillUserProcesses 默认关,进程本就不随登出死);
- 换检出目录后重装一次即可(status 会提示 timer 指向的 ROOT 与当前不一致);
- 无 systemd 用户会话的环境(如某些容器)install 会拒绝并给出 cron 降级命令:
`* * * * * <ROOT>/scripts/svc.sh recover --quiet >/dev/null 2>&1`。
一键自恢复配方:
```bash
scripts/svc.sh dev deploy # 启动成功 → autostart=1
scripts/svc.sh watchdog install # 之后 kill -9 / OOM / 重启,约 1 分钟内自动拉回
```
### 6.7 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 recovery 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=、dbref=/dsn=/db=——数据库配置专键)
> SVC_<NAME>_* 环境变量(.svc.env 自动加载,同层)
> 默认值
```
数据库配置由脚本在启动时**统一解析**(`start`/`restart`/`deploy`/`update`/`db`
共用单点):托管绑定(`dbref=`)从实例登记重建 DSN,外部直连读受保护 `dsn=` 字段,
配置文件读 `conffile` 内的 `dsn` 键。连接串不进 `exec=` 与命令行,经
`AGENT_SYSTEM_DSN` 环境变量交付进程。**修改端口、监听地址或部署版本不改变数据库
选择**;绑定失效或配置冲突时拒绝启动,绝不静默回退内嵌 SQLite(见 §9.4)。
### 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_SRC_DIR` | 独立部署源仓路径(默认 `~/.cache/agent-system-svc/src`) |
| `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_*`)出现即
**整体重建**启动参数;两种路径共用同一数据库配置解析——数据库选择与认证设置不随
端口/地址/版本变化,`conffile` 引用随重建保留;
- **数据库配置可恢复**:托管绑定 `dbref=<pg>/<库>` 在启动时由实例登记重建 DSN
(实例端口/密码变更自动跟随,密码不重复保存);显式外部直连(`deploy --dsn`)持久化
为受保护 `dsn=` 字段;显式解绑记录 `db=sqlite`(清除 `dbref`/`dsn`,此后残留的
`SVC_*_DSN` 不再悄悄重新选库,显式 `--dsn` 仍可覆盖);存量 `exec=` 里的 `--dsn`
首次启动时自动迁移为上述形态(幂等);
- **绑定失效拒绝启动**(返回非零并输出脱敏原因,不静默回退内嵌 SQLite):
`dbref` 实例未登记/类型错误/登记字段不全、已选 PostgreSQL 但无可恢复 DSN、直接
DSN 与 `dbref` 指向不同库、配置文件 `dsn` 与绑定不一致、mock 档与数据库配置共存、
产物不消费环境变量 DSN(`AGENT_SYSTEM_DSN`,旧制品)。`restart`/`deploy` 在停止
旧进程**之前**完成这些静态检查——配置性拒绝不制造停机窗口;
- 标签合并去重、跨重启保留;postgres 的用户/密码/库名/认证模式持久化,重启不丢。
## 10. 登记文件与事件流
### 10.1 svc.env 字段
| 键 | 说明 |
|---|---|
| `kind` | `agent` / `static` / `postgres`(缺省视为 agent) |
| `env` | 服务名 |
| `pid` | 最近启动 pid |
| `addr` | 监听地址(持久化,下次 start 复用) |
| `tags` | 标签(逗号分隔) |
| `source` | 部署来源:`local@<sha>` / `dev@<sha>` / `release@<版本>` / `rdm@<sha>` / `dir@<路径>` / `postgres@local` / `clone@<名>` |
| `version` | 产物版本串(`agent-system version` 输出) |
| `started` | 最近启动时间 |
| `pidcheck` | 进程判定标记(见 §6.1) |
| `exec` | agent 上次完整启动参数(非敏感项;DSN 不在其中,经环境变量交付) |
| `db` | agent 数据库模式:`pg` / `sqlite`(显式解绑)/ `mock`;从未配置则无此键 |
| `dbref` | agent 托管数据库绑定 `<pg名>/<库名>`(启动时由实例登记重建 DSN;见 §4.5) |
| `dsn` | agent 外部直连 DSN(受保护;未命中本机实例登记的直连存放于此) |
| `conffile` | agent 配置文件绝对路径(`<name> config` 命令写入,跨重启保留;见 §4.6) |
| `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 旧 → 新`;`db bind → <pg>/<库>` / `db unbind`) |
| `backup` | ok / fail | 备份(文件、大小) |
| `restore` | ok / fail | 恢复(库、文件、表数) |
| `clone` | ok / fail | 克隆(物理/在线) |
| `copy` | ok / fail | 搬库 |
| `update` | begin / ok / fail | 自动更新(from → to) |
| `rm` | ok | 注销(是否 purge) |
## 11. 安全与边界
- **写入边界**:只写 `runtime/<name>/`、`runtime/.worktrees/` 与独立部署源仓
(`SVC_SRC_DIR`,默认 `~/.cache/agent-system-svc/src`,可随时删除重建);不改主检出
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` 正常显示;存量 `exec=` 里的 `--dsn` 首次启动自动迁移为可恢复配置(幂等);
旧产物二进制不识别的 `--auth`/`--config` 自动省略并提示;不消费环境变量 DSN 的
旧产物在需要交付数据库配置时**拒绝启动/部署**(不静默按内嵌 SQLite 运行)。
## 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 指向未启动的库 |
| `该产物不支持部分启动参数` | 提示而非错误:旧版本制品自动省略了不支持的 flag;需要该能力请部署新提交 |
| `拒绝启动:dbref=… 无法重建 DSN` | 实例未登记/登记不全:恢复实例登记,或 `db <pg名>` 重新绑定;不会回退 SQLite |
| `拒绝启动:直接 DSN 与 dbref 指向不同数据库` | 配置冲突:重新绑定或 `db --clear` 后重新直连,二者取一 |
| `拒绝启动:产物不消费环境变量 DSN` | 旧制品:deploy 新提交,或改用携带 dsn 的配置文件(`<name> config`) |
| `拒绝启动:mock 档与数据库配置冲突` | 先 `db --clear` 再用 mock,或去掉 `--mock` / `SVC_*_MOCK` |
| `拒绝启动:非回环地址且认证判定为关` | 绑定数据库 / 显式 `--auth on` / 回环地址 / `SVC_*_INSECURE_LISTEN=1` 显式放行 |
| `环境产物缺失` | 该环境没有部署过(或旧版脚本部署):先 `deploy` |
| `pg_ctl start 失败` | 看 pg.log 常见:端口被占、pgdata 权限(须 700)、postmaster 残留 |
| `pidfile 指向的进程已不存在` | 脚本自动清理 pidfile;直接重新 start 即可 |
| `sha256 校验失败` | 制品与 manifest 不符:不要部署;在 Releases 仓重新 build |
| `部署源仓无此提交` | 源仓已自动 fetch 仍无此对象:确认 sha 正确(`scripts/svc.sh commits dev`) |
| `worktree 回收失败` | 按提示手动 `git -C <部署源仓> worktree remove --force runtime/.worktrees/<sha>` |
| `子模块物化失败` | 网络或子模块对象问题:重试(部署源仓会自动 fetch);或检查 git 远端可达性 |
| `目标库已有 N 张表——拒绝覆盖` | 确认后加 `--force`(会 drop 重建) |
| `提交不在远端(不自动更新)` | 该服务部署自未推送的本地提交(如 `--from local`):重新 `deploy`(缺省即远端最新)即可纳入更新轨道 |
| `list 里服务显示未运行但进程在` | 手工起的进程不满足 pidcheck 标记:脚本不会动它;纳入管理请走 svc 部署 |
## 14. 配方索引
```bash
# ① 演示后端(缺省部署远端 main 最新;--from local 构建当前工作区)
scripts/svc.sh dev deploy --mock
scripts/svc.sh dev deploy --from local --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 update --check # 看哪些落后
scripts/svc.sh update # 更新过时且运行中的(数据与配置保留)
scripts/svc.sh update dev # 只刷新指定服务
# ⑪ 总览与追溯
scripts/svc.sh list --tag test
scripts/svc.sh history pg1 50
```
---
*本文档对应 `scripts/svc.sh`(Agent-System-Dev 仓)。命令行为以实现为准;发现文档与
实现不一致时,以任务卡记录的方式提出修正。*