Step 1 of PLAN.md §5.2's four, and it has to be first. Two rules in this job are strict in opposite directions: an unrecognized link asset name fails the run, and a missing REQUIRED platform key fails it too. So the name must be taught before the release that carries it, and the key can only be required after one exists -- requiring it first would fail every bundle for as long as the gap lasts. This is therefore the mapping only. linux-aarch64 is not in REQUIRED yet; step 3 promotes it once a link release actually ships the binary, after which a dropped target reddens CI instead of vanishing silently from every bundle. The compose step needed no change: it builds the asset map from the platform TSV, so a third key costs it nothing. Edited on `main` and deliberately not on `edge`. The compose job runs from `main`, and leaving `edge`'s copy untouched means the eventual cutover merge has nothing to conflict over. Co-Authored-By: Claude <noreply@anthropic.com>
4.2 KiB
Bundles
These files are generated. Do not edit them by hand.
A bundle names one exact, protocol-checked combination of the two components the installer
deploys — a uo-link sidecar release and a servuo-plugins overlay release. The installer does not
hardcode versions and does not resolve "latest" at run time; it fetches one of these documents and
installs what it names. The bundle is the compat matrix.
They are written by .gitea/workflows/bundle.yml, which composes
one whenever a component publishes a release (dispatched by that release's own workflow) and
nightly, so a missed dispatch self-heals. A run that finds nothing changed writes nothing.
See docs/installer/PLAN.md §7 for the design.
Layout
| File | What it is |
|---|---|
current.json |
The bundle the installer uses by default. Always a copy of the newest bundle-*.json. |
bundle-<tag>.json |
Every bundle ever published, kept forever so --bundle <tag> stays reproducible. |
Tags are UTC dates — 2026.08.04. A second bundle on the same day (a sidecar release in the
morning, an overlay release in the afternoon) becomes 2026.08.04.2, so one tag always names
exactly one matrix.
How the installer fetches these
Plain anonymous GETs against a public repo. The shard host gets no git and no Gitea credentials
(PLAN.md §1), so nothing here may require auth:
https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/current.json
https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/bundle-2026.08.04.json
Bundles are committed rather than published as Gitea releases because this repo's own releases are
the installer binaries, and /releases/latest returns whichever release is newest regardless of
kind — interleaving the two would make "latest" intermittently resolve to a release containing no
installer binary.
Schema
schema is the version of this document's shape, and is unrelated to protocol (the uo-link wire
protocol) or to either component's release version. All three move independently.
{
"schema": 1,
"bundle": "2026.08.04", // this bundle's tag; what --bundle takes
"generated": "2026-08-04T16:07:13Z",
"protocol": 3, // the wire protocol both halves speak (gate 1 proved it)
"link": {
"repo": "RunicGateway/link",
"tag": "v1.1.0",
"version": "1.1.0",
"protocol": 3,
"assets": { // per-platform: the installer runs on each
"linux-x86_64": { "name": "…", "url": "…", "sha256": "…" },
"windows-x86_64": { "name": "…", "url": "…", "sha256": "…" }
// "linux-aarch64" joins these from link's first release built for it;
// bundle.yml already knows the name (PLAN.md §5.2)
}
},
"overlay": {
"repo": "RunicGateway/servuo-plugins",
"tag": "v0.1.1",
"version": "0.1.1",
"commit": "3a52abb…", // recorded into install.json at deploy time
"protocol": 3,
"servuo": {
"min_version": "57.4", // base overlay: only adds files
"patches_verified_against": "57.4" // patch tier: skipped with a warning elsewhere
},
"asset": { "name": "runicgateway-overlay-0.1.1.tar.gz", "url": "…", "sha256": "…" }
}
}
The two things worth knowing
sha256 is load-bearing, not decorative. Every artifact Runic Gateway publishes is deliberately
unsigned (PLAN.md §3) — the checksum is the entire trust anchor. Each hash here was computed by
CI from the asset it actually downloaded, after verifying it against the SHA256SUMS the
publishing repo shipped beside it. The installer must verify every download against these values and
refuse on a mismatch. A bundle whose hashes are trusted but never checked buys nothing.
link.protocol and overlay.protocol are always equal, and that is the point. The sidecar
rejects a protocol mismatch with 409 rather than mis-parsing, so a mismatched pair is a shard
emitting into a void. CI refuses to publish one: it reads PROTOCOL_VERSION from the sidecar's
source at its release tag and the declared protocol from the overlay tarball's manifest.json, and
fails if they differ. The top-level protocol is that agreed value.