跳转到主要内容

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

返回本页常规视图.

功能

选择仓库模式,了解包策略、签名、发布与审计功能。

SOW 用两种模式创建仓库索引。可以随时重建的包目录使用 Plain; 需要持续更新、策略、签名和发布的仓库使用 Managed。

功能对照

功能 Plain Managed
RPM 与 DEB 索引 一个目录,可包含任一种或两种格式 一个仓库,分别使用不同 Dist
持久化成员与构建快照 — 支持
分架构视图 — 支持,包含架构无关包
包排除 --pigsty 使用固定清理规则 按 Dist 配置
版本数量限制 sow prune 清理旧版 RPM 按 Dist 配置
元数据签名 — RPM 和 DEB
RPM 包签名 --sign-with never、fill、always
中断恢复 重新运行 create 或 prune 操作记录和发布尝试
操作日志与 JSONL 导出 — 支持
发布目标 — Filesystem 与 R2

SOW 自行解析包并生成索引,无需 createrepo_c、dpkg-scanpackages 或 reprepro。 RPM 包签名使用主机上的 rpm 和 GPG 环境;元数据密钥引用解析为 GPG 密钥标识时,也需要 gpg。

生成格式

格式 Plain Managed 增加的内容
RPM primary、filelists、other XML 和 repomd.xml 架构视图,可选 repomd.xml.asc
DEB Packages、Packages.gz Release、by-hash 索引,可选 InRelease、Release.gpg

RPM XML 使用 gzip 压缩。SOW 不生成 SQLite rpm-md、zchunk、modulemd 或源码包索引。 生成结果由你配置的 HTTP 服务或对象存储对外提供。 客户端细节见平台与集成。

按任务阅读

任务 文档
在现有包旁生成索引 Plain 仓库
管理持续的包更新 Managed 工作区
在不同 Dist 视图间共用包字节 包池与视图
排除软件包或保留最新版本 成员策略
为包与索引签名 签名
中断后继续执行 事务与恢复
查看状态、验证输出与读取历史 可观测与审计

1 - Plain 平面仓库

生成平面 RPM、DEB 索引,继续中断构建,并使用签名或 Pigsty 完成标记。

用 sow create 为一个 RPM 或 DEB 软件包目录生成索引。索引与包文件放在一起。 该命令不读取 sow.yml,也不创建状态数据库。

sow create /srv/offline

输入与输出

SOW 只扫描目录顶层以 .rpm 或 .deb 结尾的普通文件,不递归,也不跟随包文件符号链接。 包名、版本和架构来自包内信息。SOW 会拒绝源码 RPM(src 和 nosrc)。

输入 生成文件
RPM repodata/repomd.xml 和 gzip 压缩的 primary、filelists、other XML
DEB Packages 和 Packages.gz
两种格式 两组索引

RPM 包路径使用文件名,DEB 的 Filename 使用 ./<文件名>。SOW 会把所有有效版本收入索引。 同一包坐标对应不同字节时,构建报冲突。SOW 会拒绝没有受支持包的目录;--pigsty 完成中断的清理操作时例外。

默认保留包文件和无关文件。某种格式不再有包时,SOW 删除该格式的旧索引。 对于 RPM,SOW 保留当前与紧邻上一轮以校验和命名的元数据,删除更早的 SOW 格式元数据,并替换不完整输出。若 repodata/ 中存在 SQLite 数据库、modulemd、comps 或 .asc 签名等非 SOW 文件,sow create 会清理这些外来元数据,并在 dropped_metadata(--json)和 stderr 告警中报告。 生成目录的权限为 0755,生成文件为 0644。

构建与重复运行

普通未签名构建只读取每个选中包一次,同时计算 SHA-256 并解析元数据。 --jobs 控制并行数,默认为逻辑 CPU 数。SOW 根据解析结果生成并检查索引。

替换索引前,SOW 再次核对包文件列表、文件身份、权限、大小和修改时间。 构建期间应停止其他程序修改包文件。文件状态发生变化时,命令停止发布并返回退出码 5。

相同包字节和选项生成相同元数据。如果生成文件与现有文件相同,命令报告 文本字段 noop=true 或 JSON 字段 noop: true,保留现有文件。

RPM 元数据的 revision 和 data timestamp 默认都是 0。更新供 EL7 YUM 使用的已有仓库时, 请设置发布时间:

sow create /srv/offline --metadata-timestamp 1791849600

该选项只改变 repomd.xml,不改变压缩 XML、包文件或 DEB 索引。时间戳应从已有仓库的值 继续递增;操作方法见 YUM 迁移。

小文件 .sow-plain-rpm-history.json 跟踪 RPM 元数据保留范围。 没有变化的构建仍保留同一份上一轮索引。复制目录后还要继续构建时,一并保留该文件。 软件包保留仍由维护者负责,详见 RPM 索引保留。

