# 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|accounts|db|dbops|config|recovery|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@ \ --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 --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__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`) | | `SVC__INSECURE_LISTEN` | 设为 `1` 时允许该服务在非回环地址上不开启认证。值传给进程的 `AGENT_SYSTEM_INSECURE_LISTEN`,不写进 `exec=`。旧二进制会忽略 | | `SVC__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 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// 整个目录一起删除(含备份!) ``` 只管理本脚本登记过的服务;未登记名字会明确报错。 ### 4.5 db(agent 服务的数据库绑定/切换) ```bash scripts/svc.sh db # 查看当前绑定 + 可用实例 scripts/svc.sh db [--db X] # 绑定/切换数据库 scripts/svc.sh db --no-restart # 只改登记,下次 start 生效 scripts/svc.sh db --clear # 解除绑定(回内嵌 SQLite 档) ``` 每个 agent 服务都可以对应一个数据库实例中的库;绑定登记在 `svc.env` (`dbref=/<库名>` + `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 config # 查看当前配置文件(掩码内容 + agent 视角生效值) scripts/svc.sh config # 更换使用的配置文件(校验通过才切换) scripts/svc.sh config --no-restart # 只改登记,下次 start 生效 scripts/svc.sh config --clear # 移除(回命令行参数形态) ``` 配置文件是 agent 二进制的 `--config`(JSON,schema 严格校验;可用 `agent-system config explain -config ` 预览每个键的来源与生效值)。 - **切换前强校验**:文件必须存在且通过二进制的 schema 校验(未知键/非法值直接拒绝, 不会把服务重启进坏配置);旧产物不识别 `--config` 时警告; - **即时生效**:运行中的服务自动重启应用新配置(agent 无 SIGHUP 热重载,重启秒级); 绑定后日常改配置 = 编辑文件 + `restart`(或重跑 `config <同一文件>` 再校验一遍); - 持久化:`exec=` 的 `--config <绝对路径>` + `conffile=` 键(跨重启保留,600); - 展示:`status`/`info ` 的"配置文件"行、`info` 配置节的文件内容快照 (敏感值掩码,`info --reveal` 显示明文);文件丢失时明确告警并提示恢复或 `--clear`。 ## 5. 命令参考:部署来源 `deploy --from` 支持四种来源(**缺省 = `dev` 远端最新**):dev/local/release 通用于 agent 与 static,rdm 仅限 agent。 ### 5.1 dev(缺省)—— 远端最新 / 指定提交 ``` scripts/svc.sh deploy # 缺省 = dev 远端最新(origin/main) --from dev # 同缺省 --from dev@ # 指定提交(短/长 sha 均可) ``` 部署源来自**独立部署源仓**:脚本自有的干净克隆,默认 `~/.cache/agent-system-svc/src` (`SVC_SRC_DIR` 覆盖;首次使用自动从本仓 origin 克隆,可随时删除、下次自动重建)。 dev 源的提交解析、`commits` 列举与构建 worktree 物化全部基于它——**更新与部署因此 不依赖当前工作区状态**:主检出脏树、任意分支、甚至未 fetch 都不影响。 流程:源仓自动 fetch → `git worktree add --detach runtime/.worktrees/ ` → 递归 `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 仓提交也可 ``` 制品取 `/build//agent-system`(+ `plugins/`),**sha256 对 `versions//manifest.json` 校验,不符即拒绝部署**;并回显该版本对应的 Dev 提交。 仓库定位:`SVC_RELEASES_DIR` > `../Agent-System-Releases`。制品缺失时提示先在 Releases 仓跑 `scripts/build-release.sh `。 ### 5.4 rdm —— 下游组合仓 Agent-System-RDM(agent 专用) ``` --from rdm # RDM 源仓 origin/main 最新 --from rdm@ # 指定提交 ``` 部署**下游组合二进制**(基座 + 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-` → `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//htdocs/`,并**自动**部署伴生 static 服务 (`scripts/svc.sh web deploy --kind static --dir runtime//htdocs`); 伴生失败或 `GET /` 非 200 时 deploy 记 `ui-missing` 并以非 ok 结束。 注意 `htdocs/` 随本环境 `rm --purge` 一并删除; - **换源门禁**:同名服务切换源种类(如 `dev@*`→`rdm@*`)须加 `--allow-source-switch`;同种类换 sha(含 `update`)不需要; - **启动兼容**:组合二进制容忍基座风格的首位 `serve` 子命令并提供 `version` 子命令;svc 的 `version`/`serve -h` 探测带 timeout 护栏; - **不支持** `--mock` 与 `--config`(组合 CLI 无此旗标);模型设置播种 (`--model-*`/`--tavily-keys`)仅基座产物支持,rdm 源跳过(登记态 `source=rdm@*` 即识别形态,跨裸 start/restart 稳定); - **PG 档**:`db ` 绑定同样走 `AGENT_SYSTEM_DSN` 环境交付;RDM CLI 在 `-dsn` 为空时回退读该环境变量(无 `config explain` 子命令时,svc 以 ELF 内静态 引用作能力证据)。 #### 5.4.1 常用入口 cutover 到 RDM 组合(审计能力) 增量 2 后,基座 React 前端仅在 `GET /api/commands` 齐备七个 `rdm.inspect.*` 且 `enabled` 时展示「追溯审计」。要把常用入口(如 `dev`)切到组合二进制: 1. **钉 pins**:Agent-System-RDM `pins/` 对齐 Dev 上含能力门控的 Frontend(及兼容 Backend/Core/UI),合入 RDM main 后记下 `rdm@`(`scripts/svc.sh commits rdm`)。 2. **备份**:`scripts/svc.sh backup`(SQLite 一致性快照在 `runtime-backups/`)。 3. **候选(禁止双写)**:先 `scripts/svc.sh stop`,再 `scripts/svc.sh deploy --from rdm@ --data runtime//data --port <空闲> --auth on`; 验登录 / `/api/projects` / `rdmAuditReady` 后停候选。 4. **Cutover**:若当前为非 rdm 源,加 `--allow-source-switch`: `scripts/svc.sh deploy --from rdm@ --allow-source-switch` (同名 redeploy 保留 data/auth;伴生 `web` 自动就位)。失败回滚: `scripts/svc.sh deploy --from dev@<旧源> --allow-source-switch`。 5. **基座 FE 验收**:`PLAYWRIGHT_API_TARGET=http://127.0.0.1: npm run preview` (或 Vite dev 代理);`svc` static **无** `/api` 反代。RDM `web/` 工作台是另一套 UI (`htdocs` + 自动伴生 static),与 React SPA 门控并行可用。 ### 5.5 辅助 ```bash scripts/svc.sh commits [dev|release|rdm] [N] # 可部署提交表(默认 20 条) scripts/svc.sh deploy --pick # 交互式编号选择 scripts/svc.sh 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 服务当前绑定的 `/<库名>`(见 §4.5;外部 DSN 显示 `外部@host:port`, 未绑定显示 `-`)。 STATE 的判定:pidfile 存在且 `/proc//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//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@` / `dev@` | 一律 `deploy --from dev@<远端 main 最新>`(独立部署源仓 + 隔离 worktree 构建;不 pull 主检出、不看工作树状态) | | `release@<版本>` | `deploy --from release@`(sha256 校验) | | `rdm@` | `deploy --from rdm@`(组合二进制;见 §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//.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 `);未开时安装会提示, 登出不受影响(KillUserProcesses 默认关,进程本就不随登出死); - 换检出目录后重装一次即可(status 会提示 timer 指向的 ROOT 与当前不一致); - 无 systemd 用户会话的环境(如某些容器)install 会拒绝并给出 cron 降级命令: `* * * * * /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 accounts db dbops config recovery examples safety # accounts:实例绑定账户 CLI(create/list/reset-password/issue-login-key/revoke-login-key/bootstrap-admin) ``` ## 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.1.1 agent 数据目录快照(SQLite 一致性备份) ```bash scripts/svc.sh dev backup # 快照 data/ + svc.env + history.jsonl scripts/svc.sh dev backup --keep 3 # 备份后只保留最近 3 份 scripts/svc.sh dev backup --out /mnt/bk/dev1 # 自定义快照目录(不参与自动清理) scripts/svc.sh dev backups # 快照列表(时间 / 大小 / 文件数) scripts/svc.sh dev backups --prune --keep 3 # 手动清理旧快照 ``` - 位置:`runtime-backups///`——**在 runtime/ 之外**。2026-10-11 发生过 `runtime/` 被整体误删的事故(SQLite 聊天/设置数据全失,且无任何备份); 放在服务目录内的备份会跟着一起消失,因此快照根独立于 runtime/; - 一致性:每个 `*.db` 经 `sqlite3 .backup` 导出(并入 WAL 内容,服务运行中也可 安全备份)并做 `PRAGMA integrity_check` 自检;`-wal`/`-shm` 不入快照。无 sqlite3 时**拒绝备份**,不回退裸拷贝(裸拷会漏 WAL 得到撕裂副本); - MANIFEST:快照内 `MANIFEST`(600)记录服务、来源版本与逐文件 sha256,恢复时 可校验完整性。非 SQLite 的 `*.db` 文件(无文件头)保留原样副本并告警; - 保留策略:默认保留最近 7 份(`--keep N` 调整;只清理符合时间戳命名的快照目录, 不动 `--out` 指定的自定义目录);`rm --purge` 会连带删除 `runtime-backups//`,`rm`(不带 --purge)保留快照; - 边界:本命令是**数据面快照**,不含 restore(恢复到新目标、manifest 合同与 外部效果对账属恢复方案 U6,见 `modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md`); postgres 实例请用 `db backup`,static 服务无数据目录。 ### 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` 指定;两者都会做占用双重校验。 ### 8.6 物理备份 + WAL 归档 / PITR 到新目标 `db backup` 只做 `pg_dump -Fc`。物理备份、WAL 归档和按时间点恢复到新数据目录不在 `svc.sh` 里,2026-10-12 在隔离实例 `u7bk` 上实跑过,入口是 `tests/ops/run_backup_drill.sh`,结果目录 `tests/ops/results/backup-20261012-153441`(SELF)。合同数据量(100 万消息 / 10 万知识分块)下:基础备份与 `pg_dump` 各约 1 秒,逻辑恢复约 4 秒,从 `pg_ctl stop -m immediate` 到恢复后应用冒烟通过约 4 秒,最后一条仍在库里的标记比故障时刻早 64 秒。 在源实例上打开归档后必须重启(`wal_level` 与 `archive_mode` 不热加载): ```bash psql -d agent -c "ALTER SYSTEM SET wal_level = 'replica'" \ -c "ALTER SYSTEM SET archive_mode = 'on'" \ -c "ALTER SYSTEM SET archive_timeout = '60s'" \ -c "ALTER SYSTEM SET archive_command = 'test ! -f /secure/wal/%f && cp %p /secure/wal/%f && chmod 600 /secure/wal/%f'" scripts/svc.sh u7bk stop && scripts/svc.sh u7bk start ``` 归档目录先 `chmod 700`。备份: ```bash pg_basebackup -h 127.0.0.1 -p <源端口> -U agent -D /secure/base -Fp -X stream -c fast pg_verifybackup /secure/base scripts/svc.sh db backup u7bk --db <应用库> --out /secure/logical.dump pg_restore --list /secure/logical.dump ``` 演练里 `pg_stat_archiver.failed_count` 为 0,`archived_count` 为 80。备份目录 700、文件 600。静态加密和凭据轮换这次没有做。归档和数据在同一块盘上,不能代表存储整盘损坏。 制造故障用的是 `pg_ctl -D stop -m immediate`,然后把源数据目录挪走。恢复到新目录: ```bash cp -a /secure/base/. /secure/restore-pgdata/ # postgresql.auto.conf 末尾追加(后写的覆盖前面的 archive_mode): # archive_mode = 'off' # restore_command = 'cp /secure/wal/%f %p' touch /secure/restore-pgdata/recovery.signal rm -f /secure/restore-pgdata/postmaster.pid pg_ctl -D /secure/restore-pgdata -l /secure/postgres.log -t 600 -w \ -o "-p <新端口> -k /tmp/u7bk-18997" start ``` `-k` 必须是短路径。结果目录下的 socket 路径超过 107 字节时,postmaster 会拒绝启动(同日 `backup-20261012-150713` 的 `restore/postgres.log`)。等到 `SELECT pg_is_in_recovery()` 返回 `f` 再让应用连这个新端口。逻辑路径是在恢复实例上建空库,再 `pg_restore --no-owner`;表计数与 dump 当时的快照一致。 工件正文的库内 `content_path` 是绝对路径(`blobstore.Local.Put` 返回最终文件路径)。快照要还原到原来的目录,换目录后按库里的路径取文件会失败。2026-10-12 那次没有跑通上传:未设置 `BLOBSTORE_S3_ENDPOINT` 时,空的 `*S3` 放进对象存储接口后不再等于 nil,上传在 `blobstore.(*S3).Put` 空指针 panic,K4 记 FAIL。2026-10-13 已在 store 边界把这种带类型空指针当作未配置并回落本地目录(Backend `46073d30`;合入 main 的 `dc736e3` 与该提交同树)。同日在 Dev `a83588dd` 上重跑 `tests/ops/run_backup_drill.sh`,结果目录 `tests/ops/results/backup-20261013-013354`(SELF,原始结果另存于仓外 `u7-k4-retest-preserved-20261013`)。K1–K7 都过:RPO 65 秒,RTO 3 秒;K4 为 5 个工件 `checked=5`、`missing=0`、`mismatch=0`,跨用户访问项目和工件均为 404。绝对路径这一条仍然有效,快照继续还原到原目录。 ### 8.7 PG / Redis 断连处置 2026-10-12 隔离实例 `u7drill` 实跑,入口 `tests/ops/run_fault_drill.sh --fault all`,结果目录 `tests/ops/results/fault-20261012-142734`(SELF)。PostgreSQL 用 `svc.sh stop`(`pg_ctl -m fast`)和直接对自有 postmaster 发 `SIGKILL` 各做一次,断连窗口 60 秒。Redis 用 `--save '' --appendonly no` 拉起后再 `SIGKILL`。 PostgreSQL: - 断连期间写入在 10 秒内返回明确错误(这次是 401),两个应用进程都还在。窗口从停机返回后再等 2 秒才开始计,避免把已经在途的成功写算进去。 - team 档新进程日志为「拒绝就绪(不回落 SQLite)」,工作目录不出现 `*.db`。 - `/api/health` 不探数据库。恢复是否完成,用新会话上的真实写入判断:同一会话上连续写会被进行中的 mock run 以 409 挡住,那不是库没起来。这次从 postgres 就绪到两侧各 5 次写入成功不到 1 秒,没有重启应用。 - 断连窗口里失败的 requestId 用同一 id 重放后各对应一条消息;`dup_seq=0`,seq 无洞,非终态 run 为 0。 Redis(限额 20 次/分钟,两实例各突发 40 个读): - 连通时两实例合计放行 20、拒绝 60,限额是共享的。 - 进程被杀掉之后合计放行 40、拒绝 40,没有 5xx。限额变成按实例计。已确认的写入仍在库里。 - 只把 Redis 拉起来、不重启应用,合计放行回到 20。`rl:*` 的 PTTL 都大于 0,没有永久计数键。 - 日志里看不到降级计数。`Degraded()` 没有暴露点。 - Redis 不通时新起的 team 进程退出,日志含 `redis ... connection refused`。 - `runtime.redisAddr` 和 `runtime.rateLimitPerMin` 属于默认 owner `local`。`PUT /api/settings/user` 只写调用者自己的设置。演练是在隔离库里用 SQL 写入 `settings(owner='local', scope='user')`,并且保留 `runtime.mock=true`。这不是产品配置入口。 ## 9. 配置系统 ### 9.1 优先级 ``` 命令行参数 > --config FILE(--env FILE 别名) > svc.env 持久化(addr=、exec=、dbref=/dsn=/db=——数据库配置专键) > SVC__* 环境变量(.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__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=/<库>` 在启动时由实例登记重建 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@` / `dev@` / `release@<版本>` / `rdm@` / `dir@<路径>` / `postgres@local` / `clone@<名>` | | `version` | 产物版本串(`agent-system version` 输出) | | `started` | 最近启动时间 | | `pidcheck` | 进程判定标记(见 §6.1) | | `exec` | agent 上次完整启动参数(非敏感项;DSN 不在其中,经环境变量交付) | | `db` | agent 数据库模式:`pg` / `sqlite`(显式解绑)/ `mock`;从未配置则无此键 | | `dbref` | agent 托管数据库绑定 `/<库名>`(启动时由实例登记重建 DSN;见 §4.5) | | `dsn` | agent 外部直连 DSN(受保护;未命中本机实例登记的直连存放于此) | | `conffile` | agent 配置文件绝对路径(` 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 → /<库>` / `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//`、`runtime/.worktrees/` 与独立部署源仓 (`SVC_SRC_DIR`,默认 `~/.cache/agent-system-svc/src`,可随时删除重建);不改主检出 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` 正常显示;存量 `exec=` 里的 `--dsn` 首次启动自动迁移为可恢复配置(幂等); 旧产物二进制不识别的 `--auth`/`--config` 自动省略并提示;不消费环境变量 DSN 的 旧产物在需要交付数据库配置时**拒绝启动/部署**(不静默按内嵌 SQLite 运行)。 ## 12. 退出码 | 码 | 含义 | |---|---| | 0 | 成功(含"已在运行,无需操作") | | 1 | 运行失败(构建失败、启动失败、校验不过、拒绝覆盖等) | | 2 | 用法错误(未知参数/服务名/来源、缺参数、服务未登记等) | ## 13. 故障排查 | 症状 | 处置 | |---|---| | `端口被占用(非本服务)` | 按提示的占用者处理,或换端口(`--port` / `set --port --apply`) | | `进程启动后即退出` | `scripts/svc.sh logs 200` / postgres 看 `runtime//pg.log`;history 里有 fail 记录 | | `20s 健康检查未通过` | 进程活着但未响应:查日志与端口;agent 常见原因是 DSN 指向未启动的库 | | `该产物不支持部分启动参数` | 提示而非错误:旧版本制品自动省略了不支持的 flag;需要该能力请部署新提交 | | `拒绝启动:dbref=… 无法重建 DSN` | 实例未登记/登记不全:恢复实例登记,或 `db ` 重新绑定;不会回退 SQLite | | `拒绝启动:直接 DSN 与 dbref 指向不同数据库` | 配置冲突:重新绑定或 `db --clear` 后重新直连,二者取一 | | `拒绝启动:产物不消费环境变量 DSN` | 旧制品:deploy 新提交,或改用携带 dsn 的配置文件(` 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/` | | `子模块物化失败` | 网络或子模块对象问题:重试(部署源仓会自动 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@ --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 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 仓)。命令行为以实现为准;发现文档与 实现不一致时,以任务卡记录的方式提出修正。*