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
+382 -58
View File
@@ -1,81 +1,89 @@
#!/usr/bin/env bash
# svc.sh —— 本机服务管理(服务模型 v2):按提交/版本部署并启动 后端(agent) / 前端(static)
# / 数据库(postgres) 三类服务,统一登记(svc.env + history.jsonl)、标签、配置分层、
# 生命周期(start/stop/restart/rm)与数据库运维(换端口/备份/恢复/克隆/搬库),
# 全程不触碰主检出与其他已启动服务。
# svc.sh —— 本机服务管理:后端(agent) / 前端(static) / 数据库(postgres) 三类服务的
# 部署、生命周期与数据库运维。每个服务自包含于 runtime/<name>/,登记与配置持久化
# (svc.env + history.jsonl),全程不触碰主检出 git 状态与其他已启动服务。
#
# 用法:
# scripts/svc.sh <name> [action] [参数...] # 服务级操作(name 见命名规则)
# action:deploy(默认,agent/static)| start | stop | restart | status | logs [行数]
# scripts/svc.sh list [--tag T] # 全服务总览(status 等价)
# scripts/svc.sh db create <name> [参数] # 创建 PostgreSQL 服务(initdb)
# scripts/svc.sh db dsn <name> [--reveal] [--db X] # 打印数据库连接串(默认掩码密码)
# scripts/svc.sh db backup <name> [--db X] [--out F] # pg_dump 备份(自动自检归档)
# scripts/svc.sh db backups <name> # 列出已有备份
# scripts/svc.sh db restore <name> --file F [--db X] [--force] # 恢复(覆盖非空库须 --force)
# scripts/svc.sh db clone <src> <dst> [--port P] [--password PW] [--online] # 克隆实例
# scripts/svc.sh db copy <src> <dst> [--db X] [--force] # 跨实例搬库(双方须运行)
# scripts/svc.sh db ls <name> # 列出实例内数据库
# scripts/svc.sh set <name> [--port N --apply] [--tag T] # 变更登记(换端口/标签)
# scripts/svc.sh rm <name> [--purge] # 注销服务(--purge 连数据一起删)
# scripts/svc.sh commits [dev|release] [N] # 列出可部署提交(默认 dev,N=20)
# scripts/svc.sh history [name] [N] # 事件历史(默认全部,最近 20 条)
# ════════════════════ 命令一览 ════════════════════
# scripts/svc.sh <name> [action] [参数...] 服务级:deploy(默认)|start|stop|restart|status|logs [N]
# scripts/svc.sh list [--tag T] 服务总览(status 等价)
# scripts/svc.sh set <name> [--port N --apply] [--tag T] 变更登记(换端口/标签)
# scripts/svc.sh rm <name> [--purge] 注销服务(--purge 连数据目录删除)
# scripts/svc.sh db create <name> [参数] 创建 PostgreSQL 实例(initdb)
# scripts/svc.sh db dsn <name> [--reveal] [--db X] 连接串(默认掩码密码)
# scripts/svc.sh db backup <name> [--db X] [--out F] 备份(pg_dump -Fc + 自检)
# scripts/svc.sh db backups <name> 备份列表
# scripts/svc.sh db restore <name> --file F [--db X] [--force] 恢复(覆盖非空库须 --force)
# scripts/svc.sh db clone <src> <dst> [--port P] [--password PW] [--online] 克隆实例
# scripts/svc.sh db copy <src> <dst> [--db X] [--force] 跨实例搬库(双方须运行)
# scripts/svc.sh db ls <name> 实例内数据库列表
# scripts/svc.sh db set <name> [...] 等价于 set
# scripts/svc.sh commits [dev|release] [N] 可部署提交列表(默认 dev,N=20)
# scripts/svc.sh history [name] [N] 事件历史(默认全部,最近 20 条)
# scripts/svc.sh help [主题] 分主题详细帮助
#
# 服务模型:
# - name:^[a-z][a-z0-9-]{0,15}$(保留字不可用);dev/prod 有默认端口,其余按名字
# 确定性派生(8800-9099,避让已登记/监听端口),SVC_<NAME 大写>_ADDR 可覆盖
# - 每个服务自包含于 runtime/<name>/:产物(bin/htdocs)/数据(data/pgdata)/svc.pid/
# svc.log|pg.log/svc.env(登记+持久化配置,600)/history.jsonl(追加事件流)
# - kind(svc.env kind=,缺省 agent,兼容旧登记):
# 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 生命周期,绝不裸杀)
# ════════════════════ 部署来源(agent/static 的 deploy)════════════════════
# --from local 构建当前工作区(唯一写主检出构建产物的路径,均 gitignore)
# --from dev[@<sha>] 隔离 worktree(runtime/.worktrees/<sha>)物化该提交的
# 子模块组合并构建,不触碰当前工作区
# --from release[@<版本|sha>] Releases 仓制品(sha256 对 versions/<v>/manifest.json 校验)
# --pick [dev|release] 交互式编号选提交;--dry-run 只打印部署计划
#
# 配置优先级(三类服务一致):命令行 > --config/--env 文件 > svc.env 持久化的上次配置
# > SVC_<NAME>_* 系统环境变量(.svc.env 同)> 默认值。裸 start/restart 复用上次持久化
# 配置(svc.env exec= 等),显式参数出现即覆盖。
#
# 常用参数(服务级):
# --config FILE / --env FILE dotenv 配置文件(KEY=VALUE;SVC_<NAME>_ADDR/DSN/...)
# ════════════════════ 常用参数(服务级)════════════════════
# --config FILE / --env FILE dotenv 配置文件(KEY=VALUE;--env 为兼容别名)
# --addr host:port / --host H / --port P 监听地址(pg=TCP,static=HTTP)
# --tag T 标签(可重复;登记于 svc.env,list --tag 过滤)
# --kind agent|static deploy 时指定类型(postgres 用 db create 建)
# --from SOURCE 部署来源(见上);--dir PATH(static)直接服务目录
# --dry-run 只打印部署计划;--pick [dev|release] 交互式选提交
# --plugins/--dsn/--auth/--mock/--data 与 --model-* / --light-* / --tavily-keys
# (agent 播种/运行参数)同上一版
# -- <剩余参数> 原样透传给 agent-system serve(escape hatch)
# db create 专属:--user U(默认 agent)--password P(缺省自动生成,db dsn --reveal 查看)
# --db NAME(默认 agent)--auth-mode scram-sha-256|md5(默认 scram)
# rm 专属:--purge 删除 runtime/<name>/ 整个目录(含数据与历史)
# --tag T 标签(可重复;登记于 svc.env,list --tag 过滤)
# --kind agent|static deploy 时指定类型(postgres 用 db create 建)
# --dir PATH static 直接服务既有目录(不构建)
# --plugins/--dsn/--auth/--mock/--data agent 运行参数
# --model-base/--model-key/--model-name 与 --light-*/--tavily-keys 启动后播种设置
# -- <剩余参数> 原样透传给 agent-system serve(escape hatch)
# db create:--user U(默认 agent)--password P(缺省自动生成)--db NAME --auth-mode scram-sha-256|md5
# db backup:--db X --out F db restore:--file F --db X --force
# db clone:--port P --host H --password PW --tag T --online
# db copy:--db X --force set:--port N --host H --apply --tag T
#
# 示例:
# ════════════════════ 配置优先级 ════════════════════
# 命令行 > --config/--env 文件 > svc.env 持久化(exec=/addr=)> SVC_<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 pg1 start && scripts/svc.sh db dsn pg1 # 起库 + 看 DSN
# scripts/svc.sh prod deploy --from release@0.1.0 --tag stable
# scripts/svc.sh prod deploy --from dev@<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-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 pg1 restart && scripts/svc.sh rm tmp1 --purge
# scripts/svc.sh set pg1 --port 8895 --apply # 换端口并重启生效(登记/DSN 联动)
# scripts/svc.sh db backup pg1 && scripts/svc.sh db backups pg1
# scripts/svc.sh db clone pg1 pg2 --port 8896 # 物理克隆一套到新端口
# scripts/svc.sh db copy pg1 pg2 --db app # 把 pg1 的 app 库搬到 pg2
# scripts/svc.sh db restore pg2 --file <dump> --db app --force
# make svc ENV=dev ARGS="--env .env.dev --port 9000"
#
# 安全约束:
# ════════════════════ 安全约束 ════════════════════
# - 只写 runtime/<name>/ 与 runtime/.worktrees/;不改主检出 git 状态、不动他人现场;
# --from local 是唯一写主检出构建产物(bin/、node_modules/、dist/,均 gitignore)
# 的路径,dev@sha/release/static 构建全部在隔离 worktree;
# - 只停自己启动的进程:pidfile + /proc/<pid>/cmdline 标记双校验;postgres 走
# pg_ctl -m fast 优雅停机,SIGKILL 不用于 postmaster;
# - 端口被非本脚本进程占用时报错并给出占用者,不抢不杀;
# - svc.env 含密码时权限 600;DSN 默认掩码密码(--reveal 才显示);
# - svc.env/备份文件权限 600;DSN 默认掩码密码(--reveal 才显示);
# - 备份/克隆只增不删;restore/copy 覆盖非空库必须显式 --force(drop 重建再恢复);
# 备份文件在 runtime/<name>/backups/(600),随 rm --purge 一并删除;
# 备份文件在 runtime/<name>/backups/,随 rm --purge 一并删除;
# - 物理克隆期间源实例短暂停止(一致性要求),完成后自动复启;--online 不停源。
#
# 完整文档:docs/svc.md
# <<usage-end>>
set -euo pipefail
@@ -94,6 +102,320 @@ usage() {
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 前缀与成对
# 引号,忽略注释/空行/非法行),不 source 执行文件内容。
load_env_file() {
@@ -165,7 +487,7 @@ parse_args() {
--auth-mode) need_value "$@"; CLI_DB_AUTH="$2"; shift 2 ;;
--) shift; CLI_EXTRA="$*"; break ;;
-h|--help) usage; exit 0 ;;
*) c_err "未知参数:$1"; usage; exit 2 ;;
*) c_err "未知参数:$1"; usage_short; exit 2 ;;
esac
done
}
@@ -1710,7 +2032,7 @@ cmd_history() {
# ---- main ----
main() {
[ $# -ge 1 ] || { usage; exit 2; }
[ $# -ge 1 ] || { usage_short; exit 2; }
# 预扫 --config/--env(FILE 与 =FILE 两种写法,可出现在任意位置)。
local svc_envfile="" pre=()
while [ $# -gt 0 ]; do
@@ -1725,7 +2047,9 @@ main() {
[ -f "$ROOT/.svc.env" ] && load_env_file "$ROOT/.svc.env"
[ -n "$svc_envfile" ] && load_env_file "$svc_envfile"
case "$1" in
-h|--help|help) usage; exit 0 ;;
-h) usage_short; exit 0 ;;
--help) usage; exit 0 ;;
help) shift; cmd_help "$@"; exit $? ;;
esac
case "$1" in
list|status)