Skip to content

Build a GB10 image as --platform gb10 - #255

Closed
jdvmi00 wants to merge 24 commits into
omacom:n1xfrom
jdvmi00:spark/gb10-image-20261010
Closed

jdvmi00 wants to merge 24 commits into
omacom:n1xfrom
jdvmi00:spark/gb10-image-20261010

Conversation

@jdvmi00

@jdvmi00 jdvmi00 commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

This adds --platform gb10 on top of #254 and #247: the generic image with the runtime's GB10 package list (omacom/omarchy#14895) in its default set, so a DGX Spark or ASUS Ascent GX10 installs the NVIDIA driver, the container toolkit, rdma-core and the Nsight profilers offline. It brings back what dragon's DGX Spark and GX10 profiles gave those machines, the way #254 brings back Snapdragon. A runtime without the GB10 list is refused at build time.

It is a draft because it is stacked on #254; only the last commit is this pull request.

Testing: the unit tests pass, with gb10 cases in the aarch64 build test. On a DGX Spark, an image built from this branch with omacom/omarchy#14895 and omacom/omarchy#10752 as --local-source took 13 minutes, then test/arm64-install installed it unattended in a KVM guest and booted it to the desktop. The one failed check was nvidia-cdi-refresh, which needs an NVIDIA GPU the VM doesn't have.

Not tested: installing on a GB10 itself.

emirb and others added 24 commits October 10, 2026 12:36
An aarch64 build had to be an N1x build: its own kernel, a package
bundle on disk and local source checkouts. None of that can be built or
booted by CI, and nothing else can boot the result.

--platform generic builds the same image without anything board-specific:
Arch Linux ARM's linux-aarch64, the published edge packages, no bundle
and no NVIDIA stack. It boots machines whose firmware describes their
hardware, which is what a VM is, so it is the image CI can install and
test, and the base a board's kernel and packages go on top of.

linux-aarch64 owns its mkinitcpio preset and names its image /boot/Image,
so the live kernel name, preset and initramfs are made after pacstrap,
inside the image (builder/linux-aarch64-live.sh). The approach is the
dragon branch's customize_airootfs.sh.

The generic image keeps both consoles, serial and panel. The N1x build is
unchanged.
No aarch64 ISO has been built by CI. This builds the generic one natively
on every push to this branch, checks that it carries the boot loader, the
kernel, the live initramfs and the root image, and keeps it as an
artifact for three days. The unit tests run beside it.

The push trigger is for the branch: a manual run needs the workflow on
the default branch first.
The runner's Python ships a "test" package. Discovery from the
repository root, as test/all does it, then looks for test.unit inside
that package and finds nothing.
Thirteen of the installer's tests stage a Node tarball named linux-x64,
and the code they exercise looks for the host's architecture, so they
fail on any aarch64 host. quattro runs them on x86; so does this.
On plain Ubuntu four tests fail for reasons that are the runner's, not the
code's: the keyboard tests need Arch's keymaps behind localectl, and two
package-signing tests meet a Python whose Path.is_file raises on a
directory it may not enter. quattro's unit-test.yml runs ./test/all in an
Arch container with systemd for exactly this; this is that step, plus the
tools the aarch64 build test uses.
The aarch64 build test compares the dev key it wrote with cmp, which the
Arch base image does not carry.
The build downloads 3.3 GB of packages into ~/.cache/omarchy on every run
and prunes what it no longer needs. On Namespace that directory can live
on a cache volume. The step is skipped, not failed, on a runner profile
that has none.
A built ISO says nothing about whether it starts. This boots it the way
a machine would, on one of Namespace's Apple-backed runners, the ARM ones
with KVM: UEFI firmware, GRUB from the ISO, the live kernel and
initramfs, the live root. It passes when the live system offers a login
on its serial port, and keeps the serial log and a picture of the screen
either way.

The machine is virt with gic-version=3, its=off and acpi=off: with the
interrupt translation service on, MSIs are lost under nested KVM there
and virtio disks hang.
The generic aarch64 ISO started its kernel and initramfs and stopped
there: mount: /run/archiso/airootfs: fsconfig() failed: Filesystem uses
"zstd" compression. This is not supported. Arch Linux ARM builds
linux-aarch64's squashfs without zstd.

Of what that kernel reads, lz4 keeps what zstd was chosen for, fast
decompression on the page-fault path. Repacked that way on the runner,
the same ISO offered a login 20 s after power-on. The root image grows
from 4,509 MB to 4,814 MB. Other kernels keep zstd.
An unattended install of the generic ISO stopped at its last step:
"Error: bundled Node.js tarball missing from /opt/packages". The ISO
carries Node for its own architecture; quattro's install/user/mise-work.sh
looks for the x64 one on every architecture. The n1x branch of omarchy
picks by uname -m, which is why the N1x image builds its packages from
--local-source. The generic image now does the same, from the n1x
branches of omarchy and omarchy-pkgs.

The ISO also goes to Namespace's artifact store for the jobs that boot
it. GitHub's store took 3 min 20 s to hand 4.5 GB back to a Namespace
runner; it stays as the fallback and for people.
The whole path with nobody at the keyboard, on a Namespace runner with
KVM: the installer takes its answers from a cidata drive, writes the
disk, and the machine restarts into the installed system, which is then
checked over SSH. The answers are the x86_64 integration harness's own
(build_cidata), changed where the architecture shows: Limine's binary,
the kernel, the mirror.

