跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

命令

SOW 命令语法、选项、选择规则与退出码。

本节列出命令语法、选择规则、输出与示例。使用 sow help COMMAND 或 sow help COMMAND SUBCOMMAND 查看已安装版本的选项。

本文用 Desired 表示期望状态,用 Built 表示已构建状态。构建快照(Generation)是一次已完成构建的结果。

命令索引

sow create 直接在目录中构建平面仓库。需要通过工作区管理软件包时,先运行 sow init。其他 Managed 命令使用已有工作区。help 与 version 可以在任意目录运行。

命令 模式 用途
sow create [DIR] Plain 就地生成平面 RPM/DEB 仓库
sow init [DIR] Managed 初始化工作区并创建已声明的仓库/Dist
sow config check|show Managed 校验配置或打印有效配置
sow repo ls|new|show|migrate|rm Managed 创建、查看、升级或删除仓库
sow dist ls|new|show|rm Managed 管理 Dist
sow add PATH... Managed 添加软件包并重建索引
sow rm PACKAGE... Managed 移除软件包并重建索引
sow ls Managed 列出期望成员及其已构建位置
sow show PACKAGE Managed 查看一个软件包
sow where PACKAGE Managed 在工作区中查找软件包
sow status Managed 快速读取仓库状态
sow build Managed 按期望状态生成构建快照
sow check Managed 在交付前校验仓库
sow changes [BASE_GENERATION] Managed 比较两个构建快照的文件差异
sow publish TARGET Managed 将构建快照发布到配置目标
sow retain add|ls|rm Managed 保留旧构建快照所需的包文件
sow gc [TARGET] Managed 回收不再需要的本地包,或维护发布目标
sow export rpm-leaf Managed 导出独立 RPM 仓库
sow log [OPERATION] Managed 查询、导出和裁剪操作记录

全局语法

sow [OPTIONS] COMMAND [ARGS]

不带参数运行 sow 会打印命令列表并退出 0。用 sow help COMMAND 或 sow help COMMAND SUBCOMMAND 查看内置帮助。sow version 与 sow --version 打印版本与构建信息。

选项只适用于列出该选项的命令。位置参数以 - 开头时,在它前面加 --。未知选项或重复的单值选项返回退出码 2。

工作区发现

Managed 命令按以下规则寻找最近的 sow.yml:

  1. 有 -C/--workdir DIR 时从 DIR 开始,否则从当前目录开始。
  2. 逐级向上查找,在第一个 sow.yml 停止。
  3. 首次查找失败且设置了 SOW_DIR 时,再从该目录查找。显式 -C 会取代当前目录候选, 但不会禁用 SOW_DIR 回退。
  4. 仍未发现工作区则退出 2。

--workdir 只改变发现起点,不会切换进程工作目录;相对位置参数仍相对于真实当前目录解析。 sow create 完全不参与工作区发现。

Repository 选择

需要唯一仓库的命令按以下顺序选择:

  1. 显式 -r/--repo NAME;
  2. 发现起点所在的仓库;
  3. 工作区中唯一的仓库;
  4. 否则退出 2 并列出候选项。

repo new 与 repo rm 用位置参数接收 NAME,不接受 -r。sow where 默认搜索所有 仓库,-r 只用于收窄范围。发布目标自身绑定仓库,因此 publish TARGET 与 gc TARGET 不再接受额外的仓库选择。

Dist 选择

add、rm、ls 要求明确的 Dist 集合,并按以下顺序选择:

  1. 一个或多个 -d/--dist NAME;
  2. 发现起点所在的 Dist;
  3. 所选仓库中唯一的 Dist;
  4. 否则退出 2 并列出候选项。

其他命令采用以下默认范围:

  • 未指定 -d 时,build、check、status 默认作用于全部 Dist;
  • show 默认搜索所选仓库,-d 只用于收窄;
  • where 默认跨工作区搜索全部匹配 Dist,-r/-d 用于收窄;
  • changes 作用于整个仓库,不接受 -d。

锁

create 锁定目标目录。Managed 写操作通过工作区锁和仓库锁协调。init 会等待锁,不提供命令行超时选项。

其他写命令接受 -T/--timeout DUR 与 -N/--no-wait。默认 --timeout 0 表示无限等待。正数时长使用 ms、s、m 或 h,例如 500ms、30s、5m。--no-wait 在锁被占用时立即失败,不能与正数超时同时使用。无法获取锁时返回 4。

大多数 Managed 只读命令使用共享锁,会等待冲突的写操作完成;这些命令不接受等锁选项。status 通常只探测仓库锁,因此可以查看正在运行的写操作。按 Ctrl-C 可以取消等待。

并发

只有需要解析软件包、哈希包体、渲染索引或执行校验的命令才接受 -j/--jobs N:create、 add、rm、build、check 与 repo migrate。默认值是逻辑 CPU 数,且不得小于 1。

JSON 输出

支持 --json 的命令在 stdout 输出一个带版本的封装,诊断信息仍写入 stderr:

{
  "schema": "sow.cli/v1",
  "command": "add",
  "ok": true,
  "repository": "demo",
  "operation": "1430722512865805553",
  "result": {},
  "errors": []
}

任何非零退出都会令 ok 为 false;部分成功的批处理仍会返回已提交项与失败项。完整结果结构见 JSON 输出。

交互操作使用文本输出,脚本使用 --json。config show 默认输出 YAML。log export 是例外:它直接输出 JSONL,不接受 --json。

退出码

代码 含义
0 成功或幂等空操作
1 运行时 I/O、解析、渲染、签名或传输错误
2 用法、工作区发现或配置错误
3 批处理部分成功
4 写锁不可用
5 完整性/恢复失败,或 check 判定目录不可交付
6 操作被拒绝:冲突、受保护对象、无匹配或架构不兼容
130 命令被 SIGINT 或 SIGTERM 中断

各命令的精确触发条件见退出码。

1 - sow create

在软件包所在目录生成 RPM 与 DEB 索引。

sow create 在软件包所在目录生成 RPM 与 DEB 索引。需要平面仓库时使用此命令,无需创建 Managed 工作区。DIR 默认为当前目录。

语法

sow create [DIR] [-j N] [--metadata-timestamp SECONDS] [--pigsty] [-S KEY [--overwrite]] [-T DUR | -N] [--json]

说明

create 读取 DIR 顶层的普通文件,按发现的内容渲染对应索引:有 RPM 就生成 repodata/,有 DEB 就 生成 Packages 与 Packages.gz,混合目录两套一起生成。架构全部来自包头——Plain 模式没有架构参数, 也没有架构许可表。

索引使用相对包路径:RPM 使用文件名,DEB 使用 ./<文件名>。可以通过 HTTP 提供整个包目录,也可以将它作为本地 file:// 软件源。

默认情况下,create 只更新索引并保留包文件。--pigsty 会删除符合规则的包,--sign-with 会用签名副本替换 RPM。

参数

参数 说明 默认
-j, --jobs N 处理软件包的并发工作线程数 逻辑 CPU 数
--metadata-timestamp SECONDS RPM repomd 的 data 时间戳,取值 0 至 253402300799 的 Unix 秒;不影响 DEB、包时间或 gzip 头 0
--pigsty 启用 Pigsty 兼容清理与完成标记 关闭
-S, --sign-with KEY 用 GPG 密钥 ID 或指纹为未签名 RPM 补签 关闭
--overwrite 重签全部 RPM;必须与 --sign-with 同用 关闭
-T, --timeout DUR 等待锁的最长时间;0 表示无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false
-h, --help 显示帮助 —

扫描规则

  • 只考虑顶层、以 .rpm 或 .deb 结尾的普通文件。
  • 不递归、不跟随符号链接、不读工作区配置。
  • 所有有效版本都进入索引。两个文件对应同一逻辑坐标但内容不同时,命令报错。
  • 默认模式要求至少有一个支持的包。--pigsty 允许清理后没有包,并为该空目录写入完成标记。
sow create /srv/empty
plain: scan /srv/empty: no supported top-level regular RPM or DEB packages

包 I/O 与最终校验

create 运行期间,保持输入目录不变。SOW 扫描软件包后,会在替换索引前检查文件是否变化。检测到变化时返回退出码 5。使用 --jobs 设置处理软件包的并发数。

确定性输出与幂等

不强制重签软件包时,相同的软件包与时间参数生成相同的元数据。repomd.xml 的 <revision> 和 data 时间戳默认为 0;--metadata-timestamp 只修改 data 时间戳。对未变化的目录使用相同选项重跑时,SOW 不改写文件,并报告 noop=true:

sow create /srv/flat
created /srv/flat: rpm=3 deb=1 signed=0 removed=0 marker=false noop=false recovered=false

sow create /srv/flat
created /srv/flat: rpm=3 deb=1 signed=0 removed=0 marker=false noop=true recovered=false

已有 YUM 仓库的发布时间

--metadata-timestamp 设置 RPM repomd 的 data 时间戳,不改变软件包、压缩 XML 文件或 DEB 索引。默认值为 0,范围为 0..253402300799 Unix 秒。此选项从 SOW 0.5.0 开始提供。

EL7 YUM 会拒收最大 data 时间戳早于本地缓存的元数据。替换已有元数据生成器时,选择不低于历史已发布最大值的时间。内容变化时,至少增加一秒,以区分各次更新。

sow create /srv/flat --metadata-timestamp "${PUBLISH_TIME:?Set the publication time}"

记录发布时间,并在每次更新时传入。省略此选项会重新使用 0。恢复旧包集合也属于新发布,因此仍需递增时间戳。使用秒值,不要传毫秒值或未来日期。

如果客户端验证 repomd.xml.asc,repomd 变化后需要重新生成该签名。create 不签署 repomd,也不更新或删除旧的分离签名。

完整流程见迁移现有 YUM 仓库。

repo_complete 标记

如果目录中有 repo_complete,使用 --pigsty 将它与索引一起更新。不带 --pigsty 运行会返回错误:

sow create /srv/pigsty
plain: marker gate /srv/pigsty/repo_complete: repo_complete exists; use --pigsty or remove it explicitly before rebuilding

需要保留完成标记时,添加 --pigsty 后重跑;不再使用此标记时,先将它移走。

–pigsty

--pigsty 按以下规则清理包文件,并写入完成标记:

  1. 删除解析架构为 i386 的 DEB;RPM 不会仅因为架构是 i386/i486/i586/i686 而被删除。
  2. 删除二进制包名恰为 patroni 且上游版本恰为 3.0.4 的 RPM/DEB。RPM 比较 VERSION,忽略 epoch 与 release;DEB 先剥掉 epoch 与 Debian revision 再比。3.0.4+foo 不算命中。
  3. 全部索引渲染成功后写出 repo_complete:剩余顶层 RPM/DEB 的 SHA-256,按文件名字节序排序, 格式为 <sha256><两个空格><basename>。