清理旧版 RPM

使用 sow prune 在 Plain 目录中按每个 RPM (name, arch) 仅保留最高 EVR 版本:

sow prune /srv/offline --check
sow prune /srv/offline

sow prune 按原生 rpmvercmp 规则(包含 ~ 预发布与 ^ 后置快照算符)比较版本。它先更新 repodata/,再删除被取代的 .rpm 文件,确保客户端不会看到指向缺失软件包的索引。如果目录中已有 repo_complete,sow prune 会自动刷新该标记。与 sow create 不同,当 repodata/ 中含有非 SOW 外来元数据文件时,sow prune 会拒绝执行。

构建中断

SOW 先在暂存区生成并检查元数据,再替换公开文件。这些替换不构成跨全部 RPM 和 DEB 文件 的单个事务,中断可能留下混合版本或不完整索引。

再次运行相同的 sow create 命令即可。它会清理自己留下的临时文件,并根据目录中当前的包 重新生成索引。Plain 模式没有操作日志恢复,JSON 字段 recovered 始终为 false。

对在线服务,在单独目录构建,成功后再把完整目录切换上线。需要记录成员关系、构建快照和 事务恢复时,使用 Managed 模式。

Pigsty 清理与完成标记

sow create /srv/offline --pigsty

--pigsty 根据包内信息删除 DEB i386 包和 Patroni 3.0.4 包。 它不会仅因 RPM 架构为 i386、i486、i586 或 i686 而删除该包。

SOW 保留 annobin-docs 等文档包,除非命中上述清理规则。

索引更新和包清理完成后,SOW 写入 repo_complete。每行包含 SHA-256、两个空格和保留的 包文件名,按文件名排序。

SOW 在替换公开文件前移除旧标记,最后写入新标记。采用此约定的使用方应等待标记出现。 如果构建中断且标记缺失,再次运行带 --pigsty 的命令。目录已有 repo_complete 时, 不带 --pigsty 的命令会拒绝重建。

RPM 包签名

sow create /srv/offline --sign-with 6D5C5A26C36B1F73

此选项为没有可解析内嵌签名的 RPM 签名。加上 --overwrite 可重签所有保留的 RPM。 签名需要主机上的 rpm 命令和可用 GPG 密钥。SOW 对暂存副本签名并检查,再先替换包文件、 后替换索引。这条路径会多次读取包字节。

密钥带口令时,可传入 --passfile PATH 或设置 SOW_GPG_PASSFILE;启用签名仍需 --sign-with。完整文件格式见口令文件。 Plain 模式不读取 Managed 的 sow.yml 配置。

Plain 模式不签仓库元数据。Managed 元数据签名和包签名策略见签名。

并发命令

sow create 锁住目标目录及其父目录。用 -N 在其他 SOW 写者占锁时立即退出, 或用 -T 30s 最多等待 30 秒。默认等待到锁可用。

完整选项与结果字段见 sow create。

2 - Managed 工作区

工作区结构、包成员关系、配置、查找规则与初始化。

定期更新的仓库适合使用 Managed 模式。SOW 记录包成员关系、执行 Dist 策略、构建元数据, 并保留操作历史。

工作区、仓库与 Dist

Workspace(工作区)
└── Repository(仓库)
    └── Dist(软件包集合)
        └── Architecture view(架构视图)
术语 用途
工作区 保存 sow.yml、私有 .sow/ 状态和允许的架构族
仓库 拥有独立包池、Dist、SQLite 数据库、构建快照(Generation)和发布状态
Dist 按名称组织一组包,格式为 rpm 或 deb
架构视图 为某种架构的客户端提供 Dist 中的包索引

一个仓库可以同时包含 RPM 和 DEB Dist。el9、trixie、el9-beta 等 Dist 名称由你指定, 不会自动检查操作系统兼容性,也不会建立版本晋级流程。

同一包可以属于一个仓库中的多个 Dist,无需复制包文件。架构无关包在每个 Dist 中有一份 成员关系,并出现在该 Dist 的各个适用架构视图中。不同仓库分别保存自己的包对象。

公开文件与私有状态

  • <workspace>/
    • sow.yml
    • .sow/
      • workspace.lock
      • workspace-ops/
      • repo-locks/<repo>.lock
      • <repo>.db
      • <repo>/
        • stage/
        • recovery/
        • pending/
        • retained/
        • transitions/
    • <repo>/
      • pool/
      • dists/

对外服务或发布完整的 <workspace>/<repo>/ 目录。sow.yml 和 .sow/ 保持私有。 需要保留可继续工作的工作区时,一起备份配置、私有状态和包文件。 详见仓库布局。

创建与配置

