Files
Agent-System-Dev/docs/svc.md
T
weichen eda942523c Add svc auto-recovery: autostart desired-state, recover, watchdog
Why: svc services are bare nohup'd processes with persisted registration
but no supervision -- a crash, an OOM kill, or a reboot left them dead
until manual restart (2026-10-11 outage: dev service lost, runtime/ also
wiped by hand, nothing brought services back).

Evidence: tests/test_svc_recover.py 10 cases green (start records
autostart, stop clears it incl. dead-process stop, SIGKILL then recover
revives with persisted exec=, lock-held skips, port-stolen fails with
recover fail event, watchdog unit text via --print); make test-governance
146/146 OK locally; real-machine watchdog install + kill -9 revival to be
recorded on the task card.

Impact: svc.sh gains autostart key in svc.env, `recover [name]
[--quiet]` (postgres-first, per-service .start.lock, recover history
events), `watchdog install|remove|status` (systemd user timer, cron
fallback hint), lifecycle lock across start/stop/restart/deploy/update;
spawned services no longer inherit the lock fd (9>&-), and lock release
no longer clobbers script stderr. Makefile adds svc-recover/svc-watchdog;
docs/svc.md documents semantics and boundaries.

Refs: task card .task/feature-svc-autorecover.md; docs/svc.md 6.5/6.6;
parallel planning card feature/service-restart-recovery-plan (same
problem space, plan-stage).
2026-10-11 20:20:27 +00:00

41 KiB
Raw Blame History

svc.sh 使用说明

scripts/svc.sh 是本仓的本机服务管理器:把 后端(agent)、前端静态服务(static)、 PostgreSQL 数据库(postgres)三类服务统一为"服务"模型——按提交/版本部署、生命周期管理、 配置分层持久化、标签、事件追溯,以及一套完整的数据库运维能力(换端口 / 备份 / 恢复 / 克隆 / 跨实例搬库)。

设计原则(也是安全边界,详见 §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. 前提条件
  2. 90 秒上手
  3. 概念与目录布局
  4. 命令参考:服务级
  5. 命令参考:部署来源
  6. 命令参考:全局命令
  7. 命令参考:数据库
  8. 数据库运维手册
  9. 配置系统
  10. 登记文件与事件流
  11. 安全与边界
  12. 退出码
  13. 故障排查
  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 秒上手

# 一套演示后端(缺省部署远端 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;启动参数组装见 §9 配置系统; 健康后按 --model-* / --light-* / --tavily-keys 播种(幂等,设置存在 agent 数据目录)。

static:

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] 交互式编号选提交(需 TTY)
--config FILE / --env FILE 配置文件(见 §9)
-- <剩余参数> 原样透传给 agent-system serve

4.2 start / stop / restart / logs / status

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(变更登记)

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(注销)

scripts/svc.sh rm <name>           # 停止 + 移除登记(svc.env/svc.pid);目录数据历史保留
scripts/svc.sh rm <name> --purge   # 连 runtime/<name>/ 整个目录一起删除(含备份!)

只管理本脚本登记过的服务;未登记名字会明确报错。

4.5 db(agent 服务的数据库绑定/切换)

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 服务的配置文件更新/生效)

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 远端最新),agent 与 static 共用:

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 辅助

scripts/svc.sh commits [dev|release] [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(总览)

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(综合信息报告,只读)

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(事件追溯)

scripts/svc.sh history [name] [N]   # 默认全部服务、最近 20 条;首参为纯数字时视为条数

合并各服务 history.jsonl(按时间排序),字段:时间 / 服务 / kind / 事件 / 结果 / 来源 / pid / 详情。事件类型见 §10.2。

6.4 update(自动检索并更新)

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 校验)

任一服务失败不中断其余服务;最终汇总 成功/失败/跳过。退出码:0 全部成功或最新; 1 有失败;2 用法错误。

6.5 recover(自恢复:拉回死亡服务)

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)

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。

一键自恢复配方:

scripts/svc.sh dev deploy            # 启动成功 → autostart=1
scripts/svc.sh watchdog install      # 之后 kill -9 / OOM / 重启,约 1 分钟内自动拉回

6.7 help

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(创建实例)

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

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 备份

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 恢复

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 克隆实例(绑新端口)

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 跨实例搬库

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。

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@<版本> / 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. 配方索引

# ① 演示后端(缺省部署远端 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 仓)。命令行为以实现为准;发现文档与 实现不一致时,以任务卡记录的方式提出修正。