sow create /srv/pigsty --pigsty
created /srv/pigsty: rpm=2 deb=0 signed=0 removed=2 marker=true noop=false recovered=false
cat /srv/pigsty/repo_complete
b4111ef2a51542eacc9bd1ebd080da02e53d400f9d172530c75a1e4ac06e7ead  centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm
d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab  epel-release-7-5.noarch.rpm

清理仅处理解析成功且命中规则的顶层普通包文件。

SOW 先撤下已有的 repo_complete,再替换索引;新元数据安装后删除命中规则的包, 最后写入新的完成标记。调用方看到标记缺失时,应等待构建完成。

RPM 包签名

-S/--sign-with KEY 通过本地 RPM/GPG 环境为 RPM 包签名。使用 16 位十六进制密钥 ID,或 40/64 位指纹;可以带 0x 或 0X 前缀。运行前,在该环境中配置私钥与口令。

  • 默认只给没有可解析嵌入 OpenPGP 签名的 RPM 补签;已有签名的包保持原字节。
  • --overwrite 必须与 --sign-with 同用,改为对全部保留 RPM 执行 rpm --resign。
  • SOW 对临时副本签名,确认包身份与内容未变后替换原 RPM。索引描述最终签名后的文件。
  • --pigsty 清理后至少要保留一个顶层 RPM,且 PATH 中要有 rpm。
sow create /srv/flat -S 0123456789ABCDEF --overwrite
plain: sign rpm epel-release-7-5.noarch.rpm: rpm executable is required for --sign-with
sow create /srv/deb-only -S 0123456789ABCDEF
plain: sign rpm: --sign-with requires at least one retained top-level RPM package
sow create /srv/flat --overwrite
usage error: --overwrite requires --sign-with
sow create /srv/flat -S ZZZZ
usage error: --sign-with must be a 16, 40, or 64 hexadecimal GPG key ID/fingerprint

锁与中断恢复

create 在更新索引期间锁定目标目录。使用 --timeout 或 --no-wait 控制等待锁的时间。

中断可能使部分索引已更新、部分仍未更新。使用原定选项重新运行 sow create,按当前软件包重新生成全部索引。Plain 模式从头重建,因此结果字段 recovered 始终为 false。

使用 --pigsty 时,在 repo_complete 出现后再对外提供仓库。需要操作日志恢复时,使用 Managed 模式。

示例

给混合目录建索引:

sow create /srv/flat
created /srv/flat: rpm=3 deb=1 signed=0 removed=0 marker=false noop=false recovered=false
ls /srv/flat
centos-release-6-0.el6.centos.5.x86_64.rpm
centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm
epel-release-7-5.noarch.rpm
libpq5_18.3-1_amd64.deb
Packages
Packages.gz
repodata

机器可读结果:

sow create /srv/flat --json
{"schema":"sow.cli/v1","command":"create","ok":true,"repository":null,"operation":null,"result":{"dir":"/srv/flat","rpm":3,"deb":1,"kept":["centos-release-6-0.el6.centos.5.x86_64.rpm","centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm","epel-release-7-5.noarch.rpm","libpq5_18.3-1_amd64.deb"],"removed":[],"marker":false,"noop":true,"recovered":false},"errors":[]}

用八个工作线程构建 Pigsty 平面仓库,并使用根据发布历史选定的时间:

sow create /www/pigsty -j 8 --pigsty --metadata-timestamp "${PUBLISH_TIME:?Set the verified publication time}"

调用者负责记录该时间,并在客户端要求时为生成的 repomd.xml 签名。

失败时的 JSON 结果:

sow create /srv/empty --json
{"schema":"sow.cli/v1","command":"create","ok":false,"repository":null,"operation":null,"result":null,"errors":[{"code":6,"class":"rejected","message":"operation rejected: plain: scan /srv/empty: no supported top-level regular RPM or DEB packages"}]}

退出码

码 触发条件
0 索引写出成功,或输入未变化产生 no-op
1 目录不可读或不存在、包解析失败、渲染失败、签名工具失败
2 用法错误——时间戳非法或重复指定、--overwrite 未配 --sign-with、密钥格式非法、--no-wait 与非零 --timeout 同用
4 目录写锁被占用,且给了 --no-wait 或 --timeout 到期
5 发布前输入文件发生变化,或受控输出路径未通过完整性检查
6 未找到受支持的包、已有 repo_complete 但未使用 --pigsty、对 DEB-only 目录用 --sign-with、坐标冲突

参见

2 - sow init

初始化工作区及 sow.yml 中声明的仓库与 Dist。

sow init 创建工作区的 sow.yml 与私有状态目录 .sow/。如果配置中已声明仓库和 Dist,它会创建尚未初始化的对象。添加声明后,可再次运行此命令完成初始化。

语法

sow init [DIR] [--json]

DIR 默认为当前目录。需要初始化其他目录时,将它的路径作为 DIR 传入。

说明

首次 init 写出最小配置与私有状态目录:

sow init .
initialized /srv/repo: config_created=true repositories_initialized=0 dists_initialized=0
cat sow.yml
schema: sow/v3
architectures:
  - x86_64
  - aarch64
ls -a /srv/repo
.  ..  .sow  sow.yml

.sow/ 保存私有状态、锁和恢复记录,目录权限为 0700。通过 HTTP 提供仓库目录时,保留 .sow/ 为私有目录。

参数

参数 说明 默认
--json 输出 JSON false
-h, --help 显示帮助 —

幂等规则

新配置使用 schema: sow/v3,默认架构为 x86_64 与 aarch64,不声明任何仓库。使用 sow repo new 创建仓库,或先编辑 sow.yml 声明仓库。

init 保留已有配置,初始化尚缺的声明对象。非空目录也可以使用,但其中的文件不能与 SOW 保留路径冲突。全部声明对象都已初始化时,本次初始化计数为零。

应用已声明的配置

如果 sow.yml 里已经描述了仓库与 Dist,init 会为它们补齐缺失的目录树、SQLite 数据库与 空索引。已初始化的对象直接跳过,因此计数器准确反映本次运行做了什么。

schema: sow/v3
architectures: [x86_64, aarch64]

repos:
  pgsql:
    dists:
      el9:
        format: rpm
        limit: 1
        exclude:
          - kind: [debuginfo, debugsource]
      trixie:
        format: deb
  infra:
    protected: true
    dists:
      el9:
        format: rpm
sow init .
initialized /srv/repo: config_created=false repositories_initialized=2 dists_initialized=3
sow repo ls
NAME	PROTECTED	DISTS	GENERATION	STATUS	PACKAGES	MEMBERSHIPS
infra	true	1	1	clean	0	0
pgsql	false	2	2	clean	0	0

新 Dist 具有有效的空索引:RPM Dist 的每个架构视图有一份空 repodata/,DEB Dist 有空的 Packages/Packages.gz、by-hash 与 Release。

再次运行时,已有对象保持不变:

sow init . --json
{"schema":"sow.cli/v1","command":"init","ok":true,"repository":null,"operation":null,"result":{"workspace":"/srv/repo","config_created":false,"repositories_initialized":0,"dists_initialized":0,"existing":["sow.yml"]},"errors":[]}

锁与恢复

初始化中断后,对同一目录重新运行 sow init。SOW 先完成或回滚被中断的操作,再初始化其余声明对象。有其他工作区写操作时,它会等待该操作结束。

示例

建好工作区后手工添加仓库:

mkdir -p /srv/repo
cd /srv/repo
sow init
sow repo new infra
sow repo new pgsql
sow dist new el9 --format rpm -r pgsql
sow dist new trixie --format deb -r pgsql

初始化当前目录之外的目录:

sow init /srv/repo

使用版本控制中的配置文件初始化:

install -m 0644 sow.yml /srv/repo/sow.yml
sow init /srv/repo
sow config check -C /srv/repo

退出码

码 触发条件
0 工作区初始化成功,或已经初始化(no-op)
1 写配置或状态目录时的运行时 I/O 错误
2 用法错误,或已存在的 sow.yml 解析/校验不通过
3 部分成功——部分声明的仓库/Dist 已提交,至少一个失败
5 工作区日志无法恢复到终态
6 已有文件与 SOW 保留路径冲突

参见

3 - sow config

校验 sow.yml 并查看生效配置。

修改 sow.yml 后,运行 sow config check。它检查配置、签名引用及配置与既有仓库状态是否兼容。使用 sow config show 查看默认值与继承设置展开后的配置。两个命令都只读。

语法

sow config check [-C DIR] [--json]
sow config show [--all] [-C DIR] [-r NAME] [-d NAME]... [--json]

sow config check

解析并校验完整的 sow.yml:schema 版本、名称、路径冲突、架构许可表、Dist 格式、成员策略与签名密钥 引用。它会回报解析到的工作区以及校验了多少对象。

sow config check
configuration valid: /srv/repo repositories=1 dists=2
sow config check --json
{"schema":"sow.cli/v1","command":"config check","ok":true,"repository":null,"operation":null,"result":{"workspace":"/srv/repo","repositories":1,"dists":2},"errors":[]}

参数

参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
--json 输出 JSON false
-h, --help 显示帮助 —

严格拒绝未知字段

未知配置键会产生配置错误:

sow config check
configuration error: load config "/srv/repo/sow.yml": parse sow.yml: yaml: unmarshal errors:
  line 8: field bogus_field not found in type config.DistConfig

使用 schema: sow/v3:

sow config check
configuration error: load config "/srv/repo/sow.yml": config schema must be "sow/v3", got "invalid"

config check 还会验证签名密钥是否可用且适用于签名。从工作区架构列表移除某个架构前,先移除 Dist 配置、成员和已构建状态对该架构的使用。

sow config show

以 YAML 输出所选范围内的生效配置。

sow config show
schema: sow/v3
architectures:
  - x86_64
  - aarch64
repos:
  pigsty:
    protected: false
    signing:
      rpm:
        packages:
          mode: never
    dists:
      el9:
        format: rpm
        architectures:
          - x86_64
          - aarch64
        limit: 0
        exclude: []
      trixie:
        format: deb
        architectures:
          - x86_64
          - aarch64
        limit: 0
        exclude: []

对比原始配置文件:

cat sow.yml
schema: sow/v3
architectures:
  - x86_64
  - aarch64
repos:
  pigsty:
    signing:
      rpm:
        packages:
          mode: never
    dists:
      el9:
        format: rpm
      trixie:
        format: deb

show 补上了 protected: false、每个 Dist 继承来的 architectures、limit: 0 与空的 exclude 列表。架构以 x86_64 和 aarch64 表示;它们在 DEB 中分别写作 amd64 和 arm64。

参数

参数 说明 默认
--all 展开整个工作区的默认值与规范化架构 关闭
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-d, --dist NAME 选择一个 Dist;可重复 按选择规则
--json 输出 JSON false
-h, --help 显示帮助 —

用 -r/-d 选择配置范围

使用 -r 与 -d 查看指定 Dist 的实际配置:

sow config show -r pigsty -d el9
schema: sow/v3
architectures:
  - x86_64
  - aarch64
repos:
  pigsty:
    protected: false
    signing:
      rpm:
        packages:
          mode: never
    dists:
      el9:
        format: rpm
        architectures:
          - x86_64
          - aarch64
        limit: 0
        exclude: []

不带 --all 时,按一般规则选择仓库。如果发现起点位于某个 Dist 内,默认只显示该 Dist,除非显式传入 -d;否则显示所选仓库的全部 Dist。--all 显示整个工作区,并忽略 -r 与 -d。

签名引用

输出包含密钥引用和解析后的指纹,不包含私钥内容或口令。

示例

在 CI 里先校验再构建:

sow config check -C /srv/repo || exit 1
sow build -C /srv/repo -r pgsql

比较两个 Dist 的有效策略:

sow config show -r pgsql -d el9 > /tmp/el9.yml
sow config show -r pgsql -d el9-beta > /tmp/beta.yml
diff -u /tmp/el9.yml /tmp/beta.yml

退出码

码 触发条件
0 配置合法,或输出成功打印
1 运行时错误或密钥解析失败
2 用法或发现错误、YAML 无效、未知字段或不支持的 schema
5 既有状态、受控路径或恢复记录不一致
6 所选对象不存在、签名配置不可用,或配置与既有状态不兼容

config check 同时检查 YAML 文件及其与已有仓库状态的兼容性。

参见

4 - sow repo

创建、列出、查看、迁移和删除仓库。

sow repo 用于创建、列出、查看、升级和删除仓库。每个仓库在 pool/ 中保存包文件,在 dists/ 中保存索引。同一仓库的 Dist 共用包文件。

语法

sow repo ls [-C DIR] [--json]
sow repo new NAME [-C DIR] [-T DUR | -N] [--json]
sow repo show [NAME] [-C DIR] [-r NAME] [--json]
sow repo migrate [NAME] [--abort] [-j N] [-C DIR] [-r NAME] [-T DUR | -N] [--json]
sow repo rm NAME [-f|--force] [-C DIR] [-T DUR | -N] [--json]

命名

仓库名必须匹配 [a-z0-9][a-z0-9._-]*,且不能是 .、..、.sow、pool、dists,也不能与工作区 保留文件冲突。

sow repo new .sow
operation rejected: managed: operation rejected: name ".sow" must match [a-z0-9][a-z0-9._-]*

SOW 在 <workspace>/<NAME>/ 创建仓库。

sow repo ls

只读列出工作区里的全部仓库。

sow repo ls
NAME	PROTECTED	DISTS	GENERATION	STATUS	PACKAGES	MEMBERSHIPS
infra	true	1	1	clean	0	0
pgsql	false	2	2	clean	0	0
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
--json 输出 JSON false

STATUS 取值为 clean、dirty、recovering 或 error。各状态对客户端意味着什么,见 事务与恢复。

sow repo new

更新 sow.yml,然后创建 <workspace>/<NAME>/{pool,dists}、SQLite 数据库与私有状态目录。新仓库 尚未构建,构建快照编号为 0,状态为 clean。

sow repo new pigsty
created pigsty: path=/srv/repo/pigsty protected=false dists=0 generation=0 status=clean packages=0 memberships=0
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

用 NAME 指定新仓库的名称。

对已存在的仓库再次运行时,命令报告当前状态,不会创建第二个仓库。

sow repo show

只读显示一个仓库的细节。省略 NAME 时按 CLI 全局约定 中的仓库选择规则 解析。

sow repo show pigsty
repository pigsty:
  path: /srv/repo/pigsty
  protected: false
  dists: 2
  generation: 6
  desired_revision: 6
  status: clean
  packages: 5
  memberships: 8
  config: {"protected":false,"signing":{"rpm":{"packages":{"mode":"never"}}},"dists":{"el9":{"format":"rpm","architectures":["x86_64","aarch64"],"limit":1,"exclude":[{"kind":["debuginfo","debugsource"]}]},"trixie":{"format":"deb","architectures":["x86_64","aarch64"],"limit":0,"exclude":null}}}
  dirty_reasons: []
  recent_operation: id=4142220455201181493 kind=add state=done error_class= created_at=2026-08-04T04:09:24.995538Z updated_at=2026-08-04T04:09:25.332772Z
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 省略 NAME 时用它选择仓库 按选择规则
--json 输出 JSON false

同时给出 NAME 与 -r 时,使用相同名称:

sow repo show demo -r empty
operation rejected: repo show NAME "demo" and --repo "empty" select different repositories

sow repo migrate

将既有 v0.3 或 v0.4 工作区升级到 SOW 0.5 时,使用 repo migrate。新建的 0.5 仓库已使用 schema v13,无需迁移。

  1. 停止工作区写操作。
  2. 备份整个工作区,并保留硬链接。
  3. 逐个迁移已配置的仓库。
  4. 对每个仓库运行 build 与 check。
rsync -aH /srv/sow/ /srv/sow.backup-before-0.5.0/
sow repo migrate pigsty -C /srv/sow
sow build -r pigsty -C /srv/sow
sow check -r pigsty -C /srv/sow

对其他仓库重复后三条命令。迁移结果包含 schema_from 与 schema_to;重复迁移报告 13 -> 13。Schema v13 增加包池路径查询索引。从 v0.3 迁移时,还会应用中间版本的状态与发布记录修复。

写操作要求当前 schema。兼容的只读命令可以查看旧状态,但不会升级数据库。迁移后使用 SOW 0.5 或更新版本;需要返回旧 schema 时,恢复备份。

--abort 只能在提交决策前放弃未完成的布局迁移,不能撤销已完成的 schema 升级。开发期 C2 布局需要从原始软件包重新建仓。

参数 含义 默认值
-j, --jobs N 并行校验/渲染工作线程 逻辑 CPU 数
--abort 在提交决策前放弃维护尝试 false
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 省略 NAME 时选择仓库 选择规则
-T, --timeout DUR 最长锁等待;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

sow repo rm

删除整个仓库,包括 sow.yml 条目、数据库、pool/ 中的包文件、dists/ 中的索引与私有状态。

不加 -f 时,只能删除空仓库——没有 Dist、没有成员关系、没有软件包对象:

sow repo rm infra
removed repository infra
sow repo rm pgsql
operation rejected: managed: operation rejected: repository "pgsql" is not empty; use --force
sow repo rm pgsql -f
removed repository pgsql
参数 说明 默认
-f, --force 删除非空的、未受保护的仓库 false
-C, --workdir DIR 工作区发现的起始目录 当前目录
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

强制删除

使用 -f 删除非空仓库。设置了 protected: true 的仓库仍然受到保护。

protected

sow.yml 中的 protected: true 阻止删除仓库,包括使用 -f 的情况:

sow repo rm alpha -f
operation rejected: managed: operation rejected: repository "alpha" is protected

需要删除受保护仓库时,在 sow.yml 中将它的 protected 设为 false。运行 sow config check,再重试删除。

protected 只作用于仓库删除。add、rm、build 与 dist rm 照常工作:

sow dist rm el9 -r alpha -f
removed dist el9 from alpha

示例

为两层结构创建仓库:

sow repo new infra
sow repo new pgsql

创建仓库时不等待已被占用的锁:

sow repo new nightly -N

逐行显示仓库名称、状态与构建快照编号:

sow repo ls --json | jq -r '.result.repositories[] | "\(.name)\t\(.status)\tgen=\(.generation)"'

退出码

码 触发条件
0 列出、创建、显示、迁移、放弃提交前的布局转换或删除成功;或 repo new 报告已存在的仓库
1 创建或删除目录树时的运行时 I/O 错误
2 用法错误、工作区未找到,或仓库选择有歧义
4 工作区锁被占用,且给了 --no-wait 或 --timeout 到期
5 工作区日志的完整性或恢复错误
6 名称非法、仓库不存在、非空但未给 -f、protected,或 NAME 与 -r 冲突

参见

5 - sow dist

创建、列出、查看和删除 RPM 或 DEB 发行版集合。

sow dist 管理客户端使用的发行版集合。每个 Dist 只包含 RPM 或 DEB 中的一种格式。同一仓库内的 Dist 共用 pool/,在 dists/ 下各有独立索引。

语法

sow dist ls [-C DIR] [-r NAME] [--json]
sow dist new NAME --format rpm|deb [-C DIR] [-r NAME] [-T DUR | -N] [--json]
sow dist show NAME [-C DIR] [-r NAME] [--json]
sow dist rm NAME [-f|--force] [-C DIR] [-r NAME] [-T DUR | -N] [--json]

命名

Dist 名与仓库名规则相同:[a-z0-9][a-z0-9._-]*,排除 .、..、.sow、pool、dists。

选择能说明发行版或渠道的名称,例如 el9、trixie 或 el9-beta。SOW 不根据名称推断包格式或策略。

sow dist ls

列出所选仓库的全部 Dist。

sow dist ls -r pigsty
NAME	FORMAT	ARCHITECTURES	DESIRED	BUILT	GENERATION	DIRTY	DIRTY_REASONS
el9	rpm	x86_64,aarch64	0	0	1	false	[]
trixie	deb	x86_64,aarch64	0	0	2	false	[]

DESIRED 与 BUILT 是成员计数。两者不一致时,DIRTY_REASONS 会说明原因:

sow dist ls -r demo
NAME	FORMAT	ARCHITECTURES	DESIRED	BUILT	GENERATION	DIRTY	DIRTY_REASONS
el9	rpm	x86_64,aarch64	2	1	4	true	["Desired and Built membership sets differ"]
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
--json 输出 JSON false

表格显示架构族,JSON 还给出包生态使用的架构名。以下摘录展示 result.dists 中一个条目的 architectures 字段,对应的 DEB 视图目录为 binary-amd64 与 binary-arm64:

{
  "architectures": [
    {"family": "x86_64", "ecosystem_arch": "amd64"},
    {"family": "aarch64", "ecosystem_arch": "arm64"}
  ]
}

sow dist new

使用 --format rpm 或 --format deb 创建 Dist。

sow dist new el9 --format rpm -r pigsty
created el9: format=rpm architectures=x86_64,aarch64 members=0/0 generation=1 dirty=false
sow dist new trixie --format deb -r pigsty
created trixie: format=deb architectures=x86_64,aarch64 members=0/0 generation=2 dirty=false
参数 说明 默认
--format FORMAT 必填;rpm 或 deb —
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

--format 必填,只接受 rpm 或 deb:

sow dist new x -r alpha
usage error: dist new requires --format rpm|deb
sow dist new x --format zip -r alpha
usage error: --format must be rpm or deb

新 Dist 继承工作区的架构列表。需要选择架构子集或设置 limit、exclude 时,编辑 sow.yml,再运行 sow config check 与 sow build。

名称与格式已经存在时,dist new 报告当前状态。同名但格式不同会被拒绝:

sow dist new el9 --format deb -r alpha
operation rejected: managed: operation rejected: dist "el9" already exists with format rpm

创建与恢复

dist new 更新 sow.yml 并构建新 Dist。操作中断后,运行 sow build 恢复。

新 Dist 具有有效的空索引。RPM Dist 在每个架构视图下有一份空 repodata/;DEB Dist 有空的 Packages、Packages.gz、by-hash/SHA256/ 条目, 以及 Release;配置签名时再生成 InRelease 与 Release.gpg。

sow dist show

显示一个 Dist 的详细信息。

sow dist show trixielim -r pgsql
dist trixielim:
  format: deb
  architectures: x86_64,aarch64
  desired_members: 3
  built_members: 3
  generation: 6
  status: clean
  dirty: false
  dirty_reasons: []
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
--json 输出 JSON false

JSON 还包含 effective_config_sha256,即解析后的 Dist 配置摘要。 修改 limit、exclude 或签名密钥后,当前配置可能与构建快照不同。 SOW 会将该 Dist 标为 dirty,重新构建后恢复一致。

sow dist show el9 -r pgsql --json
{"schema":"sow.cli/v1","command":"dist show","ok":true,"repository":"pgsql","operation":null,"result":{"name":"el9","format":"rpm","architectures":[{"family":"x86_64","ecosystem_arch":"x86_64"},{"family":"aarch64","ecosystem_arch":"aarch64"}],"desired_members":0,"built_members":0,"generation":"00000000000000000001","dirty":false,"status":"clean","effective_config_sha256":"a0b3ae2f943bc4fce951aaadda0fc8fb146ccf7944b0193a0dcc2b86ddc7ce7e","config":{"format":"rpm","architectures":["x86_64","aarch64"],"limit":1,"exclude":[{"kind":["debuginfo","debugsource"]}]}},"errors":[]}
sow dist show nope -r demo
operation rejected: managed: operation rejected: dist "nope" does not exist

sow dist rm

删除一个 Dist 的成员关系与衍生索引。

sow dist rm el9 -r pgsql
operation rejected: managed: operation rejected: dist "el9" is not empty; use --force
sow dist rm el9 -r pgsql -f
removed dist el9 from pgsql
参数 说明 默认
-f, --force 删除成员与索引,但保留 pool 中的包 false
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

删除 Dist 不会删除 pool 字节

SOW 删除该 Dist 的配置、成员关系与索引。包文件仍保存在 pool/ 中:

sow dist rm el9 -r pgsql -f
removed dist el9 from pgsql

find pgsql -type f
pgsql/pool/e/epel-release/epel-release-7-5.noarch.rpm

运行 sow gc 可以回收当前状态与保留状态都不再需要的包文件。

如果已配置的目标发布过这个 Dist,移除会被拒绝。详见移除已发布视图。

仓库的 protected: true 只阻止仓库删除;受保护仓库上的常规 Dist 维护照常进行。

示例

在同一个仓库中创建 RPM 与 DEB Dist:

sow dist new el9 --format rpm -r pgsql
sow dist new trixie --format deb -r pgsql

创建 beta Dist,在 sow.yml 中设置包保留策略,再构建:

sow dist new el9-beta --format rpm -r pgsql
$EDITOR sow.yml          # el9-beta: { limit: 0 }
sow config check
sow build -r pgsql -d el9-beta

列出需要构建的 Dist:

sow dist ls -r pgsql --json | jq -r '.result.dists[] | select(.dirty) | .name'

退出码

码 触发条件
0 列出、创建、显示或删除成功;或 dist new 报告已存在的 Dist
1 创建空索引时的运行时 I/O 或渲染错误
2 用法错误——--format 缺失或非法、工作区未找到、仓库选择有歧义
4 工作区或仓库锁被占用,且给了 --no-wait 或 --timeout 到期
5 操作日志的完整性或恢复错误
6 名称非法、Dist 不存在、同名不同格式冲突、非空但未给 -f

参见

6 - sow add

导入软件包、应用成员策略并构建本地索引。

sow add 将软件包导入指定 Dist,并构建本地索引。它从包头读取格式与架构,再应用各 Dist 的成员策略。需要分批导入后统一构建时,使用 --skip。需要更新远程目标时,在构建后运行 sow publish。

语法

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 解析、哈希与渲染的并发工作线程数 逻辑 CPU 数
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-d, --dist NAME 选择一个 Dist;可重复 按选择规则
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

输入与目标

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

选择一个仓库和一个或多个 Dist。可用 -r、-d 指定,也可按当前目录自动选择。混合添加 RPM 与 DEB 时,每个包只进入格式相同的 Dist。没有兼容目标的包会被拒绝。

根据软件包适用的发行版选择 Dist。SOW 检查包格式与架构,不测试操作系统或依赖兼容性。

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

汇总行给出操作记录 ID、逐项计数、成员增减、新的期望状态修订号、构建快照,以及仓库是否 留在 dirty 状态。随后是每个输入一行 item,顺序稳定。

逐项状态

每行 item 带一个总体 status,以及 dists= 中的逐 Dist 判定。

状态 含义
accepted 新建软件包对象,且至少增加一条成员关系
reused 内容已存在于本仓库;可能只是新增了成员关系引用
excluded 策略把它从所有目标 Dist 中移除——看 dists= 区分是 excluded 还是 limited
failed 该包被拒绝,error= 字段说明原因

reused 表示复用已有内容:同一个文件加两次不会产生第二个对象或重复成员关系。默认重复执行 add 时还会构建所选 Dist。Dist 已是最新状态时,构建快照不变。先前 --skip 或配置变更留下 dirty 状态时,SOW 会补做构建,并可能生成新的构建快照:

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——包池只保留一份,只是多了一条成员关系。 需要继续延后构建时,再次使用 --skip。

软件包架构

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"

RPM 的 noarch 与 DEB 的 all 是架构中性(neutral)的。它们只产生一个软件包对象与一条 成员关系,但会渲染进目标 Dist 的每个有效架构视图。只有所选 Dist 会包含这些包。

策略:exclude 与 limit

SOW 将输入包与 Dist 的现有成员合并,先执行 exclude,再执行 limit。被策略移除的包会显示为 excluded,不算解析失败。

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 因版本上限被移除并报告为 limited,18.3-1 被保留。两者的顶层状态都是 excluded, 用 dists= 字段区分。

limit 按 (二进制包名, 规范架构) 分组,因此 18.3-1:amd64 与 18.3-1:arm64 在 limit: 1 下都能 留下。同一次运行中,一个包可以被某个 Dist 接受、被另一个 Dist 跳过。

放宽 exclude 或 limit 后,需要重新运行 sow add 来恢复软件包。策略作用于期望成员,而不是 pool/ 中的全部残留文件。

部分成功的批次

即使同批有失败项,合法且无冲突的包依然会提交。失败的输入原地不动,各自带自己的错误信息,命令退出 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 被拒绝,期望成员与公开内容保持原样。人类输出会列出 每个失败输入及其原因,JSON 则在 result.items 中保留这些信息:

sow add /srv/pkg/centos-release-3.1-1.i386.rpm -r pigsty -d el9

被策略排除不属于输入失败;全部被排除的批次可以正常成功,包括首次向空 Dist 添加时。 使用 SIGINT 或 SIGTERM 中断命令时,命令返回 130。使用原始输入文件重新执行;恢复会保留已经提交的成员变更。

包内容改变时,使用新的版本与文件名。本地 GC 后,已发布的包池路径仍然保留。路径冲突只拒绝对应输入,其他有效输入仍可成功。

重新添加同一个包时,保持文件名大小写一致。仅大小写不同的路径会产生冲突。在大小写不敏感的文件系统上,源码包目录名也必须可以区分,详见工作区文件系统。

–skip

--skip 提交期望成员,保持 pool/、dists/ 与构建快照不变。新包存放在私有待构建目录,供后续构建使用。期望状态与已构建状态不同时,仓库为 dirty;重复添加未变化的成员可以保持 clean。

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 的结果中。

批量导入时用 --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

处理顺序

SOW 检查软件包,并按配置为新 RPM 签名。随后执行 exclude 与 limit,记录最终成员;未设置 --skip 时,继续构建。

输入文件保持原样,签名在私有副本上完成。

RPM 签名模式

Managed 模式的 RPM 包签名在 sow.yml 的 signing.rpm.packages.mode 中配置,命令行没有覆盖开关。

模式 行为
never 完整保留输入字节
fill 无签名或签名不受信任时用配置密钥签名;已有能被 trusted_keys 验证的签名则保持字节。配置了密钥时的默认值
always 确保最终包由配置密钥有效签名;否则对暂存副本重签

没有配置密钥时只能用 never。

重复导入时,SOW 复用内容相同的软件包。如果 RPM 只有签名不同,且已有包满足当前签名策略,也可以复用。包内容改变或已有签名不符合策略时,会产生坐标冲突。完整匹配规则见签名模型。

退出码

码 触发条件
0 输入处理完成,包括按策略排除的输入;构建完成或已跳过
1 运行时 I/O、解析器、渲染器或签名失败
2 用法错误、工作区未找到,或仓库/Dist 选择有歧义
3 部分批次——至少一项已提交,至少一项失败
4 仓库锁被占用,且给了 --no-wait 或 --timeout 到期
5 完整性或恢复错误,包括在 applied 之后构建失败
6 全部输入失败,或选择范围、签名、状态前提不允许执行操作

参见

7 - sow rm

从指定 Dist 移除软件包,或预览结果。

sow rm 从指定 Dist 移除软件包,并重建本地索引。使用 --check 预览删除结果,使用 --skip 延后构建。包文件仍保留在 pool/ 中,由 sow gc 回收不再需要的文件。

语法