sow init /srv/repo
sow repo new demo -C /srv/repo
sow dist new el9 --format rpm -r demo -C /srv/repo
sow dist new trixie --format deb -r demo -C /srv/repo

初始配置使用 schema: sow/v3,架构族为 [x86_64, aarch64]。 除非配置中已经声明仓库,init 不会创建仓库。仓库与 Dist 的创建、删除命令会更新 sow.yml。

编辑 sow.yml 设置架构限制、成员策略、签名和发布目标,然后检查配置:

sow config check -C /srv/repo
sow config show --all -C /srv/repo

解析器拒绝未知字段。config check 还会比较配置与已初始化状态,并解析签名引用。 config show --all 展开默认值。配置接受 amd64、arm64,分别规范化为 x86_64、aarch64;DEB 索引仍使用原生名称 amd64、arm64。

要防止删除整个仓库,设置 protected: true。它会阻止 repo rm(包括带 -f 的调用), 但不限制包和 Dist 的修改。

期望状态与构建状态

期望状态记录各 Dist 应包含哪些包;构建状态记录最近完成的元数据构建快照。

默认情况下,add 和 rm 更新成员后会构建相关输出。带 --skip 时,只更新成员关系, 不构建。新包字节先保存在私有 pending/ 中,构建时再进入包池。

sow add ./incoming/ -C /srv/repo -r demo -d el9 --skip
sow status -C /srv/repo -r demo
sow build -C /srv/repo -r demo
sow check -C /srv/repo -r demo

dirty 表示仓库需要构建,公开目录仍对应上一次完成的构建快照。 策略、架构或签名变化也会使 Dist 变脏。成员关系或构建状态仍引用某个架构时,SOW 会拒绝从配置中删除它。如果已发布到目标的某个 Dist 或架构随后被移除,必须先执行覆盖整库的 sow build -r REPO 生成新的完整构建快照,之后 sow publish 才会撤下远端已移除的入口指针。

新 Dist 会立即获得各架构的有效空索引。已配置元数据签名时,空 Dist 也会签名。 移除最后一个成员后,索引为空,但入口仍保留。

查找工作区

Managed 命令按以下顺序查找最近祖先目录中的 sow.yml:

  1. 指定 -C/--workdir DIR 时从 DIR 开始,否则从当前目录开始。
  2. 没有找到工作区时,从 $SOW_DIR 开始查找。
  3. 两次都没有找到时,返回工作区发现错误。

命令使用找到的第一个配置,即使该配置有错误。--workdir 只改变工作区查找起点, 不改变进程目录。相对输入路径仍相对于当前目录:

sow add ./build/example.rpm -C /srv/repo -r demo -d el9

查找起点可以是符号链接,但搜索沿输入路径的祖先目录进行。工作区内的符号链接会阻止 隐式推断仓库或 Dist。需要时使用物理工作区路径,并明确指定 -r、-d。

Plain 命令 sow create 不参与工作区查找。

选择仓库与 Dist

仓库选择使用第一个可用结果:

  1. 显式 -r/--repo NAME。
  2. 工作区查找起点所在的仓库。
  3. 工作区中唯一的仓库。

add、rm 和 ls 按以下顺序选择 Dist:

  1. 显式 -d 参数;可重复使用,选择多个 Dist。
  2. 工作区查找起点所在的 Dist。
  3. 所选仓库中唯一的 Dist。

如果仍有多个选择,命令要求显式指定。

build、check 和 status 默认覆盖所选仓库的全部 Dist。 show 搜索所选仓库,where 搜索工作区全部仓库。支持 -d 的命令可用它缩小范围。 各命令的选择规则见命令参考。

重复初始化与升级

再次运行 init 会补齐配置中尚未初始化的仓库和 Dist。已有状态只做检查,不重置构建快照。 给已有 Dist 新增架构后,运行 build 生成新视图。

初始化已提交部分对象、后续又失败时,命令返回退出码 3 并报告已完成内容。 修正错误后再次运行 init。

SOW 0.7.1 与 0.5.0 至 0.7.0 均使用数据库 schema v13。从这些版本升级均无需迁移数据库。 对于 0.3 或 0.4 工作区,先备份完整工作区,再执行 sow repo migrate。 不要手动修改 SQLite schema 版本。

数据库还缓存解析后的包信息。后续构建可以复用缓存和文件指纹;sow check 则读取包字节 并计算哈希,执行完整一致性检查。

继续阅读成员策略、视图和 事务与恢复。

3 - 包池与元数据视图

在 RPM 与 APT 视图间共用包文件,导出独立 RPM 仓库,并交付完整仓库。

Managed 仓库将每个包对象保存在 pool/ 中。Dist 和架构视图只包含引用这些包字节的索引。

<repo>/pool/                              包文件
<repo>/dists/<rpm-dist>/<arch>/repodata/   RPM 元数据
<repo>/dists/<deb-dist>/main/binary-*/     APT 元数据

