Skip to main content

One Repo, One Pipeline, One Version

ยท 9 min read

If you've built Kairos from source in the last few days, you've noticed: kairos-agent, immucore, kairos-sdk, kairos-init, and kcrypt-discovery-challenger are gone. Not deleted. Moved. They now live inside kairos-io/kairos, as agent/, immucore/, sdk/, kairos-init/, and kcrypt/.

TL;DR, if you just need to keep building:

  • The five repos are archived, not deleted. Their code, full git history, and existing published tags all still work. Receipts at the bottom if you want them.
  • Using the SDK: import github.com/kairos-io/kairos/sdk/... (e.g. sdk/bus, sdk/collector, sdk/machine) instead of github.com/kairos-io/kairos-sdk. Code pinned to old kairos-sdk tags keeps building unchanged.
  • Using kairos-init, kairos-agent, immucore, kcrypt-discovery-challenger: same binaries, same container images, same tag pattern, just built from the monorepo now, versioned off kairos-io/kairos's own tag instead of their own.
  • Their next release will jump from their own v0.x / v2.x number straight to the shared v4.x.x line. That is not a wall of breaking changes compressed into one release. See "About that version jump" at the bottom before you panic.
  • The SDK doesn't get this jump at all: it's not an independently tagged artifact anymore, so there's no "next sdk version" to be surprised by. Details below.

The problem this actually solvesโ€‹

Kairos has never shipped as a single thing. A user running Ubuntu Kairos 3.5 was told to move to kairos-init 2.6. Running --version on the agent gave 2.2. Three numbers, one product, and no way to tell which of them was "your" version.

That wasn't a labeling problem. It was a real coordination cost. kairos-init alone was maintaining four minor lines at once in July, because each one tracked a different Kairos release under the hood. The mapping between components already existed; only maintainers could read it. We wrote up the case for a single shared version number in detail, and partway through that discussion the obvious question came up: why keep the mapping in a table at all, when the fix is to stop having five things to map between?

So: monorepo. Not because monorepos are fashionable, but because it's the most direct way to make "one version" mechanically true instead of a promise five release pipelines have to keep independently.

What actually moved, and howโ€‹

Each component came in with git subtree add, one PR per component: kairos-sdk, then agent, immucore, kairos-init, and kcrypt together. The five source repos are archived, with a banner pointing here.

Nothing published from them stops working. Every Go module tag (go get github.com/kairos-io/kairos-agent/v2@v2.31.1) still resolves through the Go module proxy forever, because the proxy caches it independently of the source repo's state. Every container tag on quay.io stays pullable. Archived just means read-only on GitHub, not gone.

Why these five, and not repos like AuroraBoot or kairos-operator? Because these five never actually had a release cadence of their own to protect. Every one of them released to serve a specific Kairos release, not on a schedule that made sense by itself. During v4.2.0's RC cycle alone (August 10 to August 18), kairos-agent shipped four releases: v2.31.1 the day RC1 went out, v2.31.4 the day the final tag did. That's not a library with its own users on its own timeline. That's the same release, wearing a second number.

AuroraBoot and kairos-operator are different in kind, not degree. They shipped zero releases during that same eight-day window, both last touched before the RC cycle even started, both went on to release again afterward without a new Kairos tag anywhere nearby. People build with them independently of whether Kairos itself shipped anything new that week. Folding them in would take away a cadence they actually use, so they stayed where they are.

The pipeline finally looks like oneโ€‹

This is the part we're most glad to be rid of. Before the move, a change to Kairos touched up to six separate CI systems that didn't know about each other: kairos's own binaries.yaml, image-master*.yaml, uki.yaml, lint.yaml, and unit-tests.yaml, plus whatever each of kairos-agent, immucore, kairos-sdk, kairos-init, and kcrypt-discovery-challenger ran on their own. A red build in kairos-init didn't stop a green kairos release from shipping against it. Finding out why something broke meant checking six Actions tabs.

master.yaml retires all five of the kairos-side files above and replaces them with one dependency graph:

unit-tests, lint, build-binaries
โ””โ”€โ”€โ–ถ build-images
โ”œโ”€โ”€โ–ถ build-iso โ”€โ”€โ–ถ qemu-tests-core / -standard / -uki
โ””โ”€โ”€โ–ถ build-iso-arm โ”‚
โ–ผ
promote-images

Here's a run of it, in progress:

Kairos master.yaml pipeline graph

The safety property is the part that's easy to miss looking at the graph: build-images doesn't push straight to :master. It pushes to :master-scratch, the ISO and qemu jobs test that, and only promote-images (after every qemu job is green, arm build included) copies the manifest across to :master. A broken push leaves :master-scratch broken and :master untouched, instead of shipping a red build to the tag everything else pulls from.