sow rm PACKAGE... [-c|--check] [--skip] [-j|--jobs N] [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [-T|--timeout DUR | -N|--no-wait] [--json]

参数

参数 说明 默认
-c, --check 预览移除结果,不修改仓库 关闭
--skip 只更新期望状态,不构建 关闭
-j, --jobs N 并发工作线程数 逻辑 CPU 数
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-d, --dist NAME 选择一个 Dist;可重复 按选择规则
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

--check 与 --skip 互斥:

sow rm epel-release -c --skip
usage error: --check and --skip are mutually exclusive

包引用

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 ls 会直接打印精确的 sha256: 引用与规范化坐标,你不需要手工 拼接。

引用没有匹配的软件包时,命令返回错误:

sow rm nosuch -r pigsty -d el9
operation rejected: managed: operation rejected: package reference not found: package reference "nosuch" matches no Desired Membership

默认行为:移除并重建

不带 --check 或 --skip 时,rm 提交期望状态变更,并在返回前重建每个受影响的 Dist。pool 对象 留在磁盘上。

sow rm 'rpm:centos-release-0:6-0.el6.centos.5.x86_64' -r demo -d el9
removed repository=demo operation=2283442100870457321 dists=el9 memberships=1 revision=2 generation=00000000000000000003 dirty=false changes=8
membership dist=el9 name="centos-release" coordinate="rpm:centos-release-0:6-0.el6.centos.5.x86_64" sha256:ffd9e7bdaa4884831a6c055ada01dac96b84c50a8d518dac409b445af5dadc16

汇总之后会为每个受影响文件输出一行 change(本次运行共八行)。加上 --json 后,同一结果 以稳定的标准封装返回。

移除一个 Dist 的最后一个成员是允许的。SOW 仍会渲染合法的空索引(配了密钥就带签名)——空的 Packages 配可验签的 InRelease,或每架构的空 repodata/。

用 –check 预览

-c/--check 预览移除结果与本地文件变化,不修改仓库。removed 只列出引用匹配到的成员。如果当前策略连带移除了其他成员,changes 会包含相关索引变化;实际执行后,sow log 会将这些成员计入移除数量。

sow rm centos-release -r demo -d el9 -c
preview repository=demo operation= dists=el9 memberships=2 revision=2 generation=00000000000000000003 dirty=false changes=2
membership dist=el9 name="centos-release" coordinate="rpm:centos-release-0:6-0.el6.centos.5.x86_64" sha256:ffd9e7bdaa4884831a6c055ada01dac96b84c50a8d518dac409b445af5dadc16
membership dist=el9 name="centos-release" coordinate="rpm:centos-release-0:7-2.1511.el7.centos.2.10.x86_64" sha256:b4111ef2a51542eacc9bd1ebd080da02e53d400f9d172530c75a1e4ac06e7ead
change op=update phase=pointer path="dists/el9/aarch64/repodata/repomd.xml" size=1509 sha256:1cfe38698967d11384f1a985618d75f5e690d1284accf951262fc663fa9afc81
change op=update phase=pointer path="dists/el9/x86_64/repodata/repomd.xml" size=1509 sha256:1cfe38698967d11384f1a985618d75f5e690d1284accf951262fc663fa9afc81

两个 centos-release 版本都被裸包名匹配。change 行描述预期的本地文件变化,按 payload → metadata → pointer → delete 排列。脚本可用 --json 读取 removed[] 与 changes[] 数组。

预览会校验候选配置与既有状态。它使用共享锁,可以等待写操作完成,但不接受 --timeout 或 --no-wait:

sow rm centos-release -r pigsty -d el9 -c -T 5s
usage error: rm --check does not accept --timeout or --no-wait

–skip

--skip 提交期望变更,公开目录保持不变。结果与已构建状态不同时,仓库为 dirty。如果移除的只是尚未构建的新成员,仓库也可能直接恢复为 clean。

sow rm 'rpm:centos-release-0:7-2.1511.el7.centos.2.10.x86_64' --skip -r demo -d el9
removed repository=demo operation=314678479940914827 dists=el9 memberships=1 revision=4 generation=00000000000000000004 dirty=true changes=0
membership dist=el9 name="centos-release" coordinate="rpm:centos-release-0:7-2.1511.el7.centos.2.10.x86_64" sha256:b4111ef2a51542eacc9bd1ebd080da02e53d400f9d172530c75a1e4ac06e7ead
sow status -r pigsty
repository=pigsty status=dirty ready_to_copy=false revision=6 generation=5 dirty_dists=el9 pending=0/0 locked=false

changes 为空是因为什么都没构建。执行 sow build 构建待处理变更。

与策略的交互

移除后,SOW 对剩余成员重新执行策略。如果从 limit: 1 的 Dist 中移除 libpq5 18.3-1,先前被排除的 18.2-1 仍保持排除。需要恢复它时,运行 sow add。

示例

先预览移除结果,再执行:

sow rm patroni -r pgsql -d el9 -c
sow rm patroni -r pgsql -d el9

一次从两个 Dist 中移除同一个精确对象:

sow rm sha256:d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab -r pgsql -d el9 -d el9-beta

批量移除后只重建一次:

sow rm old-tool legacy-agent -r pgsql -d el9 --skip
sow build -r pgsql -d el9
sow check -r pgsql

将预览结果传给其他工具:

sow rm patroni -r pgsql -d el9 -c --json | jq -r '.result.changes[] | "\(.phase)\t\(.op)\t\(.path)"'

退出码

码 触发条件
0 成员已移除并重建,或 --check 预览已打印
1 运行时 I/O 或渲染失败
2 用法错误——--check 与 --skip 同用、--check 与锁参数同用、选择有歧义、工作区未找到
4 仓库锁被占用,且给了 --no-wait 或 --timeout 到期
5 完整性或恢复错误
6 引用无匹配,或非裸名的引用有歧义;整批拒绝,不删除任何成员

参见

8 - sow ls

列出期望成员及其已构建位置。

sow ls 列出指定 Dist 的期望成员。每行还显示该包在整个仓库中的期望成员位置与已构建位置。可从输出中获取供 show 或 rm 使用的包引用。

语法

sow ls [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [--json]
参数 含义 默认值
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
-d, --dist NAME 选择 Dist;可重复 选择规则
--json 输出 JSON false

输出

sow ls -r pigsty -d el9
repository=pigsty dists=el9 dirty=false
SHA256	COORDINATE	DISTS	BUILT_DISTS	POOL_PATH
sha256:d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab	rpm:epel-release-0:7-5.noarch	el9	el9	pool/e/epel-release/epel-release-7-5.noarch.rpm
列 含义
SHA256 包内容的唯一标识,可直接传给 show 或 rm
COORDINATE 规范的 rpm: 或 deb: 包引用
DISTS 整个仓库内的期望成员位置
BUILT_DISTS 当前构建快照中的成员关系
POOL_PATH 包文件相对于仓库的路径

首行显示所选 Dist 是否为 dirty。即使成员数量相同,配置变化也可能使 Dist 变为 dirty。

-d 按期望成员筛选包行。DISTS 与 BUILT_DISTS 列随后显示每个包在整个仓库中的全部位置。通过 rm --skip 移除的包不会出现在列表中,即使旧索引仍包含它。BUILT_DISTS 为空表示当前没有本地已构建 Dist 包含此包。

每个包只输出一行,多个 Dist 用逗号分隔。空 Dist 返回成功,输出中没有软件包行。

选择范围

ls 要求 Dist 集合无歧义。仓库包含多个 Dist 时,应传入一个或多个 -d,或从 <repo>/dists/<dist>/ 内运行。

sow ls -r pigsty
workspace discovery error: managed: workspace discovery error: repository "pigsty" has multiple Dists (el9, trixie); select one or more with --dist

加上 --json 后,同一批记录位于 result.packages 中。

示例

列出尚未构建对象的精确引用:

sow ls -r pgsql -d el9 --json |
  jq -r '.result.packages[] | select(.built_dists | length == 0) | "sha256:" + .sha256'

按路径列出包体:

sow ls -r pgsql -d el9 --json | jq -r '.result.packages[].pool_path' | sort

退出码

代码 触发条件
0 已输出成员列表,包括空列表
1 运行时 I/O 错误
2 用法错误、未发现工作区或隐式仓库/Dist 选择有歧义
5 仓库状态库不可读或不一致
6 显式指定的仓库或 Dist 未配置

参见

  • sow show —— 查看一个已列出的对象
  • sow where —— 在工作区中定位对象
  • sow rm —— 从期望成员集中移除引用
  • 包引用 —— 包引用的写法

9 - sow show

查看一个软件包的存储位置与成员关系。

sow show 查看所选仓库中的一个软件包。文本输出包含包信息、存储位置与成员关系。需要完整的软件包记录时,加上 --json。

语法

sow show PACKAGE [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [--json]
参数 含义 默认值
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
-d, --dist NAME 将候选项收窄到指定 Dist;可重复 整个仓库
--json 输出 JSON false

包引用

PACKAGE 可使用 sha256:<hex> 内容哈希、规范的 rpm:<NEVRA> 或 deb:<name>=<version>:<arch> 坐标、完整包文件名或裸二进制包名。完整格式见 包引用。

裸包名必须在所选范围内唯一。sow rm foo 会移除所有匹配版本,而 sow show foo 只允许返回 一个对象;有歧义时会列出候选项:

sow show libpq5 -r pgsql -d trixie
operation rejected: managed: operation rejected: package reference "libpq5" is ambiguous: deb:libpq5=18.2-1:amd64 sha256:fa84dc64..., deb:libpq5=18.3-1:amd64 sha256:491992c5..., deb:libpq5=18.3-1:arm64 sha256:3a2f7ef7...

从错误信息或 sow ls 复制精确坐标/SHA-256 后重试。

输出

不带 --json 时,show 输出包信息、存储路径,以及期望与已构建状态中的位置:

sow show centos-release-6-0.el6.centos.5.x86_64.rpm -r demo -d el9
package repository=demo coordinate="centos-release-0:6-0.el6.centos.5.x86_64" sha256:ffd9e7bdaa4884831a6c055ada01dac96b84c50a8d518dac409b445af5dadc16 format=rpm architecture=x86_64 size=19776 storage=pool
pool=pool/c/centos-release/centos-release-6-0.el6.centos.5.x86_64.rpm
dists=el9 built_dists=el9

加上 --json 后,result.package 会返回完整软件包记录,包括下列规范化字段。

字段 含义
canonical_arch x86_64、aarch64,或 RPM noarch / DEB all 对应的 neutral
kind 策略分类:main、debuginfo、debugsource、llvmjit、dbgsym、dbg
source 标准化源码包名
payload_sha256 RPM 不含签名部分的摘要,用于判断是否可以复用包文件
signature_key 包内签名的密钥 ID(如有)
storage 构建前为 pending,进入仓库树后为 pool
dists / built_dists 期望与当前已构建状态中的位置

不带 -d 时,查找范围包括当前没有成员关系的已存储对象。带 -d 时,候选包必须属于所选 Dist 的期望成员。返回的 dists 与 built_dists 列表覆盖整个仓库。

退出码

代码 触发条件
0 已输出一个软件包对象
1 运行时 I/O 错误
2 用法错误、未发现工作区或隐式仓库选择有歧义
5 仓库状态库不可读或不一致
6 显式范围未配置,或引用没有匹配/匹配多个对象

参见

  • sow ls —— 从 Dist 成员关系获取精确包引用
  • sow where —— 跨仓库搜索
  • sow rm —— 移除匹配的期望成员
  • JSON 输出 —— 完整结果结构

10 - sow where

在工作区中查找一个软件包。

sow where 在工作区中查找一个软件包的期望成员位置。默认搜索全部仓库;使用 -r 或 -d 限定范围。

语法

sow where PACKAGE [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [--json]
参数 含义 默认值
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 将搜索限制到一个仓库 全部仓库
-d, --dist NAME 将搜索限制到指定 Dist;可重复 全部 Dist
--json 输出 JSON false

引用解析

PACKAGE 与 sow show 使用相同格式:SHA-256、规范 RPM/DEB 坐标、 完整文件名或裸包名。

引用必须确定一个软件包,即格式、坐标和 SHA-256 均相同的包。同一个包可以位于多个仓库。如果匹配到不同的包,使用 -r 或 -d 收窄范围,或提供精确坐标、SHA-256。

输出

不带 --json 时,where 先输出摘要,再为每个位置输出一行:

sow where 'rpm:centos-release-0:6-0.el6.centos.5.x86_64'
reference="rpm:centos-release-0:6-0.el6.centos.5.x86_64" locations=1
repository=demo coordinate="rpm:centos-release-0:6-0.el6.centos.5.x86_64" sha256:ffd9e7bdaa4884831a6c055ada01dac96b84c50a8d518dac409b445af5dadc16 dists=el9 built_dists=el9

每个位置同时给出该仓库内的期望位置 dists 与已构建位置 built_dists。查找候选包时使用期望成员集。如果包已从全部所选 Dist 中移除,命令将不再找到它,即使旧索引仍包含它。要查看这样的已存储对象,使用 sow show PACKAGE -r NAME,不要加 -d。

加上 --json 后,位置列表位于 result.locations。没有匹配包时,命令返回错误:

sow where nosuchpkg
operation rejected: managed: operation rejected: package reference "nosuchpkg" was not found in the selected Workspace scope

示例

列出期望状态中包含此包的 Dist:

sow where 'rpm:patroni-0:3.0.4-1.noarch' --json |
  jq -r '.result.locations[] | "\(.repository)/\(.dists | join(","))"'

退出码

代码 触发条件
0 已输出一个解析后的软件包对象及其位置
1 运行时 I/O 错误
2 用法错误或未发现工作区
5 某个仓库状态库不可读或不一致
6 显式仓库/Dist 未配置,或引用没有匹配/在所选范围内有歧义

参见

  • sow show —— 查看解析后的软件包对象
  • sow ls —— 列出一个 Dist 集合
  • 包引用 —— 完整格式与匹配规则

11 - sow status

查看仓库状态、待构建数据与锁占用情况。

sow status 显示仓库是否需要构建、待构建数据量及写锁占用情况。它读取已记录状态,不扫描包文件内容。需要验证文件内容时,使用 sow check。

语法

sow status [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [--json]
参数 含义 默认值
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
-d, --dist NAME 只查看指定 Dist;可重复 全部 Dist
--json 输出 JSON false

仓库状态

SOW 记录期望的软件包集合,以及上一次构建完成的构建快照。

状态 含义 下一步
clean 期望状态与已构建状态一致 复制仓库前运行 check
dirty 两种状态不同,常见于 --skip 或配置变化之后 运行 build 应用变更
recovering 本地仓库操作被中断 运行 build 恢复
error 状态、文件或恢复记录需要处理 查看 dirty_reasons,运行 check 与 log

输出

sow status -r pgsql
repository=pgsql status=dirty ready_to_copy=false revision=4 generation=3 dirty_dists=trixie pending=4/2326 locked=false

人类可读输出包含仓库状态、ready_to_copy、期望状态修订号、构建快照、 受影响 Dist、待处理对象数量/字节数与写锁状态。

JSON 结果还包含 dirty_reasons 与最近一次操作记录:

{
  "repository": "demo",
  "status": "dirty",
  "ready_to_copy": false,
  "desired_revision": 5,
  "built_generation": "00000000000000000004",
  "dirty_dists": ["el9"],
  "dirty_reasons": ["dist el9 Desired and Built membership sets differ"],
  "pending": {"count": 1, "bytes": 19776},
  "repository_locked": false
}

ready_to_copy 汇总已记录的状态。为 false 时,dirty_reasons 给出原因。文件内容由 sow check 校验。

读取行为

status 读取状态,不迁移或修复数据库。它可以读取 schema v6、v12 与当前 v13 数据库。不支持或无法读取的状态返回 5。对旧工作区执行写操作前,先备份,再逐个运行 sow repo migrate。

退出行为

只要状态可读,status 在 clean、dirty、recovering、error 四种状态下都返回 0。 脚本应读取结构化状态,而不是把后三者当作命令执行失败。

代码 触发条件
0 仓库状态可读
1 运行时 I/O 错误
2 用法错误、未发现工作区或隐式仓库选择有歧义
5 状态库不可读或不一致
6 显式指定的仓库或 Dist 未配置

参见

12 - sow build

根据期望成员与当前配置构建本地索引。

sow build 更新本地索引,使其与期望成员和当前配置一致。在 add --skip、rm --skip 或修改策略后运行此命令。开始新构建前,它会恢复未完成的本地仓库操作。

语法

sow build [-j|--jobs N] [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [-T|--timeout DUR | -N|--no-wait] [--json]
参数 含义 默认值
-j, --jobs N 并行工作线程数,不得小于 1 逻辑 CPU 数
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
-d, --dist NAME 构建指定 Dist;可重复 全部受影响 Dist
-T, --timeout DUR 最长等锁时间;0 表示无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

不带 -d 时,SOW 构建所选仓库中全部受影响 Dist;带 -d 时只构建指定 Dist, 未选择的变化继续保持 dirty。

结果

不带 --json 时,build 输出一行人类可读摘要:

sow build -r demo -d el9
built repository=demo operation=2769214987359113555 dists=el9 revision=4 generation=00000000000000000005 dirty=false

脚本读取结果时,使用 --json。

空操作构建

成员关系、相关策略、渲染设置与签名配置均未变化时,build 不执行构建,也不生成新的构建快照:

sow build -r demo -d el9
build repository=demo dists=el9 already current (noop) revision=4 generation=00000000000000000005 dirty=false

应用策略变更

build 会重新执行当前 exclude 与 limit 策略。收紧策略可能移除期望成员; 放宽策略不会自动恢复已被移除的包,需要重新运行 sow add。

提交与恢复

SOW 先将所需的待构建包放入 pool/,再写入索引,最后更新 repomd.xml、Release 等客户端入口。一次构建可以包含多个 Dist,它们共用本次构建快照;未选择的 dirty Dist 仍需要构建。

构建中断后,重新运行此命令。SOW 根据操作日志完成或回滚未完成的工作。如果日志、数据库与文件不一致,命令会报告完整性错误。使用 sow check 与 sow log 查看受影响的状态。

进度事件

耗时较长的构建会向操作日志追加结构化 build_progress 记录。每条事件包含 phase、 completed、total 与 jobs。当前阶段为:

  • rendering;
  • promoting_payload;
  • publishing_dists;
  • normalizing_public_tree;
  • finalizing。

使用 sow log OPERATION 查看明细。

元数据签名

在 sow.yml 中配置元数据签名。配置的密钥引用或指纹改变时, 相关 Dist 变为 dirty,下一次 build 重新签名。

  • RPM:总是生成 repodata/repomd.xml;配置签名后额外生成 repomd.xml.asc。
  • DEB:总是生成 Release;配置签名后额外生成 InRelease 与 Release.gpg。

退出码

代码 触发条件
0 构建成功或无需操作
1 渲染、签名或文件系统错误
2 用法错误、未发现工作区或隐式仓库选择有歧义
4 仓库写锁不可用
5 无法安全完成恢复,或仓库处于 error
6 显式范围未配置,或当前配置拒绝既有状态

参见

13 - sow check

交付前校验仓库文件、索引、状态与签名。

交付 Managed 仓库前,使用 sow check 检查包哈希、状态、索引、签名与构建快照清单。此命令只读;如果期望状态与已构建状态不同,先运行 sow build。

语法

sow check [-j|--jobs N] [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]... [--json]
参数 含义 默认值
-j, --jobs N 并行校验工作线程数,不得小于 1 逻辑 CPU 数
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
-d, --dist NAME 校验指定 Dist;可重复 全部 Dist
--json 输出 JSON false

校验层

仓库没有未完成的布局迁移时,check 按顺序报告九层校验:

层 校验内容 checked 计数
config sow.yml 能否针对该仓库解析并通过校验 配置对象
state SQLite quick_check、外键、日志与恢复证据 一个状态库
public-modes 服务目录中所有文件与目录权限 已检查路径
retained 保留记录与保存的构建快照清单;损坏记录的处理方法见 retain rm 保留记录
package-bytes 包池与私有待构建包文件的 SHA-256 软件包对象
desired-membership 成员关系能否在当前策略下解析到真实对象 成员关系
index 渲染索引是否与其声明的成员关系一致 Dist
signature 所有已声明元数据/软件包签名是否有效 签名
generation-manifest 构建快照清单是否与磁盘文件一致 一个清单

仓库存在未完成的布局迁移时,check 报告 config、state、public-modes 与 layout-transition,随后停止。按报告中的 repo migrate 指令完成或中止迁移。

软件包校验

每次运行都会重新计算包文件的哈希。同一物理文件的硬链接共用一次哈希结果。带签名的 RPM 还会进行签名校验。checked 统计软件包对象数量,不是文件读取次数。

sow check
repository=pigsty status=clean ready_to_copy=true revision=5 generation=5
config	ok=true	checked=5
state	ok=true	checked=1
public-modes	ok=true	checked=67
retained	ok=true	checked=0
package-bytes	ok=true	checked=8
desired-membership	ok=true	checked=8
index	ok=true	checked=2
signature	ok=true	checked=9
generation-manifest	ok=true	checked=1

dirty 不可交付

dirty 仓库需要先构建,才能通过 check。旧构建快照与新的期望状态可以分别通过校验,但二者仍有差异:

sow check
repository=pigsty status=dirty ready_to_copy=false revision=6 generation=5
...
integrity or recovery error: managed: repository is not ready to copy: repository status is dirty

此时退出 5。运行 sow build 后,再运行 sow check。

退出码

代码 触发条件
0 全部校验层通过,仓库可复制交付
1 校验期间发生 I/O 错误
2 用法错误、未发现工作区或隐式仓库选择有歧义
5 某个校验层失败,或仓库不可交付
6 显式指定的仓库或 Dist 未配置

参见

14 - sow changes

比较两个构建快照的文件差异。

sow changes 列出基准构建快照与当前构建快照之间的文件差异。路径相对于仓库根目录。需要包含尚未构建的期望变化时,先运行 sow build。

语法

sow changes [BASE_GENERATION] [-C|--workdir DIR] [-r|--repo NAME] [--json]
参数 含义 默认值
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
--json 输出 JSON false

该命令作用于整个仓库,不接受 -d/--dist。

输出

sow changes
base=4 generation=5 dirty=false
add	payload	pool/c/centos-release/centos-release-6-0.el6.centos.5.x86_64.rpm	19776	ffd9e7bd...
add	metadata	dists/el9/x86_64/repodata/5bc463cb...-primary.xml.gz	1460	5bc463cb...
update	pointer	dists/el9/x86_64/repodata/repomd.xml	1514	05d3d5bf...
delete	delete	dists/el9/x86_64/repodata/0df96f0b...-primary.xml.gz	0	

各列依次为操作、阶段、仓库相对路径、大小、SHA-256。

字段 取值
操作 add、update、delete
阶段 payload、metadata、pointer、delete

此计划用于查看本地文件变化。需要交付时,使用 sow publish,或暂存完整副本后再切换服务目录。

基准构建快照

不带参数时,SOW 比较当前构建快照与前一次构建快照。

BASE_GENERATION 是 0..当前编号 范围内的十进制整数。基准为 0 时输出当前构建快照的完整 交付清单,但不包含私有 sow.yml 与 .sow/。基准等于当前编号时输出空计划;从未构建的 仓库同样输出空的 0 -> 0 计划。

sow changes 99
operation rejected: managed: operation rejected: base generation 99 is outside 0..2

dirty 与恢复状态

期望状态尚未构建时,首行显示 dirty=true。结果仍以当前构建快照为终点,不包含待构建包文件。

仓库为 recovering 或 error 时,先按诊断信息恢复或修复仓库,再运行 changes。

示例

输出当前完整清单:

sow changes 0 -r pgsql --json > pgsql-current.json

生成仓库级计划后按路径筛选一个 Dist:

sow changes -r pgsql --json |
  jq '.result.changes[] | select(.path | startswith("dists/el9/"))'

退出码

代码 触发条件
0 已输出计划,包括空计划
1 运行时 I/O 错误
2 用法错误、传入 -d、未发现工作区或隐式仓库选择有歧义
5 仓库处于 recovering/error,或状态证据不一致
6 显式仓库未配置,或基准编号超出有效范围

参见

15 - sow publish

将构建快照发布到文件系统或 R2 目标。

sow publish 将当前构建快照复制到 sow.yml 的 targets: 中配置的目标。目标配置指定仓库与存储类型。需要发布最新期望状态时,先完成构建。

语法

sow publish TARGET [--abort | --rebind] [-C|--workdir DIR] [-T|--timeout DUR | -N|--no-wait] [--json]
参数 含义 默认值
--abort 放弃尚未进入提交阶段的中断发布 false
--rebind 确认并记录允许修改的目标名称、公共端点或缓存 TTL false
-C, --workdir DIR 工作区发现起点 当前目录
-T, --timeout DUR 最长仓库等锁时间;0 表示无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

TARGET 必须是已配置的 filesystem 或 r2 发布目标。--abort 与 --rebind 互斥。

发布前准备

需要包含最新软件包与策略变更时,先运行 build。dirty 仓库仍可以发布上一个已完成的构建快照。

配置目标的 Web 服务或存储桶访问权限,使 public_endpoint 能读取发布文件,然后运行 sow publish TARGET。

发布协议

SOW 先核对本地文件与已完成的构建快照清单,再按以下顺序发布:

  1. 包文件;
  2. 使用独立内容 URL 的元数据;
  3. repomd.xml、Release 等客户端入口;
  4. 验证公共访问并保存发布检查点。

SOW 记录进度,中断后可用同一命令继续发布。目标已包含当前构建快照时,重复运行不产生变化。

sow publish local
published demo generation=00000000000000000005 to local (filesystem): phase=grace objects=14
sow publish local
publication demo generation=00000000000000000005 to local is already current (noop)

重绑定可变目标配置

首次发布会记录仓库与目标绑定。修改目标名称、公共地址或缓存 TTL 后,使用 --rebind 记录新设置并继续发布:

sow publish prod --rebind

如果修改了 targets: 中的目标名,命令中使用新名称。

可以修改 不可变;必须配置新目标
目标名称 仓库身份
public_endpoint 存储类型、存储 endpoint 或区域
max_cache_ttl 存储桶或 prefix

修改 max_cache_ttl 前,先完成目标维护。文件系统目标有未完成的删除操作时,也需先完成删除,再修改 public_endpoint。首次发布到新目标时,直接运行 sow publish TARGET。

Abort 与恢复

发布中断后,重新运行 sow publish TARGET 继续发布。

需要放弃本次发布时,在进入 commit_intent 阶段前运行 sow publish TARGET --abort。SOW 核对已上传的对象,记录放弃结果,并保留远端文件。进入 commit_intent 后,使用普通发布命令完成本次发布。

已发布的包 URL 对应固定内容。内容改变时,使用新的包版本与文件名。如果远端文件与本次发布记录不符,按报告处理冲突,详见故障处理。

公共可见性校验

记录发布检查点前,SOW 会通过 public_endpoint 读取并验证已发布文件。HTTP(S) 校验使用普通 GET 请求。内容过期或缺失时,在 max_cache_ttl 范围内重试。

文件系统目标支持 file:// 或 HTTP(S) 公共地址,R2 目标要求 HTTP(S)。每次 R2 发布都会列出目标前缀并逐个用 HEAD 检查对象;有更新时执行两次清单检查。保留 R2 中的旧对象:目标 GC 只报告、不删除,后续发布还会核对远程前缀与已记录清单。

移除已发布视图

只要目标仍在配置中,SOW 就不允许本地变更移除它已发布的入口,例如整个 Dist、RPM 架构视图或配套签名文件。

需要停用该目标时,先停止向它发布,从 sow.yml 的 targets: 中删除对应配置,再运行 sow config check。随后可以移除本地视图。此操作保留原有远程文件与发布历史。需要发布删减后的仓库时,配置使用不同存储前缀的新目标,并将客户端切换到该目标。

退出行为

代码 触发条件
0 发布完成,或目标已经是当前构建快照
1 文件系统、存储类型、网络、验证或绑定冲突(包括必须 rebind)
2 用法、工作区发现或 sow.yml 无效
4 仓库写锁不可用
5 本地/发布恢复证据不一致,或源不可交付
6 目标不存在/不安全,或其他安全前置条件拒绝 Publish/Abort/Rebind

参见

16 - sow retain

保留或释放一个构建快照所需的包文件。

sow retain 保留指定构建快照所需的包文件,防止本地 GC 回收。保留内容包括包文件与冻结清单;此命令不恢复或发布旧仓库视图。

语法

sow retain add GENERATION [-C|--workdir DIR] [-r|--repo NAME] [-T|--timeout DUR | -N|--no-wait] [--json]
sow retain ls             [-C|--workdir DIR] [-r|--repo NAME] [--json]
sow retain rm GENERATION  [-C|--workdir DIR] [-r|--repo NAME] [-T|--timeout DUR | -N|--no-wait] [--json]

GENERATION 必须是大于零的十进制整数。

retain add

传入当前构建快照的编号。SOW 校验该快照,保存清单,并记录 GC 需要保留的包文件。应在生成下一个构建快照前添加此记录。

sow retain add 12 -r pgsql
retained generation 00000000000000000012: /srv/sow/.sow/pgsql/retained/00000000000000000012

重复添加同一个构建快照时,如果保留记录仍可通过校验,命令不产生变化。

retain ls

列出仓库的保留记录。

sow retain ls -r pgsql
GENERATION	RECORD_IDENTITY	PATH
00000000000000000012	678beeae...	/srv/sow/.sow/pgsql/retained/00000000000000000012

空列表也是成功结果。

retain rm

移除指定构建快照的保留记录:

sow retain rm 12 -r pgsql
removed retained generation 00000000000000000012

该命令不删除软件包体。指定的构建快照没有保留记录时,命令成功返回,不作修改。只有在其他保留记录与操作也不再 引用这些包体时,后续本地 sow gc 才可能回收。

保留记录损坏时,retain rm、retain ls、本地 gc 与 check 都会报告错误。check 的 retained 层会指出受影响的构建快照。需要丢弃该损坏记录时:

  1. 停止工作区写操作。
  2. 将 .sow/<repo>/retained/<GENERATION> 移到工作区外的备份位置。
  3. 再次运行 sow check。

移走记录也会移除它提供的 GC 保护。

参数

参数 适用命令 含义
-C, --workdir DIR 全部 工作区发现起点
-r, --repo NAME 全部 选择仓库
-T, --timeout DUR add、rm 最长写锁等待时间
-N, --no-wait add、rm 锁被占用时立即失败
--json 全部 输出 JSON

退出行为

代码 触发条件
0 操作完成,包括空列表
1 文件系统或运行时 I/O 错误
2 构建快照编号格式无效、发现错误或隐式仓库选择有歧义
4 add/rm 无法获取写锁
5 构建快照清单或仓库状态不一致
6 显式仓库未配置、retain add 指定的不是当前构建快照编号,或其他安全规则拒绝请求

参见

  • sow gc —— 回收保留状态不再需要的包文件
  • sow changes —— 查看构建快照差异
  • 仓库布局 —— 私有保留记录位置

17 - sow gc

回收不再需要的本地包,或维护发布目标。

使用 sow gc 回收本地不再需要的包文件。使用 sow gc TARGET 维护配置的发布目标。文件系统目标可以删除符合条件的对象;R2 目标只报告候选对象。

语法

sow gc          [-C|--workdir DIR] [-r|--repo NAME] [-T|--timeout DUR | -N|--no-wait] [--json]
sow gc TARGET   [-C|--workdir DIR]                  [-T|--timeout DUR | -N|--no-wait] [--json]
参数 含义 默认值
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 仅用于选择本地 GC 的仓库 选择规则
-T, --timeout DUR 最长仓库等锁时间;0 表示无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

目标自身已绑定仓库,因此 gc TARGET -r NAME 属于用法错误。两种模式都不接受 --dist。

本地 GC

仓库完成构建且状态为 clean 时,可以执行本地 GC。状态为 dirty 时,先运行 sow build。只有以下记录都不再引用某个包时,GC 才会删除它:

  • 当前期望成员、已构建成员,以及为旧客户端保留的旧成员记录;
  • 显式 retain 记录;
  • 恢复状态与非终态操作记录;
  • 发布记录;
  • 活跃维护操作。

操作会记入日志。实际删除包文件时,仓库生成新的构建快照;没有符合条件的包时,命令不作修改。

sow gc -r pgsql
local gc pgsql: generation=00000000000000000013 objects=4 bytes=1834200

目标 GC

目标 GC 等待配置的缓存宽限期到期,再检查哪些对象仍需保留:

存储类型 行为
filesystem 宽限期到期后,按条件删除合格对象,再验证公共 URL 已无法访问该对象,并记录结果
r2 保存候选对象报告,不删除对象
sow gc prod
target gc pgsql/prod (filesystem): phase=done candidates=14 deleted=8 retained=6 pending=0

空操作表示当前没有到期维护任务。

退出行为

代码 触发条件
0 GC 完成或没有合格对象
1 文件系统、存储服务、网络或其他运行时错误
2 用法、工作区发现、sow.yml 无效或隐式仓库选择有歧义
4 仓库写锁不可用
5 恢复、状态、回执或清单证据不一致
6 显式仓库/目标未配置或不安全,或删除被安全前置条件拒绝

参见

18 - sow export

将一个 RPM 架构视图导出为独立仓库。

sow export rpm-leaf 将一个 Dist 的指定架构导出为独立 RPM 仓库。它把软件包复制到外部目录,并将元数据中的包路径改为本地 pool/... 路径。

语法

sow export rpm-leaf DIST ARCH DIR [--hardlink] [-C|--workdir DIR] [-r|--repo NAME] [--json]
参数 要求
DIST 已配置的规范 RPM Dist 名称
ARCH x86_64 或 aarch64
DIR 不存在或为空,且不与仓库、私有状态、filesystem 目标根重叠的目录
选项 含义 默认值
--hardlink 对同一文件系统上的只读输出使用硬链接 复制文件
-C, --workdir DIR 工作区发现起点 当前目录
-r, --repo NAME 选择仓库 选择规则
--json 输出 JSON false

输出

sow export rpm-leaf el9 x86_64 /srv/export/el9-x86_64
exported RPM leaf el9/x86_64 generation=00000000000000000012 method=copy packages=84 to /srv/export/el9-x86_64

目标目录包含:

  • 使用本地包路径的 RPM 元数据;
  • 所需软件包目录树;
  • 导出清单;
  • .sow-export.json 来源记录。

源仓库必须为 clean,有已完成的构建快照,且没有未完成的操作。所选 RPM 视图必须属于当前构建快照。导出物独立于工作区,不会阻止 GC 回收源仓库中的包。

导出失败后,修正报告的问题,再换一个不存在或为空的目录重试。失败的目标目录中可能留有部分文件。

Managed RPM 与 export rpm-leaf 的 repomd data 时间戳固定为 0,没有 --metadata-timestamp 选项。因此它们不能直接接管保留了正数时间戳缓存的同一个 EL7 repository ID;换 baseurl 本身不能解决。需要客户端采用新的 repository ID 或清理元数据缓存。 若必须保留旧 ID 和缓存继续更新,请使用带显式时间戳的 Plain create,按 迁移指南 维护发布时间并重签 repomd。

复制与硬链接

默认复制文件。--hardlink 只适用于同一文件系统上的只读输出。修改硬链接包文件也会修改 SOW 包池中的源文件。

退出行为

代码 触发条件
0 独立 RPM 仓库导出完成
1 文件系统、复制、硬链接或元数据写入错误
2 命令语法、Dist/架构 Token 无效,或发现/隐式仓库选择有歧义
5 源构建快照或仓库状态不一致
6 显式仓库未配置、Dist 不是 RPM、视图/签名者不可用,或目标不安全/非空/重叠

参见

19 - sow log

查看、导出和裁剪操作记录。

sow log 列出仓库操作记录,并查看单次操作的详情。使用 log export 将已结束的操作导出为 JSONL,使用 log prune 从数据库中删除符合条件的旧记录。

语法

sow log [OPERATION] [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME] [--json]
sow log export [FILE] [-C|--workdir DIR] [-r|--repo NAME] [-d|--dist NAME]
sow log prune BEFORE [-C|--workdir DIR] [-r|--repo NAME] [-T|--timeout DUR | -N|--no-wait] [--json]

sow log

不带参数时,按由新到旧打印最近 50 条操作记录。

sow log -r pigsty --json

输出节选,operations 数组中的一个操作记录对象:

{
  "id": "4262183287563704350",
  "kind": "build",
  "state": "done",
  "payload_json": "{\"version\":2,\"repository\":\"pigsty\",\"kind\":\"build\",\"config_sha256\":\"37eb6dcf...\",\"skip\":false,\"dists\":[\"el9\"],\"build_dists\":[\"el9\"],\"manifest_sha256\":\"678beeae...\"}",
  "result_json": "{\"dists\":1,\"dropped_pending\":[]}",
  "created_at": "2026-08-04T04:07:40.334787Z",
  "updated_at": "2026-08-04T04:07:40.907125Z"
}

payload_json 记录意图——包括当时生效配置的摘要 config_sha256,以及生成的构建快照的 manifest_sha256。result_json 记录结果。失败的操作记录还会带 error_class 与 error_message:

{
  "id": "5995346754219751025",
  "kind": "add",
  "state": "failed",
  "result_json": "{\"accepted\":0,\"failed\":1}",
  "error_class": "rejected",
  "error_message": "no input package was accepted"
}
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-d, --dist NAME 只显示触及该 Dist 的操作记录 全部
--json 输出 JSON false

查看单条 Operation

传入操作记录 ID 查看摘要。加上 --json 后,可在 result.detail 中读取完整状态变化、耗时、软件包、成员与文件记录。

sow log 4262183287563704350 -r pigsty --json

输出节选:

{
  "duration_ms": 572,
  "events": [
    {"sequence": 0, "state": "planned",  "occurred_at": "2026-08-04T04:07:40.334787Z"},
    {"sequence": 1, "state": "staged",   "occurred_at": "2026-08-04T04:07:40.380963Z"},
    {"sequence": 2, "state": "applied",  "occurred_at": "2026-08-04T04:07:40.386186Z"},
    {"sequence": 3, "state": "built",    "occurred_at": "2026-08-04T04:07:40.904730Z"},
    {"sequence": 4, "state": "done",     "occurred_at": "2026-08-04T04:07:40.907125Z"}
  ],
  "packages": [],
  "memberships": [],
  "files": [
    {"sequence": 0, "action": "update", "phase": "pointer", "path": "dists/el9/aarch64/repodata/repomd.xml", "size": 1511, "sha256": "ef071821e06c9e86ab4f6d2a56906d82bb66df251e79d1086cfd44dc8395513e"},
    {"sequence": 1, "action": "update", "phase": "pointer", "path": "dists/el9/x86_64/repodata/repomd.xml",  "size": 1514, "sha256": "a31e90ec39169f0373b108458908333c96c5f600f3c63a50c44257856f0d2d55"}
  ]
}

files 数组使用与 sow changes 相同的 phase 词表:payload、 metadata、pointer、delete。

Build 操作记录还会包含进度事件。它们保持当前 state,并把版本化对象放入 detail_json:

{
  "state": "applied",
  "detail_json": "{\"version\":1,\"kind\":\"build_progress\",\"phase\":\"rendering\",\"completed\":1,\"total\":2,\"jobs\":8}"
}

阶段包括 rendering、promoting_payload、publishing_dists、normalizing_public_tree 与 finalizing。可用这些记录查看长时间构建的进度。

按 Dist 过滤

-d 把列表限制为触及该 Dist 的操作记录——一个仓库服务多个发行版时很有用:

sow log -d trixie -r pigsty

操作状态

state 字段表示操作进行到哪一步:

planned → staged → applied → built → done
                        └────────→ done_dirty
   any nonterminal → recovering → built / done / done_dirty / rolled_back / failed
   pre-apply error  → failed
状态 含义
planned 命令、参数、目标与预期动作已持久化
staged 新包/元数据已写入临时位置并校验通过
applied 期望状态与所需的私有待构建包文件已提交
built 已切换到新构建快照
done 终态——一次正常成功的命令
done_dirty 操作已结束,仍有变化需要构建,通常出现在 --skip 之后
failed 终态——在 applied 之前失败,什么都没提交
rolled_back 终态——恢复在操作记录改变 Desired 之前放弃了它,例如被中断命令留下的 planned 操作记录
recovering 非终态;下一条写命令必须先完成或回滚它

仓库日志包含 dist new 和 dist rm。工作区操作 init、repo new、repo rm 使用独立的恢复记录,不出现在这里。

sow log export

将已结束的操作导出为 JSONL,每行包含一次操作的完整记录,可用于归档或日志分析。

sow log export /srv/audit/pigsty-ops.jsonl -r pigsty
exported 12 operations to /srv/audit/pigsty-ops.jsonl

省略 FILE 或传 - 则写到 stdout:

sow log export - -r pigsty | gzip > pigsty-ops-$(date +%F).jsonl.gz
参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-d, --dist NAME 只导出触及该 Dist 的操作记录 全部

export 直接输出 JSONL,不需要 --json。

输出路径

在已有目录中选择一个新文件名。文件已存在时,log export 返回 6。直接父目录必须是真实目录,不能是符号链接。在 macOS 上使用 /private/tmp/file.jsonl,不要使用 /tmp/file.jsonl。

sow log prune

删除早于 BEFORE 且符合条件的已结束操作记录,并回收数据库空间。如果其他读连接正在使用数据库,记录可以先删除,空间回收会延后。此时结果显示 compaction_deferred: true。关闭读连接后,再运行 prune 回收空间。

sow log prune 2027-01-01 -r pigsty
pruned log repository=pigsty operation=8150803833883584722 before=2026-12-31T16:00:00.000000000Z operations=1

输出以 UTC 显示截止时间(此处工作站时区为 UTC+8)。若有读者使空间回收 被推迟,输出会多一行 log deletion committed; space reclamation deferred while the database is busy; 使用 --json 时,日志 ID 与 compaction_deferred 位于 result 中。

参数 说明 默认
-C, --workdir DIR 工作区发现的起始目录 当前目录
-r, --repo NAME 选择一个仓库 按选择规则
-T, --timeout DUR 等待锁的最长时间;0 无限等待 0
-N, --no-wait 锁被占用时立即失败 false
--json 输出 JSON false

prune 作用于整个仓库,不接受 -d。

BEFORE 语法

BEFORE 是 ISO-8601 日期 YYYY-MM-DD(按本地时区零点解释),或带时区的 RFC 3339 时间戳。

sow log prune yesterday -r pigsty
usage error: BEFORE must be YYYY-MM-DD or an RFC 3339 timestamp with timezone

prune 保留的记录

prune 保留以下记录:

  • 非终态的操作记录;
  • 当前恢复仍需要的记录;
  • 当前的软件包或成员状态;
  • 构建快照及其文件差异记录。

pruned 显示已删除的操作记录数。

示例

排查最近一次写入:

sow log -r pgsql --json | jq -r '.result.operations[0] | "\(.id)\t\(.kind)\t\(.state)"'

从最近 50 条记录中筛选失败的操作:

sow log -r pgsql --json | jq -r '.result.operations[] | select(.state=="failed") | "\(.id)\t\(.error_class)\t\(.error_message)"'

归档记录,再删除指定日期之前的旧记录:

sow log export /srv/audit/pgsql-$(date +%Y%m).jsonl -r pgsql
sow log prune 2026-05-01 -r pgsql

查看最后修改某个 Dist 的操作:

sow log -d el9 -r pgsql --json | jq -r '.result.operations[0].id'

退出码

命令 码 触发条件
log 0 记录已打印,包括空账本
log 2 用法错误(包括非数字的操作记录 ID)、工作区未找到,或选择有歧义
log 5 状态数据库不可读
log 6 给定的操作记录 ID 不存在,或给出了多个不同的 -d
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 完整性或恢复错误

参见