存储范围是一个仓库。同一个包放入其他仓库或发布目标后,分别占用相应仓库或目标的命名空间。

RPM 视图

RPM Dist 为每个配置架构生成一个元数据目录:

  • demo/
    • pool/
      • e/example/example-1.0-1.x86_64.rpm
      • d/demo-data/demo-data-1.0-1.noarch.rpm
    • dists/el9/
      • x86_64/repodata/
        • <sha256>-primary.xml.gz
        • <sha256>-filelists.xml.gz
        • <sha256>-other.xml.gz
        • repomd.xml
      • aarch64/repodata/
        • ...

x86_64 视图包含 x86_64 和 noarch 包,aarch64 视图包含 aarch64 和 noarch 包。 两个视图引用同一份 noarch 文件。

包位置相对于架构目录:

<location href="../../../pool/d/demo-data/demo-data-1.0-1.noarch.rpm"/>

客户端指向 dists/el9/x86_64/,服务端提供完整 demo/ 目录,使相对包路径可以解析。 sow check 检查索引路径没有逃出仓库,并指向预期包对象。

APT 视图

APT 索引使用相对于仓库根的路径:

Filename: pool/e/example/example_1.0-1_amd64.deb

每个配置架构在 dists/<dist>/main/binary-<arch>/ 下有 Packages、Packages.gz 和 SHA-256 by-hash 条目。Dist 根包含 Release,已签名时还有 InRelease、Release.gpg。

all 包出现在每个适用架构索引中,包体仍只有一份包池对象。 SOW 不生成每视图包副本,也不生成每架构 Release 文件。

为 dnf reposync 导出

默认 dnf reposync 会拒绝越过其单仓库下载目录的 Managed RPM 路径。 此工作流可使用独立仓库导出:

RPM Dist 必须属于当前构建快照且处于干净状态。即使后续构建只更新了其他 Dist,只要该 RPM Dist 状态干净,也可以直接导出,因为 SOW 会把未变更的视图沿用到当前构建快照中。运行前先检查导出前提。

sow export rpm-leaf el9 x86_64 /srv/exports/el9-x86_64 -r demo

导出包含本地包路径、独立 repodata、清单和 .sow-export.json 完成标记。 目标目录应位于仓库和所有已配置 filesystem 发布目标的预期路径(endpoint + prefix)之外。

警告

默认复制文件。--hardlink 要求同一文件系统,使导出包与源包共用 inode,因此应保持导出文件只读。导出不会创建 Dist 成员或保留构建快照,也不是 sow publish 的输入。

交付完整仓库

在线更新使用已配置的发布目标。删除某个 Dist 或架构后,先运行覆盖整库的 sow build -r REPO 再发布,使当前构建快照覆盖完整仓库并允许 sow publish 安全撤下远端已移除的入口指针。需要其他传输方式时,先将完整、已稳定的 pool/ + dists/ 复制到单独暂存目录,检查副本后再切换上线。缓存中的索引所引用的包 URL 应保持可访问,直到缓存过期。完整传输流程见对外服务。 只复制某个 RPM 架构目录,会使包路径无法解析。

sow changes 0 在 pool/ 下列出每个包一次,再列元数据和客户端入口。 它描述本地文件;目标进度与缓存处理由 sow publish 管理。

继续阅读仓库布局、 对外服务和 export。

4 - 成员策略

配置包排除与版本数量限制,读取策略结果,恢复已移除成员。

用 Dist 策略排除不需要的包,并限制索引中的版本数量。在 sow.yml 中配置 exclude 和 limit:

dists:
  el9:
    format: rpm
    limit: 2
    exclude:
      - kind: [debuginfo, debugsource, llvmjit]

SOW 先执行 exclude,再执行 limit。add、rm 和 build 都会对所选 Dist 的完整候选 成员集合应用策略。编辑策略后,先运行 sow config check,再运行 sow build, 使现有成员符合新规则。

排除软件包

每条规则包含一个或多个字段。规则内的所有字段都要匹配;一个字段内,任一模式匹配即可。 任一规则匹配时,SOW 排除该软件包。

exclude:
  - kind: [debuginfo, debugsource, dbgsym, dbg, llvmjit]
  - name: ["test-*", "*-experimental"]
    arch: [aarch64]

此配置在所有架构上排除列出的包类型,同时排除名称以 test- 开头或以 -experimental 结尾的 aarch64 包。

字段 匹配的包属性
name 二进制包名
source 规范化的源码包名
arch x86_64、aarch64 或 neutral
kind 下表中的包类型
format rpm 或 deb

模式区分大小写,支持精确字符串和 shell 通配符 *、?、[...],不支持正则表达式、 版本比较或取反。空规则、空模式、首尾有空白的模式、同字段重复模式和非法通配符都是配置错误。

