Versioning and Release Tarball — Design
Date: 2026-05-05
Status: Approved (brainstorm complete); implementation plan to follow
Scope: Establish a single canonical version for the zyginit suite (zyginit + zygctl + sysv-wrapper), wire --version flags into all three binaries, and add scripts to bump the version and produce a source release tarball for vendoring into Hammerhead.
1. Goal
Hammerhead consumes zyginit as a vendored source drop into hammerhead-userland (per docs/SMF_MIGRATION.md). To make that drop reproducible and auditable, zyginit needs:
- A single canonical version, tracked in source.
- A scripted bump that updates every place the version appears, in lock-step.
- A scripted release that produces
releases/zyginit-<version>-source.tar.xzplus a.sha256companion. - A runtime
--versionflag on every shipped binary, so an installed Hammerhead system can answer "which zyginit am I running?".
This work intentionally mirrors Coral's scripts/make-release.sh pattern (single tarball, sha256, --transform-prefixed paths) — the Hammerhead team gets a consistent shape across Zygaena projects.
2. Non-goals
- No CI/CD release automation. Bump and release are run by hand by a maintainer; no GitHub/Hg-server hooks, no signing, no upload step.
- No reproducible-build flags (
--mtime,--owner=0,--sort=name). Coral doesn't bother and Hammerhead's packaging step is what produces the OS-level reproducible artifact. Adding these is a trivial follow-up if needed. - No multi-tarball releases. The whole suite (zyginit + zygctl + sysv-wrapper) ships and versions together, in one tarball.
- No
CHANGELOG.mdintegration. The bump script doesn't touch a changelog. (We can add this later; there is no changelog file today to integrate with.) - No GPG signing. Hammerhead handles signing at the OS package layer.
3. Versioning policy
Semver: MAJOR.MINOR.PATCH.
| Bump | Reason |
|---|---|
| MAJOR | Incompatible socket protocol change, incompatible service-definition (TOML) schema change, removal of a published zygctl subcommand |
| MINOR | Backward-compatible additions: new service-definition fields with defaults, new zygctl subcommands, new runlevel features |
| PATCH | Bug fixes only; no API or protocol changes |
While zyginit is pre-1.0, breaking changes may land in MINOR per semver convention. Once 1.0 ships (target: when SMF removal from Hammerhead is complete and the suite has been running unmodified for one release cycle), strict semver applies.
4. Source of truth
The root reef.toml [package].version is canonical. Every other location is derived and regenerated by the bump script.
Today's drift inventory (rev 82, version 0.1.0) — the bump script must keep these all consistent:
| # | Location | Form |
|---|---|---|
| 1 | reef.toml (root) |
version = "0.1.0" (canonical) |
| 2 | tools/zygctl/reef.toml |
version = "0.1.0" |
| 3 | src/version.reef (new, generated) |
pub fn VERSION(): string returning "0.1.0" |
| 4 | tools/zygctl/src/version.reef (new, generated) |
same |
| 5 | tools/sysv-wrapper/version.h (new, generated) |
#define ZYGINIT_VERSION "0.1.0" |
Generated files (version.reef × 2 and version.h) are committed to the repository so a tarball recipient can build without rerunning the bump script. They carry a "Generated by scripts/bump-version.sh — do not edit by hand" comment.
5. scripts/bump-version.sh
Usage: scripts/bump-version.sh <new-version>
Behavior:
- Validates
<new-version>matches^[0-9]+\.[0-9]+\.[0-9]+$. Refuses if not. - Reads current version from root
reef.toml. Refuses if<new-version>equals current. - Rewrites all five locations listed in §4.
- Prints a summary diff (
hg diff --stat) and a suggested commit command:$ hg ci -m 'Bump version to <new-version>' - Does not auto-commit. The maintainer reviews and commits by hand.
Generated file templates:
src/version.reef (and identical-content tools/zygctl/src/version.reef):
/* SRCHEADER... */
module version
// Generated by scripts/bump-version.sh — do not edit by hand.
pub fn VERSION(): string
return "0.1.0"
end VERSION
end module
tools/sysv-wrapper/version.h:
/* SRCHEADER... */
/* Generated by scripts/bump-version.sh — do not edit by hand. */
#ifndef ZYGINIT_VERSION_H
#define ZYGINIT_VERSION_H
#define ZYGINIT_VERSION "0.1.0"
#endif
Both templates use the project's standard SRCHEADER.txt header. The bump script fills in ${project} from each subproject's own reef.toml [package].name (i.e., zyginit for src/version.reef, zygctl for tools/zygctl/src/version.reef), ${file.name} from the filename, and ${file.description} with a fixed string ("Generated version constant").
6. scripts/make-release.sh
Usage: scripts/make-release.sh [version-override]
Behavior (mirrors Coral's make-release.sh):
- Reads version from root
reef.toml, or uses[version-override]if provided. - Creates
releases/if missing. - Produces
releases/zyginit-<version>-source.tar.xzviatar --transform "s,^,zyginit-<version>-source/," -cJf .... - Generates
releases/zyginit-<version>-source.tar.xz.sha256. - Prints final paths and the sha256.
Tarball contents (top-level paths, --transform-prefixed into zyginit-<version>-source/):
| Include | Why |
|---|---|
reef.toml |
Build manifest (carries the canonical version) |
README.md |
Project orientation |
ROADMAP.md |
Forward-looking roadmap |
CLAUDE.md |
Project context (useful even outside Claude — accurate in-tree docs) |
SRCHEADER.txt |
Source-header template (referenced by all source files) |
src/ |
Main daemon Reef source |
docs/ |
Architecture docs (DESIGN.md, SMF_MIGRATION.md, etc.) |
tests/ |
Unit + integration tests |
services/ |
Example + Hammerhead service TOMLs (the v2 BSD-pivot tree) |
tools/ |
zygctl/, sysv-wrapper/ (subprojects) |
utils/ |
contract_probe and any other utility tools |
scripts/ |
bump-version.sh, make-release.sh, install-to-be.sh |
Tarball excludes:
| Exclude | Why |
|---|---|
build/ (root and any tools/*/build/) |
Build artifacts |
releases/ |
Output of this very script — don't ship past tarballs |
.hg/, .hgignore, .hgtags |
VCS metadata |
*.o, *.a |
Compiled object files |
*.swp |
Editor cruft |
resume/ |
Local scratch / resume notes (per user instruction) |
ss/ |
Local scratch (per user instruction) |
stub/ |
Old Rust-like design archeology, kept for reference only (per CLAUDE.md) |
tar's --exclude patterns are passed in for each item. The exclusion of tools/*/build/ is critical — the global build/ exclude only covers the repo-root build dir.
7. Code changes (one-time)
These ride along with the introduction of the bump/release scripts:
| File | Change |
|---|---|
src/main.reef:67-69 |
Drop the inline fn VERSION(): string definition; import version and use version.VERSION() |
src/socket.reef:248 |
Replace literal "zyginit 0.1.0\n" with "zyginit " + version.VERSION() + "\n" |
tools/zygctl/src/main.reef:54-57 |
Replace literal "zygctl 0.1.0" with "zygctl " + version.VERSION() |
tools/zygctl/src/main.reef |
Add import version |
tools/sysv-wrapper/wrapper.c |
Add --version / -V handling: prints "<argv[0]-basename> <ZYGINIT_VERSION>\n" and exits 0. #include "version.h". |
tools/sysv-wrapper/Makefile |
Add version.h to the dependency list of wrapper.o |
The argv[0]-basename trick in sysv-wrapper means halt --version prints halt 0.1.0, reboot --version prints reboot 0.1.0, etc. — consistent with how each personality identifies itself in normal output.
8. Output format
A single line, <binary> <version>, exit 0. Matches Coral and the GNU/BSD common case:
$ zyginit --version
zyginit 0.1.0
$ zygctl --version
zygctl 0.1.0
$ halt --version
halt 0.1.0
zygctl version (no dashes — already a subcommand) continues to work and prints the same line. Both zygctl --version and zygctl version are strictly local — they print zygctl's compiled-in version and never round-trip the socket. This is the standard Unix convention and keeps the answer trustworthy when the daemon is wedged.
Querying the running daemon's version (useful during a partial upgrade) is an existing socket command (socket.reef:247) but is currently unreachable from zygctl. Wiring up a separate zygctl daemon-version subcommand to surface it is out of scope for this design — see §10.
9. End-to-end workflow
Maintainer cuts release 0.2.0:
$ scripts/bump-version.sh 0.2.0
zyginit: bumping 0.1.0 -> 0.2.0
rewriting reef.toml
rewriting tools/zygctl/reef.toml
regenerating src/version.reef
regenerating tools/zygctl/src/version.reef
regenerating tools/sysv-wrapper/version.h
done. review with `hg diff` and commit:
hg ci -m 'Bump version to 0.2.0'
$ hg diff --stat
reef.toml | 2 +-
tools/zygctl/reef.toml | 2 +-
src/version.reef | 2 +-
tools/zygctl/src/version.reef | 2 +-
tools/sysv-wrapper/version.h | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
$ hg ci -m 'Bump version to 0.2.0'
$ scripts/make-release.sh
Creating release: zyginit-0.2.0-source
Created: releases/zyginit-0.2.0-source.tar.xz
-rw-r--r-- 1 ctusa ctusa 1234567 May 5 15:30 releases/zyginit-0.2.0-source.tar.xz
Checksum: releases/zyginit-0.2.0-source.tar.xz.sha256
abc123... releases/zyginit-0.2.0-source.tar.xz
Hammerhead then consumes the tarball + sha256 into hammerhead-userland, per the integration steps in docs/SMF_MIGRATION.md.
10. Open questions / future work
scripts/install-to-be.shalready exists. Out of scope for this design — it's an in-place dev install, not a release artifact path.- Reproducible tarballs (
--mtime,--sort=name,--owner=0,--group=0). Add when the Hammerhead build needs byte-identical tarballs across maintainer machines. CHANGELOG.md— add when there's enough release history to warrant one. The first one or two release notes can live in commit messages.- GPG signing — Hammerhead handles this at the OS package layer; no zyginit-side signing planned.
- Pre-release versions (e.g.,
0.2.0-rc1). Not supported by the regex in §5. Add when needed. zygctl daemon-versionsubcommand. The socket already exposes aversioncommand (socket.reef:247) that returns the running daemon's version, but no client surfaces it. Add azygctl daemon-version(or similar) subcommand to make partial-upgrade diagnosis easy. Until then,socket.reef'sversionhandler stays as accessible-but-unused (anecho version | nc -U /var/run/zyginit.sockstill works).