Skip to content

luban: aarch64 images (try, export --drive/--iso), tiny-musl on aarch64, and qemu resolving to a package that does not exist #657

Open

Description

@Sunrisepeak

Summary

Luban environments already run on aarch64. These all work there today, and the host matrix checks them on ubuntu-22.04-arm and ubuntu-24.04-arm:

  • luban new from luban-nano, luban-tiny, core and agent-workspace;
  • the bwrap sandbox, using a static aarch64 bwrap from xim-pkgindex#953;
  • upgrade;
  • proxy-only networking.

Making and trying an image is still x86_64-only, and luban-tiny-musl is not offered on aarch64. This issue collects what is missing on both sides (xlings and xim-pkgindex), a proposed design, and how to test it. Nothing here is urgent.

It also covers a bug that affects x86_64 too: the tool table resolves qemu to a package that does not exist, so a qemu installed by xlings is never used.

1. Bug: qemu resolves to a package that does not exist (all architectures)

modules/subos/src/sandbox/tools.cpp:73-80:

{{"qemu-system-x86_64", "qemu"}, "xim-x-qemu", ...},
{{"qemu-system-aarch64", "qemu"}, "xim-x-qemu", ...},
{{"qemu-img", "qemu"}, "xim-x-qemu", ...},

The index has no qemu package. It has:

  • qemu-x86: qemu-system-x86_64, payload dir xim-x-qemu-x86;
  • qemu-arm: qemu-system-aarch64 and qemu-system-arm, payload dir xim-x-qemu-arm, ships share/qemu/edk2-aarch64-code.fd;
  • qemu-riscv.

What follows from that:

  • The Source::Payload lookup never finds an installed qemu. luban try only works when the host has /usr/bin/qemu-system-*.
  • The printed remedy xlings install qemu fails with 'qemu' matches more than one package.
  • CI hides both problems because it installs qemu-system-x86 and ovmf with apt (xlings-ci-linux.yml, the "Lane capabilities" step). tests/e2e/luban_image_test.sh also looks for OVMF under /usr/share only.

Fix: map each emulator to the package that ships it:

  • qemu-system-x86_64 → qemu-x86 / xim-x-qemu-x86;
  • qemu-system-aarch64 → qemu-arm / xim-x-qemu-arm.

Then either drop qemu-img or map it to a package that ships it. It is not used by try today. Add a unit test that every Entry with a package names a package present in the published index. That test could run in index acceptance, or as a static check against a snapshot.

Other entries without an index package, for the same audit:

  • xorriso, squashfs-tools: the host is the only real source today;
  • xlings-vm: macOS-only, not in the index yet.

A remedy should not name a package that is not there. Either publish one or print the host package manager's name.

A leftover comment in the same file says "The host's clipboard (xlings clipboard; ...)". That command was replaced by the clipboard grants before #653 merged; reword it to the grant.

2. aarch64 images

What is already in place (index side)

