Runbook — packaging the standalone CLI
What this produces: four single-file executables of openregs — linux-amd64,
linux-arm64, macos-arm64, windows-amd64 — that run on a machine with no
Python installed, plus one SHA256SUMS over the set, a cosign-format signature
beside each file and beside the checksum file, and the signer’s public key.
The whole surface is python -m openregs.standalone, with three subcommands
(build, sign, verify) and a make package shortcut for the first.
1. Why this is a matrix and not a loop
Section titled “1. Why this is a matrix and not a loop”PyInstaller does not cross-compile. Freezing a program means shipping the interpreter and the compiled extension modules of the machine the build ran on, so a linux-amd64 host can produce a linux-amd64 executable and nothing else.
openregs.standalone.build therefore refuses any target that is not its host,
and names the runner that does build it:
$ uv run --frozen python -m openregs.standalone build --target macos-arm64package: cannot build macos-arm64 on this host (Linux x86_64): PyInstaller doesnot cross-compile — it freezes the interpreter and extension modules of themachine it runs on. macos-arm64 is built on macos-14; see.github/workflows/package.ymlThat refusal is the honest behaviour. A file named openregs-macos-arm64 that no
macOS has ever executed is worse than no file at all, because somebody will
download it.
The four builds happen in .github/workflows/package.yml, one matrix leg per
target on the runner named in openregs.standalone.TARGETS. Each leg uploads
standalone-<target>; the sign job downloads all four into one directory,
which is the only place in the pipeline that holds the whole set at once.
2. Build one binary
Section titled “2. Build one binary”On a machine of the platform you are targeting:
make package # the host's target, into dist/standalone/# or, explicitly:uv run --frozen python -m openregs.standalone build \ --target linux-amd64 --commit "$(git rev-parse HEAD)" --out dist/standaloneTwo things travel inside the executable:
spec/— the JSON Schemas, taxonomies and the AKN XSD. A binary on a machine with no checkout still validates documents, becauseopenregs/standalone/hook.pypointsOPENREGS_SPEC_DIRat the bundled copy before anything imports. ExportOPENREGS_SPEC_DIRyourself to override it.build-info.json— the version, the schema majors and the commit. That is whatopenregs --versionprints:
$ ./dist/standalone/openregs-linux-amd64 --versionopenregs 0.1.0schema majors: 1commit: <the 40-hex sha the binary was built from>The frozen record wins over OPENREGS_BUILD_COMMIT and over git rev-parse, so
a binary carried onto another machine keeps reporting the commit it was built
from rather than the commit of whatever checkout it is standing in.
Canon, releases and config/trust.yaml are not bundled. They belong to the
working tree, and a binary carrying its own copy of the law would answer from it.
Point the binary at a checkout with --root, the way every subcommand already
allows.
3. Sign the set
Section titled “3. Sign the set”Once all four binaries are in one directory:
uv run --frozen python -m openregs.standalone sign --dir dist/standalone --fixture-keyIt refuses until every declared target is present. A checksum file written over three binaries would be a true statement about the wrong set.
It writes SHA256SUMS in sha256sum format (so sha256sum -c SHA256SUMS reads
it), a .sig beside each binary, a .sig over SHA256SUMS itself, and
cosign.pub. Signing the checksum file as well as the binaries is what stops a
tamper that rewrites both: recomputed digests would agree with the rewritten
file, and the signature over it would not.
--fixture-key signs with the published, non-secret fixture key — it is what CI
uses, because this repository holds no release signing key, and the command says
so on stderr. A deployment with a real key passes --key <pem> instead;
--signer cosign forces the cosign binary rather than the in-process signer that
produces the identical artifact.
4. Verify a download
Section titled “4. Verify a download”This is the consumer’s command, and it is what CI runs immediately after signing:
uv run --frozen python -m openregs.standalone verify --dir dist/standalonepackage: verify dist/standalone binaries ok 4 declared target(s) present checksums ok 4 digest(s) recomputed from the bytes on disk trust ok signing key openregs-fixture (24f0b959505940ce) is in config/trust.yaml (fixture key) signature ok 5 signature(s) valid under key 24f0b959505940cepackage: OK — dist/standalone verifiesEvery digest is recomputed from the bytes on disk rather than read out of
SHA256SUMS, and the signing key is anchored in config/trust.yaml — never in
the cosign.pub shipped beside the download, which proves nothing on its own.
Exit codes: 0 verified, 1 a check failed, 2 the directory could not be read.
5. Checking a binary on a machine with nothing on it
Section titled “5. Checking a binary on a machine with nothing on it”The claim “no runtime dependency on a system interpreter” is checked by running the binary in a container that has no interpreter in it:
docker run --rm --network=none \ --entrypoint /usr/local/bin/openregs \ --volume "$PWD/dist/standalone/openregs-linux-amd64:/usr/local/bin/openregs:ro" \ --volume "$PWD:/repo:ro" \ gcr.io/distroless/base-debian13:nonroot --versiongcr.io/distroless/base-debian13 carries a C library and nothing else: no shell,
no package manager, no Python, no Node. Swap --version for
verify --release fixreg@2025.04 --path /repo/fixtures/registry/fixreg/2025.04 --root /repo, or for pull or diff, to exercise the real commands there.
The image must be at least as new as the glibc the binary was linked against — a binary built on Ubuntu 24.04 (glibc 2.39) will not start on a debian-12 base. That is a property of dynamic linking, not of the packaging: build on the oldest platform you intend to support.
tooling/tests/test_standalone.py runs all of this. The container checks skip
when the runtime or the image is not already on the machine, and never pull,
because a test that reaches a registry is not an offline test.
Rendered from openregs/openregs@cbe9615:docs/runbooks/package.md