Skip to content

Build a generic aarch64 ISO - #149

Open
maralcbr wants to merge 2 commits into
omacom:quattrofrom
maralcbr:omacom/aarch64-iso
Open

Build a generic aarch64 ISO#149
maralcbr wants to merge 2 commits into
omacom:quattrofrom
maralcbr:omacom/aarch64-iso

Conversation

@maralcbr

@maralcbr maralcbr commented Sep 3, 2026

Copy link
Copy Markdown

Summary

Implements the x86_64/pc and aarch64/generic targets from plans/aarch64-support.md. omarchy-iso-make --arch aarch64 builds a bootable generic ARM64 ISO in an Arch Linux ARM container. The default stays x86_64, and every new branch is gated on the architecture, so x86_64 builds are unchanged.

Stacked on #148 (the new tests need manifest entries). Only the last commit is this PR.

What changed

Architecture selection moves out of build-iso.sh into two sourced files.

  • builder/architecture.sh validates OMARCHY_ARCH:OMARCHY_MEDIA_TARGET, exports the platform, boot backend, live kernel names and the mkarchiso command, and prepares the releng profile for aarch64: packages.aarch64, a generated linux-aarch64 preset, no microcode or memdisk hooks, GRUB pointed at Image and initramfs-linux.img.
  • builder/package-architecture.sh picks the keyring, Node dist arch, pacman config and package lists per architecture, and filters x86-only packages (microcode, tzupdate) out of the shipped lists on aarch64, mapping linux to linux-aarch64.
  • build-iso.sh stays one linear script and calls these. On x86_64 the new calls are no-ops.

Build entry point. omarchy-iso-make gains --arch. On aarch64 it runs the build under --platform linux/arm64 in an Arch Linux ARM image, scopes the offline mirror cache per architecture (the x86_64 directory keeps its name) and picks up *-aarch64.iso. The sudo-docker fallback is kept.

Profile. profiledef.sh reads the arch from the environment, uses uefi.grub only on aarch64, and switches squashfs to xz there because the generic ARM kernel does not enable squashfs zstd. builder/archiso-aarch64.patch is applied to the submodule's mkarchiso for aarch64 builds.

Installer made architecture-agnostic. The configurator picks the limine EFI binary from uname -m, detects linux-aarch64 as the kernel and guards ttfx behind command -v. The orchestrator derives the EFI binary names from the machine instead of hard-coding limine_x64.efi. Keyboard selection reads the kbd catalog from the target instead of running localectl, which needs PID 1.

Test harness. omarchy-iso-test boots aarch64 ISOs with edk2-armvirt on a virt machine (HVF on macOS, KVM on Linux) and infers the guest arch from the ISO name.

What it does not do

  • configs/pacman-online-arm.conf points [omarchy] at pkgs.omarchy.org/$arch. That tree does not exist for aarch64 yet (Make aarch64 a first-class architecture in the scheduled pipeline omarchy-pkgs#277, #199), so an aarch64 online install cannot complete until it is published. The offline mirror build path is unaffected.
  • No Apple Silicon image. That is a separate PR once the generic target is in.
  • omarchy-iso-boot, omarchy-vm and omarchy-iso-release are not arch-aware yet; the status table in plans/aarch64-support.md lists them.

Testing

  • New unit tests: architecture-selector-test.sh (5), arm-profile-test.sh (10), test_arm_limine.py; test_keyboard.py updated. All pass locally; test/all in CI.
  • builder/packages.aarch64-exclude is the list of packages that do not resolve on aarch64 today, split into x86-only hardware support and not-yet-built; it is the per-arch package list question in concrete form.
  • The archiso patch was verified to apply cleanly against the pinned archiso v87 mkarchiso.
  • The x86_64 path is untouched by construction. A real aarch64 ISO was built from this branch on an Apple Silicon host and boots to the live login prompt under QEMU; details in the first comment.

test/all used to run every test serially under set -e, so one slow pty
test held everything and a hung test hung the run forever. Replace the
loop with test/run-manifest.py:

- Two lanes. test/parallel-safe.tests lists tests that only touch their
  own throwaway directory and run in a thread pool; test/serial.tests
  lists tests that own something global (a pty, a port, the docker
  socket) and run one at a time afterwards. Each serial entry says what
  the global thing is.
- Every file under test/unit/ must appear in exactly one manifest or the
  runner refuses to start (exit 2). A forgotten test is an error, not a
  silent skip.
- Per-test wall-clock timeout (OMARCHY_TEST_TIMEOUT_SECONDS, 300 by
  default). Each test runs in its own session so a timeout or failure
  kills the whole process group, and a test that passes but leaves a
  background process behind is failed.
- Python modules run one per process with test/unit on PYTHONPATH and
  fail closed when zero cases collect.
- A JSON ledger (test-runs/unit-test-results.json) is written as
  "incomplete" before the run and finalised after, and a flock lease
  stops two runs from interleaving.

Exit status: 0 all passed, 1 any failure/timeout/cancellation (fail-fast,
as before), 2 the runner refused to start.

Add a test workflow that runs test/all in an archlinux container on push
and pull_request, since the tests reach for parted, util-linux script,
bsdtar and localectl at the versions that ship on the ISO.
Implements the x86_64/pc and aarch64/generic targets from
plans/aarch64-support.md. The default stays x86_64 and every new branch
is gated on the target architecture, so x86_64 builds are unchanged.

Architecture selection moves out of build-iso.sh into two sourced files:
builder/architecture.sh validates OMARCHY_ARCH:OMARCHY_MEDIA_TARGET,
exports the platform, boot backend, live kernel names and the mkarchiso
command, and prepares the releng profile for aarch64 (packages.aarch64,
a generated linux-aarch64 preset, no microcode/memdisk hooks, grub
pointing at Image/initramfs-linux.img). builder/package-architecture.sh
picks the keyring, the Node dist arch, the pacman config and the package
lists per architecture, and filters x86-only packages (microcode,
tzupdate) out of the shipped lists on aarch64. build-iso.sh stays one
linear script and calls these.

omarchy-iso-make gains --arch x86_64|aarch64. On aarch64 it runs the
build in an Arch Linux ARM image under --platform linux/arm64, scopes the
offline mirror cache per architecture and names the output
*-aarch64.iso. The sudo-docker fallback is kept.

profiledef.sh reads the arch from OMARCHY_ARCH, uses uefi.grub only on
aarch64 and switches squashfs to xz there (the generic ARM kernel does
not ship squashfs zstd). builder/archiso-aarch64.patch is applied to the
submodule's mkarchiso for aarch64 builds.

The installer becomes architecture-agnostic: the configurator picks the
limine EFI binary from uname -m, detects linux-aarch64 as the kernel and
guards ttfx behind command -v; the orchestrator derives the EFI binary
names from the machine instead of hard-coding limine_x64.efi; keyboard
selection reads the kbd catalog from the target instead of running
localectl, which needs PID 1.

omarchy-iso-test boots aarch64 ISOs with edk2-armvirt on a virt machine
(HVF on macOS, KVM on Linux) and infers the guest arch from the ISO name.

configs/pacman-online-arm.conf points [omarchy] at pkgs.omarchy.org for
$arch; that tree does not exist for aarch64 yet (omacom/omarchy-pkgs#277,
issue #199), so an aarch64 online install cannot complete until it is
published.

Verified by building on an Apple Silicon host and booting the ISO under
QEMU virt with edk2-aarch64: the live system reaches the login prompt.
Doing that surfaced four more differences from x86_64:

- ALARM ships .pkg.tar.xz, so the offline mirror's repo-add and the
  local-source package copy now match *.pkg.tar.* minus signatures
  instead of only .zst.
- ALARM's linux-aarch64 ships its own mkinitcpio preset, so the profile
  ships none (pacstrap would fail on the conflicting file) and instead
  adds a last-sorting mkinitcpio.conf.d drop-in that re-asserts archiso's
  hooks and resets MODULES, since omarchy-settings' drop-ins otherwise
  override them under the default config.
- pacman 7 refuses to sync without Landlock, which Docker Desktop's
  kernel lacks. The builder detects that and disables the sandbox for the
  build container only; the media's pacman.conf is untouched.
- 37 packages in the shared lists do not resolve for aarch64.
  builder/packages.aarch64-exclude lists them, split into x86-only
  hardware support and packages not built for aarch64 yet, and the
  package filter reads it on aarch64 only.
@maralcbr

maralcbr commented Sep 3, 2026

Copy link
Copy Markdown
Author

Build and boot evidence

Built from this branch on an Apple Silicon Mac (native arm64 Docker, no QEMU emulation) with omarchy-iso-make --arch aarch64, and booted the result under QEMU virt with HVF and edk2-aarch64. The live system comes up to the login prompt on the serial console (GRUB, ALARM Image, archiso initramfs, squashfs root).

Because pkgs.omarchy.org has no aarch64 tree yet, the [omarchy] section was pointed at a local test repository for the build: the 33 aarch64 packages from my fork's channel plus the arch-independent packages from the x86_64 tree. Nothing of that is in the PR.

Getting a real build through surfaced four things the first draft had wrong, all fixed here:

  • Package archives. Arch Linux ARM ships .pkg.tar.xz, Arch ships .pkg.tar.zst. The offline mirror's repo-add globbed only .zst, so the database held 6 of 2212 packages. It now matches *.pkg.tar.* minus signatures; the local-source package copy does the same.
  • Kernel preset. Unlike Arch's kernels, ALARM's linux-aarch64 ships its own /etc/mkinitcpio.d/linux-aarch64.preset, so a preset in the profile makes pacstrap fail on a conflicting file (NoExtract does not help, the conflict check runs first). The package's preset builds from the default config, where every /etc/mkinitcpio.conf.d/*.conf drop-in applies in name order and omarchy-settings' omarchy_hooks.conf replaced archiso's hooks (the first image dropped to an emergency shell). The profile now ships a last-sorting zz-archiso-live.conf on aarch64 that re-asserts archiso's hooks and resets MODULES (the same package forces thunderbolt, which the generic ARM kernel does not build).
  • pacman sandbox. pacman 7 refuses to sync without Landlock, and Docker Desktop's kernel has none, so a build on a macOS host died at the first -Sy. The builder detects that and disables the sandbox for the build container only; the media's own pacman.conf is unchanged. This helps x86_64 builds on macOS hosts too.
  • Per-arch package list. 37 packages in the shared lists do not resolve on aarch64. builder/packages.aarch64-exclude lists them in two groups: x86-only hardware support (microcode, T2, NVIDIA, Intel media, Tuxedo, ASUS, Dell) that stays out for good, and packages simply not built for aarch64 yet (ttfx, omacalc, herdr, tensaku, mise-bin, qmk-hid, dotnet-runtime, obsidian, obs-studio, pinta, hyprland-preview-share-picker, yay-debug, tzupdate) to remove as they appear. The filter reads that file only on aarch64.

The tests for these are in arm-profile-test.sh.

@birkskyum

birkskyum commented Sep 3, 2026

Copy link
Copy Markdown

@Chessing234 Chessing234 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--arch flag with per-arch docker image/cache isolation looks correct.

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