这是本节的多页打印视图。 .
命令
- 1: sow create
- 2: sow init
- 3: sow config
- 4: sow repo
- 5: sow dist
- 6: sow add
- 7: sow rm
- 8: sow ls
- 9: sow show
- 10: sow where
- 11: sow status
- 12: sow build
- 13: sow check
- 14: sow changes
- 15: sow publish
- 16: sow retain
- 17: sow gc
- 18: sow export
- 19: sow log
每条顶层命令单独成页;config、repo、dist、retain、
export、log 等命令组在同一页说明其子命令。
二进制内置的 sow help 是语法权威。本手册在此基础上补充选择规则、状态变化、输出契约、
失败行为与可直接使用的示例。
命令索引
sow create 是 Plain 模式的仓库命令,直接作用于目录。sow init 用于启动 Managed 模式,
必要时会创建 sow.yml;其余有状态命令发现既有工作区。help 与 version 是工具命令,
不需要进入任何模式。
| 命令 | 模式 | 用途 |
|---|---|---|
sow create [DIR] |
Plain | 就地生成平面 RPM/DEB 仓库 |
sow init [DIR] |
Managed | 初始化工作区并收敛已声明的 Repository/Dist |
sow config check|show |
Managed | 校验配置或打印有效配置 |
sow repo ls|new|show|migrate|rm |
Managed | 管理 Repository;migrate 是专用维护命令 |
sow dist ls|new|show|rm |
Managed | 管理 Dist |
sow add PATH... |
Managed | 将软件包加入期望成员集 |
sow rm PACKAGE... |
Managed | 从期望成员集中移除软件包 |
sow ls |
Managed | 列出期望成员与已构建成员 |
sow show PACKAGE |
Managed | 查看一个 Package Object |
sow where PACKAGE |
Managed | 在整个工作区定位 Package Object |
sow status |
Managed | 快速读取 Repository 状态 |
sow build |
Managed | 将 Desired 状态收敛为 Built Generation |
sow check |
Managed | 校验配置、状态、包体、视图、签名与清单 |
sow changes [BASE_GENERATION] |
Managed | 将 Generation 差异输出为文件交付计划 |
sow publish TARGET |
Managed | 将已验证 Generation 发布到配置目标 |
sow retain add|ls|rm |
Managed | 管理显式保留的 Generation 根 |
sow gc [TARGET] |
Managed | 回收本地不可达包体,或维护发布目标 |
sow export rpm-leaf |
Managed | 生成独立的 RPM 兼容 leaf |
sow log [OPERATION] |
Managed | 查询、导出与裁剪 Operation 审计账本 |
全局语法
不带参数运行 sow 会打印命令列表并退出 0。用 sow help COMMAND 或
sow help COMMAND SUBCOMMAND 查看内置帮助。sow version 与 sow --version 打印二进制身份。
SOW 没有全局 --format、--yes、--dry-run、-q、-v 或 --config。未知参数直接按
用法错误处理。
工作区发现
Managed 命令按以下规则寻找最近的 sow.yml:
- 有
-C/--workdir DIR时从DIR开始,否则从当前目录开始。 - 逐级向上查找,在第一个
sow.yml停止。 - 首次查找失败且设置了
SOW_DIR时,再从该目录查找。显式-C会取代当前目录候选, 但不会禁用SOW_DIR回退。 - 仍未发现工作区则退出
2。
--workdir 只改变发现起点,不会切换进程工作目录;相对位置参数仍相对于真实当前目录解析。
sow create 完全不参与工作区发现。
Repository 选择
需要唯一 Repository 的命令按以下顺序选择:
- 显式
-r/--repo NAME; - 发现起点所在的 Repository;
- 工作区中唯一的 Repository;
- 否则退出
2并列出候选项。
repo new 与 repo rm 用位置参数接收 NAME,不接受 -r。sow where 默认搜索所有
Repository,-r 只用于收窄范围。发布目标自身绑定 Repository,因此 publish TARGET 与
gc TARGET 不再接受额外的 Repository 选择。
Dist 选择
add、rm、ls 要求明确的 Dist 集合,并按以下顺序选择:
- 一个或多个
-d/--dist NAME; - 发现起点所在的 Dist;
- 所选 Repository 中唯一的 Dist;
- 否则退出
2并列出候选项。
其他命令有意采用不同规则:
- 未指定
-d时,build、check、status默认作用于全部 Dist; show默认搜索所选 Repository,-d只用于收窄;where默认跨工作区搜索全部匹配 Dist,-r/-d用于收窄;changes作用于整个 Repository,明确拒绝-d。
锁
除 init 外,写命令接受 -T/--timeout DUR 与 -N/--no-wait。init 获取 Workspace 锁,且不提供
命令行超时覆盖;其他锁均为 Repository 级,但 repo new 与 repo rm 同样使用 Workspace 锁。
--timeout 0 表示无限等待;正数使用 Go duration,例如 500ms、30s、5m。--no-wait
立即失败;它与正数 timeout 互斥。获取锁失败退出 4。
只读命令不获取写锁;status 仍会报告 Repository 是否正被写者持锁。
并发
只有需要解析软件包、哈希包体、渲染索引或执行校验的命令才接受 -j/--jobs N:create、
add、rm、build、check 与 repo migrate。默认值是逻辑 CPU 数,且不得小于 1。
JSON 输出
支持 --json 的命令在 stdout 输出一个带版本的 Envelope,诊断信息仍写入 stderr:
任何非零退出都会令 ok 为 false;部分成功的批处理仍会返回已提交项与失败项。完整结果结构见
JSON 输出。
不带 --json 时,每条 Managed 命令都有稳定的人类可读 renderer,适合交互使用,但不属于机器
协议。需要结构化字段的脚本应始终使用 --json;它也是唯一受支持的机器接口。
退出码
| 代码 | 含义 |
|---|---|
0 |
成功或幂等空操作 |
1 |
运行时 I/O、解析、渲染、签名或传输错误 |
2 |
用法、工作区发现或配置错误 |
3 |
批处理部分成功 |
4 |
写锁不可用 |
5 |
完整性/恢复失败,或 check 判定目录不可交付 |
6 |
可预期拒绝:冲突、受保护对象、无匹配或架构不兼容 |
各命令的精确触发条件见退出码。
1 - sow create
sow create 把一个已经放着 .rpm / .deb 的目录变成平面仓库(flat repository):在包旁边写出索引
文件。它就是 Plain 平面模式的全部——没有 sow.yml、没有 SQLite、不做工作区发现。本页讲清单遍扫描
契约、--pigsty 完成门禁,以及 --sign-with 的 RPM 包签名。
语法
DIR 默认为当前目录。
说明
create 读取 DIR 顶层的普通文件,按发现的内容渲染对应索引:有 RPM 就生成 repodata/,有 DEB 就
生成 Packages 与 Packages.gz,混合目录两套一起生成。架构全部来自包头——Plain 模式没有架构参数,
也没有架构许可表。
平面元数据只引用同目录的包:RPM 的 location 是裸 basename,DEB 的 Filename 是
./<basename>。无论目录作为 file:// 源还是 HTTP 根暴露,两者都保持相对引用。
默认情况下 create 不删除、不移动、不重命名、不重签、不改写任何一个包字节。它只替换自己拥有的索引
路径,未知文件原样保留。
参数
| 参数 | 说明 | 默认 |
|---|---|---|
-j, --jobs N |
唯一一次包哈希/解析扫描的并发 worker 数 | 逻辑 CPU 数 |
--pigsty |
启用 Pigsty 兼容清理与完成 marker | 关闭 |
-S, --sign-with KEY |
用 16/40/64 位十六进制 GPG key ID 给未签名 RPM 补签 | 关闭 |
--overwrite |
重签全部 RPM;必须与 --sign-with 同用 |
关闭 |
-T, --timeout DUR |
等待锁的最长时间;0 表示无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
-h, --help |
显示帮助 | — |
扫描规则
- 只考虑顶层、以
.rpm或.deb结尾的普通文件。 - 不递归、不跟随符号链接、不读工作区配置。
- 所有有效版本都进入索引。两个文件对应同一逻辑坐标但内容不同时,硬失败。
- 默认模式下没有受支持包会被拒绝;
--pigsty接受空权威集合,以便中断的“删除全部包”清理能够收敛并写 marker。
包 I/O 与最终校验
默认未签名路径中,每个选中包恰好只有一次完整内容扫描。worker 打开包、计算一次 SHA-256、解析
header/control,并保留完整解析结果。RPM XML 与 DEB Packages 都从该结果渲染;渲染和生成元数据
校验都不会重新打开包体。--jobs 并行化这一次扫描,规范结果顺序保证 worker 调度不改变输出字节。
发布前,create 重新列出顶层包集合,把文件 identity、类型/mode、size 与 mtime 同扫描后快照比较。
这是便宜的 stat 校验,不是第二次哈希。集合或 stat 变化会在任何 stage 输出发布前以完整性错误 5
退出。原地改字节同时刻意保持 inode、size、mtime 不变,不属于本机协作写者契约。
显式 RPM 签名是例外:复制、签名、签名验证以及解析最终签后 RPM,会对实际修改的包增加必要读取。
确定性输出与幂等
对给定输入集,渲染出的元数据是字节稳定的:gzip 输出确定,repomd.xml 写 <revision>0</revision>
与 timestamp 0。对未变化的目录重跑 create 不写任何字节,报告 noop=true:
repo_complete 门禁
默认模式永不生成 repo_complete。如果 marker 已经存在,create 宁可拒绝写索引,也不留下一个内容
已过期却仍宣称"完成"的旧 marker:
要么加 --pigsty 重跑(由它按文档顺序撤下并重新发布 marker),要么自己先把 marker 移走。
–pigsty
--pigsty 在一次调用中同时启用三项相互关联的兼容动作。发布顺序受 marker 门禁保护,但中断后是
重新扫描重建,不会从 journal 恢复:
- 删除解析架构为
i386的 DEB;RPM 不会仅因为架构是i386/i486/i586/i686而被删除。 - 删除二进制包名恰为
patroni且 upstream 版本恰为3.0.4的 RPM/DEB。RPM 比较VERSION,忽略 epoch 与 release;DEB 先剥掉 epoch 与 Debian revision 再比。3.0.4+foo不算命中。 - 全部索引渲染成功后写出
repo_complete:剩余顶层 RPM/DEB 的 SHA-256,按 basename 字节序排序, 格式为<sha256><两个空格><basename>。
清理只触碰解析成功且命中规则的顶层普通包文件,绝不按宽泛 glob 删目录或未知文件。
发布顺序对以 marker 为门禁的调用方很关键:先撤下已有的 repo_complete,再切换索引,只在替换
元数据安装后删除命中包,最后才写入新 marker。调用方必须把 marker 缺失视为尚未完成。
把 repo_complete 缺失当作"构建进行中"。这正是 --pigsty 设计围绕的契约。
RPM 包签名
-S/--sign-with KEY 是修改 RPM 字节的显式授权。KEY 必须是恰好 16、40 或 64 位十六进制 GPG key ID/fingerprint,不接受 0x 前缀。SOW 将其规范化为大写,通过 _gpg_name macro 传给环境中的
rpm --addsign。私钥、passphrase、GPG home、pinentry 以及额外 RPM macro 都由你的运行环境提供——
SOW 不接收、不持久化、不回显任何秘密。
- 默认只给没有可解析嵌入 OpenPGP 签名的 RPM 补签;已有签名的包保持原字节。
--overwrite必须与--sign-with同用,改为对全部保留 RPM 执行rpm --resign。- 签名发生在同文件系统的私有 stage 副本上。每个结果都会重新解析以确认嵌入签名存在、 signature-neutral digest 与 NEVRA 未变,并以最终完整字节生成 rpm-md。
--pigsty清理后至少要保留一个顶层 RPM,且PATH中要有rpm。
锁、staging 与覆盖重建
create 对目标目录取写锁,服从 --timeout/--no-wait。全部元数据先写入私有 stage 并验证,之后
才开始发布。锁协调本机 SOW 写者;任意外部进程同时修改包不属于受支持负载。
Plain create 不创建持久操作 journal、回滚 pre-image 或 recovery trash。发布由多个单文件 rename 组成,
因此崩溃可能留下部分替换的派生文件。使用你当前想要的参数重新执行 sow create:它丢弃保留命名空间
中的陈旧 Plain 临时状态,再按现在仍存在的包重建全部索引。recovered 始终为 false;
重跑是一次全新覆盖构建,不是事务重放。
平面目录没有整个仓库的 generation 指针,RPM 与 DEB 入口也无法用一次 POSIX rename 同时切换。因此
Plain 不承诺跨文件瞬时原子性。--pigsty 用 repo_complete 做门禁;需要事务恢复时使用 Managed。
示例
给混合目录建索引:
机器可读结果:
用八个 worker 替换 Pigsty 现有的平面构建:
失败时的 envelope:
退出码
| 码 | 触发条件 |
|---|---|
0 |
索引写出成功,或输入未变化产生 no-op |
1 |
目录不可读或不存在、包解析失败、渲染失败、签名工具失败 |
2 |
用法错误——--overwrite 未配 --sign-with、key 格式非法、--no-wait 与非零 --timeout 同用 |
4 |
目录写锁被占用,且给了 --no-wait 或 --timeout 到期 |
5 |
发布前输入集合/stat 变化,或受控输出路径未通过完整性检查 |
6 |
未找到受支持的包、撞上 repo_complete 门禁、对 DEB-only 目录用 --sign-with、坐标冲突 |
参见
- Plain 平面仓库 ——
create背后的设计 - 快速上手 —— 五分钟平面仓库演练
- 仓库布局 —— 平面目录树长什么样
- 仓库签名 —— 生成与使用签名钥
2 - sow init
sow init 创建根级 sow.yml 与私有状态目录 .sow/,这两样东西让一个目录成为工作区(Workspace)。
它同时也是手写配置的收敛命令:如果 sow.yml 里已经声明了 Repository 与 Dist,init 会把还不存在
的那些实体化出来,已完成的原样跳过。
语法
DIR 默认为当前目录。init 不接受 -C/--workdir——位置参数已经明确指定了目标。
说明
首次 init 写出最小配置与私有状态目录:
.sow/ 里放着 workspace.lock、工作区生命周期命令使用的持久文件 journal workspace-ops/、
repo-locks/,以及后续每个 Repository 一个的 SQLite 数据库。它的权限是 0700,绝不能对外提供
HTTP 访问。
参数
| 参数 | 说明 | 默认 |
|---|---|---|
--json |
输出版本化 JSON envelope | false |
-h, --help |
显示帮助 | — |
幂等规则
init 被设计成可以反复运行——无论是在 provisioning 脚本里还是手工执行:
-
创建新配置时写入
schema: sow/v3与默认architectures: [x86_64, aarch64]。 -
它从不自动创建 Repository。请用
sow repo new,或先在sow.yml中声明。 -
它从不覆盖已存在的
sow.yml。重复运行只报告现状,并列出发现了什么: -
非空目录可以初始化,但若已有文件与 SOW 保留路径冲突则失败。
收敛已声明的配置
如果 sow.yml 里已经描述了 Repository 与 Dist,init 会为它们补齐缺失的目录树、SQLite 数据库与
空索引。已初始化的对象直接跳过,因此计数器准确反映本次运行做了什么。
这样创建出来的 Dist 立刻具备协议完整的空发布面:RPM Dist 每个架构视图有一份空
repodata/,DEB Dist 有空的 Packages/Packages.gz、by-hash 与 Release。
再跑一次什么都不会变:
锁与恢复
工作区生命周期命令——init、repo new、repo rm——运行在目标 Repository 数据库存在之前或被删除
之后,因此它们使用 .sow/workspace.lock 加 .sow/workspace-ops/ 里的持久文件 journal,而不是
SQLite Operation Journal。被中断的 init 会由下一条工作区生命周期命令前滚完成或回滚。
示例
建好工作区后手工添加 Repository:
初始化当前目录之外的目录:
从版本控制中的配置文件 provision:
退出码
| 码 | 触发条件 |
|---|---|
0 |
工作区创建成功,或已收敛(no-op) |
1 |
写配置或状态目录时的运行时 I/O 错误 |
2 |
用法错误,或已存在的 sow.yml 解析/校验不通过 |
3 |
部分成功——部分声明的 Repository/Dist 已提交,至少一个失败 |
5 |
工作区 journal 无法恢复到终态 |
6 |
已有文件与 SOW 保留路径冲突 |
参见
- 第一个工作区 —— 十分钟带练版本
- Managed 工作区 —— 三层模型
- sow.yml 配置参考 —— 全部配置键
- sow repo 与 sow dist
- 仓库布局 ——
.sow/里有什么
3 - sow config
sow config 有两个只读子命令。config check 是对 sow.yml 的全量预检——每次手工改完配置以及在
CI 里都该跑一遍。config show 打印 SOW 实际算出来的配置,用它确认默认值、继承的架构与规范化别名
是不是按你预期解析的。
两个子命令都不创建目录、不碰数据库、不自动修正你的文件。
语法
sow help config 会列出两者。
sow config check
解析并校验完整的 sow.yml:schema 版本、名称、路径冲突、架构许可表、Dist 格式、成员策略与签名 key
引用。它会回报解析到的工作区以及校验了多少对象。
参数
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
--json |
输出版本化 JSON envelope | false |
-h, --help |
显示帮助 | — |
严格拒绝未知字段
未知键是错误,不是警告。一个拼写错误不会静默地让某条策略失效:
schema 版本被钉死:
唯一有效值是 schema: sow/v3。不要靠修改 Schema 字符串绕过校验错误。
check 还会验证声明的每个签名 key 引用可解析且适用于签名——过程中绝不打印密钥材料。如果你从许可表
里删掉一个架构,而仍有 Dist 配置、Membership 或已构建代在用它,config check 会拒绝该配置。
sow config show
以 YAML 打印当前选定作用域的有效配置。
对比磁盘上的文件——里面只有你写的内容:
show 补上了 protected: false、每个 Dist 继承来的 architectures、limit: 0 与空的 exclude
列表。架构一律以规范化 family(x86_64、aarch64)打印,绝不用生态别名——amd64 与 arm64 只是
同两个 family 的 DEB 写法。
参数
| 参数 | 说明 | 默认 |
|---|---|---|
--all |
展开整个工作区的默认值与规范化架构 | 关闭 |
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-d, --dist NAME |
选择一个 Dist;可重复 | 按选择规则 |
--json |
输出版本化 JSON envelope | false |
-h, --help |
显示帮助 | — |
用 -r/-d 做作用域投影
-r 与 -d 把输出收窄到选中的对象。要回答"这一个 Dist 上实际生效的策略是什么",这是最快的方式:
--all 方向相反:无论你站在哪里,它都展开整个工作区。
秘密永不输出
密钥材料与 passphrase 不会出现在 config show、JSON、操作日志或错误文本中。只显示引用形态
(file://…、env://…、agent://…)与 fingerprint。
示例
在 CI 里先校验再构建:
比较两个 Dist 的有效策略:
退出码
| 码 | 触发条件 |
|---|---|
0 |
配置合法,或输出成功打印 |
1 |
读取配置文件时的运行时 I/O 错误 |
2 |
用法错误、工作区未找到、未知字段、schema 不符,或任何校验失败 |
6 |
指定的仓库或 Dist 不存在 |
config check 把校验失败报为退出码 2 而不是 6:非法的 sow.yml 属于配置错误,不是被拒绝的
操作。
参见
- sow.yml 配置参考 —— 全部配置键与完整示例文件
- 成员策略 ——
exclude与limit如何求值 - 签名模型 —— key 引用文法与两条信任链
- sow init —— 收敛手写配置
- sow check —— 校验磁盘字节的运行时对照命令
4 - sow repo
一个仓库(Repository)独占一份 pool/、一份 dists/、一个 SQLite 数据库与一个私有状态目录。它是
锁、事务恢复、Generation 编号与 Changeset 的边界——跨仓库不去重,也不承诺跨仓库原子提交。
sow repo 管理的就是这条边界。
语法
命名
仓库名必须匹配 [a-z0-9][a-z0-9._-]*,且不能是 .、..、.sow、pool、dists,也不能与工作区
保留文件冲突。
路径不可指定。仓库永远位于 <workspace>/<NAME>/。
sow repo ls
只读列出工作区里的全部仓库。
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
--json |
输出版本化 JSON envelope | false |
STATUS 取值为 clean、dirty、recovering 或 error。各状态对客户端意味着什么,见
事务与恢复。
sow repo new
原子更新 sow.yml,然后创建 <workspace>/<NAME>/{pool,dists}、SQLite 数据库与私有状态目录。新仓库
处于 Generation 0、clean 状态。
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
repo new 取的是工作区锁而不是仓库锁——此时仓库数据库还不存在。它不接受 -r,位置参数已经指明了
目标。
对已存在的仓库再跑一次是收敛型 no-op,只报告当前状态,因此在 provisioning 脚本里是安全的。
sow repo show
只读显示一个仓库的细节。省略 NAME 时按 CLI 全局约定 中的仓库选择规则
解析。
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
省略 NAME 时用它选择仓库 |
按选择规则 |
--json |
输出版本化 JSON envelope | false |
同时给出 NAME 与 -r 时两者必须一致;不一致会在读取任何状态前失败:
sow repo migrate
这是专用维护命令,不属于全新的 0.4 Managed 工作流。由 SOW 0.4 创建的 Repository 已经使用 当前单包体布局与 Schema。
但从既有 v0.3 Workspace 升级时,迁移是强制步骤:先停止全部 Workspace 写入并完成备份,再在 执行普通读写之前逐个迁移所有已配置 Repository。
0.4 Transition 会安装 Schema v11 与 v12:按全部 Dist 重新派生 Repository 状态;在不猜测缺失 历史签名者的前提下修复 Publication 与 Generation Signer Projection;移除陈旧 abandoned-object evidence;并回填 append-only publication-target binding ledger 的 Revision 1。v0.3 未记录的历史 Signer 保持显式未验证,不能进入 Current Head,也不能成为 retained trust assertion。
Schema Transition 完成后不可逆,不要再用 SOW 0.3 打开数据库,也不要手工修改
PRAGMA user_version。--abort 只适用于诊断出的 pre-commit layout-maintenance attempt,不能
撤销已经完成的 Schema Migration。除升级或 SOW 明确诊断外,不要试探性执行 migrate。
| 参数 | 含义 | 默认值 |
|---|---|---|
-j, --jobs N |
并行校验/渲染 worker | 逻辑 CPU 数 |
--abort |
在提交决策前放弃维护尝试 | false |
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
省略 NAME 时选择仓库 |
选择规则 |
-T, --timeout DUR |
最长锁等待;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
sow repo rm
删除一个仓库:它在 sow.yml 中的条目、数据库、pool/、dists/ 与私有状态。绝不跟随符号链接,
也绝不越出固定的仓库路径。
不加 -f 时,只能删除空仓库——没有 Dist、没有 Membership、没有 Package Object:
| 参数 | 说明 | 默认 |
|---|---|---|
-f, --force |
删除非空的、未 protected 的仓库 | false |
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
-f 到底降级了什么
-f 只放宽为空这一个前置条件。它不绕过路径安全检查、不绕过符号链接拒绝、也不绕过 protected
门禁。
protected
sow.yml 中的 protected: true 直接封死仓库删除,加 -f 也不行:
要删除受保护的仓库,必须先改 sow.yml,通过
sow config check,再重试。没有 --yes,也没有临时覆盖开关。
protected 只作用于仓库删除。受保护仓库上的包级操作不受影响——add、rm、build,乃至
dist rm 都照常工作:
示例
为两层结构创建仓库:
在 cron 任务中快速失败,而不是排队等另一个写者:
一行一个仓库地做审计:
退出码
| 码 | 触发条件 |
|---|---|
0 |
列出、创建、显示、迁移、放弃 pre-commit transition 或删除成功;或 repo new 收敛了已存在的仓库 |
1 |
创建或删除目录树时的运行时 I/O 错误 |
2 |
用法错误、工作区未找到,或仓库选择有歧义 |
4 |
工作区锁被占用,且给了 --no-wait 或 --timeout 到期 |
5 |
工作区 journal 的完整性或恢复错误 |
6 |
名称非法、仓库不存在、非空但未给 -f、protected,或 NAME 与 -r 冲突 |
参见
- sow dist —— 下一层
- Managed 工作区 —— 三层模型与发现规则
- 事务与恢复 —— 锁作用域与
recovering状态 - sow.yml 配置参考 ——
protected与仓库级签名配置 - 仓库布局 —— 固定目录结构
5 - sow dist
Dist 是一个仓库内、单一格式(rpm 或 deb)的具名包集合。客户端指向的就是它。一个仓库可以同时拥有
RPM Dist 与 DEB Dist,两者共用一份 pool/,但渲染进完全独立的 dists/ 子树。
语法
命名
Dist 名与仓库名规则相同:[a-z0-9][a-z0-9._-]*,排除 .、..、.sow、pool、dists。
对 SOW 而言这个名字是不透明字符串。el9、trixie、el9-beta、customer-acme、2026-07-31 都只是
名字——beta 频道、按客户切分的视图、快照,都是你自己施加的命名约定,不是 SOW 建模的功能。
sow dist ls
只读平铺列出选定仓库的全部 Dist。
DESIRED 与 BUILT 是成员计数。两者不一致时,DIRTY_REASONS 会说明原因:
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
--json |
输出版本化 JSON envelope | false |
架构按规范化 family 打印。JSON 输出同时给出两种写法,用它可以确认 DEB Dist 渲染的是
binary-amd64 与 binary-arm64:
sow dist new
创建一个普通的、后续可继续修改的 Dist。唯一的业务参数是 --format。
| 参数 | 说明 | 默认 |
|---|---|---|
--format FORMAT |
必填;rpm 或 deb |
— |
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
--format 必填且取值封闭:
没有 --arch。架构从工作区许可表继承;高级用户在 sow.yml 里为某个 Dist 声明子集来收窄。策略
(limit、exclude)同样只在 sow.yml 中配置,绝不在命令行上重复建模。
用相同名称与相同格式重跑 dist new 是收敛操作,只报告当前状态。同名但格式不同会被拒绝:
三方事务
dist new 要在三个地方同时提交:sow.yml 条目、仓库数据库、磁盘目录树。它走 SQLite Operation
Journal(此时仓库数据库已存在,与 repo new 不同),并产生一个带空索引的新 Built Generation。
因此新建的 Dist 立刻具备协议完整的空发布面。RPM Dist 在每个架构视图下有一份空
repodata/;DEB Dist 有空的 Packages、Packages.gz、by-hash/SHA256/ 条目,
以及 Release;配置签名时再生成 InRelease 与 Release.gpg。
sow dist show
只读显示一个 Dist 的细节。
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
--json |
输出版本化 JSON envelope | false |
JSON 形态额外给出 effective_config_sha256,即解析后 Dist 配置的摘要。当你改动 limit、exclude
或签名 key 时,正是这个摘要让 Dist 变 dirty——配置身份变了,已构建代就不再等于期望状态。
sow dist rm
删除一个 Dist 的 Membership 与衍生索引。
| 参数 | 说明 | 默认 |
|---|---|---|
-f, --force |
删除成员与索引,但保留 pool 中的包 | false |
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
删除 Dist 不会删除 pool 字节
删除 Dist 绝不会从 pool/ 删包。整个 Dist 目录被移入恢复区后原子移除,包池完全不受影响:
失去引用的 Pool 对象会继续保留,直到 sow gc 证明它不再被当前、保留、恢复、发布以及
活动维护操作等任何安全根引用。
仓库的 protected: true 只封死仓库删除;受保护仓库上的常规 Dist 维护照常进行。
示例
给一个仓库同时配上 RPM 与 DEB 两副面孔:
加一个带独立保留策略的 beta 频道——先建 Dist,再在 sow.yml 里写策略并收敛:
哪些 Dist 落后于期望状态:
退出码
| 码 | 触发条件 |
|---|---|
0 |
列出、创建、显示或删除成功;或 dist new 收敛了已存在的 Dist |
1 |
创建空索引时的运行时 I/O 或渲染错误 |
2 |
用法错误——--format 缺失或非法、工作区未找到、仓库选择有歧义 |
4 |
仓库锁被占用,且给了 --no-wait 或 --timeout 到期 |
5 |
Operation Journal 的完整性或恢复错误 |
6 |
名称非法、Dist 不存在、同名不同格式冲突、非空但未给 -f |
参见
6 - sow add
sow add 是主要的写入路径。它解析你指定的包,从包头推导格式与架构,执行 Dist 的成员策略,并且——
除非你加 --skip——在返回前重建全部受影响的索引。命令退出码为 0 时,客户端已经能看到新包了。
语法
参数
| 参数 | 说明 | 默认 |
|---|---|---|
-R, --recursive |
递归进入 PATH 目录的子目录 |
关闭(只扫顶层) |
--skip |
只更新期望状态,不构建 | 关闭 |
-j, --jobs N |
解析、哈希与渲染的并发 worker 数 | 逻辑 CPU 数 |
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-d, --dist NAME |
选择一个 Dist;可重复 | 按选择规则 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
输入与目标
PATH 可以是文件或目录。目录默认只扫描顶层,除非你加 -R。
最终必须确定恰好一个仓库与至少一个目标 Dist——见选择规则。RPM 与 DEB 混合批次是允许的:每个包只会被考虑放进格式相同的目标 Dist;一个包如果没有任何兼容目标,则该包失败。
SOW 绝不从 manifest、目录名或宿主机 OS 推断目标。
汇总行给出 Operation ID、逐项计数、成员增减、新的 Desired Revision、Built Generation,以及仓库是否
留在 dirty 状态。随后是每个输入一行 item,顺序稳定。
逐项状态
每行 item 带一个总体 status,以及 dists= 中的逐 Dist 判定。
| 状态 | 含义 |
|---|---|
accepted |
新建 Package Object,且至少增加一条 Membership |
reused |
内容已存在于本仓库;可能只是新增了 Membership 引用 |
excluded |
策略把它从所有目标 Dist 中移除——看 dists= 区分是 excluded 还是 limited |
failed |
该包被拒绝,error= 字段说明原因 |
reused 表示内容幂等:同一个文件加两次绝不会产生第二个对象或重复 Membership。默认重复执行
add 时还会收敛所选 Dist;Dist 已经最新时 Generation 不变,先前 --skip 或配置变更留下
dirty 状态时,则会补做构建并可能推进 Generation:
把同一个对象加进第二个 Dist 同样是 reused——包池只保留一份,只是多了一条 Membership。
如果仍想保留 dirty 批次,应再次显式使用 --skip。
架构是读出来的,不是猜的
add 从包头读取格式与原生架构,再对照工作区许可表。不在许可表中的架构会让该包失败,并明确告诉你
要改什么:
它不会创建目录,也不会修改 sow.yml。
RPM 的 noarch 与 DEB 的 all 是架构中性(neutral)的。它们只产生一个 Package Object 与一条
Membership,但会渲染进目标 Dist 的每个有效架构视图。它们不会自动扩散到你没有用 -d 选中的 Dist。
策略:exclude 与 limit
合并进目标 Membership 之后,SOW 会在完整的 Dist 候选集上重新求值 exclude,再求值 limit。被策略
移除的包会被明确报告,不算解析失败。
这里 trixielim 配了 exclude: [{kind: [dbgsym]}] 与 limit: 1。dbgsym 包被规则排除;
libpq5 18.2-1 在版本上限下输给了 18.3-1,报告为 limited。两者的顶层状态都是 excluded,
靠 dists= 字段区分。
limit 按 (二进制包名, 原生架构) 分组,因此 18.3-1:amd64 与 18.3-1:arm64 在 limit: 1 下都能
留下。同一次运行中,一个包可以被某个 Dist 接受、被另一个 Dist 跳过。
exclude 与 limit 移除的是真实的期望成员。之后放宽策略不会把它们变回来——pool/ 里残留的字节
不构成候选集。请重新执行 sow add。
部分成功的批次
即使同批有失败项,合法且无冲突的包依然会提交。失败的输入原地不动,各自带自己的错误信息,命令退出
3:
如果一个都没被接受,整个操作以退出码 6 被拒绝,仓库保持原样:
没有 rejected/隔离目录。
–skip
--skip 在期望状态提交后就停下。公开的 pool/ 与 dists/ 字节不变,Built Generation 保持原位,
仓库变为 dirty。新包字节被持久保存在私有 pending 存储中,直到下一次 build 才发布。
pending=4/2326 表示私有存储里有 4 个对象、共 2326 字节在等待。它们不会出现在
sow changes 中——只有成功的 build 才会把它们提升进可交付树。
批量导入时用 --skip,最后一次性收敛:
处理顺序
一次 add 的执行顺序如下:
- 取得仓库写锁,并恢复任何未完成的 Operation。
- 在 SQLite 中提交一条
plannedOperation。 - 只读解析输入,计算逻辑坐标与输入字节 SHA-256(RPM 还会计算 signature-neutral payload digest)。
- 校验架构许可表,并查询已有坐标。
- 只对确实全新的坐标,在 stage 副本上执行可选的 RPM 签名并计算最终 SHA-256,再校验内容与路径唯一 性。
- 合并目标 Membership,然后在完整 Dist 集合上执行
exclude与limit。 - 提交期望状态;新字节写入私有 pending 内容存储。
- 除非给了
--skip,把仍被需要的 pending 对象发布进pool/并渲染索引——一次命令中每个 Dist 最多 构建一次。
任何模式下,输入文件都不会被修改、移动或删除。
RPM 签名模式
Managed 模式的 RPM 包签名在 sow.yml 的 signing.rpm.packages.mode 中配置,命令行没有覆盖开关。
| 模式 | 行为 |
|---|---|
never |
完整保留输入字节 |
fill |
无签名或签名不受信任时用配置 key 签名;已有能被 trusted_keys 验证的签名则保持字节。配置了 key 时的默认值 |
always |
确保最终包由配置 key 有效签名;否则对 stage 副本重签 |
没有配置 key 时只能用 never。
由于签名包含非确定字段,SOW 无法先重签再比较最终哈希。重试幂等因此建立在坐标上:输入字节完全相同
则直接复用;RPM signature-neutral digest 相同、且既有对象满足当前策略时也复用。payload digest 不同,
或既有对象已不满足策略,则是硬冲突——add 不会静默地对同一坐标原地重签。
退出码
| 码 | 触发条件 |
|---|---|
0 |
全部输入被接受或复用;索引已重建(或因 --skip 跳过) |
1 |
运行时 I/O、解析器、渲染器或签名失败 |
2 |
用法错误、工作区未找到,或仓库/Dist 选择有歧义 |
3 |
部分批次——至少一项已提交,至少一项失败 |
4 |
仓库锁被占用,且给了 --no-wait 或 --timeout 到期 |
5 |
完整性或恢复错误,包括在 applied 之后构建失败 |
6 |
一个都没接受——架构不受支持、没有兼容的目标 Dist,或坐标冲突 |
参见
7 - sow rm
sow rm 把包从你选定的 Dist 的期望成员集中拿掉,并默认立即重建受影响的索引。它不会从 pool/
删除字节——成员关系与内容是两个概念,回收由独立的保守操作 sow gc 完成。
语法
参数
| 参数 | 说明 | 默认 |
|---|---|---|
-c, --check |
只预览:计算并打印方案,不写任何东西 | 关闭 |
--skip |
只更新期望状态,不构建 | 关闭 |
-j, --jobs N |
并发 worker 数 | 逻辑 CPU 数 |
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-d, --dist NAME |
选择一个 Dist;可重复 | 按选择规则 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
--check 与 --skip 互斥:
包引用
PACKAGE 接受五种形态。完整文法与歧义规则见包引用,简版如下:
| 形态 | 例子 |
|---|---|
| 内容哈希 | sha256:d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab |
| RPM 坐标 | rpm:epel-release-0:7-5.noarch |
| DEB 坐标 | deb:libpq5=18.3-1:amd64 |
| 完整文件名 | epel-release-7-5.noarch.rpm |
| 裸二进制包名 | epel-release |
裸名表示选定 Dist 中该名称的全部版本与原生架构——正因如此,sow rm patroni 才是一条好用的下架
命令。非裸名的模糊短引用会失败并列出候选,而不是替你猜。
sow ls 会直接打印精确的 sha256: 引用与规范化坐标,你不需要手工
拼接。
引用匹配不到任何东西属于拒绝,不是静默成功:
没有 --allow-empty、没有 --all、没有 --yes、没有 --source-list。
用 –check 预览
-c/--check 精确算出会移除什么、策略随后会怎么判定、以及立即构建会触碰哪些文件——并且什么都不写。
注意两个 centos-release 版本都被裸名命中了。change 行是一份真实交付计划,按
payload → metadata → pointer → delete 排列。其他程序需要对应的 removed[] 与 changes[]
数组时应使用 --json。
预览与写操作使用同一套候选配置和完整性预检;预览未通过门禁时,不能据此认为实际写入会成功。
--check 有意不取写锁。把它与锁参数一起用是用法错误,免得有人以为预览会排队等待写事务:
默认行为:移除并重建
不带 --check 或 --skip 时,rm 提交期望状态变更,并在返回前重建每个受影响的 Dist。pool 对象
留在磁盘上。
汇总之后会为每个受影响文件输出一行 change(本次运行共八行)。加上 --json 后,同一结果
以稳定的标准 Envelope 返回。
移除一个 Dist 的最后一个成员是允许的。SOW 仍会渲染合法的空索引(配了 key 就带签名)——空的
Packages 配可验签的 InRelease,或每架构的空 repodata/。
–skip
--skip 提交期望状态变更并把仓库标为 dirty,不触碰公开树。旧的 Built Generation 对客户端依然完全
自洽。
changes 为空是因为什么都没构建。执行 sow build 收敛。
与策略的交互
移除属于期望状态编辑,因此策略会在新的候选集上重新求值——移除操作绝不会让先前被 limit 挤掉的包
复活。如果你从一个 limit: 1 的 Dist 里删掉 libpq5 18.3-1,18.2-1 不会回来;需要重新显式 add。
示例
安全下架——先预览,再执行:
一次从两个 Dist 中移除同一个精确对象:
批量移除后只重建一次:
把预览计划喂给其他工具:
退出码
| 码 | 触发条件 |
|---|---|
0 |
成员已移除并重建,或 --check 预览已打印 |
1 |
运行时 I/O 或渲染失败 |
2 |
用法错误——--check 与 --skip 同用、--check 与锁参数同用、选择有歧义、工作区未找到 |
3 |
部分批次——至少一个引用被移除,至少一个失败 |
4 |
仓库锁被占用,且给了 --no-wait 或 --timeout 到期 |
5 |
完整性或恢复错误 |
6 |
引用无匹配,或非裸名的引用有歧义 |
参见
8 - sow ls
sow ls 是针对 Package Object 与 Dist Membership 的只读查询。它显示所选 Dist 应包含哪些包,
以及这些成员是否已经进入当前 Built Generation。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
-d, --dist NAME |
选择 Dist;可重复 | 选择规则 |
--json |
输出 sow.cli/v1 Envelope |
false |
该命令没有 --pool、--match 或输出格式参数。
输出
| 列 | 含义 |
|---|---|
SHA256 |
不可变内容身份,可直接传给 show 或 rm |
COORDINATE |
规范的 rpm: 或 deb: 包引用 |
DISTS |
所选范围内的 Desired Membership |
BUILT_DISTS |
当前 Built Generation 中的成员关系 |
POOL_PATH |
Repository 内的不可变包体路径 |
Desired 与 Built 不一致时,首行显示 dirty=true。BUILT_DISTS 为空表示该包已进入期望状态,
但客户端尚不可见;运行 sow build 完成收敛。
多个所选 Dist 共享同一对象时,只输出一行,成员列表用逗号分隔。空 Dist 只有表头、没有包行, 仍然是成功结果。
选择范围
ls 要求 Dist 集合无歧义。Repository 包含多个 Dist 时,应传入一个或多个 -d,或从
<repo>/dists/<dist>/ 内运行。
该命令不获取写锁,也不重新哈希包文件。--json 在 result.packages 中返回同一批记录。
示例
列出尚未构建对象的精确引用:
按路径列出包体:
退出码
| 代码 | 触发条件 |
|---|---|
0 |
已输出成员列表,包括空列表 |
1 |
运行时 I/O 错误 |
2 |
用法错误、未发现工作区或隐式 Repository/Dist 选择有歧义 |
5 |
Repository 状态库不可读或不一致 |
6 |
显式指定的 Repository 或 Dist 未配置 |
参见
9 - sow show
sow show 在所选 Repository 中解析一个包引用,并打印完整 Package Object。该命令只读,
不获取写锁。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
-d, --dist NAME |
将候选项收窄到指定 Dist;可重复 | 整个 Repository |
--json |
将结果包装进 sow.cli/v1 Envelope |
false |
包引用
PACKAGE 可使用 sha256:<hex> 内容身份、规范的 rpm:<NEVRA> 或
deb:<name>=<version>:<arch> 坐标、完整包文件名或裸二进制包名。精确文法见
包引用。
裸包名必须在所选范围内唯一。sow rm foo 会移除所有匹配版本,而 sow show foo 只允许返回
一个对象;有歧义时会列出候选项:
从错误信息或 sow ls 复制精确坐标/SHA-256 后重试。
输出
不带 --json 时,show 以紧凑的人类可读格式输出身份、存储路径与 Desired/Built 位置:
加上 --json 后,标准 Envelope 的 result 会返回完整 Package Object,包括下列规范化字段。
| 字段 | 含义 |
|---|---|
canonical_arch |
x86_64、aarch64,或 RPM noarch / DEB all 对应的 neutral |
kind |
策略分类:main、debuginfo、debugsource、llvmjit、dbgsym、dbg |
source |
标准化源码包名 |
payload_sha256 |
RPM 去签名摘要,用于保证重签名幂等 |
signature_key |
包内签名的 Key ID(如有) |
storage |
构建前为 pending,进入仓库树后为 pool |
dists / built_dists |
Desired 与当前 Built Membership |
-d 只收窄候选解析范围,不改变包身份。
退出码
| 代码 | 触发条件 |
|---|---|
0 |
已输出一个 Package Object |
1 |
运行时 I/O 错误 |
2 |
用法错误、未发现工作区或隐式 Repository 选择有歧义 |
5 |
Repository 状态库不可读或不一致 |
6 |
显式范围未配置,或引用没有匹配/匹配多个对象 |
参见
10 - sow where
sow where 用于回答工作区中哪些 Dist 仍包含某个 Package Object。它默认搜索全部 Repository,
只读且不获取写锁。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
将搜索限制到一个 Repository | 全部 Repository |
-d, --dist NAME |
将搜索限制到指定 Dist;可重复 | 全部 Dist |
--json |
输出 sow.cli/v1 Envelope |
false |
引用解析
PACKAGE 与 sow show 使用相同文法:SHA-256、规范 RPM/DEB 坐标、
完整文件名或裸包名。
解析范围是完整的所选工作区范围。裸包名必须标识唯一 Package Object;即使同名对象位于不同
Repository,也会产生歧义。使用 -r/-d 收窄范围,或提供精确坐标/SHA-256。
输出
不带 --json 时,where 先输出摘要,再为每个位置输出一行:
每个位置同时给出 Desired dists 与当前 built_dists,可用于确认已移除或已替换版本是否仍对
客户端可见。
加上 --json 后,同一对象位于 result 下。引用不存在属于明确拒绝,而不是空成功:
示例
列出仍在提供某个精确版本的全部位置:
退出码
| 代码 | 触发条件 |
|---|---|
0 |
已输出一个解析后的 Package Object 及其位置 |
1 |
运行时 I/O 错误 |
2 |
用法错误或未发现工作区 |
5 |
某个 Repository 状态库不可读或不一致 |
6 |
显式 Repository/Dist 未配置,或引用没有匹配/在所选范围内有歧义 |
参见
11 - sow status
sow status 是低成本的 Repository 状态查询。它读取状态,但不哈希文件、不验签、不恢复
Operation、不构建元数据,也不获取写锁。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
-d, --dist NAME |
只查看指定 Dist;可重复 | 全部 Dist |
--json |
输出 sow.cli/v1 Envelope |
false |
Repository 状态
每个 Repository 同时跟踪 SQLite 中的 Desired Revision,以及公开 dists/ 树对应的
Built Generation。
| 状态 | 含义 | 公开视图 |
|---|---|---|
clean |
Desired 与 Built 一致 | 当前且完整的 Generation |
dirty |
Desired 已领先,常见于 --skip 或配置变化之后 |
上一个完整 Generation |
recovering |
存在非终态 Operation,下一条写命令必须先恢复 | 上一个已完成的协议指针 |
error |
自动恢复无法安全裁决 | 保留上一个完整视图,不尝试覆盖 |
dirty 不代表仓库只写了一半。协议指针最后切换,因此读者看到的始终是完整旧视图或完整新视图。
输出
人类可读输出包含 Repository 状态、ready_to_copy、Desired Revision、Built Generation、
受影响 Dist、待处理对象数量/字节数与写锁状态。
JSON 结果还包含 dirty_reasons 与最近一次 Operation:
ready_to_copy=false 是明确警告;true 只是廉价状态判断,并非字节级完整性证明。交付前应运行
sow check。
只读契约
status 不迁移也不修复状态。Repository 数据库无法安全读取时,命令退出 5;请先执行
诊断信息明确指出的维护命令,再重新查询。尤其是 v0.3 Repository,使用 0.4 读取表面前必须先
备份,并逐个执行 sow repo migrate。
退出行为
只要状态可读,status 在 clean、dirty、recovering、error 四种状态下都返回 0。
脚本应读取结构化状态,而不是把后三者当作命令执行失败。
| 代码 | 触发条件 |
|---|---|
0 |
Repository 状态可读 |
1 |
运行时 I/O 错误 |
2 |
用法错误、未发现工作区或隐式 Repository 选择有歧义 |
5 |
状态库不可读或不一致 |
6 |
显式指定的 Repository 或 Dist 未配置 |
参见
12 - sow build
sow build 是显式的 Desired-to-Built 收敛命令。它获取 Repository 写锁,恢复任何可裁决的
未完成 Operation,渲染并验证完整 Generation,最后切换协议指针。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-j, --jobs N |
并行 Worker 数,不得小于 1 |
逻辑 CPU 数 |
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
-d, --dist NAME |
构建指定 Dist;可重复 | 全部受影响 Dist |
-T, --timeout DUR |
最长等锁时间;0 表示无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
将结果包装进 sow.cli/v1 Envelope |
false |
不带 -d 时,SOW 收敛所选 Repository 中全部受影响 Dist;带 -d 时只收敛指定 Dist,
未选择的变化继续保持 dirty。
结果
不带 --json 时,build 输出一行人类可读摘要:
需要标准 Envelope 中的命令专属对象时使用 --json。
空操作构建
成员关系、相关策略、渲染设置与签名配置均未变化时,build 是幂等空操作,不增加 Generation:
策略收敛
build 会重新执行当前 exclude 与 limit 策略。收紧策略可能移除 Desired Membership;
放宽策略不会从残留包池字节恢复历史成员,需要重新运行 sow add。
提交与恢复
SOW 在同一文件系统暂存新元数据,验证完成后再切换可变协议指针。RPM 校验和命名元数据与 APT by-hash 确保新旧读者看到的视图始终自洽。
Pending 包体提升采用有界单写者 group commit。每批最多 512 个对象或 1 GiB:先创建 Pool 链接并持久化所有不同的目标父目录,再删除 pending 名称并持久化共享 pending 目录。中断只会 留下 pending-only、指向同一 inode 的双链接或 Pool-only 状态,都能按 journal 恢复;不会 持久地同时丢失两个名称。
一个 Operation 可以覆盖多个 Dist。每个 Dist 始终暴露完整视图;build 返回时,本次包含的所有
Dist 属于同一个 Built Generation。
开始新工作前,build 会尝试前向恢复或安全回滚非终态 Operation。如果日志、数据库与文件系统
证据互相矛盾,Repository 进入 error,build 拒绝猜测;不存在强制修复参数。
进度事件
耗时较长的构建会向 Operation Log 追加结构化 build_progress 记录。每条事件包含 phase、
completed、total 与 jobs。当前阶段为:
rendering;promoting_payload;publishing_dists;normalizing_public_tree;finalizing。
这些事件不会推进 Operation 状态,也不会在每次更新后 checkpoint SQLite;它们只用于审计
与可观测性,不参与恢复决策。使用 sow log OPERATION
查看明细。
元数据签名
Managed 元数据签名只从 sow.yml 读取,没有命令行 Key 覆盖。配置的 Key 引用或指纹改变时,
相关 Dist 变为 dirty,下一次 build 重新签名。
- RPM:总是生成
repodata/repomd.xml;配置签名后额外生成repomd.xml.asc。 - DEB:总是生成
Release;配置签名后额外生成InRelease与Release.gpg。
退出码
| 代码 | 触发条件 |
|---|---|
0 |
收敛成功或无需操作 |
1 |
渲染、签名或文件系统错误 |
2 |
用法错误、未发现工作区或隐式 Repository 选择有歧义 |
4 |
Repository 写锁不可用 |
5 |
无法安全完成恢复,或 Repository 处于 error |
6 |
显式范围未配置,或当前配置拒绝既有状态 |
参见
sow status—— 判断是否需要收敛sow check—— 验证构建结果sow changes—— 查看生成的文件差异- 事务与恢复 —— 完整提交协议
13 - sow check
sow check 是 Managed Repository 的深度只读门禁。它哈希包体、校验状态、重建期望视图并验证
已声明签名;不会修复、构建、恢复 Operation,也不会获取写锁。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-j, --jobs N |
并行校验 Worker 数,不得小于 1 |
逻辑 CPU 数 |
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
-d, --dist NAME |
校验指定 Dist;可重复 | 全部 Dist |
--json |
输出 sow.cli/v1 Envelope |
false |
校验层
稳态下,checker 按顺序报告九层校验:
| 层 | 校验内容 | checked 计数 |
|---|---|---|
config |
sow.yml 能否针对该 Repository 解析并通过校验 |
配置对象 |
state |
SQLite quick_check、外键、日志与恢复证据 |
一个状态库 |
public-modes |
服务目录中所有文件与目录权限 | 已检查路径 |
retained |
显式保留记录与冻结 Generation Manifest | 保留记录 |
package-bytes |
包池与私有 pending 包体的 SHA-256 | Package Object |
desired-membership |
Membership 能否在当前策略下解析到真实对象 | 成员关系 |
index |
渲染索引是否与其声明的成员关系一致 | Dist |
signature |
所有已声明元数据/软件包签名是否有效 | 签名 |
generation-manifest |
Built Generation Manifest 是否与磁盘文件一致 | 一个 Manifest |
Repository 处于未完成布局迁移时,check 改为依次报告 config、state、public-modes 与条件
层 layout-transition,随后停止,并在诊断指定的
repo migrate 完成或在 commit 前中止前返回不可交付。
物理证据与 I/O 契约
package-bytes 绝不会把缓存指纹当作真实性证明。每次运行都会对每个唯一物理包体执行恰好一次
哈希;证据绑定设备号、inode、size、mtime、ctime 与真正读取的文件描述符。同 inode 的硬链接
共享证明;Retained Generation、最终 Manifest 遍历与 changes 复用它,不再扫描包体。
checked 列统计逻辑对象,不代表全文流数量。
DEB 或无签名 RPM 只需一遍完整包体流。带签名 RPM 最多再用一遍从主 Header 到 EOF 的流, 对全部签名 Packet 与候选 Trust Ring 验证;成本不会随 Dist、Retained Generation 或 Trust Ring 数量增加。伪造或并发替换文件会让描述符证据失效并失败关闭。
dirty 不可交付
dirty Repository 的九层校验可以分别成立:旧 Built Generation 完整,新 Desired 状态也有效; 但二者不一致,因此整体仍未通过交付门禁:
此时退出 5。运行 sow build 后重新校验,不应让发布流水线放行该状态。
退出码
| 代码 | 触发条件 |
|---|---|
0 |
全部校验层通过,Repository 可复制交付 |
1 |
校验期间发生 I/O 错误 |
2 |
用法错误、未发现工作区或隐式 Repository 选择有歧义 |
5 |
某个校验层失败,或 Repository 不可交付 |
6 |
显式指定的 Repository 或 Dist 未配置 |
参见
sow status—— 低成本状态查询sow build—— 收敛 Desired 与 Built- 退出码 —— dirty 为什么映射到
5 - 可观测与审计 —— 组合使用校验与审计
14 - sow changes
sow changes 比较 Built Generation,输出物理的 Repository 相对文件差异。它不显示尚未构建的
Desired 变化,也不是远端事务协议。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
--json |
输出 sow.cli/v1 Envelope |
false |
该命令作用于整个 Repository,明确拒绝 -d/--dist。
输出
各列依次为操作、阶段、Repository 相对路径、大小、SHA-256。
| 字段 | 取值 |
|---|---|
| 操作 | add、update、delete |
| 阶段 | payload、metadata、pointer、delete |
阶段描述 SOW 如何构建本地 Generation。不要把单行直接重放到线上目录;应使用
sow publish,或先暂存完整副本再原子切换。
Base Generation
不带参数时,SOW 比较当前 Built Generation 与它的前一代。
BASE_GENERATION 是 0..当前代 范围内的十进制整数。Base 0 输出当前 Generation 的完整
交付清单,但不包含私有 sow.yml 与 .sow/。Base 等于当前代时输出空计划;从未构建的
Repository 同样输出空的 0 -> 0 计划。
dirty 与恢复状态
Desired 为 dirty 时,首行显示 dirty=true,但计划仍以当前 Built Generation 结束。私有 pending
包体尚不可交付,不会出现在结果中。
Repository 为 recovering 或 error 时,changes 拒绝输出计划,避免把待定文件动作误认为已完成
Generation。
示例
输出当前完整清单:
生成 Repository 级计划后按路径筛选一个 Dist:
退出码
| 代码 | 触发条件 |
|---|---|
0 |
已输出计划,包括空计划 |
1 |
运行时 I/O 错误 |
2 |
用法错误、传入 -d、未发现工作区或隐式 Repository 选择有歧义 |
5 |
Repository 处于 recovering/error,或状态证据不一致 |
6 |
显式 Repository 未配置,或 Base Generation 超出有效范围 |
参见
sow build—— 创建下一代 Generationsow publish—— 使用受支持的发布协议sow log—— 语义 Operation 及其文件动作- 仓库布局 —— 公开/私有路径边界
15 - sow publish
sow publish 将某个 Repository 的当前 Built Generation 交付到 sow.yml 的 targets: 中指定的
目标。目标已经绑定 Repository 与 Provider,因此命令不接受 --repo 或 --dist。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
--abort |
放弃已对账、但尚未写入持久 commit intent 的尝试 | false |
--rebind |
确认并记录允许修改的目标名称、公共端点或缓存 TTL | false |
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-T, --timeout DUR |
最长 Repository 等锁时间;0 表示无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出 sow.cli/v1 Envelope |
false |
TARGET 必须是已配置的 filesystem 或 r2 发布目标。--abort 与 --rebind 互斥。
发布协议
交付前,SOW 要求存在已完成的 Built Generation,并验证公开树与冻结 Generation Manifest 精确 一致;然后按以下顺序规划并写入对象:
- 不可变包体;
- 校验和寻址元数据;
- 可变协议指针;
- 验证并持久化 Checkpoint。
精确对象集合、Receipt、阶段与 commit intent 都会落盘,确保中断后可以对账恢复。目标已经位于 当前 Generation 时,重复发布是幂等空操作。
重绑定可变目标配置
首次成功 publish 会持久绑定 Repository、存储命名空间与 Target Identity。之后配置发生漂移时,
SOW 会拒绝静默接受。只有诊断信息明确提示 --rebind,并且你已经复核变更后,才执行:
如果改的是 targets: Map Key,请使用新目标名。Rebind 保留 active attempt 与 checkpoint identity,
并追加不可变、由操作者确认的 binding revision。
| 可以修改 | 不可变;必须配置新目标 |
|---|---|
| 目标名称 | Repository Identity |
public_endpoint |
Provider、存储 endpoint 或 region |
max_cache_ttl |
bucket 或 prefix |
Rebind 与发布使用同一组 Workspace/Repository 锁,并在数据库事务内重新核对不可变字段;它可以
继续前滚 active commit-intent attempt。Target Maintenance 未完成时禁止改变 TTL;filesystem
正在进行 conditional-delete 维护时禁止改变 public_endpoint。首次绑定必须运行普通 publish,
不能使用 --rebind。
Abort 与恢复
--abort 只允许在持久 commit intent 之前使用。SOW 会对账已经创建的对象,保留后续安全判断所需
证据,并在不继续复制或删除远端对象的前提下放弃本次尝试。
写入 commit intent 后只能前向恢复:重新运行 sow publish TARGET,不能使用 --abort。
公共可见性校验
Provider 存储写入成功还不够:写入 Checkpoint 之前,发布流程还要校验 canonical
public_endpoint。HTTP(S) 目标以普通 GET 为最终权威;no-cache Probe 只能促进 Revalidation,
必须由之后的普通 GET 才能通过。陈旧内容与缺失对象按 max_cache_ttl 重试;408、425、429 与
5xx 使用较短的有界重试窗口。等待 Header 与 Body 空闲进度分别计时;响应超长时失败关闭。
Filesystem 可使用 file:// 或 HTTP(S) 公共端点。执行条件删除时,file:// 需要精确文件身份
缺失,HTTP(S) 则需要 canonical 404/410。R2 必须使用 HTTP(S) 公共端点;R2 Target GC 仍然
只报告候选,不执行远端删除。
安全边界
- SOW 只发布到配置目标,不接受任意目标路径。
- 尚未构建的 Desired 变化不会进入发布。dirty Repository 因而可以发布上一个完整 Built
Generation;如果目标必须反映当前 Desired 状态,应先运行
build。 - 布局迁移与相互矛盾的恢复证据会阻止发布;可裁决的未完成 Dist 操作会在选择源 Generation 之前恢复。
- 对象顺序保证包管理器指针不会引用尚不存在的内容。
- 发布命令不负责外部 Web Server、Bucket Policy、DNS 路由或缓存配置。
退出行为
| 代码 | 触发条件 |
|---|---|
0 |
发布完成,或目标已经是当前 Generation |
1 |
文件系统、Provider、网络、验证或绑定冲突(包括必须 rebind) |
2 |
用法、工作区发现或 sow.yml 无效 |
4 |
Repository 写锁不可用 |
5 |
本地/发布恢复证据不一致,或源不可交付 |
6 |
目标不存在/不安全,或其他安全前置条件拒绝 Publish/Abort/Rebind |
参见
sow status与sow check—— 判断当前 Built Generation 是否正是准备交付的版本sow gc—— 保守的目标维护sow.yml发布目标 —— Provider 配置- 发布模型 —— 阶段、Receipt、恢复与 Cache Grace
16 - sow retain
sow retain 管理显式的本地 Generation 根。retain add 只能冻结当前 Built Generation;后续
构建使它成为历史版本后,该代所需的软件包体仍受保护。
语法
GENERATION 必须是大于零的十进制整数。
retain add
要求 GENERATION 等于当前 Built Generation,校验后将其 Manifest 冻结到工作区私有状态中,
并添加显式 GC 根。不能在事后用 retain add 重建一个更老的 Generation。
保留记录只保护包体,不切换当前视图,也不执行发布。重复添加同一 Generation 时,只有已验证记录 与当前证据一致才可视为幂等。
retain ls
列出显式保留记录。它是只读命令,因此不接受锁参数或 --dist。
空列表也是成功结果。
retain rm
只移除显式保留根:
该命令不删除软件包体。移除一个未被保留的 Generation 是幂等空操作。只有在其他安全根也无法
到达这些包体时,后续本地 sow gc 才可能回收。
参数
| 参数 | 适用命令 | 含义 |
|---|---|---|
-C, --workdir DIR |
全部 | 工作区发现起点 |
-r, --repo NAME |
全部 | 选择 Repository |
-T, --timeout DUR |
add、rm |
最长写锁等待时间 |
-N, --no-wait |
add、rm |
锁被占用时立即失败 |
--json |
全部 | 输出 sow.cli/v1 Envelope |
退出行为
| 代码 | 触发条件 |
|---|---|
0 |
操作完成,包括空列表 |
1 |
文件系统或运行时 I/O 错误 |
2 |
Generation 语法无效、发现错误或隐式 Repository 选择有歧义 |
4 |
add/rm 无法获取写锁 |
5 |
Generation Manifest 或 Repository 状态不一致 |
6 |
显式 Repository 未配置、retain add 不是当前 Built Generation,或其他安全规则拒绝请求 |
参见
sow gc—— 使用保留根执行回收sow changes—— 查看 Built Generation 差异- 仓库布局 —— 私有保留记录位置
17 - sow gc
sow gc 有两种严格分离的模式:不带位置目标时,回收本地不可达包体;带 TARGET 时,维护一个
已配置发布目标。
语法
| 参数 | 含义 | 默认值 |
|---|---|---|
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
仅用于选择本地 GC 的 Repository | 选择规则 |
-T, --timeout DUR |
最长 Repository 等锁时间;0 表示无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出 sow.cli/v1 Envelope |
false |
目标自身已绑定 Repository,因此 gc TARGET -r NAME 属于用法错误。两种模式都不接受 --dist。
本地 GC
本地 GC 只删除所有安全根都无法到达的包池对象。安全根包括:
- 当前 Built Generation;
- 显式
retain记录; - 恢复状态与非终态 Operation;
- 发布尝试及其证据;
- 活跃维护操作。
操作会记入日志。实际删除包体时,Repository 前进到新 Generation;没有合格对象时为幂等空操作。
目标 GC
目标维护使用发布 Checkpoint、不存在性证据与配置的 Cache Grace,具体行为取决于 Provider:
| Provider | 行为 |
|---|---|
filesystem |
仅在 Grace 到期且已有存储/公开不存在性记录后,条件删除合格对象 |
r2 |
持久化精确的只报告候选集合;绝不发送对象删除请求 |
空操作表示当前没有到期维护任务,并不代表目标已经做过穷尽式重新验证。
退出行为
| 代码 | 触发条件 |
|---|---|
0 |
GC 完成或没有合格对象 |
1 |
文件系统、Provider、网络或其他运行时错误 |
2 |
用法、工作区发现、sow.yml 无效或隐式 Repository 选择有歧义 |
4 |
Repository 写锁不可用 |
5 |
恢复、状态、Receipt 或 Manifest 证据不一致 |
6 |
显式 Repository/目标未配置或不安全,或删除被安全前置条件拒绝 |
参见
sow retain—— 创建与移除显式本地根sow publish—— 创建目标 Checkpoint 与 Receipt- 发布模型 —— Provider 保证与 Cache Grace
18 - sow export
SOW 提供一个导出子命令:sow export rpm-leaf。它创建外部、独立的 RPM 仓库,
repodata 使用本地 pool/... href。
语法
| 参数 | 要求 |
|---|---|
DIST |
已配置的规范 RPM Dist 名称 |
ARCH |
x86_64 或 aarch64 |
DIR |
不存在或为空,且不与 Repository、私有状态、filesystem 目标根重叠的目录 |
| 选项 | 含义 | 默认值 |
|---|---|---|
--hardlink |
对可信、同文件系统、只读目标使用硬链接 | 复制文件 |
-C, --workdir DIR |
工作区发现起点 | 当前目录 |
-r, --repo NAME |
选择 Repository | 选择规则 |
--json |
输出 sow.cli/v1 Envelope |
false |
该命令不接受 --dist、jobs、timeout 或锁参数。
输出
目标目录包含:
- 使用本地包体 href 重写的 RPM repodata;
- 所需软件包目录树;
- 导出 Manifest;
.sow-export.json来源记录。
源必须是已完成的 Built Generation。导出物是独立制品,不属于 Desired Membership、 Built Generation、发布输入或 GC 根。
复制与硬链接
复制是安全默认值。--hardlink 只适用于同一文件系统、且消费者无法修改的可信只读目标。硬链接
包体与 SOW 包池共享 inode,不能用于可写或不可信目标。
SOW 会拒绝与已配置 filesystem 发布根重叠的输出,避免导出物被误认为或修改 Managed 发布目标。
退出行为
| 代码 | 触发条件 |
|---|---|
0 |
独立 RPM leaf 导出完成 |
1 |
文件系统、复制、硬链接或元数据写入错误 |
2 |
命令语法、Dist/架构 Token 无效,或发现/隐式 Repository 选择有歧义 |
5 |
源 Generation 或 Repository 状态不一致 |
6 |
显式 Repository 未配置、Dist 不是 RPM、视图/签名者不可用,或目标不安全/非空/重叠 |
参见
- 平台与集成 —— 已验证与明确不支持的工作流
- 仓库布局 —— 源目录与导出边界
sow publish—— 向配置目标执行 Managed 交付
19 - sow log
仓库内的每条写命令,都会先在该仓库的 SQLite 中提交一条应用级 Operation,然后才产生任何外部文件
副作用。这条记录让崩溃恢复成为可能——而当 Operation 进入终态之后,同一条记录就是你的审计轨迹。
sow log 读的就是它。
语法
Operation 生命周期
读懂 state 字段,日志就读懂了一大半:
| 状态 | 含义 |
|---|---|
planned |
命令、参数、目标与预期动作已持久化 |
staged |
新包/元数据已写入临时位置并校验通过 |
applied |
期望状态与所需的私有 pending 载荷已提交 |
built |
完整的静态 Generation 已切换 |
done |
终态——一次正常成功的命令 |
done_dirty |
终态——给了 --skip,公开树被有意保留在旧代 |
failed |
终态——在 applied 之前失败,什么都没提交 |
rolled_back |
终态——applied 之后失败,但进程安全地回滚了 |
recovering |
非终态;下一条写命令必须先完成或回滚它 |
工作区生命周期命令(init、repo new、repo rm)走的是工作区文件 journal,不会出现在仓库的
SQLite 日志中。dist new/dist rm 会出现——那时仓库数据库已经存在。
sow log
不带参数时,按由新到旧打印最近 50 条 Operation。
输出节选,operations 数组中的一个 Operation 对象:
payload_json 记录意图——包括当时生效配置的摘要 config_sha256,以及结果 Generation 的
manifest_sha256。result_json 记录结果。失败的 Operation 还会带 error_class 与
error_message:
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-d, --dist NAME |
只显示触及该 Dist 的 Operation | 全部 |
--json |
输出版本化 JSON envelope | false |
查看单条 Operation
给出 Operation ID,就能得到它的完整状态迁移、耗时、包、成员与文件动作。
输出节选:
files 数组使用与 sow changes 相同的 phase 词表:payload、
metadata、pointer、delete。
Build Operation 还会包含进度事件。它们保持当前 state,并把版本化对象放入 detail_json:
阶段包括 rendering、promoting_payload、publishing_dists、
normalizing_public_tree 与 finalizing。进度行是持久审计数据,但不会推进恢复状态机,也不会
单独触发 SQLite checkpoint。
按 Dist 过滤
-d 把列表限制为触及该 Dist 的 Operation——一个仓库服务多个发行版时很有用:
sow log export
把终态 Operation 以 JSONL 写出——每行一条完整的 Operation 明细记录——用于归档或送入日志管道。
省略 FILE 或传 - 则写到 stdout:
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-d, --dist NAME |
只导出触及该 Dist 的 Operation | 全部 |
export 没有 --json——JSONL 就是它的输出格式。
它拒绝覆盖
目标已存在属于拒绝,绝不覆盖——审计导出不能静默毁掉上一份:
export 同样拒绝父目录不是真实目录的目标——符号链接,或根本不存在的目录:
macOS 上 /tmp 是指向 /private/tmp 的符号链接,所以在那里会触发这条拒绝。请写到明确的真实路径。
sow log prune
删除早于 BEFORE 且符合条件的终态审计记录,并安全压缩数据库。
绝对时间戳会被回显,这样本地时区的解释永远不含糊。
| 参数 | 说明 | 默认 |
|---|---|---|
-C, --workdir DIR |
工作区发现的起始目录 | 当前目录 |
-r, --repo NAME |
选择一个仓库 | 按选择规则 |
-T, --timeout DUR |
等待锁的最长时间;0 无限等待 |
0 |
-N, --no-wait |
锁被占用时立即失败 | false |
--json |
输出版本化 JSON envelope | false |
prune 在仓库级工作,不接受 -d——清掉半条 Operation 只会留下毫无意义的记录。
BEFORE 语法
BEFORE 是 ISO-8601 日期 YYYY-MM-DD(按本地时区零点解释),或带时区的 RFC 3339 时间戳。
prune 永不删除什么
prune 在设计上是保守的。它绝不会删除:
- 非终态的 Operation;
- 当前恢复仍需要的记录;
- 当前的 Package 或 Membership 状态;
- Built Generation 或其 Changeset。
pruned 计数准确告诉你有多少条记录符合条件——通常少于截止时间之前的 Operation 总数。日志与
Changeset 位于同一个 SQLite 数据库,但保留规则不同。
示例
排查最近一次写入:
列出所有失败:
每月归档并收缩:
哪条 Operation 最后触碰了某个 Dist:
退出码
| 命令 | 码 | 触发条件 |
|---|---|---|
log |
0 |
记录已打印,包括空账本 |
log |
2 |
用法错误(包括非数字的 Operation ID)、工作区未找到,或选择有歧义 |
log |
5 |
状态数据库不可读 |
log |
6 |
给定的 Operation ID 不存在 |
log export |
0 |
导出成功 |
log export |
1 |
写目标时 I/O 失败,或父目录不是真实目录 |
log export |
2 |
用法错误或选择有歧义 |
log export |
6 |
目标已存在 |
log prune |
0 |
清理完成,包括一条都没清 |
log prune |
2 |
BEFORE 格式非法、给了 -d,或选择有歧义 |
log prune |
4 |
仓库锁被占用,且给了 --no-wait 或 --timeout 到期 |
log prune |
5 |
完整性或恢复错误 |
参见
- 可观测与审计 ——
log与status、check、changes的配合 - 事务与恢复 —— 日志所记录的那个 journal
- sow changes —— 语义 Operation 的物理对照物
- JSON 输出 —— 完整的 log result 结构