ci: build the headless Linux kernel the way it is deployed - #562
Merged
Conversation
A server deployment had no supported way to obtain a kernel binary. The release workflow builds the desktop app — installers, .deb, .AppImage — and never emits a bare kernel, and the dashboard the kernel serves is embedded at compile time, so a binary built against the placeholder index.html serves an empty page with nothing left to fix afterwards short of rebuilding it. This adds a workflow that builds the kernel from the real dashboard build and packages it with the marketplace install engine. The engine has to sit beside the binary and carry the same version: the kernel refuses a mismatched engine only after the install handler has already answered 200, so an engine that arrives by a separate route turns a failed install into one that looks successful. The job proves the bundle rather than assuming it. It rejects a placeholder dashboard before spending the Rust compile, checks the engine identifies itself as this kernel's engine, and starts the binary from a production layout to require a 200 from the health endpoint, a real SPA on /, and a refusal on an admin route. That last one matters because an unknown /api/* path falls through to the SPA and answers 200, so a route that answered 200 there would be indistinguishable from a typo in the path. The pull_request trigger is scoped to this file alone. Compiling the workspace on Linux is already covered for every PR, so repeating it here would buy nothing; what a normal PR cannot prove is this workflow itself. The release workflow is the cautionary case — it has no pull_request trigger at all, so a change to it cannot be verified before merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A server deployment had no supported way to obtain a kernel binary.
release.ymlbuilds the desktop app — installers,.deb,.AppImage— and never emits a bare kernel, and the dashboard the kernel serves is embedded at compile time (rust_embedinhandlers/assets.rs), so a binary built against the placeholderindex.htmlthatci.ymlwrites serves an empty page with nothing left to fix afterwards short of rebuilding it.This adds a workflow that builds the kernel from the real dashboard build and packages it with the marketplace install engine. The engine has to sit beside the binary (
managers/installer.rsresolvesexe_dir().join("cloto-installer")) and carry the same version: the kernel refuses a mismatched engine only after the install handler has already answered200 {"started":true}, so an engine that arrives by a separate route turns a failed install into one that looks successful.What the job checks, and why each check is there
dashboard/disthas a realindex.html+ JS chunkscloto-installer versionnames this kernel's version/api/system/health→ 200/returns ≥200 bytes containing<script/api/llm/providers→ 401/403/api/*path falls through to the SPA and answers 200, so a route that answered 200 there would be indistinguishable from a typo in the path.Verification
Locally, in a production layout outside the workspace (a copy of the kernel under a directory with no
Cargo.tomlin any parent):/api/system/health→200/→ 1475 bytes, 2<scripttags/api/llm/providers→403did not identify itself (exit Some(126)), which is the probe path this workflow's engine check guardsThe dashboard-shape guard was run in both directions: the placeholder
ci.ymlwrites (27 bytes, 0 chunks) exits 1; the real build (1475 bytes, 26 chunks) exits 0.scripts/build-installer.sh --target x86_64-unknown-linux-gnuwas run on a non-Linux host and produced a staticELF 64-bit LSB executable, x86-64— the cross-build needs no toolchain beyond Go, as the script documents.Trigger scope
The
pull_requesttrigger is scoped to this file alone. Compiling the workspace on Linux is already covered for every PR, so repeating it here would buy nothing; what a normal PR cannot prove is this workflow itself.release.ymlis the cautionary case — it has nopull_requesttrigger at all, so a change to it cannot be verified before merge.glibc
ubuntu-latestlinks against an older glibc than Debian 13 / Ubuntu 24.04 ship, and glibc is backward compatible, so the artifact runs there. It does not run on a host older than the runner (Debian 12 is glibc 2.36); that would need a container matching the target, and the header comment says so.