SOW 根据二进制包名确定 kind:

格式 包名后缀 kind
RPM -debuginfo debuginfo
RPM -debugsource debugsource
RPM -llvmjit llvmjit
DEB -dbgsym dbgsym
DEB -dbg dbg
任意 没有匹配后缀 main

用 sow show PACKAGE --json 查看计算出的类型。SOW 不会按此表校验模式值: kind: [debug] 是合法配置,但不会匹配任何包类型。

限制版本数量

limit 按二进制包名和规范架构族分组。架构无关包(noarch 或 all)单独成组。

值 结果
0 保留所有版本,默认值
正整数 N 每组保留最新的 N 个版本
负数 配置错误

RPM 使用原生 rpmvercmp 的 epoch-version-release 排序(包含 ~ 预发布与 ^ 后置快照算符,2.0~rc1 < 2.0 < 2.0^20250611 < 2.0.1),DEB 使用原生 Debian 版本排序,从不按普通字符串排序。

limit: 2 时,向已有版本 1、2 的 Dist 加入版本 3,会在同一次操作中移除版本 1 的成员关系。 即使包名相同,x86_64 包与 noarch 包也属于不同分组。

读取策略结果

输入包被所有选中 Dist 排除时,整体状态为 excluded。各 Dist 的结果中, excluded 表示命中排除规则,limited 表示超过版本数量限制。 策略排除不计为输入失败,也不会因此使命令失败。

一个 Dist 可以接受某个包,同时另一个 Dist 排除它。没有 Dist 接受新包时, SOW 不会为它保存一个无成员关系的包对象。

恢复已移除的版本

放宽策略不会恢复旧成员。提高 limit、删除排除规则或移除最新版本,只会改变当前及后续 候选包的处理结果。要恢复某个版本,请重新加入其包文件:

sow add ./archive/example-1.0-1.x86_64.rpm -r demo -d el9

策略移除的是成员关系,不是包字节。已构建或保留的构建快照可能仍引用该包,使其继续留在包池。 用 sow gc 回收无引用对象;对已发布目标使用 sow gc TARGET。

预览移除结果

sow rm example -r demo -d el9 --check

预览只报告成员移除和预期文件变化,不写入状态。把文件列表视为完整计划前,请检查 JSON 中 的 prediction_complete。--check 不能与 --skip、--timeout 或 --no-wait 同用。

完整配置见配置参考,输入结果见 sow add。

5 - 签名模型

配置元数据与 RPM 包签名、引用密钥,并轮换签名身份。

配置元数据签名,让客户端验证仓库索引。需要客户端验证 RPM 文件内嵌签名时, 另行配置 RPM 包签名。

签名对象

配置 签名内容 输出 客户端设置
signing.rpm.metadata RPM 索引清单 repodata/repomd.xml.asc DNF repo_gpgcheck=1
signing.deb.metadata APT 发布清单 InRelease、Release.gpg APT Signed-By
signing.rpm.packages RPM 包 内嵌 OpenPGP 签名 DNF gpgcheck=1

元数据签名不改变包字节。RPM 包签名会改变包字节及 SHA-256。 SOW 不给 DEB 包文件增加签名。

配置元数据签名

repos:
  demo:
    signing:
      rpm:
        metadata:
          key: file:///secure/repo-signing.asc
      deb:
        metadata:
          key: file:///secure/repo-signing.asc
    dists:
      el9:
        format: rpm
      trixie:
        format: deb

RPM 与 DEB 可使用相同或不同密钥。密钥带口令时,在 key 旁增加 passfile: /run/secrets/gpg-password,或将 SOW_GPG_PASSFILE 设置为口令文件路径。 先检查配置,再构建:

sow config check
sow build -r demo
sow check -r demo

已配置签名的空 Dist 与非空 Dist 生成相同种类的签名文件。InRelease 的签名正文与 Release 一致。未配置密钥时,SOW 生成未签名索引。 Managed 的密钥与签名策略来自 sow.yml;--passfile PATH 可为本次执行覆盖口令来源, build 同样支持。

密钥与口令引用

元数据密钥引用 含义 签名实现
keys/repo.asc 相对于工作区根的 OpenPGP 密钥文件 进程内
file:///secure/repo.asc 绝对路径的 OpenPGP 密钥文件 进程内
env://SOW_METADATA_KEY ASCII 装甲格式密钥、文件路径或 GPG 密钥标识 取决于变量值
agent://KEY_ID_OR_FINGERPRINT 本地 GPG 环境可用的密钥 外部 gpg

密钥文件支持 ASCII 装甲格式和二进制 OpenPGP 数据。GPG 密钥标识可以是 16 位 key ID, 或 40、64 位指纹。引用解析为 GPG 密钥标识时,使用外部 gpg 访问私钥; 显式指定口令文件后,通过 GPG loopback 模式非交互解锁。

