Wheelhouse docs

Channel index format#

Each channel is one small JSON document at the root of the download host, regenerated whenever a tag publishes. A person, a script or a monitoring check can poll it for the current version without scraping a page.

https://releases.rhymelikedi.me/stable.json
https://releases.rhymelikedi.me/beta.json

The document#

json
{
  "channel": "stable",
  "version": "0.5.1",
  "tag": "v0.5.1",
  "released": "2026-09-01T12:00:00Z",
  "base": "https://releases.rhymelikedi.me/v0.5.1/",
  "notes": "https://releases.rhymelikedi.me/v0.5.1/CHANGELOG.md",
  "checksums": "SHA256SUMS",
  "signature": "SHA256SUMS.asc",
  "signed": false,
  "artifacts": [
    {"name": "wheelhouse-0.5.1-amd64.iso", "kind": "iso", "arch": "amd64",
     "size": 512483328, "sha256": "…"},
    {"name": "wheelhouse-agent_0.5.1_amd64.deb", "kind": "deb", "arch": "amd64",
     "size": 9911244, "sha256": "…"}
  ]
}
FieldTypeMeaning
channelstringstable or beta.
versionstringThe tag without its leading v. This is the comparison key.
tagstringThe git tag, e.g. v0.5.1.
releasedstringWhen the index was written, UTC, %Y-%m-%dT%H:%M:%SZ.
basestringThe directory URL. base + an artefact's name is the download URL.
notesstring or nullThe release notes file, absolute. Null when the tag directory has none.
checksumsstringAlways SHA256SUMS, relative to base.
signaturestringAlways SHA256SUMS.asc, relative to base.
signedboolWhether that signature file actually exists.
artifactsarrayEvery published file in the tag directory.

Each artefact:

FieldMeaning
nameThe file name, relative to base.
kindiso, deb, qcow2, tarball, sources, notes, or file for anything else.
archamd64 or arm64, matched as a substring of the file name; null when the name says neither.
sizeBytes.
sha256Hex digest.

Fields are stable. Unknown fields may be added; a consumer must ignore what it does not recognise.

Building a download URL#

bash
curl -fsS https://releases.rhymelikedi.me/stable.json |
python3 -c '
import json,sys
i = json.load(sys.stdin)
for a in i["artifacts"]:
    if a["kind"] == "iso" and a["arch"] == "amd64":
        print(i["base"] + a["name"], a["sha256"])
'

Which channel a tag lands in#

Derived from the tag, not chosen by hand:

TagChannel
v1.2.3stable
v1.2.3-rc1, v1.2.3+build4beta
Anything that is not vX.Y.Z[-suffix]Refused: the publisher exits rather than guessing.

A channel is never moved backwards. Re-running the publisher for an old tag notices that the current index names a newer version and leaves it alone. --force overrides that.

/latest/ is the stable channel's pointer, not the newest tag's. Publishing a beta tag prints latest follows the stable channel and <tag> is a beta release; leaving it alone and moves nothing, once any stable.json exists; only while no stable release has ever been published does /latest/ fall back to the newest tag of any kind. A beta tester watches beta.json or the tag's own directory, not /latest/.

The index is written to a temporary file and renamed into place, so a reader never sees a half-written document, and it is left at mode 0644.

What is published beside it#

Both publish jobs — the .deb from the CI workflow and the ISO from the image workflow — rescan the whole tag directory, so whichever finishes last leaves a complete index and a complete SHA256SUMS.

SHA256SUMS covers every artefact in the directory. It does not cover itself, its own signature, .json files, or the per-file .sha256 sidecars it supersedes.

Verifying a download#

bash
curl -fsSLO https://releases.rhymelikedi.me/latest/SHA256SUMS
curl -fsSLO https://releases.rhymelikedi.me/latest/wheelhouse-0.5.1-amd64.iso
sha256sum --ignore-missing -c SHA256SUMS

Two places#

WhereWhat
The forge releaseEvery artefact and SHA256SUMS. The forge is private and needs a login.
https://releases.rhymelikedi.me/<tag>/The same files, public. /latest/ follows the newest stable tag; a beta tag is published here without moving it.

A checksum and the artefact it attests to do not have to come from the same place.

Polling for a new version#

bash
#!/bin/sh
# Exits 0 when the channel names a version newer than the one given.
have=$1
want=$(curl -fsS https://releases.rhymelikedi.me/stable.json |
       python3 -c 'import json,sys; print(json.load(sys.stdin)["version"])')
[ "$have" = "$want" ] || echo "newer release available: $want"

Compare on version, not on released: the timestamp is when the index was written, which a re-publish changes.

See also#

Updated 2026-09-03 releases channels downloads checksums