Checked on a runner by hand before this: the install runs to its last
step and writes a Limine entry and a unified kernel image for
linux-aarch64, and after a reset the firmware starts Limine from the
disk.
An unattended install of the generic aarch64 image did everything and
then stopped at its last check: "Validating boot setup failed: no
installed kernel found in target". The installer finds installed kernels
by the pkgbase file Arch's kernel packages leave next to their modules.
Arch Linux ARM's linux-aarch64 leaves none, though its modules, headers
and Limine's unified kernel image were all in place.

A kernel's module directory without a pkgbase is now named by the kernel
the image was built to install. modules.builtin marks a kernel's own
directory, so a leftover of out-of-tree modules is still not a kernel,
and kernels that do record a pkgbase are found exactly as before.
With the kernel recognised, the same install stopped one check later:
"/boot/EFI/Linux/omarchy_linux-aarch64.efi missing or empty". The
Omarchy settings add thunderbolt to the initramfs. linux-aarch64 has no
such module, mkinitcpio reports a name it cannot find as an error, and
limine-mkinitcpio-hook then builds the unified kernel image and does not
install it: "mkinitcpio failed for kernel 7.2.9-1-aarch64-ARCH,
skipping".

Before the boot image is built, the installer now asks modinfo which of
the listed modules the installed kernel has, and writes a drop-in that
sorts last and takes the others back out. With this on the runner, the
install finished, the machine restarted into the installed system, and
Hyprland was running with no failed unit.
The test waited out its whole timeout on an installer that had stopped
after two minutes, and said nothing about why. It now has a way in to
the live system (the live ISO runs cloud-init, so the cidata drive hands
root a key), fails as soon as the installer's log says it halted, and
prints the end of that log. It also checks that no unit failed, that
Hyprland is running, and that the installed system's packages come from
Omarchy's mirror and nowhere else.
The install test's last failing check is the installed system's
mirrorlist. The Omarchy runtime writes it from
default/pacman/mirrorlist-aarch64, which on n1x names Arch Linux ARM's
own mirror. omarchy's arm64-generic branch is n1x plus that one line
changed to arm-mirror.omarchy.org. The workflow builds from it until the
line lands in n1x.
The aarch64 build test runs ssh-keygen, patch and cmp. quattro's
unit-test.yml installs none of them, so it stopped at 'ssh-keygen:
command not found' on every pull request into n1x. With that fixed,
arm64-iso.yml no longer needs its own copy of the unit job.
* Validate Linux boot entries when Limine disables UKIs

* Provide matching kernel headers in boot validation fixtures
It is the generic image on Arch Linux ARM's kernel with one thing added.
Snapdragon laptops get no device tree from their firmware, so GRUB cannot boot
them by itself. Once the live kernel and initramfs exist, the build wraps them
in a UKI that carries a device tree for every supported laptop, and
systemd-stub picks one by the machine's SMBIOS data. A second archiso patch
copies that UKI onto the ISO, and GRUB chainloads it where it exists.

The live image gets ukify for that, and the firmware tools the installer
uses. The installed system takes its Snapdragon packages from the runtime's
own list for the platform, like the N1x does.

A runtime without the Snapdragon hardware setup is refused: a laptop
installed from it would have no device tree to boot with.

Co-authored-by: Jimmy Van Veen <jimmy.van.veen@gmail.com>
…ritten

The GPU, audio and compute DSP firmware of these laptops is signed by the
vendor and ships only with Windows. The configurator copies it off the
Windows partition before a full-disk install removes it, an unattended install
does the same, and the copy travels into the target, where the runtime's
hardware setup installs it. Without Windows on the disk, or off Snapdragon,
nothing happens.

Co-authored-by: Jimmy Van Veen <jimmy.van.veen@gmail.com>
The DSP driver stays out of the live initramfs, where loading it can reset the
USB-C port the install media is on. Without it the live system has no USB
hotplug and no battery reading. On the one laptop this is tested on, a service
loads the driver after checking that the live root is entirely in RAM, with no
mounted block device and no active swap.
They write an x86_64 Limine binary, and the name checked follows the host, so
the tests failed on an aarch64 machine.
The file permission check used an associative array and grep -P, which bash
3.2 and BSD grep do not have, so an ARM image could not be built from a Mac.
It reads the declared paths with sed and looks each one up with grep -F.
It is the generic image on Arch Linux ARM's kernel with the runtime's GB10
package list (install/omarchy-aarch64-gb10.packages) in its default set, so a
DGX Spark or an ASUS Ascent GX10 installs the NVIDIA driver, the container
toolkit, rdma-core and NVIDIA's profilers offline, as DGX OS ships them.

A runtime without the GB10 list is refused: it would make a generic image
under the GB10 name.
@github-actions

Copy link
Copy Markdown

Install test passed

This PR quattro Change
Install 164.8 s 74.2 s +90.6 s
First boot 20.1 s 15.7 s +4.5 s
Everyday boot 14.8 s 14.8 s +0.0 s
Install phases
Phase This PR quattro
Preparing live environment 0.2 s 0.1 s
Preparing install target 0 s 0 s
Installing Arch + Omarchy 142.9 s 61.4 s
Configuring hibernation 0.3 s 0.2 s
Configuring system 3.3 s 2.2 s
Staging provisioning 0.2 s 0.1 s
Finalizing Limine boot 13.6 s 7.1 s
Finalizing user 3.9 s 2.6 s
Configuring login 0.1 s 0.1 s
Configuring SSH access 0.3 s 0.2 s
Configuring Tailscale 0 s 0 s
Configuring DNS resolver 0 s 0 s
Validating boot setup 0 s 0 s
Creating factory snapshot 0.1 s 0 s

Run · a3883fe

@jdvmi00
jdvmi00 deleted the spark/gb10-image-20261010 branch October 14, 2026 01:18
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.

3 participants