Need Package aarch64
kernel for a VM linux-kernel-virt 6.12 (virtio, EFI stub) through luban-boot-virt yes (xim-pkgindex#945)
UEFI loader limine 12.9.3; the payload carries BOOTAA64.EFI and limine-uefi-cd.bin yes
emulator + firmware qemu-arm (qemu-system-aarch64, edk2-aarch64-code.fd) yes
userland busybox, bwrap, glibc yes
kernel for real hardware linux-kernel through luban-boot-generic no (x86_64 only, and this is fine for now; see out of scope)

What is x86_64-specific in xlings

  1. luban try (luban/src/cli/cli.cpp, try_cmd_):

    • it refuses any machine that is not x86_64;
    • it always runs qemu-system-x86_64;
    • it boots the ISO with -cdrom ... -boot d.

    qemu's virt machine on aarch64 has no IDE/AHCI CD-ROM and no BIOS. It needs -M virt, -cpu host (KVM) or -cpu max (TCG), UEFI firmware (-bios edk2-aarch64-code.fd), and the disk as -drive if=virtio.

  2. export --drive (src/core/subos/root/root_cmd.cpp, write_drive_). The expected UEFI loader name depends on the architecture: BOOTX64.EFI on x86_64, BOOTAA64.EFI on aarch64. Today the code:

    • requires BOOTX64.EFI and copies it to EFI/BOOT/BOOTX64.EFI;
    • always creates a 1 MiB "BIOS boot" partition;
    • runs limine bios-install.

    On aarch64 none of the BIOS parts apply.

  3. export --iso (root_cmd.cpp, the ISO stage):

    • it copies only BOOTX64.EFI;
    • without xorriso, the in-process ISO writer produces a BIOS-only El Torito image (ArchiveOptions{.boot = "boot/limine/limine-bios-cd.bin"}), which cannot boot on aarch64;
    • UEFI boot of the ISO depends on a host xorriso, and the index has no xorriso package.
  4. limine_data_ accepts a limine directory only if it has limine-bios-cd.bin and limine-bios.sys. The limine payload ships them on every architecture, so this does not fail today. The check should still ask for what this architecture boots with: the UEFI loader for this architecture, plus the BIOS files only on x86_64.

  5. The "not a static build" hint suggests mcpp build --target x86_64-linux-musl. It should name the machine's own triple.

Proposed design

  • The image's architecture is the root's architecture, which is the machine's. Cross-architecture images (an aarch64 image built or tried on x86_64) are out of scope.

  • One small per-architecture table in root_cmd / luban.image, instead of scattered literals. It gives:

    • the UEFI loader file name (BOOTX64.EFI / BOOTAA64.EFI);
    • whether BIOS boot applies (x86_64 only), which decides the BIOS boot partition and bios-install;
    • the qemu program and its machine arguments;
    • the firmware file name inside a qemu payload.

    Use platform::build_arch() or the root's recorded ABI, never /proc/sys/kernel/arch in one place and a literal in another.

  • --drive on aarch64:

    • GPT with only the FAT system partition (EFI/BOOT/BOOTAA64.EFI, limine config, kernel, initramfs if any) and the ext4 root by PARTUUID;
    • no BIOS boot partition and no bios-install;
    • everything is already written in-process (luban.image), so no new host tool is needed.
  • luban try on aarch64 boots a drive image, not the ISO:

    • qemu-system-aarch64 -M virt -cpu host|max -m <mem> -smp 2 -bios <edk2-aarch64-code.fd> -drive file=<img>,format=raw,if=virtio;
    • networking as today (-nic user, or restrict=on + guestfwd with --proxy);
    • the default boot profile stays virt.

    The firmware is looked up the way limine's files are: the qemu payload's share/qemu, then /usr/share/AAVMF, /usr/share/qemu-efi-aarch64, /usr/share/edk2/aarch64. Missing firmware is refused with the install hint.

    This avoids xorriso entirely, and also the virt machine's lack of a CD-ROM.

  • --iso on aarch64. Two options:

    • (a) refuse with "an aarch64 ISO needs UEFI El Torito; use --drive";
    • (b) teach the in-process ISO writer a second El Torito entry, platform 0xEF (EFI), pointing at limine-uefi-cd.bin. That would also give x86_64 ISOs UEFI boot without xorriso.

    Proposed: (a) first, (b) as a follow-up, since (b) changes the x86_64 ISO too and needs its own BIOS + UEFI qemu tests.

  • luban try <file>.iso on aarch64 is refused the same way. try <file>.img / .qcow2 works.

3. luban-tiny-musl on aarch64 (index side, plus one xlings test)

luban-tiny-musl is archs = {"x86_64"}. Since 2026.10.11.2, an aarch64 machine refuses it before anything is made. This is checked by tests/host-matrix (#655). The only missing piece is musl itself: pkgs/m/musl.lua is x86_64-only.

Its comment gives two reasons: "matching glibc.lua", and "the build script's checks actually RUN the loader ... none of that can run here". Both are out of date:

  • glibc ships aarch64, fixed in xim-pkgindex#956;
  • res-build.yml builds on native aarch64 runners, so the checks can run for real.

Work (xim-pkgindex):

  1. Add musl to tools/res/build.sh and res-build.yml. Build 1.2.5 natively on the arm runner, with the same relocatable layout as .agents/tools/build-musl.sh: the loader symlink under the prefix and relative, no RPATH. Run the checks there: run the loader, link and run a program.
  2. Publish to xlings-res (GitHub), then fill GitCode with gtc. Verify by content (GET + sha256).
  3. In musl.lua, add the aarch64 sha256 and loader = "lib/ld-musl-aarch64.so.1" for aarch64, and archs += aarch64. The x86_64 payload is unchanged, so revision does not move.
  4. Add aarch64 to luban-tiny-musl's archs. This is package metadata: the published 2026.10.11.1 manifest does not change, and it pins xim:musl@1.2.5, which will then exist for aarch64. No new edition version is needed.

Work (xlings): turn the host-matrix aarch64 check for tiny-musl back from "refused" into "made, entered, and a musl program runs". The loader handling is meant to be architecture-neutral (ld-musl-* in elf_same_source, the loader path from the payload's data), but it has never run on aarch64. This test is what proves it.

4. Tests and CI

  • tests/e2e/luban_image_test.sh: run on ubuntu-24.04-arm too.

    • aarch64: export --drive from tiny with --boot virt, boot it under qemu-system-aarch64 -M virt with edk2, and wait for the console marker.
    • aarch64: check that --iso is refused as designed.
    • x86_64: keep the BIOS and UEFI legs as they are.

    GitHub's arm runners have no /dev/kvm, so the aarch64 leg runs under TCG. Give it its own, larger timeout; tiny with the virt kernel should still boot within a few minutes.

  • Install qemu and firmware through xlings (qemu-x86, qemu-arm) in the image lane instead of apt. That is the only way §1's fix stays fixed. Keep apt OVMF only if qemu-x86 turns out not to ship an x86_64 UEFI firmware; check share/qemu/edk2-x86_64-code.fd in its payload.

  • luban try: a non-interactive smoke on both architectures, for example try --nographic with a console marker and a timeout, as luban_image_test.sh already does for the ISO.

  • Host matrix: flip the tiny-musl aarch64 assertion (§3).

  • Requirements: extend LUBAN-DRIVE, LUBAN-TRY and LUBAN-BOOT-PROFILE coverage to aarch64. Add a requirement for "the tool table names only packages that exist".

Acceptance criteria

  • On an aarch64 Linux machine with no host qemu or firmware:
    • xlings install qemu-arm, then luban new box luban-tiny, then luban try box boots to a shell;
    • luban export box --drive box.img produces an image that boots under qemu-system-aarch64 -M virt with edk2.
  • On x86_64, luban try uses a qemu-x86 installed by xlings when the host has none. The printed install hint is an installable package.
  • luban new m luban-tiny-musl works on aarch64, and a musl-linked program runs inside it.
  • CI covers all of the above on both architectures. The x86_64 ISO and drive legs are unchanged.

Out of scope / later

  • aarch64 on real hardware (luban-boot-generic with an aarch64 kernel). Device tree vs ACPI and board-specific firmware make this a separate design; virt is the default on aarch64.
  • UEFI El Torito in the in-process ISO writer (§2, option b).
  • Cross-architecture images and try, such as an aarch64 image on x86_64 under TCG.
  • riscv64.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions