sow add

把包加入期望成员集,执行成员策略,并重建受影响的索引。

sow add 是主要的写入路径。它解析你指定的包,从包头推导格式与架构,执行 Dist 的成员策略,并且—— 除非你加 --skip——在返回前重建全部受影响的索引。命令退出码为 0 时,客户端已经能看到新包了。

语法

sow add PATH... [-R|--recursive] [--skip] [-j|--jobs N] [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [-T|--timeout DUR | -N|--no-wait] [--json]

参数

参数说明默认
-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 envelopefalse

输入与目标

PATH 可以是文件或目录。目录默认只扫描顶层,除非你加 -R

最终必须确定恰好一个仓库与至少一个目标 Dist——见选择规则。RPM 与 DEB 混合批次是允许的:每个包只会被考虑放进格式相同的目标 Dist;一个包如果没有任何兼容目标,则该包失败。

SOW 绝不从 manifest、目录名或宿主机 OS 推断目标。

sow add /srv/pkg/centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm /srv/pkg/epel-release-7-5.noarch.rpm -r pigsty -d el9
add repository=pigsty operation=8677129233475584643 accepted=2 failed=0 memberships=+2/-0 revision=3 generation=3 dirty=false
item input="/srv/pkg/centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm" status=accepted format=rpm coordinate="centos-release-0:7-2.1511.el7.centos.2.10.x86_64" sha256:b4111ef2a51542eacc9bd1ebd080da02e53d400f9d172530c75a1e4ac06e7ead dists=el9:accepted
item input="/srv/pkg/epel-release-7-5.noarch.rpm" status=accepted format=rpm coordinate="epel-release-0:7-5.noarch" sha256:d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab dists=el9:accepted

汇总行给出 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 就是幂等的样子。同一个文件加两次不会产生第二个对象,也不会推进 Generation:

sow add /srv/pkg/epel-release-7-5.noarch.rpm -r pigsty -d el9
add repository=pigsty operation=656950149626836753 accepted=1 failed=0 memberships=+0/-0 revision=4 generation=4 dirty=false
item input="/srv/pkg/epel-release-7-5.noarch.rpm" status=reused format=rpm coordinate="epel-release-0:7-5.noarch" sha256:d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab dists=el9:accepted

把同一个对象加进第二个 Dist 同样是 reused——包池只保留一份,只是多了一条 Membership。

架构是读出来的,不是猜的

add 从包头读取格式与原生架构,再对照工作区许可表。不在许可表中的架构会让该包失败,并明确告诉你 要改什么:

sow add /srv/pkg/centos-release-3.1-1.i386.rpm -r pigsty -d el9
item input="/srv/pkg/centos-release-3.1-1.i386.rpm" status=failed error="managed: operation rejected: unknown rpm package architecture \"i386\"; supported rpm package architectures are [x86_64, aarch64, noarch] (canonical families [x86_64, aarch64, neutral]); use a supported package or update only supported architecture families in sow.yml"

它不会创建目录,也不会修改 sow.yml

RPM 的 noarch 与 DEB 的 all 是架构中性(neutral)的。它们只产生一个 Package Object 与一条 Membership,但会渲染进目标 Dist 的每个有效架构视图。它们不会自动扩散到你没有用 -d 选中的 Dist。

策略:exclude 与 limit

合并进目标 Membership 之后,SOW 会在完整的 Dist 候选集上重新求值 exclude,再求值 limit。被策略 移除的包会被明确报告,不算解析失败。

sow add /srv/pkg/debs -r pgsql -d trixielim
add repository=pgsql operation=4142220455201181493 accepted=3 failed=0 memberships=+3/-0 revision=6 generation=6 dirty=false
item input="/srv/pkg/debs/libpq5-dbgsym_18.3-1_amd64.deb" status=excluded format=deb coordinate="libpq5-dbgsym=18.3-1:amd64" sha256:cf491b9d9b218fa49ad2b41b4740d62cd972e1b515bf33677c2c3ead75acc60a dists=trixielim:excluded
item input="/srv/pkg/debs/libpq5_18.2-1_amd64.deb" status=excluded format=deb coordinate="libpq5=18.2-1:amd64" sha256:fa84dc641b7c686be2f9b512311ad0b74eac03e2afc9eff7e9af75b82b68ff41 dists=trixielim:limited
item input="/srv/pkg/debs/libpq5_18.3-1_amd64.deb" status=reused format=deb coordinate="libpq5=18.3-1:amd64" sha256:491992c502113627d44d0d66a2b189cdaa8accff293ebaf84fe10ccbc9da574c dists=trixielim:accepted
item input="/srv/pkg/debs/libpq5_18.3-1_arm64.deb" status=reused format=deb coordinate="libpq5=18.3-1:arm64" sha256:3a2f7ef7cddfa3dc06280ef59eda1dab9724d57499931ee80758b11531c1f40c dists=trixielim:accepted
item input="/srv/pkg/debs/pg-sample_1.17-1_all.deb" status=reused format=deb coordinate="pg-sample=1.17-1:all" sha256:f23581c5164a143e5e902232589adf1d30b73ba3857a692a11da607f246aacc3 dists=trixielim:accepted

这里 trixielim 配了 exclude: [{kind: [dbgsym]}]limit: 1。dbgsym 包被规则排除; libpq5 18.2-1 在版本上限下输给了 18.3-1,报告为 limited。两者的顶层状态都是 excluded, 靠 dists= 字段区分。

limit 按 (二进制包名, 原生架构) 分组,因此 18.3-1:amd6418.3-1:arm64limit: 1 下都能 留下。同一次运行中,一个包可以被某个 Dist 接受、被另一个 Dist 跳过。

部分成功的批次

即使同批有失败项,合法且无冲突的包依然会提交。失败的输入原地不动,各自带自己的错误信息,命令退出 3

sow add /srv/pkg/centos-release-3.1-1.i386.rpm /srv/pkg/centos-release-6-0.el6.centos.5.x86_64.rpm -r pigsty -d el9
add repository=pigsty operation=4623871845694427260 accepted=1 failed=1 memberships=+1/-0 revision=5 generation=5 dirty=false
item input="/srv/pkg/centos-release-3.1-1.i386.rpm" status=failed error="managed: operation rejected: unknown rpm package architecture \"i386\"; ..."
item input="/srv/pkg/centos-release-6-0.el6.centos.5.x86_64.rpm" status=accepted format=rpm coordinate="centos-release-0:6-0.el6.centos.5.x86_64" sha256:ffd9e7bdaa4884831a6c055ada01dac96b84c50a8d518dac409b445af5dadc16 dists=el9:accepted
managed: batch partially succeeded

如果一个都没被接受,整个操作以退出码 6 被拒绝,仓库保持原样:

sow add /srv/pkg/centos-release-3.1-1.i386.rpm -r pigsty -d el9
operation rejected: managed: operation rejected: no input package was accepted

没有 rejected/隔离目录。

–skip

--skip 在期望状态提交后就停下。公开的 pool/dists/ 字节不变,Built Generation 保持原位, 仓库变为 dirty。新包字节被持久保存在私有 pending 存储中,直到下一次 build 才发布。

sow add /srv/pkg/tree -R --skip -r pgsql -d trixie
add repository=pgsql operation=8405631664133415270 accepted=6 failed=0 memberships=+4/-0 revision=4 generation=3 dirty=true
sow status -r pgsql
repository=pgsql status=dirty ready_to_copy=false revision=4 generation=3 dirty_dists=trixie pending=4/2326 locked=false

pending=4/2326 表示私有存储里有 4 个对象、共 2326 字节在等待。它们不会出现在 sow changes 中——只有成功的 build 才会把它们提升进可交付树。

批量导入时用 --skip,最后一次性收敛:

sow add /srv/build/ -R -r pgsql -d el9 --skip
sow status -r pgsql
sow build -r pgsql -j 12
sow check -r pgsql

处理顺序

一次 add 的执行顺序如下:

  1. 取得仓库写锁,并恢复任何未完成的 Operation。
  2. 在 SQLite 中提交一条 planned Operation。
  3. 只读解析输入,计算逻辑坐标与输入字节 SHA-256(RPM 还会计算 signature-neutral payload digest)。
  4. 校验架构许可表,并查询已有坐标。
  5. 只对确实全新的坐标,在 stage 副本上执行可选的 RPM 签名并计算最终 SHA-256,再校验内容与路径唯一 性。
  6. 合并目标 Membership,然后在完整 Dist 集合上执行 excludelimit
  7. 提交期望状态;新字节写入私有 pending 内容存储。
  8. 除非给了 --skip,把仍被需要的 pending 对象发布进 pool/ 并渲染索引——一次命令中每个 Dist 最多 构建一次。

任何模式下,输入文件都不会被修改、移动或删除。

RPM 签名模式

Managed 模式的 RPM 包签名在 sow.ymlsigning.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,或坐标冲突

参见

最后修改:2026-08-08: init commit (fe725aa)