This is the multi-page printable view of this section. .
Commands
- 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
Use these pages for command syntax, selection rules, results, and examples. Run sow help COMMAND or sow help COMMAND SUBCOMMAND to check the options in your installed version.
Command index
sow create builds a flat repository directly in a directory. To manage packages in a Workspace, start with sow init. All other Managed commands use an existing Workspace. help and version can run anywhere.
| Command | Mode | Purpose |
|---|---|---|
sow create [DIR] |
Plain | Generate a flat RPM/DEB repository in place |
sow init [DIR] |
Managed | Initialize a Workspace and create declared Repositories/Dists |
sow config check|show |
Managed | Validate or print the effective configuration |
sow repo ls|new|show|migrate|rm |
Managed | Create, inspect, upgrade, or remove Repositories |
sow dist ls|new|show|rm |
Managed | Manage Dists |
sow add PATH... |
Managed | Add packages and rebuild indexes |
sow rm PACKAGE... |
Managed | Remove packages and rebuild indexes |
sow ls |
Managed | List Desired packages with their Built locations |
sow show PACKAGE |
Managed | Inspect one package |
sow where PACKAGE |
Managed | Find a package across the Workspace |
sow status |
Managed | Show Repository state and pending changes |
sow build |
Managed | Build a Generation from Desired state |
sow check |
Managed | Verify a Repository before delivery |
sow changes [BASE_GENERATION] |
Managed | Compare files in two Built Generations |
sow publish TARGET |
Managed | Publish a verified Generation to a configured target |
sow retain add|ls|rm |
Managed | Protect a Generation’s package files from local GC |
sow gc [TARGET] |
Managed | Reclaim unused local packages or maintain a publication target |
sow export rpm-leaf |
Managed | Export a standalone RPM repository |
sow log [OPERATION] |
Managed | Read, export, and prune operation records |
Global syntax
Running sow with no arguments prints the command list and exits 0. Use sow help COMMAND or
sow help COMMAND SUBCOMMAND for built-in usage. sow version and sow --version print version and build information.
Options apply only to the commands that list them. Use -- before a positional argument that starts with -. Unknown or repeated single-value options return exit code 2.
Workspace discovery
Managed commands find the nearest ancestor containing sow.yml:
- Start at
-C/--workdir DIR, when supplied; otherwise start at the current directory. - Search upward and stop at the first
sow.yml. - If that search finds nothing, repeat from
SOW_DIRwhen set. An explicit-Csuppresses the current-directory candidate, but not theSOW_DIRfallback. - If no Workspace is found, exit
2.
--workdir changes only the discovery start directory. It does not change the process working
directory, so relative positional paths still resolve against the actual current directory.
sow create never performs Workspace discovery.
Repository selection
Commands that require one Repository select it in this order:
- explicit
-r/--repo NAME; - the Repository containing the discovery start directory;
- the only Repository in the Workspace;
- otherwise fail with exit
2and list the candidates.
repo new and repo rm take NAME positionally and do not accept -r. sow where searches all
Repositories by default; -r narrows its scope. Publication targets select their configured
Repository, so publish TARGET and gc TARGET do not accept an additional Repository selection.
Dist selection
add, rm, and ls require a concrete Dist set and select it in this order:
- repeated
-d/--dist NAMEvalues; - the Dist containing the discovery start directory;
- the only Dist in the selected Repository;
- otherwise fail with exit
2and list the candidates.
Other commands use these defaults:
build,check, andstatusdefault to all Dists when-dis absent;showsearches the whole selected Repository unless-dnarrows it;wheresearches all matching Dists across the Workspace unless-r/-dnarrow it;changesis Repository-wide and rejects-d.
Locking
create locks its target directory. Managed mutations coordinate through Workspace and Repository locks. init waits for its locks without a CLI timeout option.
Other write commands accept -T/--timeout DUR and -N/--no-wait. The default --timeout 0 waits indefinitely. Positive durations use ms, s, m, or h, for example 500ms, 30s, or 5m. --no-wait fails immediately if a lock is held. It cannot be combined with a positive timeout. Lock acquisition failure returns 4.
Most read-only Managed commands use shared locks and wait for a conflicting writer. They do not accept lock wait options. status normally probes the Repository lock so you can inspect an active writer. Cancel a waiting command with Ctrl-C.
Parallelism
-j/--jobs N is available only where SOW parses packages, hashes bytes, renders indexes, or verifies
state: create, add, rm, build, check, and repo migrate. It defaults to the logical CPU
count and must be at least 1.
JSON output
Commands with --json emit one versioned envelope on stdout; diagnostics remain on stderr:
ok is false for every non-zero exit. A partial batch still returns its committed and failed items.
See JSON Output for complete result shapes.
Use text output for interactive work and --json for scripts. config show prints YAML by default. log export is the exception: it writes JSONL directly and does not accept --json.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success or idempotent no-op |
1 |
Runtime I/O, parser, renderer, signing, or transport error |
2 |
Usage, Workspace discovery, or configuration error |
3 |
Partial batch success |
4 |
Write lock unavailable |
5 |
Integrity/recovery failure, or check ruling the tree not deliverable |
6 |
Rejected operation: conflict, protected object, no match, or incompatible architecture |
130 |
Command interrupted by SIGINT or SIGTERM |
See Exit Codes for command-specific triggers.
1 - sow create
sow create writes RPM and DEB indexes beside the package files in a directory. Use it for a flat repository that does not need a managed Workspace. DIR defaults to the current directory.
Synopsis
Description
create reads the top-level regular package files in DIR. For RPMs, it generates repodata/. For DEBs, it generates Packages and Packages.gz. A mixed directory gets both formats. Architectures come from the package headers; Plain mode accepts all architectures found in the packages.
Indexes use relative package paths: the filename for RPM, and ./<filename> for DEB. Serve the package directory through HTTP or use it as a local file:// source.
By default, create updates its indexes and preserves the package files. --pigsty can delete matching packages, and --sign-with can replace RPMs with signed copies.
Options
| Flag | Description | Default |
|---|---|---|
-j, --jobs N |
Parallel package-processing workers | logical CPU count |
--metadata-timestamp SECONDS |
Unix seconds from 0 to 253402300799 for RPM repomd data timestamps; leaves DEB, package times and gzip headers unchanged |
0 |
--pigsty |
Enable Pigsty compatibility cleanup and completion marker | off |
-S, --sign-with KEY |
Sign unsigned RPMs with a GPG key ID or fingerprint | off |
--overwrite |
Re-sign every RPM; requires --sign-with |
off |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
-h, --help |
Show help | — |
Scan rules
- Only top-level regular files ending in
.rpmor.debare considered. - No recursion, no symlink following, no Workspace config.
- Every valid version enters the index. Two files claiming the same logical coordinate with different content is a hard failure.
- A directory with no supported package is rejected in default mode.
--pigstyaccepts an empty package set so rerunning an interrupted cleanup can finish and write its marker.
Package I/O and final validation
Keep the input directory unchanged while create runs. SOW scans the packages, then checks for file changes before replacing the indexes. A detected change returns exit code 5. Use --jobs to set the number of parallel package-processing workers.
Deterministic output and idempotence
Without forced package re-signing, the rendered metadata is byte-stable for a given input set
and timestamp: gzip output is deterministic.
repomd.xml carries <revision>0</revision> and data timestamps defaulting to 0;
--metadata-timestamp overrides the latter. Repeating the same options on an unchanged directory
rewrites nothing and reports noop=true:
The repo_complete gate
If the directory contains repo_complete, use --pigsty to refresh it with the indexes. Running without --pigsty returns an error:
To keep using the completion marker, rerun with --pigsty. If you no longer use it, move the marker out of the directory first.
Publication time for existing YUM repositories
--metadata-timestamp sets the RPM repomd data timestamps. It does not change package files, compressed XML streams, or DEB indexes. The default is 0, and the accepted range is 0..253402300799 Unix seconds. This option was added in SOW 0.5.0.
EL7 YUM rejects metadata whose maximum data timestamp is older than its cached copy. When replacing an existing metadata generator, choose a timestamp at least as large as the highest value previously published. For changed content, advance it by at least one second to distinguish updates.
Record the publication time and pass it on every update. Omitting the option resets it to 0. Restoring older package content is a new publication, so keep the timestamp increasing. Use seconds, not milliseconds, and avoid future dates.
If clients verify repomd.xml.asc, regenerate that signature after repomd changes. create neither signs repomd nor updates or removes its old detached signature.
See Migrating an Existing YUM Repository for the complete procedure.
–pigsty
--pigsty applies these cleanup rules and writes a completion marker:
- Delete DEB packages whose parsed architecture is
i386. RPMs are not removed merely for carrying ani386/i486/i586/i686architecture. - Delete RPM/DEB whose binary package name is exactly
patroniand whose upstream version is exactly3.0.4. RPM comparesVERSION, ignoring epoch and release; DEB strips epoch and Debian revision first.3.0.4+foois not a match. - After all indexes render successfully, write
repo_complete: the SHA-256 of every remaining top-level RPM/DEB, sorted by basename byte order, formatted<sha256><two spaces><basename>.
Cleanup applies only to top-level regular package files that parsed successfully and matched a rule.
SOW removes the existing repo_complete before replacing indexes. It then deletes the packages matched by the cleanup rules and writes the new marker last. A missing marker means the build is incomplete.
Signing RPMs
-S/--sign-with KEY signs RPM package files through the local RPM/GPG environment. Use a 16-digit hexadecimal key ID or a 40- or 64-digit fingerprint. An optional 0x or 0X prefix is accepted. Configure the private key and passphrase in that environment before running the command.
- Default: only RPMs with no parseable embedded OpenPGP signature are signed. Anything already signed keeps its bytes.
--overwriterequires--sign-withand switches torpm --resignover every retained RPM.- SOW signs a temporary copy, checks that package identity and content are unchanged, then replaces the original RPM. The indexes describe the final signed file.
- At least one top-level RPM must remain after
--pigstycleanup, andrpmmust be onPATH.
Locking, staging and overwrite rebuild
create locks the target directory while it updates the indexes. Use --timeout or --no-wait to control lock waiting.
An interruption can leave only part of the index set updated. Rerun sow create with the same intended options to rebuild from the current packages. Plain mode rebuilds from scratch; the result field recovered remains false.
For --pigsty, expose the repository only after repo_complete appears. Use Managed mode when you need journaled recovery.
Examples
Index a mixed directory:
Machine-readable result:
Build a Pigsty flat repository with eight workers and a publication time chosen from its history:
The caller records this time and signs the resulting repomd.xml when clients require it.
Failure envelope:
Exit codes
| Code | Trigger |
|---|---|
0 |
Indexes written, or unchanged input produced a no-op |
1 |
Directory unreadable or missing, package parse failure, renderer failure, signing tool failure |
2 |
Usage error — invalid or repeated timestamp, --overwrite without --sign-with, malformed key, --no-wait with a non-zero --timeout |
4 |
Directory write lock held and --no-wait given or --timeout expired |
5 |
Input set/stat changed before publication, or a controlled output path failed an integrity check |
6 |
No supported package found, repo_complete gate hit, --sign-with on a DEB-only directory, coordinate conflict |
See also
- Plain Flat Repositories — the design behind
create - Quick Start — five-minute flat repository walkthrough
- Repository Layout — what the flat tree looks like
- Sign Your Repository — generating and using a signing key
- Migrate an Existing YUM Repository — replacing an existing metadata generator
2 - sow init
sow init creates a Workspace with sow.yml and private .sow/ state. If the configuration already declares Repositories and Dists, it creates the missing ones. Run it again to complete initialization after adding declarations.
Synopsis
DIR defaults to the current directory. To initialize another directory, pass its path as DIR.
Description
A fresh init writes a minimal config and the private state directory:
.sow/ stores private state, locks, and recovery records with directory permissions 0700. Serve the Repository directories over HTTP; keep .sow/ private.
Options
| Flag | Description | Default |
|---|---|---|
--json |
Output JSON | false |
-h, --help |
Show help | — |
Idempotence rules
A new configuration uses schema: sow/v3 and architectures x86_64 and aarch64. It declares no Repository. Add one with sow repo new or edit sow.yml first.
init preserves an existing configuration and initializes missing declared objects. It can use a non-empty directory if no file conflicts with a reserved SOW path. When all declared objects are already initialized, the counters are zero.
Apply a declared configuration
If sow.yml already describes Repositories and Dists, init creates the missing directory trees,
SQLite databases and empty indexes for them. Already-initialized objects are skipped, so the counters
tell you exactly what this run did.
Every Dist created this way has valid empty indexes. An RPM Dist gets empty repodata/ in each architecture view. A DEB Dist gets empty Packages and Packages.gz files, by-hash entries, and a Release file.
Running it a second time changes nothing:
Locking and recovery
If initialization is interrupted, rerun sow init for the same directory. SOW first completes or rolls back the interrupted operation, then initializes any remaining declared objects. It waits for other Workspace writers to finish.
Examples
Bootstrap a Workspace and add Repositories by hand:
Initialize a directory other than the current one:
Initialize from a config file under version control:
Exit codes
| Code | Trigger |
|---|---|
0 |
Workspace initialized, or already initialized (no-op) |
1 |
Runtime I/O error writing the config or state directory |
2 |
Usage error, or an existing sow.yml that fails to parse or validate |
3 |
Partial success — some declared Repositories/Dists were committed and at least one failed |
5 |
The Workspace journal could not be recovered to a terminal state |
6 |
An existing file collides with a SOW reserved path |
See also
- First Workspace — the ten-minute guided version
- Managed Workspaces — the three-layer model
- sow.yml Reference — every configuration key
- sow repo and sow dist
- Repository Layout — what
.sow/contains
3 - sow config
Use sow config check after editing sow.yml. It validates the configuration, signing references, and compatibility with existing Repository state. Use sow config show to inspect resolved defaults and inherited settings. Both commands are read-only.
Synopsis
sow config check
Parses and validates the complete sow.yml: schema version, names, path collisions, the architecture
permit list, Dist formats, membership policy and signing key references. It reports the Workspace it
resolved and how much it validated.
Options
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
--json |
Output JSON | false |
-h, --help |
Show help | — |
Strict field rejection
Unknown keys produce a configuration error:
Use schema: sow/v3:
config check also verifies that the signing keys are available and suitable for signing. Before removing an architecture from the Workspace list, remove its use from Dist configuration, membership, and Built state.
sow config show
Prints the effective configuration as YAML for the currently selected scope.
Compare that with the file on disk, which carries only what you wrote:
show filled in protected: false, the inherited per-Dist architectures, limit: 0 and an empty
exclude list. Architectures use canonical family names: x86_64 and aarch64. The corresponding DEB names are amd64 and arm64.
Options
| Flag | Description | Default |
|---|---|---|
--all |
Expand defaults and normalized architectures across the whole Workspace | off |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-d, --dist NAME |
Select a distribution; repeatable | selection rules |
--json |
Output JSON | false |
-h, --help |
Show help | — |
Select configuration with -r and -d
-r and -d narrow the output to the selected objects. This is the fast way to answer “what policy
is actually in effect for this one Dist”:
Without --all, normal Repository selection applies. If the discovery start directory is inside a Dist, that Dist is selected unless you provide -d. Otherwise, all Dists of the selected Repository are shown. --all shows the entire Workspace and ignores -r and -d.
Signing references
The output contains key references and resolved fingerprints. Private key contents and passphrases are omitted.
Examples
Validate before a build in CI:
Diff effective policy across two Dists:
Exit codes
| Code | Trigger |
|---|---|
0 |
Configuration valid, or output printed |
1 |
Runtime or key-resolution failure |
2 |
Usage or discovery error, invalid YAML, unknown field, or unsupported schema |
5 |
Existing state, controlled paths, or recovery records are inconsistent |
6 |
Unknown selection, unusable signing configuration, or configuration incompatible with existing state |
config check validates both the YAML file and its compatibility with existing Repository state.
See also
- sow.yml Reference — every key, with a complete example file
- Membership Policy — how
excludeandlimitare evaluated - Signing Model — key reference grammar and the two trust chains
- sow init — create objects declared in a hand-written config
- sow check — the runtime counterpart that verifies bytes on disk
4 - sow repo
sow repo creates, lists, inspects, upgrades, and removes Repositories. Each Repository stores its packages in pool/ and its indexes in dists/. Packages are shared among Dists in the same Repository.
Synopsis
Naming
A Repository name must match [a-z0-9][a-z0-9._-]* and may not be ., .., .sow, pool,
dists, or collide with a Workspace reserved file.
SOW creates each Repository at <workspace>/<NAME>/.
sow repo ls
Read-only listing of every Repository in the Workspace.
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
--json |
Output JSON | false |
STATUS is one of clean, dirty, recovering or error. See
Transactions & Recovery for what each one implies for clients.
sow repo new
Updates sow.yml, then creates <workspace>/<NAME>/{pool,dists}, the SQLite database and
the private state directory. A new Repository is Generation 0 and clean.
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
Pass the new Repository name as NAME.
Running it again on an existing Repository reports the current state without creating another Repository.
sow repo show
Read-only detail for one Repository. With NAME omitted, the usual
Repository selection rules apply.
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select the repository when NAME is omitted |
selection rules |
--json |
Output JSON | false |
If you give both NAME and -r, use the same name:
sow repo migrate
Use repo migrate when upgrading an existing v0.3 or v0.4 Workspace to SOW 0.5. New 0.5 Repositories already use schema v13 and need no migration.
- Stop Workspace writers.
- Back up the whole Workspace, preserving hard links.
- Migrate each configured Repository.
- Run
buildandcheckfor each Repository.
Repeat the last three commands for each Repository. The migration reports schema_from and schema_to; a repeated migration reports 13 -> 13. Schema v13 adds pool-path lookup indexes. Migration from v0.3 also applies the intervening state and publication repairs.
Writes require the current schema. Compatible read-only commands can inspect older state without upgrading it. After migration, use SOW 0.5 or later; restore the backup to return to the older schema.
--abort abandons an unfinished layout transition before its commit decision. It does not undo a completed schema migration. Pre-release C2 layouts must be rebuilt from their original packages.
| Flag | Description | Default |
|---|---|---|
-j, --jobs N |
Parallel verification/render workers | logical CPUs |
--abort |
Abandon a maintenance attempt before its commit decision | false |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select the repository when NAME is omitted |
selection rules |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
sow repo rm
Removes a Repository, including its sow.yml entry, database, package files in pool/, indexes in dists/, and private state.
Without -f, only an empty Repository — no Dists, no Memberships, no Package Objects — can be
removed:
| Flag | Description | Default |
|---|---|---|
-f, --force |
Remove a non-empty unprotected repository | false |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
Force removal
Use -f to remove a non-empty Repository. A protected Repository remains protected when you use -f.
protected
protected: true in sow.yml blocks Repository deletion outright, -f included:
To remove a protected Repository, set its protected value to false in sow.yml. Run sow config check, then retry the removal.
protected applies only to Repository deletion. add, rm, build, and dist rm continue to work:
Examples
Create the Repositories for a two-tier layout:
Create a Repository without waiting for a held lock:
Print each Repository’s name, state, and Generation:
Exit codes
| Code | Trigger |
|---|---|
0 |
Listed, created, shown, migrated, abandoned a pre-commit transition, or removed; or repo new reported an existing Repository |
1 |
Runtime I/O error creating or removing the tree |
2 |
Usage error, Workspace not found, or an ambiguous Repository selection |
4 |
Workspace lock held and --no-wait given or --timeout expired |
5 |
Integrity or recovery error in the Workspace journal |
6 |
Invalid name, unknown Repository, non-empty without -f, protected, or NAME conflicting with -r |
See also
- sow dist — the layer below
- Managed Workspaces — the three-layer model and discovery rules
- Transactions & Recovery — lock scopes and the
recoveringstate - sow.yml Reference —
protectedand per-Repository signing - Repository Layout — the fixed directory structure
5 - sow dist
sow dist manages the distributions that clients use. Each Dist contains either RPM or DEB packages. Dists in one Repository share pool/ and have separate indexes under dists/.
Synopsis
Naming
Dist names follow the same rule as Repository names: [a-z0-9][a-z0-9._-]*, excluding ., ..,
.sow, pool and dists.
Choose names that identify your distributions or channels, such as el9, trixie, or el9-beta. SOW does not infer package format or policy from the name.
sow dist ls
Read-only flat listing of the selected Repository’s Dists.
DESIRED and BUILT are membership counts. When they diverge, DIRTY_REASONS says why:
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
--json |
Output JSON | false |
The table shows architecture families. JSON also shows the package ecosystem names.
This excerpt contains the architectures field from one entry in result.dists; the DEB
view directories are binary-amd64 and binary-arm64:
sow dist new
Create a Dist with --format rpm or --format deb.
| Flag | Description | Default |
|---|---|---|
--format FORMAT |
Required; rpm or deb |
— |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
--format is required and accepts only rpm or deb:
A new Dist inherits the Workspace architectures. Edit sow.yml to select a subset or set limit and exclude, then run sow config check and sow build.
If the name and format already exist, dist new reports the current state.
Using the same name with a different format is rejected:
Creation and recovery
dist new updates sow.yml and builds the new Dist. If it is interrupted, run sow build to recover the operation.
The new Dist has valid empty indexes. An RPM Dist gets empty repodata/ in each architecture view. A DEB Dist gets empty Packages and Packages.gz files, by-hash/SHA256/ entries, and a Release file. When signing is configured, it also gets InRelease and Release.gpg.
sow dist show
Read-only detail for one Dist.
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
--json |
Output JSON | false |
JSON also includes effective_config_sha256, the digest of the resolved Dist configuration.
Changing limit, exclude, or a signing key can make the current configuration differ
from the Built Generation. SOW then marks the Dist dirty until it is rebuilt.
sow dist rm
Removes a Dist’s Membership and derived indexes.
| Flag | Description | Default |
|---|---|---|
-f, --force |
Remove membership and indexes but retain pool packages | false |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
Dist removal does not delete Pool bytes
SOW removes the Dist’s configuration, membership, and indexes. Package files remain in pool/:
Run sow gc to reclaim package files when no current or retained state needs them.
If a configured target has published this Dist, removal is rejected. See Removing a published view.
A Repository’s protected: true blocks Repository deletion only; normal Dist maintenance on a
protected Repository continues to work.
Examples
Create an RPM Dist and a DEB Dist in one Repository:
Create a beta Dist, set its package-retention policy in sow.yml, then rebuild it:
List Dists that need a build:
Exit codes
| Code | Trigger |
|---|---|
0 |
Listed, created, shown or removed; or dist new reported an existing Dist |
1 |
Runtime I/O or renderer error creating the empty indexes |
2 |
Usage error — missing or invalid --format, Workspace not found, ambiguous Repository |
4 |
Workspace or Repository lock held and --no-wait given or --timeout expired |
5 |
Integrity or recovery error in the Operation Journal |
6 |
Invalid name, unknown Dist, format conflict with an existing name, non-empty without -f |
See also
- sow repo — the layer above
- Pool & Architecture Views — how one Dist becomes several client-visible views
- Membership Policy — configuring
limitandexclude - sow build — rebuild after a policy edit
- Repository Layout — the RPM and DEB
dists/structures
6 - sow add
sow add imports packages into the selected Dists and builds their local indexes. It reads package format and architecture from the package headers, then applies each Dist’s policy. Use --skip to import several batches before one build. To update a remote target, run sow publish after the build.
Synopsis
Options
| Flag | Description | Default |
|---|---|---|
-R, --recursive |
Descend into subdirectories of a PATH directory |
off (top level only) |
--skip |
Update Desired state only; do not build | off |
-j, --jobs N |
Parallel workers for parsing, hashing and rendering | logical CPU count |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-d, --dist NAME |
Select a distribution; repeatable | selection rules |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
Inputs and targets
PATH can be a file or a directory. A directory is scanned top level only unless you pass -R.
Select one Repository and one or more Dists. Use -r and -d, or the directory-based defaults. In a mixed RPM/DEB batch, each package goes only to Dists of its format. A package with no compatible target fails.
Choose Dists that match the package’s intended distribution. SOW checks package format and architecture; it does not test operating-system or dependency compatibility.
The summary reports the Operation ID, item counts, and membership changes. It also shows the Desired Revision, Built Generation, and dirty state. Each input then has one item line, in stable order.
Item statuses
Each item line carries an overall status plus a per-Dist verdict in dists=.
| Status | Meaning |
|---|---|
accepted |
New Package Object created and at least one Membership added |
reused |
The content already exists in this Repository; only Membership references may change |
excluded |
Policy removed it from every target Dist — see the dists= field for excluded vs limited |
failed |
The package was rejected; the error= field says why |
reused is content idempotence: adding the same file twice never creates a second object or a
duplicate Membership. A repeated add also builds the selected Dist by default. If it is already
current, the Generation stays unchanged. If an earlier --skip or configuration change left it dirty, SOW completes the build and may advance the Generation:
The same object added to a second Dist is also reused — the pool keeps one copy and gains a second
Membership. Use --skip again to postpone the build.
Package architecture
add reads format and native architecture from the package header, then checks the Workspace permit
list. An unsupported architecture or one outside the configured list rejects that package:
RPM noarch and DEB all are architecture-neutral. They create one Package Object and one
Membership, and render into every effective architecture view of the target Dist. Only the selected Dists receive them.
Policy: exclude and limit
After merging into the target Memberships, SOW re-evaluates exclude and then limit over the
complete Dist candidate set. A package removed by policy is reported, not treated as a parse failure.
Here trixielim has exclude: [{kind: [dbgsym]}] and limit: 1. The dbgsym package was excluded by
rule; libpq5 18.2-1 lost to 18.3-1 under the version limit and is reported as limited. Both
appear as excluded in the top-level status, and the dists= field distinguishes them.
Limit groups by (binary name, canonical architecture), so 18.3-1:amd64 and 18.3-1:arm64 both
survive a limit: 1. A package can be accepted by one Dist and skipped by another in the same run.
To restore a package after relaxing exclude or limit, run sow add again. Policy applies to Desired membership, not every file left in pool/.
Partial batches
Valid, conflict-free packages are committed even when siblings fail. Failed inputs stay where they
are, each with its own error, and the command exits 3:
If every input fails, the operation exits 6; Desired memberships and public content
remain unchanged. Human output includes each failed input and its reason, and JSON includes
them in result.items:
Policy-excluded inputs are not input failures; an entirely excluded batch can succeed even
when the Dist was empty. If you interrupt the command with SIGINT or SIGTERM, it returns 130. Rerun it with the original input files; recovery preserves committed membership changes.
For changed package content, use a new version and filename. A previously published pool path remains reserved after local GC. A path conflict rejects that input; other valid inputs can still succeed.
Keep filename capitalization consistent when adding the same package again. Paths that differ only by case conflict. On a case-insensitive filesystem, source-package directory names must also be distinct; see Workspace filesystem.
–skip
--skip commits Desired membership and leaves pool/, dists/, and the Built Generation unchanged. New package files stay in private pending storage until a build needs them. The Repository is dirty when the resulting Desired state differs from Built state; reusing an unchanged member can leave it clean.
pending=4/2326 is four objects totalling 2326 bytes waiting in the private store. They never appear
in sow changes — only a successful build promotes them into the
deliverable tree.
Use --skip for bulk imports, then build once:
Processing order
SOW inspects the packages and signs new RPMs when configured. It applies exclude and limit, records the resulting membership, then builds unless --skip is set.
Input files remain unchanged. Signing uses private copies.
RPM signing modes
Managed RPM package signing is configured in sow.yml under signing.rpm.packages.mode; there is no
command-line override.
| Mode | Behavior |
|---|---|
never |
Keep the input bytes exactly |
fill |
Sign when unsigned or when the signature is not trusted; keep bytes when a trusted_keys signature verifies. Default when a key is configured |
always |
Ensure the final package is validly signed by the configured key; re-sign a stage copy otherwise |
Without a configured key only never is available.
Repeated imports reuse identical package content. They can also reuse an RPM whose only difference is its signature, if the stored package satisfies the current signing policy. A changed payload or an incompatible stored signature causes a coordinate conflict. See Signing Model for the matching rules.
Exit codes
| Code | Trigger |
|---|---|
0 |
Inputs processed, including policy-excluded inputs; build completed or skipped |
1 |
Runtime I/O, parser, renderer or signing failure |
2 |
Usage error, Workspace not found, or ambiguous Repository/Dist selection |
3 |
Partial batch — at least one item committed and at least one failed |
4 |
Repository lock held and --no-wait given or --timeout expired |
5 |
Integrity or recovery error, including a build that failed after applied |
6 |
Every input failed, or selection/signing/state preconditions reject the operation |
See also
- sow rm — the inverse operation
- sow build — build after
--skip - Membership Policy —
excluderules andlimitsemantics in full - Signing Model — the two trust chains and key references
- Package References — the coordinate grammar printed on each item line
7 - sow rm
sow rm removes packages from the selected Dists and rebuilds their local indexes. Use --check to preview the removal, or --skip to postpone the build. Package files remain in pool/ until sow gc can reclaim them.
Synopsis
Options
| Flag | Description | Default |
|---|---|---|
-c, --check |
Preview only; compute and print the plan without writing anything | off |
--skip |
Update Desired state only; do not build | off |
-j, --jobs N |
Parallel workers | logical CPU count |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-d, --dist NAME |
Select a distribution; repeatable | selection rules |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
--check and --skip are mutually exclusive:
Package references
PACKAGE accepts five forms. The full grammar and disambiguation rules are on
Package References; the short version:
| Form | Example |
|---|---|
| Content hash | sha256:d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab |
| RPM coordinate | rpm:epel-release-0:7-5.noarch |
| DEB coordinate | deb:libpq5=18.3-1:amd64 |
| Full filename | epel-release-7-5.noarch.rpm |
| Bare binary name | epel-release |
A bare name matches every version and architecture of that name in the selected Dists. Other reference forms must identify one object. An ambiguous reference returns an error with the matching candidates.
sow ls prints exact sha256: references and canonical coordinates,
so you never have to assemble one by hand.
If no package matches a reference, the command returns an error:
Default behavior: remove and rebuild
Without --check or --skip, rm commits the Desired change and rebuilds every affected Dist
before returning. Pool objects stay on disk.
The summary is followed by one change line per affected file (eight in this run). Add --json to
receive the same result as a stable standard envelope.
Removing the last member of a Dist is fine. SOW still renders a valid, signed-if-configured empty
index — an empty Packages with a verifiable InRelease, or empty per-architecture repodata/.
Preview with –check
-c/--check previews the removal and resulting local file changes without modifying the Repository. removed lists memberships matched by your references. If the current policy removes additional members, their index changes appear in changes; their membership count appears in sow log after the removal runs.
Both centos-release versions matched the bare name. The change lines describe the expected local file changes in payload → metadata → pointer → delete order. Use --json to read the removed[] and changes[] arrays in a script.
Preview validates the candidate configuration and existing state. It uses shared locks and can wait for a writer, but does not accept --timeout or --no-wait:
–skip
--skip commits the Desired change and leaves the public tree unchanged. The Repository is dirty if the resulting Desired state differs from Built state. Removing a pending-only addition can return it to clean without a build.
changes is empty because nothing was built. Run sow build to
build the pending changes.
Policy interaction
After removal, SOW applies policy to the remaining members. If you remove libpq5 18.3-1 from a limit: 1 Dist, the previously excluded 18.2-1 stays excluded. Run sow add to restore it.
Examples
Preview a removal, then apply it:
Remove one exact object from two Dists with one command:
Batch several removals, then rebuild once:
Feed the preview plan to another tool:
Exit codes
| Code | Trigger |
|---|---|
0 |
Memberships removed and rebuilt, or a --check preview printed |
1 |
Runtime I/O or renderer failure |
2 |
Usage error — --check with --skip, --check with lock flags, ambiguous selection, Workspace not found |
4 |
Repository lock held and --no-wait given or --timeout expired |
5 |
Integrity or recovery error |
6 |
A reference matched nothing, or a non-bare reference was ambiguous; the whole batch is rejected and no memberships are removed |
See also
- Package References — the full reference grammar
sow ls,show, andwhere— finding the exact reference to remove- sow add — the inverse operation
- sow build — build after
--skip - Membership Policy — why removed members do not come back
sow retainandsow gc— when bytes can be reclaimed
8 - sow ls
sow ls lists packages in the selected Dists’ Desired membership. Each row also shows the package’s Desired and Built locations across the Repository. Use it to obtain package references for show or rm.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
-d, --dist NAME |
Select a Dist; repeatable | selection rules |
--json |
Output JSON | false |
Output
| Column | Meaning |
|---|---|
SHA256 |
Immutable content identity; valid input to show and rm |
COORDINATE |
Canonical rpm: or deb: package reference |
DISTS |
Desired membership across the Repository |
BUILT_DISTS |
Membership present in the current Built Generation |
POOL_PATH |
Repository-relative immutable payload path |
The first line reports whether the selected Dists are dirty. Configuration changes can make them dirty even when membership counts match.
-d selects rows by Desired membership. The DISTS and BUILT_DISTS columns then show all locations of each returned package in the Repository. A package removed with rm --skip is absent from this list even while an older Built index still contains it. An empty BUILT_DISTS means no local Built Dist currently includes that package.
Each package appears once, with multiple Dists separated by commas. An empty Dist produces a successful result with no package rows.
Selection
ls requires an unambiguous Dist set. In a multi-Dist Repository, pass one or more -d values or
run from inside <repo>/dists/<dist>/.
With --json, the same rows appear in result.packages.
Examples
List exact references for all objects not yet built:
List pool paths in deterministic order:
Exit codes
| Code | Trigger |
|---|---|
0 |
Membership printed, including an empty list |
1 |
Runtime I/O failure |
2 |
Usage error, Workspace not found, or implicit Repository/Dist selection is ambiguous |
5 |
Repository state database unreadable or inconsistent |
6 |
Explicit Repository or Dist is not configured |
See also
sow show— inspect one listed objectsow where— locate an object across the Workspacesow rm— remove a listed reference from Desired Membership- Package References — accepted identity forms
9 - sow show
sow show displays one package in the selected Repository. The text output summarizes its identity, storage, and membership. Add --json for the complete stored package record.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
-d, --dist NAME |
Narrow candidates to these Dists; repeatable | Repository scope |
--json |
Output JSON | false |
Package reference
PACKAGE accepts a sha256:<hex> content identity, canonical rpm:<NEVRA> or
deb:<name>=<version>:<arch> coordinate, full package filename, or bare binary name. See
Package References for the exact grammar.
A bare name must resolve to exactly one Package Object in the selected scope. Unlike sow rm foo,
which removes every matching version from Desired Membership, sow show foo refuses ambiguity and
prints the candidates:
Copy an exact coordinate or SHA-256 from the error or from sow ls and retry.
Output
Without --json, show prints the identity, storage path, and Desired/Built locations in a compact
human-readable form:
Adding --json returns the complete Package Object under result.package in the standard envelope,
including the normalized fields below.
| Field | Meaning |
|---|---|
canonical_arch |
x86_64, aarch64, or neutral for RPM noarch / DEB all |
kind |
Policy class: main, debuginfo, debugsource, llvmjit, dbgsym, or dbg |
source |
Normalized source-package name |
payload_sha256 |
RPM signature-neutral digest used for re-signing idempotence |
signature_key |
Embedded package-signature key ID, when present |
storage |
pending before build; pool once placed in the local package pool |
dists / built_dists |
Desired and current Built Membership |
Without -d, lookup includes stored objects with no current membership. With -d, candidates must be Desired members of a selected Dist. The returned dists and built_dists lists cover the entire Repository.
Exit codes
| Code | Trigger |
|---|---|
0 |
One Package Object printed |
1 |
Runtime I/O failure |
2 |
Usage error, Workspace not found, or implicit Repository selection is ambiguous |
5 |
Repository state database unreadable or inconsistent |
6 |
Explicit scope is not configured, or the reference matched nothing/several objects |
See also
sow ls— obtain exact identities from Dist Membershipsow where— search across Repositoriessow rm— remove matching Desired Membership- JSON Output — complete result schema
10 - sow where
sow where finds the Desired membership locations of one package across a Workspace. It searches all Repositories by default. Use -r or -d to limit the search.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Restrict the search to one Repository | all Repositories |
-d, --dist NAME |
Restrict the search to named Dists; repeatable | all Dists |
--json |
Output JSON | false |
Reference resolution
PACKAGE uses the same grammar as sow show: SHA-256 identity, canonical
RPM/DEB coordinate, full filename, or bare name.
The reference must identify one package: the same format, coordinate, and SHA-256. That package can exist in several Repositories. If different packages match, use -r or -d to narrow the search, or supply an exact coordinate or SHA-256.
Output
Without --json, where prints a summary followed by one line per location:
Each location reports Desired dists and current built_dists across its Repository. Candidate lookup uses Desired membership. A package removed from every selected Dist is no longer found, even if a previous Built index still contains it. Use sow show PACKAGE -r NAME without -d to inspect such a stored object.
With --json, the locations appear in result.locations. If no package matches, the command returns an error:
Example
List the Dists that include this package in Desired state:
Exit codes
| Code | Trigger |
|---|---|
0 |
One resolved Package Object and its locations printed |
1 |
Runtime I/O failure |
2 |
Usage error or Workspace not found |
5 |
A Repository state database is unreadable or inconsistent |
6 |
Explicit Repository/Dist is not configured, or the reference matched nothing/was ambiguous |
See also
sow show— inspect the resolved Package Objectsow ls— list one Dist set- Package References — exact grammar and ambiguity rules
11 - sow status
sow status reports whether a Repository needs a build, how much pending data it holds, and whether a writer is active. It reads stored state without hashing package files. Use sow check to verify file contents.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
-d, --dist NAME |
Restrict status to these Dists; repeatable | all Dists |
--json |
Output JSON | false |
Repository states
SOW records the intended package set as Desired state and the last build as a Built Generation.
| State | Meaning | Next step |
|---|---|---|
clean |
Desired state matches Built state | Run check before copying the Repository |
dirty |
Desired and Built state differ, commonly after --skip or a config change |
Run build to apply the changes |
recovering |
A local Repository operation was interrupted | Run build to recover it |
error |
State, files, or recovery records need attention | Read dirty_reasons, then run check and log |
Output
The human line reports:
- Repository state and
ready_to_copy; - Desired Revision and current Built Generation;
- affected Dists;
- pending object count and bytes;
- write-lock state.
The JSON result additionally includes dirty_reasons and the most recent Operation:
ready_to_copy summarizes the recorded state. When it is false, dirty_reasons explains why. sow check performs the file-content validation.
Reading stored state
status reports stored state without migrating or repairing it. It can read database schemas v6, v12, and the current v13. Unsupported or unreadable state returns 5. Before writing to an older Workspace, back it up and run sow repo migrate for each Repository.
Exit behavior
status returns 0 for every readable state, including dirty, recovering, and error. Scripts
should inspect the structured state rather than treating those conditions as command failures.
| Code | Trigger |
|---|---|
0 |
Repository state is readable |
1 |
Runtime I/O failure |
2 |
Usage error, Workspace not found, or implicit Repository selection is ambiguous |
5 |
State database unreadable or inconsistent |
6 |
Explicit Repository or Dist is not configured |
See also
sow build— build pending Repository changessow check— validate the repository before deliverysow log— inspect the recent Operation reported here- Transactions & Recovery — state transitions and pointer safety
12 - sow build
sow build updates local indexes to match Desired membership and current configuration. Run it after add --skip, rm --skip, or a policy change. It recovers unfinished local Repository operations before starting the next build.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-j, --jobs N |
Parallel workers; must be at least 1 |
logical CPU count |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
-d, --dist NAME |
Build named Dists; repeatable | all affected Dists |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
Without -d, SOW builds every affected Dist in the selected Repository. With -d, only those
Dists are built; unselected changes remain dirty.
Result
Without --json, build prints one human-readable summary:
Use --json to read the result in a script.
No-op builds
When membership, relevant policy, renderer settings, and signing configuration already match the
Built Generation, build is an idempotent no-op and does not increment the Generation:
Apply policy changes
build re-evaluates the current exclude and limit policy. Tightening policy may remove Desired
Membership. Loosening policy does not reconstruct historical members from leftover pool bytes; run
sow add again for packages you want to restore.
Commit and recovery
SOW moves required pending packages into pool/, writes the indexes, then updates client entry points such as repomd.xml and Release. One build can include several Dists. They share the resulting Generation; unselected dirty Dists still need a build.
If a build stops, rerun it. SOW uses the operation journal to finish or roll back the unfinished work. If the journal, database, and files disagree, the command reports an integrity error. Use sow check and sow log to identify the affected state.
Progress events
Long builds append structured build_progress records to the Operation log. Each event
contains phase, completed, total, and jobs. Current phases are:
rendering;promoting_payload;publishing_dists;normalizing_public_tree;finalizing.
Inspect the events with sow log OPERATION.
Metadata signing
Configure metadata signing in sow.yml.
Changing a configured key reference or fingerprint makes affected Dists dirty, and the next build
re-signs their metadata.
- RPM: always writes
repodata/repomd.xml; writesrepomd.xml.ascwhen configured. - DEB: always writes
Release; writesInReleaseandRelease.gpgwhen configured.
Exit codes
| Code | Trigger |
|---|---|
0 |
Build completed or nothing to do |
1 |
Renderer, signing, or filesystem failure |
2 |
Usage error, Workspace not found, or implicit Repository selection is ambiguous |
4 |
Repository write lock unavailable |
5 |
Recovery cannot complete safely, or Repository is in error |
6 |
Explicit scope is not configured, or current configuration rejects existing state |
See also
sow status— determine whether a build is neededsow check— verify the resulting treesow changes— inspect the resulting file delta- Transactions & Recovery — full commit protocol
13 - sow check
sow check verifies a managed Repository before delivery. It checks package hashes, stored state, indexes, signatures, and the Generation manifest. The command is read-only; run sow build first if Desired state differs from the Built Generation.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-j, --jobs N |
Parallel verification workers; at least 1 |
logical CPU count |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
-d, --dist NAME |
Verify named Dists; repeatable | all Dists |
--json |
Output JSON | false |
Verification layers
For a Repository with no unfinished layout migration, check reports nine layers:
| Layer | Verification | checked counts |
|---|---|---|
config |
sow.yml parses and validates for the Repository |
configuration objects |
state |
SQLite quick_check, foreign keys, journal, and recovery evidence |
one state database |
public-modes |
File and directory permissions across the served tree | inspected paths |
retained |
Retained records and their saved Generation manifests; see retain rm to handle a damaged record |
retained records |
package-bytes |
SHA-256 of pool and private pending payloads | Package Objects |
desired-membership |
Membership rows resolve under current policy | memberships |
index |
Rendered indexes match the membership they claim | Dists |
signature |
Every declared metadata and package signature verifies | signatures |
generation-manifest |
Built Generation manifest matches files on disk | one manifest |
During an unfinished layout migration, check reports config, state, public-modes, and layout-transition, then stops. Follow the reported repo migrate instruction to complete or abort that migration.
Package verification
Each run hashes the package files again. Hard links to the same physical file share one hash check. Signed RPMs also undergo signature verification. The checked count refers to package objects, not file reads.
Dirty is not deliverable
A dirty Repository needs a build before it can pass check. The old Built Generation and new Desired state can each pass their own checks while differing from each other:
The exit code is 5. Run sow build, then run sow check again.
Exit codes
| Code | Trigger |
|---|---|
0 |
Every layer passes and the Repository is ready to copy |
1 |
I/O failure during verification |
2 |
Usage error, Workspace not found, or implicit Repository selection is ambiguous |
5 |
A verification layer failed, or Repository is not deliverable |
6 |
Explicit Repository or Dist is not configured |
See also
sow status— cheap state querysow build— build indexes from Desired state- Exit Codes — why a dirty Repository maps to
5 - Observability & Audit — operating checks and audit together
14 - sow changes
sow changes lists file differences between a base Generation and the current Built Generation. Paths are relative to the Repository root. Build first to include pending Desired changes.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
--json |
Output JSON | false |
The command is Repository-wide and rejects -d/--dist.
Output
Columns are operation, phase, Repository-relative path, size, and SHA-256.
| Field | Values |
|---|---|
| operation | add, update, delete |
| phase | payload, metadata, pointer, delete |
Use this plan to inspect the local file delta. To deliver it, use sow publish or stage a complete copy and switch it into service.
Base Generation
Without an argument, SOW compares the current Built Generation with its predecessor.
BASE_GENERATION is a decimal integer in the inclusive range 0..current. Base 0 produces the
complete delivery manifest for the current Generation, excluding private sow.yml and .sow/.
Using the current Generation as base produces an empty plan. A Repository never built also yields an
empty 0 -> 0 plan.
Dirty and recovery states
When Desired state is dirty, the header says dirty=true, but the plan still ends at the current
Built Generation. Private pending payloads are excluded because they are not deliverable yet.
When the Repository is recovering or error, follow the diagnostic to recover or repair it before running changes.
Examples
Produce a complete manifest:
Filter one Dist by path after producing the Repository-level plan:
Exit codes
| Code | Trigger |
|---|---|
0 |
Plan printed, including an empty plan |
1 |
Runtime I/O failure |
2 |
Usage error, -d supplied, Workspace not found, or implicit Repository selection is ambiguous |
5 |
Repository is recovering or error, or state evidence is inconsistent |
6 |
Explicit Repository is not configured, or Base Generation is outside the valid range |
See also
sow build— create the next Generationsow publish— apply the supported publication protocolsow log— operation records and their file changes- Repository Layout — public and private path boundaries
15 - sow publish
sow publish copies the current Built Generation to a target in the targets: section of sow.yml. The target selects the Repository and storage provider. Build first when the target must include the latest Desired changes.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
--abort |
Abandon an interrupted publication before its commit phase | false |
--rebind |
Confirm and record permitted target name/public endpoint/cache-TTL changes | false |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-T, --timeout DUR |
Maximum Repository-lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
TARGET must name a configured filesystem or r2 publication target. --abort and
--rebind are mutually exclusive.
Before publication
Run build first to include the latest package and policy changes. A dirty Repository can still publish its previous completed Built Generation.
Configure the target’s web server or bucket access so that public_endpoint serves the published files. Then run sow publish TARGET.
Publication protocol
SOW verifies the local files against the completed Generation manifest, then publishes in this order:
- package files;
- metadata with content-specific URLs;
- client entry points, such as
repomd.xmlandRelease; - public visibility checks and a saved publication checkpoint.
SOW records progress so the same command can resume an interrupted publication. When the target already contains the current Generation, repeating the command changes nothing.
Rebind mutable target settings
The first publication records the Repository and target binding. After changing the target name, public endpoint, or cache TTL, use --rebind to record the updated settings and continue publication:
If you renamed the target in targets:, use its new name.
| May change | Immutable; configure a new target |
|---|---|
| target name | Repository identity |
public_endpoint |
provider, storage endpoint, or region |
max_cache_ttl |
bucket or prefix |
Complete pending target maintenance before changing max_cache_ttl. For a filesystem target, also complete pending deletion before changing public_endpoint. To publish to a new target for the first time, use sow publish TARGET without --rebind.
Abort and recovery
After an interruption, rerun sow publish TARGET to resume the publication.
To abandon the attempt, use sow publish TARGET --abort before it reaches commit_intent. SOW checks the objects already uploaded and records the abandonment. It leaves remote files in place. Once the attempt reaches commit_intent, rerun the ordinary publish command to finish it.
A published package URL identifies fixed content. Use a new package version and filename for changed content. If existing remote files differ from the recorded attempt, follow the reported conflict diagnosis; see Troubleshooting.
Public visibility checks
Before recording a checkpoint, SOW verifies that the published files can be read through public_endpoint. HTTP(S) verification uses ordinary GET requests. Stale or missing content is retried up to max_cache_ttl.
Filesystem targets support file:// or HTTP(S) public endpoints. R2 targets require HTTP(S). Each R2 publication lists the target prefix and checks each object with HEAD; an update performs this inventory check twice. Keep old objects on R2: target GC reports them without deleting them, and later publication checks the prefix against the recorded inventory.
Removing a published view
While a target remains configured, its published entry points must remain available locally. These include Dists, RPM architecture views, and signing companion files.
To retire that target, stop publishing to it, remove its entry from targets: in sow.yml, and run sow config check. You can then remove the local view. This leaves the old remote files and publication history intact. To publish the reduced repository, configure a new target with a different storage prefix and update clients to use it.
Exit behavior
| Code | Trigger |
|---|---|
0 |
Publication completed or target was already current |
1 |
Filesystem, provider, network, verification, or binding conflict (including required rebind) |
2 |
Usage, Workspace discovery, or invalid sow.yml error |
4 |
Repository write lock unavailable |
5 |
Local or publication recovery evidence is inconsistent, or source is not deliverable |
6 |
Target is missing/unsafe, or another safety precondition rejects publish/abort/rebind |
See also
sow statusandsow check— decide whether the Built Generation is the one you intend to deliversow gc— reclaim unused target filessow.ymltargets — provider configuration- Publication Model — phases, receipts, recovery, and cache grace
16 - sow retain
sow retain protects the package files needed by a selected Generation from local GC. Add the current Built Generation before a later build replaces it. Retention preserves package files and a frozen manifest; it does not restore or publish an older repository view.
Synopsis
GENERATION must be a decimal integer greater than zero.
retain add
Pass the current Built Generation number. SOW verifies the Generation, saves its manifest, and records that GC must keep its package files. Add this record before building a newer Generation.
Adding the same Generation again changes nothing if its retained record still passes verification.
retain ls
Lists retained records for the Repository.
An empty list is a successful result.
retain rm
Removes the retention record for a Generation:
It does not delete package bytes. Removing a Generation that is not retained is an idempotent no-op.
A later local sow gc can reclaim these packages when no other state or operation needs them.
If a retained record is damaged, retain rm, retain ls, local gc, and check report the error. The retained layer in check identifies the affected Generation. To discard that damaged record:
- Stop Workspace writers.
- Move
.sow/<repo>/retained/<GENERATION>to a backup location outside the Workspace. - Run
sow checkagain.
Moving the record also removes its protection against GC.
Options
| Flag | Commands | Meaning |
|---|---|---|
-C, --workdir DIR |
all | Workspace discovery start directory |
-r, --repo NAME |
all | Select a Repository |
-T, --timeout DUR |
add, rm |
Maximum write-lock wait |
-N, --no-wait |
add, rm |
Fail immediately when locked |
--json |
all | Output JSON |
Exit behavior
| Code | Trigger |
|---|---|
0 |
Requested operation completed, including an empty list |
1 |
Filesystem or runtime I/O failure |
2 |
Invalid Generation syntax, discovery error, or implicit Repository selection is ambiguous |
4 |
Write lock unavailable for add or rm |
5 |
Generation manifest or Repository state is inconsistent |
6 |
Explicit Repository is not configured, retain add does not name the current Built Generation, or another safety rule rejects the request |
See also
sow gc— reclaim packages that no retained state needssow changes— inspect a Built Generation delta- Repository Layout — private retained-record location
17 - sow gc
Run sow gc to reclaim local package files that are no longer needed. Run sow gc TARGET to maintain a configured publication target. Filesystem targets can delete eligible objects; R2 targets only report candidates.
Synopsis
| Flag | Meaning | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository for local GC only | selection rules |
-T, --timeout DUR |
Maximum Repository-lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
gc TARGET -r NAME is a usage error because the target already binds a Repository. --dist is not
accepted in either mode.
Local GC
Run local GC after the Repository has a completed, clean build. If it is dirty, run sow build first. GC deletes a pool package only when none of these records references it:
- current Desired or Built membership, or prior Built membership kept for older clients;
- explicit
retainrecords; - recovery and non-terminal Operation state;
- publication records;
- active maintenance work.
The operation is journaled. When payloads are removed, the Repository advances to a new Generation. When nothing is eligible, it is an idempotent no-op.
Target GC
Target GC waits for the configured cache grace period and checks which objects are still needed:
| Provider | Behavior |
|---|---|
filesystem |
After the grace period, conditionally delete eligible objects, verify that their public URLs are absent, and record the result |
r2 |
Save a report of eligible objects; leave the objects in storage |
A no-op means no maintenance is due.
Exit behavior
| Code | Trigger |
|---|---|
0 |
GC completed or nothing was eligible |
1 |
Filesystem, provider, network, or runtime failure |
2 |
Usage, Workspace discovery, invalid sow.yml, or implicit Repository ambiguity |
4 |
Repository write lock unavailable |
5 |
Recovery, state, receipt, or manifest evidence is inconsistent |
6 |
Explicit Repository/target is not configured or safe, or deletion is rejected by a safety precondition |
See also
sow retain— protect package files from local GCsow publish— create target checkpoints and receipts- Publication Model — provider guarantees and cache grace
18 - sow export
sow export rpm-leaf creates a standalone RPM repository for one Dist and architecture. It copies the packages into an external directory and rewrites metadata to use local pool/... paths.
Synopsis
| Argument | Requirement |
|---|---|
DIST |
Canonical configured RPM Dist name |
ARCH |
x86_64 or aarch64 |
DIR |
Absent or empty destination outside Repository, private-state, and filesystem-target roots |
| Flag | Meaning | Default |
|---|---|---|
--hardlink |
Use hard links for read-only output on the same filesystem | copy files |
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a Repository | selection rules |
--json |
Output JSON | false |
Output
The destination contains:
- RPM metadata with local package paths;
- the required package subtree;
- an export manifest;
.sow-export.jsonprovenance.
The source Repository must be clean, have a completed Built Generation, and have no unfinished operation. The requested RPM view must belong to that Generation. The export is independent of the Workspace and does not protect source packages from GC.
If export fails, correct the reported cause and retry into a new or empty directory. The failed destination can contain partial files.
Managed RPM and export rpm-leaf use a fixed 0 repomd data timestamp and expose no
--metadata-timestamp option. They cannot directly take over the same EL7 repository ID
while clients retain a positive cached timestamp; changing the base URL alone does not help.
Use a new client repository ID or clear its metadata cache. To keep the old ID and cache,
continue with Plain create and an explicit publication time, following the
migration guide and re-signing repomd.
Copy versus hard link
Copying is the default. Use --hardlink only for read-only output on the same filesystem. Editing a hard-linked package also changes the source package in SOW’s pool.
Exit behavior
| Code | Trigger |
|---|---|
0 |
Standalone RPM leaf exported |
1 |
Filesystem, copy, hard-link, or metadata-write failure |
2 |
Invalid syntax, malformed Dist/architecture token, discovery, or implicit Repository ambiguity |
5 |
Source Generation or Repository state is inconsistent |
6 |
Explicit Repository is not configured, Dist is not RPM, view/signer is unavailable, or destination is unsafe/non-empty/overlapping |
See also
- Platforms & Integrations — client and tool compatibility
- Repository Layout — source tree and export boundaries
sow publish— managed delivery to configured targets
19 - sow log
sow log lists Repository operations and shows the details of one operation. Use log export to archive completed records as JSONL. Use log prune to remove eligible old records from the database.
Synopsis
sow log
With no argument, prints the 50 most recent Operations, newest first.
Output excerpt, one Operation object from the operations array:
payload_json records the intent — including config_sha256, the digest of the configuration in
force, and manifest_sha256 for the resulting Generation. result_json records the outcome. A
failed Operation additionally carries error_class and error_message:
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-d, --dist NAME |
Show only Operations touching this Dist | all |
--json |
Output JSON | false |
One Operation in detail
Pass an Operation ID for a summary. Add --json to read the full transitions, timing, package, membership, and file records under result.detail.
Output excerpt:
The files array uses the same phase vocabulary as
sow changes: payload, metadata, pointer, delete.
Build Operations also contain progress events. They keep the current state and put a
versioned object in detail_json:
The phases are rendering, promoting_payload, publishing_dists, normalizing_public_tree, and finalizing. Use them to follow long builds.
Filtering by Dist
-d restricts the listing to Operations that touched that Dist — useful when one Repository serves
several distributions:
Operation lifecycle
The state field shows how far an operation has progressed:
| State | Meaning |
|---|---|
planned |
Command, arguments, targets and intended actions are durably recorded |
staged |
New packages/metadata written to temporary locations and verified |
applied |
Desired state and required private pending payloads committed |
built |
The complete static Generation has been switched in |
done |
Terminal — a normal successful command |
done_dirty |
Operation ended with changes that still need a build, usually after --skip |
failed |
Terminal — the operation failed before applied, nothing was committed |
rolled_back |
Terminal — recovery abandoned the operation before it changed Desired state, for example a planned operation left by an interrupted command |
recovering |
Non-terminal; the next write command must complete or roll it back |
The Repository log includes dist new and dist rm. Workspace operations (init, repo new, repo rm) use separate recovery records and do not appear here.
sow log export
Writes terminal Operations as JSONL — one complete Operation detail record per line — for archival or ingestion into a log pipeline.
Omit FILE, or pass -, to write to stdout:
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-d, --dist NAME |
Export only Operations touching this Dist | all |
export has no --json; JSONL is its output format.
Output path
Choose a new filename in an existing directory. log export returns 6 if the file already exists. Its immediate parent must be a real directory, not a symbolic link. On macOS, use /private/tmp/file.jsonl instead of /tmp/file.jsonl.
sow log prune
Deletes eligible completed operation records older than BEFORE and reclaims database space. If another reader is using the database, record deletion can finish while space reclamation waits. The result then reports compaction_deferred: true. Close the reader and rerun prune to reclaim the space.
The output shows the cutoff in UTC (this example uses a workstation at UTC+8). When a reader defers space reclamation, a second line reads
log deletion committed; space reclamation deferred while the database is busy; with --json
the journal ID and compaction_deferred appear in result.
| Flag | Description | Default |
|---|---|---|
-C, --workdir DIR |
Workspace discovery start directory | current directory |
-r, --repo NAME |
Select a repository | selection rules |
-T, --timeout DUR |
Maximum lock wait; 0 waits indefinitely |
0 |
-N, --no-wait |
Fail immediately when the lock is held | false |
--json |
Output JSON | false |
prune applies to the whole Repository and does not accept -d.
BEFORE syntax
BEFORE is an ISO-8601 date YYYY-MM-DD, interpreted as local midnight, or an RFC 3339 timestamp
with a timezone.
Records kept by prune
prune keeps:
- a non-terminal Operation;
- a record still required for recovery;
- current Package or Membership state;
- a Built Generation or its Changeset.
The pruned counter reports the number of deleted operation records.
Examples
Investigate the most recent write:
Find failed operations among the 50 most recent records:
Archive records, then remove those older than a chosen date:
Which Operation last touched a Dist:
Exit codes
| Command | Code | Trigger |
|---|---|---|
log |
0 |
Records printed, including an empty ledger |
log |
2 |
Usage error (including a non-numeric Operation ID), Workspace not found, or ambiguous selection |
log |
5 |
State database unreadable |
log |
6 |
The given Operation ID does not exist, or several different -d values were given |
log export |
0 |
Export written |
log export |
1 |
I/O failure writing the target, or the parent is not a real directory |
log export |
2 |
Usage error or ambiguous selection |
log export |
6 |
Target already exists |
log prune |
0 |
Prune completed, including pruning nothing |
log prune |
2 |
Malformed BEFORE, -d given, or ambiguous selection |
log prune |
4 |
Repository lock held and --no-wait given or --timeout expired |
log prune |
5 |
Integrity or recovery error |
See also
- Observability & Audit — how
logfits withstatus,checkandchanges - Transactions & Recovery — the journal that the log records
- sow changes — compare files between builds
- JSON Output — the full log result schema