Skip to content

Stop building a second QEMU and mirroring somebody else's kernel - #3

Merged
aledbf merged 4 commits into
mainfrom
feat/use-spin-machine
Sep 8, 2026
Merged

Stop building a second QEMU and mirroring somebody else's kernel#3
aledbf merged 4 commits into
mainfrom
feat/use-spin-machine

Conversation

@aledbf

@aledbf aledbf commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

This repository built a QEMU from a recipe modelled on spinbox's, against a device list that was byte-for-byte spinbox's, and carried a Dockerfile.guest-kernel that built nothing at all — it existed to move spinbox's kernel through a registry and back out, so a machine with no sibling checkout could name one by digest instead of by a relative path into somebody's home directory.

That is the plumbing across a repository boundary that spin-machine was created to delete. This deletes it.

−1,692 lines.

What goes

Dockerfile.qemu                  275   a second QEMU
hack/qemu-devices.mak            133   the same device list
Dockerfile.guest-kernel                built nothing
hack/guest-kernel.sh             233
hack/publish-guest-inputs.sh     426
taskfiles/qemu.yml               275
.github/workflows/qemu.yml

…plus the five guest:kernel:* and guest:inputs:* targets. taskfiles/guest.yml keeps the one artefact this repository does build: the initramfs.

What arrives

task machine puts one machine under _output/both qemu-system binaries, qemu-img and the kernel — from a sibling spin-machine checkout or its published release, pinned by version and SHA-256 (v20260907.01).

Both binaries, because the split matters here: the one a host runs must refuse to emulate and CI's must be able to. task machine asserts that, and that what it fetched actually runs.

The wrapper goes with it

This is the part worth noticing. qemu:tools extracted the binaries, computed their library closure inside the image, copied the loader and every .so out, and generated a shell wrapper that invoked that loader — because a dynamically linked QEMU extracted onto a bare runner died with liburing.so.2: cannot open shared object file.

The release is statically linked, so an extracted tree runs wherever the kernel does. All of that is one install now, and two paragraphs of CI comment explaining the problem went with it.

Verified

  • from a sibling checkout, and by fetching the real release with the SHA-256 checked;
  • task ci passes.

Left for a follow-up

docs/ still carries ADR-0021 and ADR-0022, which describe the kernel-mirroring mechanism this PR removes. They should be superseded rather than silently contradicted, and that is a separate change.

🤖 Generated with Claude Code

https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a

This repository built a QEMU from a recipe modelled on another project's, against
a device list that was byte-for-byte that project's, and carried a
Dockerfile.guest-kernel that built nothing at all - it existed to move that
project's kernel through a registry and back out, so a machine with no sibling
checkout could name one by digest instead of by a relative path into somebody's
home directory. That was the plumbing across a repository boundary that
spin-machine was created to delete, and this deletes it.

Gone: Dockerfile.qemu, hack/qemu-devices.mak, Dockerfile.guest-kernel,
hack/guest-kernel.sh, hack/publish-guest-inputs.sh, taskfiles/qemu.yml, the five
guest:kernel:* and guest:inputs:* targets, and .github/workflows/qemu.yml. `task
machine` puts one machine under _output/ - both qemu-system binaries, qemu-img and
the kernel - from a sibling spin-machine checkout or its published release,
pinned by version and by SHA-256.

The wrapper goes with it, and that is the part worth noticing. qemu:tools
extracted the binaries, then computed their library closure inside the image,
copied the loader and every .so out, and generated a shell wrapper that invoked
that loader - because a dynamically linked QEMU extracted onto a bare runner died
with `liburing.so.2: cannot open shared object file`. The release is statically
linked, so an extracted tree runs wherever the kernel does and all of that is one
`install` now. Two paragraphs of CI comment explaining the problem went too.

Verified both ways: from a sibling checkout, and by fetching the real release
v20260907.01 with the SHA-256 checked. `task ci` passes, and `task machine` now
asserts what it fetched actually runs and that the accelerator split holds - the
binary a host runs refuses to emulate, the CI one can.

Not verified from here: CI fetching from a private repository. The default
GITHUB_TOKEN is scoped to this repository, so the workflow prefers a
SPIN_MACHINE_TOKEN secret and says so - `gh release download` failing on
permissions reads like a missing tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a
The workflow preferred a SPIN_MACHINE_TOKEN secret because the default token is
scoped to one repository and spin-machine was private. It is public now, so the
default token reads its releases and the fallback, and the paragraph explaining
why the failure would read as a missing tag, are both gone. gh still wants a token
to run at all, which is the only reason GH_TOKEN is still set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a
The integration job failed with gh's own advice — "set the GH_TOKEN environment
variable" — because `task integration` runs `task release`, which runs `task
build`, which fetches the machine, and only the release workflow had been given a
token. Every job that reaches the fetch would have needed one, and the ones that
did not would have failed the same way: naming gh rather than anything about the
machine.

spin-machine is public, and a release asset on a public repository has a plain URL
that needs no authentication at all. gh needs a token even to read one. So the
fetch is curl now, no job carries GH_TOKEN, and there is nothing left to forget.

--fail so a 404 is an error instead of a tarball full of HTML, --location because
the asset redirects to object storage, --retry because this is the one step that
depends on somebody else's network, and the download lands on a .part that is
renamed only once it is whole — an interrupted one left in place is a cached
tarball every later run would reuse and every later run would reject.

Verified by fetching v20260907.01 with no credentials in the environment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a
…r it

Every guest lane died at start-up with

  qemu-system-x86_64-tcg: failed to find romfile "vgabios-stdvga.bin"

which reads like a missing firmware file. It is a device nobody wanted: the demos
pass -display none, which only says not to open a window, and q35 still creates a
VGA adapter. The QEMU this repository used to build shipped a vgabios for it; the
machine's does not, because it has no display adapter compiled in at all.

`-vga none` on every demo, which is the right side to fix: the machine ships the
firmware its own devices need, and a VGA is not one of them. Reproduced and
verified with the exact line the lane runs — without the flag, the romfile error;
with it, the kernel boots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a
@aledbf
aledbf merged commit 0f78393 into main Sep 8, 2026
1 of 5 checks passed
@aledbf
aledbf deleted the feat/use-spin-machine branch September 8, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants