No description
  • Shell 97.3%
  • Makefile 2.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-11 13:08:55 +02:00
docs Document qualified native tools and uncached disk packaging 2026-09-11 06:45:29 +02:00
tests Bake the proven ARM canary OS for the worker fleet 2026-09-11 13:08:55 +02:00
tools Remove legacy Python node-image recipe and packaging paths 2026-09-11 04:05:06 +02:00
inputs.json Bake the proven ARM canary OS for the worker fleet 2026-09-11 13:08:55 +02:00
Makefile Remove legacy Python node-image recipe and packaging paths 2026-09-11 04:05:06 +02:00
NODE_IMAGE_PROFILE Declare native image inputs while retaining legacy bake compatibility 2026-09-11 01:43:53 +02:00
README.md Document qualified native tools and uncached disk packaging 2026-09-11 06:45:29 +02:00
VERSION Bake the proven ARM canary OS for the worker fleet 2026-09-11 13:08:55 +02:00

Hetzner ARM64 node image

Build a file-backed boot disk from an exact ARM64 bootc image. Verify it with an isolated QEMU guest. Do not use SSH, cloud servers, or host disks.

The kernel and ZFS inputs are cross-compiled in builder-images/Linux builds. The pinned native helper only installs an already built OS. The installer runs under the cluster's existing ARM user-mode emulator. QEMU system emulation verifies boot.

The runner needs a privileged, isolated Kubernetes Job with no host namespaces, hostPath mounts, credentials, or service-account token. /workspace and /var/lib/containers must be scratch volumes. Privilege is required for loopback disk installation, not for access to a worker root disk. Never run the bake helper directly on a worker or developer host.

Source inputs and the helper image must be frozen before a build. The output records the exact image digest, kernel release, ZFS version, disk hash, and serial boot log. A successful QEMU test is not proof of Hetzner hardware compatibility. Worker replacement is a separate, explicitly approved operation.

A successful Build requires the exact disk hash and QEMU boot proof. Read the Build result for the candidate digest; do not infer verification from a log line.

Binjovi uses VERSION as the immutable disk package version. Increase it for each new published image. The release exports node.raw.zst, artifact.json, serial.log, and package.json. Verify the package hashes before decompression. The exact kernel must include built-in XFS, XFS quota and POSIX ACL support. The native formatter pins the XFS feature set to the shipped GRUB reader.

Run make check inside the pinned tool image for local contracts. It only reads inputs and exercises command plans, guest-script generation and proof refusal; it does not run a bake. On a development machine, pass NODE_IMAGE_TOOL=/path/to/binjovi-node-image-tools from the exact published helper source. That checks source behavior, not the Linux image or a booted disk. The Binjovi project supplies an isolated Kubernetes Job and a new ephemeral volume for each build. Do not apply the old prototype Jobs. A failed Pod can retain its scratch volume until the Pod is removed. This is not a durable artifact store. Read the container logs and the termination message before cleanup. The tests inject a service into a separate disk copy because cloud-init is masked in the node OS. The published raw disk remains unbooted.

Use native overlay container storage; a scratch test proved it works on the cluster's ZFS volume. VFS copies every parent layer and is too slow here. Use bootc's explicit OCI archive source. Its default self-container inspection re-enters mount namespaces, which fails under ARM user-mode emulation. The installer shares the outer Pod's PID namespace, never the worker's namespace. Create the GPT, EFI and 24 GiB XFS root with native tools. Call bootc's to-filesystem installer on those mounted filesystems. to-disk calls another host-namespace helper for udev and cannot run under this user-mode emulator. Mirror /dev, /var/lib/containers, /var/tmp, and /run/udev from the outer Pod into the installer. These are Pod-local paths, not worker hostPath mounts.

Native task evidence

Binjovi uses a fenced native admission task to control the isolated bake Job. The dashboard shows checkout, bake, build, and collect container times, plus bounded log tails. Native task telemetry can also retain the owned Pod and container identities, worker name, image IDs and restart counts from the same observation. A requested architecture is not an observed executor architecture. CPU and syscall profiles inside the QEMU guest are not collected. The native agent does not receive Kubernetes credentials or run the privileged recipe in its own process.

Release an exact successful Build with binjovictl release hetzner-node-image --build BUILD_ID --wait. This project has no node deployment target. A green Release is not permission to replace a cluster worker or provision a cloud server.

Native source contract

NODE_IMAGE_PROFILE declares the native.v1 input contract. This revision has no Python recipe, packager or tests. The active catalog must select native.v1. The native reader executes only the helper in the exact published tool image:

registry.sean.farm/build/node-image-tools:v3@sha256:0c23a97c3a56bd6f238af7ee8229d3596b4d21ad7c09a2bf14cccbb0ec4da66c.

The catalog selects this image explicitly. Historical native plans that omit the tool selection keep their exact v2 image; legacy plans retain their original source and runtime. Package version 4 gives this release a new immutable tag.

The catalog can select build.package_cache: "none" with these v3 tools. This removes registry cache import and export for disk packaging. Each bake still creates a new disk and boot proof; the published image and its independent digest read remain required. A long cache export can overlap image export, so its duration is not a direct estimate of pipeline time saved.

The marker alone does not change the catalog or execute a bake. Before this cleanup is admitted, the retained-file source must pass full isolated native qualification and the active catalog must select the qualified native profile. Keep the owned Job/Pod identity, exact source and plan digests, container results, full disk hash, QEMU boot proof and independently read candidate digest. Then require the normal native CI for this exact cleanup revision. See docs/native-source-cutover.md. No local check permits catalog activation.

make check runs Bash/jq source and helper contracts. The source contract requires exactly native.v1 and one newline, a valid positive package version, regular input files and the fixed input schema. It rejects Python source files. The helper contract checks this source's exact formatter, installer and QEMU commands, required guest checks and refusal of forged boot/package evidence. The native reader validates the marker and inputs independently; it does not execute the repository Makefile or tests inside the trusted agent process.

The first native disk cleanup release used package version 3. Version 2 belongs to the earlier additive source. Native qualification writes only its unique quarantine candidate. Never replace an existing version tag or rewrite a frozen plan.

The helper image build also owns exact guest-service byte checks. The full isolated bake verifies the actual filesystem and guest boot. The old Python private filesystem-probe tests did not test this Go implementation; the Go private wrong-filesystem case has local development coverage, not source-side Bash coverage. Do not claim a local contract or successful positive bake tests that negative branch.