Channel index format#
Each channel is one small JSON document at the root of the download host, regenerated whenever a tag publishes. It exists so a person, a script or a monitoring check can ask "is there a newer release" 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, so a re-run of last
month's tag does not tell every router that the newest release is last month's.
--force overrides that.
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 —
call the same script after copying their files in, and it rescans the whole tag directory
each time. Whichever job finishes last therefore leaves a complete index and a complete
SHA256SUMS; the jobs do not have to know about each other.
SHA256SUMS covers every artefact in the directory. It deliberately 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, on purpose#
| 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 tag. |
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
Checked against#
.forgejo/workflows/release-index.py
(main, artefacts, kind_of, arch_of, parse_version, KINDS, SKIP_SUFFIXES),
.forgejo/workflows/publish.sh,
install/sign.sh,
docs/deploy.md "Releases and update channels",
docs/upgrade.md.