Skip to content

sow create

Generate a flat RPM/DEB repository in an ordinary directory — the Plain mode entry point.

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.

Version availability

--metadata-timestamp requires SOW 0.5.0 or later. Check the exact binary’s create --help before switching an existing maintenance script.

Synopsis

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

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 .rpm or .deb are 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. --pigsty accepts an empty authoritative set so an interrupted all-package cleanup can converge and write its marker.
sow create /srv/empty
plain: scan /srv/empty: no supported top-level regular RPM or DEB packages

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:

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

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

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:

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

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-timestamp on every update. Omitting it uses 0 again; SOW does not inherit the previous value. The accepted range is 0..253402300799 Unix 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 .asc signature before publishing: create neither refreshes nor removes an old repomd.xml.asc.

sow create /srv/flat --metadata-timestamp 1790049000

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:

  1. Delete DEB packages whose parsed architecture is i386. RPMs are not removed merely for carrying an i386/i486/i586/i686 architecture.
  2. Delete RPM/DEB whose binary package name is exactly patroni and whose upstream version is exactly 3.0.4. RPM compares VERSION, ignoring epoch and release; DEB strips epoch and Debian revision first. 3.0.4+foo is not a match.
  3. 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>.
sow create /srv/pigsty --pigsty
created /srv/pigsty: rpm=2 deb=0 signed=0 removed=2 marker=true noop=false recovered=false
cat /srv/pigsty/repo_complete
b4111ef2a51542eacc9bd1ebd080da02e53d400f9d172530c75a1e4ac06e7ead  centos-release-7-2.1511.el7.centos.2.10.x86_64.rpm
d6f332ed157de1d42058ec785b392a1cc4b5836c27830af8fbf083cce29ef0ab  epel-release-7-5.noarch.rpm

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.

Marker semantics

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.
  • --overwrite requires --sign-with and switches to rpm --resign over 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 --pigsty cleanup, and rpm must be on PATH.
sow create /srv/flat -S 0123456789ABCDEF --overwrite
plain: sign rpm epel-release-7-5.noarch.rpm: rpm executable is required for --sign-with
sow create /srv/deb-only -S 0123456789ABCDEF
plain: sign rpm: --sign-with requires at least one retained top-level RPM package
sow create /srv/flat --overwrite
usage error: --overwrite requires --sign-with
sow create /srv/flat -S ZZZZ
usage error: --sign-with must be a 16, 40, or 64 hexadecimal GPG key ID/fingerprint

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:

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

Machine-readable result:

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

Build a Pigsty flat repository with eight workers and a publication time chosen from its history:

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

The caller records this time and signs the resulting repomd.xml when clients require it.

Failure envelope:

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

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