sow create
sow create turns a directory that already contains .rpm and .deb files into a flat repository
by writing indexes next to the packages. It is the whole of Plain mode: no sow.yml, no SQLite, no
Workspace discovery. This page covers the one-pass scan contract, explicit RPM publication time,
the --pigsty completion gate, and RPM signing with --sign-with.
--metadata-timestamp requires SOW 0.5.0 or later.
Check the exact binary’s create --help before switching an existing maintenance script.
Synopsis
DIR defaults to the current directory.
Description
create reads the top-level regular files in DIR and renders the index formats implied by what it
finds: repodata/ when RPMs are present, Packages and Packages.gz when DEBs are present, both
when the directory is mixed. All architectures come from the package headers — Plain mode has no
architecture flag and no permit list.
Flat metadata only ever references packages in the same directory. RPM location is the bare
basename and DEB Filename is ./<basename>, so both remain relative whether the directory is
exposed as a file:// source or an HTTP root.
By default create does not delete, move, rename, re-sign or rewrite a single package byte. It only
replaces index paths it owns; unknown files are left alone.
Options
| Flag | Description | Default |
|---|---|---|
-j, --jobs N |
Parallel workers for the single package hash/parse pass | 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 16/40/64-hex GPG key ID | 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 |
Emit the versioned JSON envelope | 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 authoritative set so an interrupted all-package cleanup can converge and write its marker.
Package I/O and final validation
For the normal unsigned path, each selected package has exactly one full content pass. A worker opens
it, computes SHA-256 once, parses its header/control metadata, and retains the complete parsed result.
RPM XML and DEB Packages are rendered from that retained result; neither rendering nor generated
metadata validation reopens package payloads. --jobs parallelizes this pass while canonical result
ordering keeps output bytes independent of worker scheduling.
Immediately before publication, create relists the top-level package set and compares file identity,
type/mode, size, and mtime with the post-scan snapshot. This is a cheap stat check, not a second hash.
A changed set or stat returns integrity error 5 before any staged output is published. Deliberately
preserving inode, size, and mtime while modifying bytes is outside the local cooperative-writer
contract.
Explicit RPM signing is an exception: copying, signing, signature verification, and parsing the final signed RPM necessarily add reads for packages that are modified.
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
Default mode never creates repo_complete. If the marker already exists, create refuses to write
indexes rather than leaving a stale marker claiming a build that no longer matches:
Either re-run with --pigsty (which withdraws and republishes the marker in its documented order)
or remove the marker yourself.
Publication time for existing YUM repositories
The default remains a deterministic zero data timestamp. Pass --metadata-timestamp SECONDS to set an explicit publication time for RPM metadata. The same packages and time produce the same output; changing only the time changes repomd, leaving its compressed XML streams and RPM bytes unchanged. DEB output is unaffected.
EL7 YUM rejects a repomd whose maximum data timestamp is older than its cached copy. When migrating an existing repository, the publisher must choose a time at least as large as every historical value clients may have seen. Updating revision or filesystem mtime alone does not fix this check. Equal timestamps are accepted by EL7; the migration guide recommends advancing by at least one second for changed content so releases remain distinguishable.
Pass
--metadata-timestampon every update. Omitting it uses0again; SOW does not inherit the previous value. The accepted range is0..253402300799Unix seconds (through year 9999). A contemporary millisecond epoch value exceeds this bound and is rejected; SOW does not detect units for smaller numbers. This bound is not a guarantee that every client accepts every date. A far-future value can make EL7 reject later ordinary timestamps until its old metadata cache is cleared. After repomd changes, replace its detached.ascsignature before publishing:createneither refreshes nor removes an oldrepomd.xml.asc.
The number is an example, not a universal migration value. A maintenance wrapper should preserve the existing repomd and valid signature when metadata content is unchanged, and advance the time for actual updates. Retain a publication high-water mark outside the upload tree; include trusted pre-migration backups when repairing a repository already reset to zero. Rolling back package content is a new publication and must not roll the clock back.
After changing repomd, the caller must regenerate its detached GPG signature. create does not sign repomd. This option does not change managed build or RPM leaf export timestamp semantics.
See Migrating an Existing YUM Repository for backup, publication-clock, signing, and cached-client acceptance steps.
–pigsty
--pigsty enables three coupled compatibility actions in one invocation. Their publication order
is marker-gated, but the operation is rebuilt on retry rather than recovered from a journal:
- 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 only touches top-level regular package files that parsed successfully and matched a rule. Directories and unknown files are never removed by glob.
The publication order matters for callers that gate on the marker: the existing repo_complete is
withdrawn before indexes switch, matched packages are deleted only after replacement metadata is
installed, and the new marker is written last. A caller must treat a missing marker as incomplete.
Treat a missing repo_complete as “build in progress”. That is the contract --pigsty is designed
around.
Signing RPMs
-S/--sign-with KEY is the explicit authorization to modify RPM bytes. KEY is exactly 16, 40 or 64
hexadecimal characters; an optional 0x or 0X prefix is accepted. SOW normalizes it to
uppercase and passes it to the environment’s rpm --addsign through the _gpg_name macro. The
private key, passphrase, GPG home, pinentry and any extra RPM macros come from your environment —
SOW never receives, persists or echoes a secret.
- 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.- Signing happens on a private same-filesystem stage copy. Each result is re-parsed to confirm the embedded signature, the signature-neutral digest and NEVRA are unchanged, and rpm-md is generated from the final complete bytes.
- At least one top-level RPM must remain after
--pigstycleanup, andrpmmust be onPATH.
Locking, staging and overwrite rebuild
create takes a write lock on the target directory and honors --timeout/--no-wait. All metadata
is written to a private stage and validated before publication begins. The lock coordinates SOW
writers on the local machine; arbitrary external package mutation is unsupported.
Plain create does not create a durable operation journal, rollback pre-images, or recovery trash.
Publication consists of several single-file renames, so a crash may leave a partially replaced set of
derived files. Re-run sow create with the intended current options: it discards reserved stale Plain
temporary state and rebuilds all indexes from the packages that currently exist. recovered is
always false; a rerun is a fresh overwrite build, not replay.
A flat directory has no whole-repository generation pointer, and RPM plus DEB entry points cannot be
swapped in one POSIX rename. Plain therefore does not promise cross-file instantaneous atomicity. Use
repo_complete as the --pigsty gate, or use Managed mode when transactional recovery is required.
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