签名 key 引用必须解析为一个主密钥。 trusted_keys 文件可以包含多个完整公钥,包括连续的 ASCII 装甲块。 SOW 读取整个密钥集合,拒绝格式错误或被截断的输入,不会仅使用第一个密钥而忽略后续内容。

口令来源优先级为 --passfile PATH、每个密钥的 passfile 配置、SOW_GPG_PASSFILE。 旧元数据配置的 passphrase 引用继续兼容,优先级与单个密钥配置相同; 同一密钥的两个字段不能都非空。三种签名设置都接受 passfile,包括 agent:// 密钥。

口令文件为非空 UTF-8 单行。SOW 只移除末尾一个 LF 或 CRLF,保留空格与制表符。 不搜索默认路径,所选来源不可用时直接失败。完整格式、相对路径基准、权限及恢复命令范围 见口令文件。

config show 显示引用和解析出的公钥身份,不显示私钥或口令内容。 config check 解锁元数据私钥文件并检查用途;外部 GPG 元数据密钥还会试签。 RPM 包签名只检查口令格式、工具和私钥是否可用,不会签包或证明口令正确, 详见 config check。 创建新元数据签名时,选中的签名密钥需要在发布时间和 当前时间都可用。历史验证使用保留的证书。

新 APT 签名使用 SHA-256。新的发布尝试要求两份 APT 签名都使用 SHA-256、SHA-384 或 SHA-512。 升级包含 SHA-1 元数据签名的工作区后,先运行 build,再开始新发布。 已有发布尝试继续使用其记录的文件。

配置 RPM 包签名

signing:
  rpm:
    packages:
      mode: fill
      key: agent://7F721C4AD40F4A9D8CA578BFAC7E4690B50CCF3B
      trusted_keys: [keys/upstream.asc]
模式 行为
never 原样保存输入字节
fill 已有签名可由当前密钥或受信密钥验证时保留原包,其余包签名
always 已由当前密钥签名的包保留原样,其余包重签

没有密钥时默认 never,有密钥时默认 fill。fill 和 always 都必须配置密钥。

包签名在私有副本上调用主机的 rpm --addsign 或 rpm --resign。 安装 rpm、gpg,并确保 rpm 使用的 GPG 环境中有对应私钥。 密钥引用只标识签名者,不会把私钥导入该环境。原始输入文件保持不变。

SOW 检查签名结果、包坐标及排除签名头后的包体摘要。已有对象的坐标和包体相同、 且符合当前策略时,可复用已有签名字节。never 模式要求字节完全相同才能复用。

包签名工作流请使用 RPM 格式 v4。SOW 支持验证 RSA、DSA、ECDSA 与 EdDSA RPM 签名,包括保存在 RPM 签名头 tag 267(RPMSIGTAG_DSA)中的纯头签名。SOW 不支持 RPM 格式 v6 使用的签名标签。 这与 OpenPGP 签名报文版本不同;SOW 支持历史 CentOS 的 OpenPGP v3、v4 签名。

更换密钥

只修改 passfile 路径不会让 Dist 变脏;签名身份变化会使相关 Dist 变脏。更换元数据密钥后,运行 build 生成新签名, 并在发布新元数据前把新公钥分发给客户端。

已有包池对象不可变,修改 RPM 包签名策略不会原地重签这些对象。 逐步轮换包签名密钥时:

  1. 将新密钥设为当前密钥,使用 mode: fill。
  2. 旧包仍为成员时,在 trusted_keys 中保留旧公钥。
  3. 加入由新密钥签名的新版本包。
  4. 旧签名包退出成员集合或被新版本替代后,再移除旧信任。

直接换新密钥并使用 always,要求所有期望成员 RPM 已符合新密钥策略。 若重签会使同一包 URL 对应不同字节,应使用新的包发行版本和文件名。

RPM 验证按包的签名时间检查证书和签名子钥是否有效。证书保留了所需历史自签名与子钥绑定时, 续期本身不会使旧包签名失效。导出续期公钥时保留这些历史公钥信息。

Plain 签名

sow create /srv/offline --sign-with 6D5C5A26C36B1F73 --passfile /run/secrets/gpg-password
sow create /srv/offline --sign-with 6D5C5A26C36B1F73 --overwrite --passfile /run/secrets/gpg-password

Plain 模式只签 RPM 包文件。不带 --overwrite 时,为没有可解析内嵌签名的包签名; 带该选项时,重签全部保留的 RPM。密钥标识为 16、40 或 64 位十六进制字符, 可带 0x 前缀。执行 --pigsty 清理后,至少需要剩余一个 RPM。

客户端配置见仓库签名,完整字段见 配置参考。

6 - 事务与恢复

恢复中断的 Managed 操作,了解本地构建顺序与锁行为。

Managed 模式先记录变更,再应用变更。中断后,SOW 根据记录完成或回滚操作。 应使用哪个命令恢复,取决于中断的操作。

恢复中断操作

先查看记录的状态:

sow status -r demo
sow log -r demo
状态 下一步
clean 交付前运行 sow check
dirty 运行 sow build,应用待构建的成员或配置变化
recovering 按下表选择与中断操作对应的恢复命令
error 根据诊断与日志检查报错文件或操作
中断操作 恢复命令
add、rm、Dist 修改或构建 sow build -r demo
init、repo new 或 repo rm 重新运行对应的创建、删除命令
发布 sow publish TARGET
本地或目标垃圾回收 sow gc -r demo 或 sow gc TARGET
移除保留构建快照 sow retain rm GENERATION -r demo
Plain 索引生成或 RPM 清理 使用原选项重新运行 sow create 或 sow prune

SOW 按未完成状态(而非记录的时间戳)选择活动的仓库变更操作,因此系统时钟回拨不会把未完成操作隐藏在更早的已完成记录之后。本地或目标 gc 以及 retain rm 在固定执行计划后中断时,SOW 会保留该计划,再次运行同一命令即可完成中断的清理。

恢复过程如果需要重新生成带签名的元数据,就需要再次访问签名密钥与口令。SOW 不在日志中保存口令值。 可用 --passfile PATH、单个密钥配置或 SOW_GPG_PASSFILE 提供来源;支持版本和优先级见 口令文件。工作区生命周期日志尚待恢复时,用命令行或 环境变量覆盖口令来源,保持日志记录的 sow.yml 不变,其摘要属于恢复校验的一部分。

先修正报错中的输入、配置或存储问题。恢复期间,工作区配置、数据库和私有状态应保持配套。 本地恢复完成后,运行 sow check -r demo。常见错误见故障处理, 发布恢复见 publish。

SOW 记录什么

记录 位置 用途
工作区生命周期日志 .sow/workspace-ops/active.json 协调配置与仓库创建、删除
仓库操作日志 .sow/<repo>.db 记录本地成员变更、构建与维护
发布状态 .sow/<repo>.db 记录目标绑定、固定的发布计划和进度

工作区生命周期恢复会比较当前 sow.yml 的摘要与日志中的新旧配置摘要。 配置替换决定该生命周期变更应回滚还是完成。两者都不匹配时,报告完整性错误。

仓库成员变更使用自己的状态转换:

planned → staged → applied → built → done
                       └────────────→ done_dirty
状态 含义
planned SOW 已记录操作及其范围
staged SOW 已为下一阶段准备输入
applied SOW 已提交期望状态,公开目录可能仍使用上一构建快照
built SOW 已安装生成的公开目录
done 操作完成
done_dirty SOW 已提交期望状态,仍需构建

其他终态包括 rolled_back 和 failed。用 sow log OPERATION -r demo 查看转换和时间。

--skip 通常以 done_dirty 结束。已识别的签名策略变化、构建计划固定前的配置变化, 或受支持的内部契约升级,也可能让已提交操作保留为脏状态。日志会记录原因。 需要时修正配置,再次运行 build。

已提交变更后的错误

数据库提交与后续维护是两个步骤。成员关系或构建已经提交后,仍可能发生检查点或清理错误。 SOW 会在命令输出中保留已提交结果,不会把这些项目报告为已回滚。

先查看命令结果与 sow status,再用 sow log 查看对应操作。 按诊断提示继续执行相应命令。确认结果前,保留原始输入文件。

本地构建顺序

SOW 在同一文件系统暂存并检查输出,再按以下顺序应用:

包字节 → 元数据 → 客户端入口 → 过时元数据清理
  1. 把包字节放入规范 pool/ 路径。
  2. 安装按校验和命名的 RPM 元数据,以及 APT 直接索引和 by-hash 索引。
  3. 安装 RPM 的 repomd.xml 和已配置的签名,或适用的 APT Release、InRelease、 Release.gpg。
  4. 替换持久化后,清理过时元数据。

构建保留紧邻前一代的 RPM 不可变元数据和 APT by-hash 对象,供仍持有旧入口的客户端读取。 显式构建快照保留是独立功能,详见 retain。

脏仓库仍服务最近完成的构建快照。多 Dist 操作中的各视图可能在不同时间切换。 远端 HTTP 请求和缓存也有各自的存续时间;在线目标更新应使用 sow publish。 sow changes 只列出本地文件变化,不执行发布或目标清理。

锁与等待

SOW 通过建议锁协调进程。工作区生命周期使用 .sow/workspace.lock, 仓库写操作使用 .sow/repo-locks/<repo>.lock。需要两把锁时,先取工作区锁。

