Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Get Started

Install SOW, create a flat repository, and learn the Managed workspace model.

SOW builds static RPM/YUM and DEB/APT repositories; it is not an HTTP daemon. Choose one of two isolated paths:

  • Plain: sow create rebuilds indexes beside the packages in an ordinary directory.

  • Managed: a workspace tracks package membership, Dists, architecture views, policy, signing, generations, audit history, and publication targets.

  • Installation — Choose a release archive, RPM/DEB package, or source build; verify the installed binary.

  • Quick Start — Create and serve a flat repository from a directory of packages.

  • First Workspace — Initialize Managed mode, create RPM and DEB Dists, add packages, build, and check.

  • Core Concepts — Workspace, Repository, Dist, Package Object, Desired Membership, and Built Generation.

Managed workspaces require a local POSIX filesystem with advisory locks, fsync, and atomic rename semantics. Metadata generation is in-process; optional RPM package signing needs rpm, while an agent:// metadata key needs gpg and gpg-agent.

1 - Installation

Install SOW from an archive, RPM/DEB package, or source, then verify the binary and filesystem requirements.

SOW is one executable: there is no service to enable and no runtime language environment. Release builds target Linux and macOS on amd64 and arm64; Linux also gets RPM and DEB packages. Windows is not supported.

Use the Download page to select the archive or Linux package that matches your operating system and architecture. It links each published artifact, its source tag, and SHA256SUMS.

Install an archive

Download one archive plus SHA256SUMS, then verify the matching line before extraction:

# Linux amd64
grep 'sow_.*_linux_amd64.tar.gz$' SHA256SUMS | sha256sum -c -
tar -xzf sow_*_linux_amd64.tar.gz
sudo install -m 0755 sow /usr/local/bin/sow

On macOS, select darwin_amd64 or darwin_arm64 and replace sha256sum -c - with shasum -a 256 -c -. Without root, install to a directory already on your PATH, such as ~/.local/bin.

Install a Linux package

Linux packages use the 1PGSTY release suffix:

sudo rpm -Uvh ./sow-*-1PGSTY.x86_64.rpm
sudo apt install ./sow_*-1PGSTY_amd64.deb

Choose only the command and architecture that match the host. RPM installs the license at /usr/share/licenses/sow/LICENSE; DEB installs copyright/license metadata under /usr/share/doc/sow/.

Build from source

The module declares Go 1.27.0. Metadata generation needs no C toolchain. Replace vX.Y.Z with the source tag linked from the Download page:

git clone https://github.com/pgsty/sow.git
cd sow
set -euo pipefail
SOW_TAG=vX.Y.Z
git checkout "$SOW_TAG"
SOW_VERSION="${SOW_TAG#v}"
CGO_ENABLED=0 go build -trimpath \
  -ldflags="-s -w -X github.com/pgsty/sow/internal/v2cli.Version=${SOW_VERSION}" \
  -o sow ./cmd/sow
sudo install -m 0755 sow /usr/local/bin/sow

This uses the release build flags and embeds the selected tag’s product version.

Verify

sow version
sow help

sow version reports product version, target OS/architecture, and build Go toolchain. sow help lists the command tree. Each archive also contains README.md, CHANGELOG.md, and the Apache-2.0 LICENSE.

Upgrade a 0.3 Managed Workspace

SOW 0.4 introduces internal database schemas v11 and v12. The public layout and schema: sow/v3 configuration identifier stay the same, but each existing v0.3 Repository must be migrated explicitly before ordinary reads or writes. Stop Workspace writers before taking the backup:

cp -a /srv/sow /srv/sow.backup-before-0.4.0
sow repo migrate REPOSITORY -C /srv/sow
sow check -r REPOSITORY -C /srv/sow

Repeat the last two commands for every Repository named in sow.yml. The database transition is one-way; do not reopen a migrated Workspace with SOW 0.3. See sow repo migrate for the repaired status, signer, and publication evidence.

Permissions and optional tools

The invoking user needs read access to package inputs and write access to the Plain target or Managed workspace. Keep Managed workspaces on a local POSIX filesystem: locks, fsync, safe paths, and atomic rename are part of the correctness contract.

Repository parsing and metadata rendering are in-process. Only two optional paths need host tools:

  • RPM package signing requires rpm and a working GPG environment;
  • an agent:// metadata key requires gpg and gpg-agent.

Next: Quick Start for Plain mode, or First Workspace for Managed mode.

2 - Quick Start

Index a directory of RPM and DEB packages, serve it, and configure a client.

Plain mode builds a flat repository in one directory. It does not read sow.yml, create a workspace, or keep a database.

Prepare a directory

Put RPM and/or DEB files at the directory top level. sow create does not recurse and does not move or rename package files.

mkdir -p /srv/repo
cp /path/to/packages/*.rpm /path/to/packages/*.deb /srv/repo/

If one glob has no matches, copy the formats you actually have instead.

Generate metadata

sow create /srv/repo

An illustrative mixed-format result is:

created /srv/repo: rpm=1 deb=1 signed=0 removed=0 marker=false noop=false recovered=false

The directory now contains:

/srv/repo/
├── package.rpm
├── package.deb
├── repodata/       # RPM: repomd.xml plus primary, filelists, other
├── Packages        # DEB flat index
└── Packages.gz

Plain mode does not generate a DEB Release, InRelease, or Release.gpg. RPM and DEB metadata are generated in one operation; a parse or render failure prevents the new indexes from being committed.

Serve the directory

For a local check, any static file server is sufficient:

cd /srv/repo
python3 -m http.server --bind 127.0.0.1 8080

Verify the protocol entry points from another shell:

curl --fail http://127.0.0.1:8080/repodata/repomd.xml >/dev/null
curl --fail http://127.0.0.1:8080/Packages.gz >/dev/null

Python’s server is only a preview. Use a maintained HTTP server for persistent service.

Configure a client

Replace REPO_HOST with the address clients can reach.

# /etc/yum.repos.d/sow-quickstart.repo
[sow-quickstart]
name=SOW Quick Start
baseurl=http://REPO_HOST:8080/
enabled=1
gpgcheck=0
repo_gpgcheck=0
# /etc/apt/sources.list.d/sow-quickstart.list
deb [trusted=yes] http://REPO_HOST:8080/ ./

Then refresh and install a package:

sudo dnf makecache
sudo dnf install PACKAGE_NAME
sudo apt update
sudo apt install PACKAGE_NAME

The APT source ends in ./ because this is a flat repository. [trusted=yes] and the disabled DNF signature checks are appropriate only for this unsigned quick start. Use a signed Managed repository when authenticity matters.

Update the repository

Change the package files and run the same command again:

sow create /srv/repo

The directory contents are the complete Plain-mode state. With unchanged package bytes, the generated metadata is deterministic and a repeat run reports noop=true.

For automation, request the versioned JSON envelope:

sow create /srv/repo --json

When to use Managed mode

Use Plain mode when the directory already contains exactly what should be published. Use a Managed workspace when you need named Dists, architecture views, membership policy, signed metadata, generations, audit, or publication targets.

See also sow create and Plain Flat Repositories.

3 - Your First Workspace

Create a workspace with RPM and DEB Dists, add packages, and verify the public tree.

Managed mode keeps configuration, membership, generations, and audit state. This example starts from an empty directory.

Initialize the workspace

sow init /srv/sow
cd /srv/sow

init creates:

/srv/sow/
├── sow.yml   # configuration; schema: sow/v3
└── .sow/     # SQLite state, locks, staging, recovery, journals

Do not edit or serve .sow/. init is idempotent: rerunning it validates and converges declared repositories and Dists; it does not reset a valid workspace.

The default architecture families are x86_64 and aarch64. Configuration accepts amd64 and arm64 as aliases and normalizes them to those family names.

Create a Repository and two Dists

sow repo new local
sow dist new el9 --format rpm
sow dist new bookworm --format deb

A Repository owns one public pool/ + dists/ tree and one private state database. A Dist has exactly one format. dist new materializes a valid empty view, so empty clients receive an empty index instead of a 404.

The public layout is now:

/srv/sow/local/
├── pool/
└── dists/
    ├── el9/
    │   ├── x86_64/repodata/
    │   └── aarch64/repodata/
    └── bookworm/
        ├── Release
        └── main/
            ├── binary-amd64/{Packages,Packages.gz,by-hash/}
            └── binary-arm64/{Packages,Packages.gz,by-hash/}

Add packages

Select the target Dist explicitly:

sow add /path/to/packages/*.rpm -d el9
sow add /path/to/packages/*.deb -d bookworm

SOW reads identity and architecture from the package itself, stores accepted bytes under local/pool/, updates Desired Membership, and builds affected Dists before returning. The package path is only an input; later builds use the managed pool.

Use --skip to stage several membership changes without rebuilding each time, then converge once:

sow add /path/to/more/*.rpm -d el9 --skip
sow build

While Desired Membership is ahead of the Built Generation, the Repository is dirty and ready_to_copy=false.

Inspect and verify

sow status
sow ls -d el9
sow ls -d bookworm
sow check

status is a cheap state read. check is the delivery gate: it verifies configuration, state, public modes, retained roots, package bytes, Desired Membership, indexes, signatures, and the Generation manifest. It writes nothing. Only a clean Repository that passes all layers returns success.

To see normalized configuration and defaults:

sow config show --all

Serve the Repository

The public unit is /srv/sow/local, not the workspace root. Serve that directory at a stable URL prefix; do not expose sow.yml or .sow/.

  • DNF base URL: https://repo.example.com/local/dists/el9/x86_64/
  • APT source: deb https://repo.example.com/local bookworm main

For a safe Nginx and filesystem-publication workflow, continue with Serve Repositories.

Selection rules

  • Workspace: search upward from the current directory, or start from -C DIR.
  • Repository: -r NAME, the containing Repository, or the only configured Repository.
  • Dist: -d NAME, repeatable; omission is accepted only when the command can resolve an unambiguous scope.

Ambiguity is an error; SOW does not pick an arbitrary Repository or Dist.

Next

4 - Core Concepts

The SOW model: Plain and Managed execution, pools and views, Desired Membership, and Built Generations.

Plain or Managed

The two execution paths are separate.

Plain Managed
Entry point sow create DIR init, repo, dist, add, rm, build
State package directory sow.yml plus private SQLite/journals
Public layout flat RPM/DEB indexes Repository pool/ + dists/
Formats RPM and DEB may share one directory one format per Dist
Architecture views no yes
Policy and audit no yes
Metadata signing and publication targets no yes

Choose Plain when directory contents already equal the desired repository. Choose Managed when SOW must own membership, policy, generations, signing, audit, or publication.

Managed hierarchy

Workspace                    /srv/sow
├── sow.yml                  configuration
├── .sow/                    private state; never served
└── Repository               /srv/sow/local
    ├── pool/                canonical package payloads
    └── dists/
        └── Dist             one named RPM or DEB membership set
            └── views        metadata rendered per architecture
  • Workspace is the configuration and discovery boundary.
  • Repository is the isolation, Generation, publication, and public-tree boundary. Repositories do not deduplicate payloads with one another.
  • Dist is a named membership set in exactly one package format.
  • Architecture view is derived output, not a second membership set. noarch RPMs and all DEBs are selected into every applicable view without duplicating their pool bytes.

One canonical payload

A package object is identified by its exact-byte SHA-256. Its logical coordinate comes from the RPM header or DEB control data, not the filename.

Each accepted payload has one canonical path under the Repository pool/. RPM architecture views contain repodata/ only; their package locations use parent-relative paths back to the pool. APT Packages entries name the same pool directly.

Ordinary package clients and mirror tools are different contracts. Default dnf reposync rejects the canonical RPM view’s parent traversal. When a self-contained RPM mirror leaf is required, create a separate artifact with sow export rpm-leaf.

Desired and Built

Managed mode tracks intent and public bytes separately:

add / rm -> Desired Membership (revision)
                    |
                  build
                    v
             Built Generation -> pool/ + dists/

add and rm build affected Dists by default. --skip records membership changes but leaves the Repository dirty; sow build later converges Desired into a new Built Generation.

  • sow status reads state cheaply and reports ready_to_copy.
  • sow check performs the full, read-only delivery proof. Dirty or recovering state is not deliverable.
  • sow changes [BASE_GENERATION] describes the physical difference between a recorded Generation and the current Built Generation. It is evidence and planning output, not a substitute for publication recovery or remote verification.
  • sow publish TARGET publishes a verified Generation through the configured provider and records target-scoped recovery/checkpoint state.

Transactions and failure states

Writes are serialized by Workspace or Repository locks and journal their intent before public mutation. Payloads and immutable metadata are prepared before mutable protocol pointers. A later writer recovers an interrupted operation before starting new work.

The operational states are:

State Meaning
clean Desired and Built agree
dirty Desired changed; Built is still the previous committed Generation
recovering a nonterminal operation must be resolved
error durable evidence conflicts; SOW refuses to guess

Use status to diagnose and check as the release gate. Do not publish a Repository with ready_to_copy=false.

Continue