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.jsonThe document#
{
"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": "…"}
]
}| Field | Type | Meaning |
|---|---|---|
channel | string | stable or beta. |
version | string | The tag without its leading v. This is the comparison key. |
tag | string | The git tag, e.g. v0.5.1. |
released | string | When the index was written, UTC, %Y-%m-%dT%H:%M:%SZ. |
base | string | The directory URL. base + an artefact's name is the download URL. |
notes | string or null | The release notes file, absolute. Null when the tag directory has none. |
checksums | string | Always SHA256SUMS, relative to base. |
signature | string | Always SHA256SUMS.asc, relative to base. |
signed | bool | Whether that signature file actually exists. |
artifacts | array | Every published file in the tag directory. |
Each artefact:
| Field | Meaning |
|---|---|
name | The file name, relative to base. |
kind | iso, deb, qcow2, tarball, sources, notes, or file for anything else. |
arch | amd64 or arm64, matched as a substring of the file name; null when the name says neither. |
size | Bytes. |
sha256 | Hex digest. |
Fields are stable. Unknown fields may be added; a consumer must ignore what it does not recognise.
Building a download URL#
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:
| Tag | Channel |
|---|---|
v1.2.3 | stable |
v1.2.3-rc1, v1.2.3+build4 | beta |
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#
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 SHA256SUMSTwo places#
| Where | What |
|---|---|
| The forge release | Every 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#
#!/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#
- Identifying the system — what version a running box reports.
sign.sh— the manifest and signature tooling.- The ISO and The
.deb— what the artefacts are. - Endpoint index —
POST /api/system/image, which installs an image from a URL. - Releases
- Watch for releases
- Download and verify