The tag-triggered release.yaml mirrors the same shape for v* tags: scratch tag, qemu-gate, promote to the release tag and :latest, then attach binary archives to the GitHub Release. One honest gap: release-legacy.yaml still runs alongside it for now, because it builds a k3s/k0s per-version ISO matrix the new pipeline doesn't produce yet. That's a tracked follow-up, not a silent omission.

Where things get released, concretelyโ€‹

One tag on kairos-io/kairos, e.g. v4.3.0, now produces everything: the kairos-agent, immucore, kcrypt-discovery-challenger, provider-kairos, and kairos-installer binaries, plus kairos-init, all built from VERSION := git describe --tags, all linked with the same -X .../internal/version.Version=$(VERSION). Every one of them reports v4.3.0 when you ask. That's #4301's ask, done: not as a policy anyone has to remember to follow, but as a Makefile variable everything shares because it's now one git describe away.

About that version jumpโ€‹

If you build your own images, four components are about to jump straight to the shared v4.x.x line, all at once:

ComponentCurrent standalone versionNext release
kairos-agentv2.31.4v4.x.x
immucorev0.20.4v4.x.x
kairos-initv0.16.3v4.x.x
kcrypt-discovery-challengerv0.13.4v4.x.x

On their own, those numbers look like a scare jump: two, or four, majors in one bump usually means "expect everything to be different." It doesn't mean that here. It's the same code, the same maintainers, the same review process, doing what would otherwise have shipped as v2.32.0 or v0.16.4 or v0.13.5 under each component's old numbering, just carrying the shared Kairos version instead of its own independent one, for exactly the reason in the first section: so nobody has to keep a private table mapping immucore 0.20.4 to Kairos 4.2.0 in their head. Check the changelog for what actually changed, not the size of the version jump. The jump itself carries no compatibility signal, this once.

The SDK is a different case, and doesn't get a version jump at all. kairos-sdk was already a v0.x Go module with no independent artifact beyond its module tag: no container image, no release binary. Now that sdk/ is a plain subpackage of github.com/kairos-io/kairos, it has no version of its own to bump: you pin the whole kairos module (go get github.com/kairos-io/kairos@v4.3.0) and the SDK code comes along at whatever that resolves to. There's no kairos-sdk v4.x.x release to look out for, because kairos-sdk as a separately versioned thing stops existing at this move, not just at this jump.

Try itโ€‹

Clone kairos-io/kairos, and agent/, immucore/, sdk/, kairos-init/, and kcrypt/ are all right there, building from the same go.mod. If something looks wrong, the tracking issue is #4301 and the pipeline work is ongoing in the open. Come poke at it.

Does the git history survive?โ€‹

Least important part of this story if you just build against Kairos, but we checked rather than assumed, so here's the receipt. Short answer: yes, but with one real wrinkle worth knowing about.

git subtree add grafts the entire source repo as a second parent of the merge commit. It doesn't rewrite or truncate anything. We verified this per component by walking the ancestry of the commit each subtree merge points at:

ComponentCommits in the original repoCommits reachable from the graft point
kairos-agent โ†’ agent/2,0312,030
immucore โ†’ immucore/648645
kairos-sdk โ†’ sdk/922921
kairos-init โ†’ kairos-init/505504
kcrypt-discovery-challenger โ†’ kcrypt/347344

(The handful missing per component are the "add archive notice" commits made on each repo after the graft, nothing from before it.) Every one of those commits is a real, permanent, unreachable-by-git gc part of kairos-io/kairos's history now.

git blame finds it without help. Blame a line in agent/internal/agent/agent.go today and it correctly walks past the merge into kairos-agent's original commit from 2022, author and all.

The wrinkle: git log -- agent/some/file.go does not walk through automatically. git subtree add merges the foreign tree in at a prefix; it doesn't rewrite the foreign history's paths to match, so git's default path-history simplification (which drives git log <path> and GitHub's "History" button on a file) doesn't connect the two sides on its own. If you want the pre-move commit log for something now under agent/, go to the graft commit directly:

git log 14f0d3bf68b60c86efbfd1dd857a0d9087bac7b1 -- some/file.go

(Swap in the commit named in agent/'s "Add 'agent/' from commit '...'" merge: immucore, sdk, kairos-init, kcrypt each have their own.) Or just clone the archived repo. It isn't going anywhere either.

So: nothing was lost. Something did get a little harder to find, and now you know where to look.