需要一致快照的读命令使用共享锁。status 探测仓库写锁,无需等写者结束即可报告活动状态。 Plain create 与 prune 锁住目标目录及其父目录。

选项 行为
-T 30s 最多等待写锁 30 秒
-T 0 等待到锁可用,默认行为
-N 只尝试一次,其他写者占锁时立即退出

无法取得锁时退出码为 4。-N 与非零 -T 同用时退出码为 2。 只使用命令支持的锁选项;只读命令不接受这些选项。

文件系统要求

使用支持建议锁、fsync 和原子重命名的本地 POSIX 文件系统,macOS 使用 APFS。 公开文件与私有暂存区应位于同一文件系统。SOW 拒绝控制路径中的符号链接、 非预期文件类型和越界路径,并在替换或删除受控对象前检查文件身份。 网络文件系统不适合作为工作区。

仓库与 Dist 名称使用 [a-z0-9][a-z0-9._-]*,保留名称见 仓库布局。脚本如何处理中断、占锁和完整性错误, 见退出码。

7 - 可观测与审计

查看状态、验证仓库内容、比较构建快照,并读取操作历史。

用 status 查看状态,check 校验仓库,changes 查看文件变化,log 读取操作历史。

命令 回答的问题
status 是否需要构建或恢复?是否有写者活动?
check 状态、包字节、索引、签名是否一致且可以交付?
changes 两个构建快照之间有哪些本地文件变化?
log 执行了哪些操作,改变了什么?
retain ls 显式保留了哪些构建快照?

这些命令不修改仓库内容。

查看状态

sow status -r demo

结果包含期望状态修订号、构建快照、脏 Dist、待构建包的数量与字节数、写锁状态及 ready_to_copy。

状态 含义
clean 期望状态与构建状态一致
dirty 成员或配置变化需要构建
recovering 已记录工作需要恢复或清理
error 记录或文件证据存在未解决错误

status 报告状态,不计算公开文件哈希。dirty 状态运行 build; recovering 或 error 状态按诊断及恢复流程处理。 交付前运行 check。

校验仓库

sow check -r demo

正常检查报告九层:

检查层 内容
config 配置与生效 Dist 设置
state SQLite schema 与关系状态
public-modes 公开文件和目录权限
retained 保留构建快照记录与元数据
package-bytes 包池和待构建包字节是否匹配记录的 SHA-256
desired-membership 包身份、架构和成员关系
index 索引内容和引用文件
signature 已配置的元数据与 RPM 包签名
generation-manifest 构建文件清单与公开目录是否一致

布局转换未完成时,改为报告 config、state、public-modes、layout-transition。 按 repo migrate 的说明完成或中止迁移。

check 忽略缓存指纹,读取每份唯一物理包并计算哈希,不修复文件。 即使上一构建快照仍一致,脏仓库或恢复中的仓库也不能通过交付检查。

比较构建快照

sow changes -r demo
sow changes 0 -r demo
sow changes 42 -r demo --json

不传基线时与上一构建快照比较;基线 0 列出当前完整目录;基线 42 与已记录构建快照 42 比较。 各行按适用情况包含动作、阶段、仓库相对路径、大小和 SHA-256。

该比较不检查发布目标。用 sow publish TARGET 更新目标并继续中断传输, 用目标 GC 回收远端对象。

保留构建快照与回收无用包

retain add 只接受当前构建快照。若 status 显示构建快照为 42,应在后续构建替换它之前保留:

sow retain add 42 -r demo
sow retain ls -r demo

命令校验该构建快照,保存元数据与引用;包文件仍由共享包池保存。 不再需要构建快照 42 时,用 sow retain rm 42 -r demo 移除保留记录, 再用 sow gc -r demo 回收无用字节。若 retain rm 或 gc 中途中断,重新执行同一命令即可继续完成已记录的计划。本地 gc 要求仓库为 clean;有待构建变更时先构建。 期望成员、已构建成员、为旧客户端保留的成员记录,以及保留构建快照、恢复或发布状态 仍需使用的包都会保留。完整规则见 gc。

普通构建保留的前一代元数据与显式保留功能相互独立。 目标清理使用 sow gc TARGET:filesystem 目标在缓存宽限期后支持条件删除, R2 目标只报告候选,不执行删除。

查看与导出日志

sow log -r demo
sow log OPERATION -r demo
sow log export operations.jsonl -r demo
sow log prune 2026-01-01 -r demo

日志记录操作状态、时间、包处理结果,以及适用的成员与文件变化。 操作详情还可能包含构建进度事件。

log export 将 JSON Lines 写入新文件或 stdout。log prune 接受日期或 RFC 3339 时间戳, 删除符合条件的终态审计记录。恢复所需记录和构建快照清单会保留。

脚本接口见 JSON 输出,中断处理见 事务与恢复。