Skip to content

Ubuntu 24.04 LTS support: stage the kernel from the ISO's live layer (refs #66) - #123

Open
adihanifsdr wants to merge 3 commits into
jamesstringer90:mainfrom
adihanifsdr:feat/ubuntu-2404-layered-iso
Open

Ubuntu 24.04 LTS support: stage the kernel from the ISO's live layer (refs #66)#123
adihanifsdr wants to merge 3 commits into
jamesstringer90:mainfrom
adihanifsdr:feat/ubuntu-2404-layered-iso

Conversation

@adihanifsdr

@adihanifsdr adihanifsdr commented Sep 3, 2026

Copy link
Copy Markdown

Refs #66. Stacked on #122 (its commit is included here; merging this closes both).

@jamesstringer90 — I know 24.04 is not a target you planned to support, but the gap turned out to be small and well-contained, so here it is in case you want it. With this a stock ubuntu-24.04.4-desktop-amd64.iso installs and comes online exactly like 26.04, including GPU-PV.

Why 24.04 hung

Ubuntu Desktop ISOs since 23.04 are "fsimage-layered". On 26.04 the base layer casper/minimal.squashfs still contains /boot/vmlinuz-*, the initrd and /usr/lib/modules, so the plain ingest boots. On 24.04 it does not (verified by walking all 85,075 entries): the kernel is only in minimal.standard.live.squashfs and the initrd only as casper/initrd. The bootstrap grub.cfg ended up pointing at /boot/vmlinuz- and the guest sat at GRUB while the UI showed "Installing Linux".

Two more 24.04-specific problems showed up once the guest booted:

  • Point-release pocket mismatch. 24.04.4's base is a noble-updates snapshot, but --prefetch-build-deps only read the noble release index, so the staged libasound2-dev pinned libasound2t64 (= 1.2.11-1build2) against an installed 1.2.11-1ubuntu0.1 and apt refused every build tool. The 24.04.4 pool also lacks libstdc++-13-dev / libgcc-13-dev / cpp-13, so build-essential could not resolve from the pool alone.
  • Wrong headers. detect_iso_kernel scanned pool/main/l/linux/ and found the GA 6.8 headers, while the ISO boots the HWE 6.17 kernel.

What this does

iso-patch

  • The squashfs ingest becomes ingest_squashfs() with an optional include-prefix filter.
  • If the base layer yields no kernel, stage_kernel_from_live_layer() walks the live overlay for /boot/{vmlinuz,System.map,config}-* + /usr/lib/modules/*, stores casper/initrd as /boot/initrd.img-<ver>, adds boot=local to the bootstrap cmdline (the live initrd defaults to BOOT=casper but honours boot= — checked in its /init), plants an /etc/default/grub.d drop-in so update-grub keeps it, and leaves /etc/appsandbox-kernel-from-iso.
  • First boot gets STEP 7.6: with that marker, apt-get install linux-image/-modules/-modules-extra-$(uname -r) from the local mirrors so dpkg owns the kernel and a regular initrd is generated. Best-effort; the ISO initrd keeps working if it can't.
  • The no-kernel hard error from iso-patch: fail loudly when the Ubuntu ISO's minimal.squashfs has no kernel (refs #66) #122 now only fires when the fallback also finds nothing.

prefetch-build-deps

  • Merges the <codename>-updates index after the release one (later stanzas win in the hash chain). Skipped gracefully if the pocket doesn't exist.
  • Seeds build-essential, dkms, initramfs-tools, linux-base.
  • Retries each download 3× with back-off (only the last attempt logs ERROR:), and wipes the output dir on failure — a half-populated dir left the raw release-pocket index behind, which is exactly what made apt pick the wrong versions in my first runs.

app

  • detect_iso_kernel reads the release out of casper/vmlinuz's bzImage header (HdrS, kernel_version at 0x20E), falling back to the pool scan (arm64 images aren't bzImage).
  • spawn_iso_patch_prefetch now captures the child's output and forwards ERROR: and summary STATUS: lines to the app log. Before, a failed prefetch was just WARN: prefetch-build-deps failed with no reason.

No behaviour change for 26.04: the fallback only runs when the base layer has no kernel, STEP 7.6 only with the marker, and the -updates merge just adds newer candidates.

Proof

Ubuntu 24.04.4 LTS guest built from the stock desktop ISO, started from the App Sandbox GUI, IDD display open (GNOME on asb_drm, agent online, GPU-PV attached, SSH key deployed):

Ubuntu 24.04 desktop in the App Sandbox IDD display

Inside that guest: uname -r = 6.17.0-14-generic, asb_drm + dxgkrnl loaded, /dev/dxg present, appsandbox-display capturing 1920x1080, root ext4 has_journal + clean, cmdline with fsck.repair=yes.

Verified

Windows 11 26200, RTX 3070 Laptop GPU, ubuntu-24.04.4-desktop-amd64.iso, via the headless daemon + asb.py:

  • build 3.5 min + prefetch (182 debs from noble + noble-updates), first boot: STEP 7.4 OK, STEP 7.6 OK (kernel registered, initrd regenerated), STEP 8 OK, STEP 12 dkms build/install asb_drm + dxgkrnl OK against 6.17.0-14-generic, reboot, state: online, agentOnline: true, sshState: 4.
  • inside the guest over SSH: uname -r = 6.17.0-14-generic, cmdline carries boot=local, linux-image/-modules/-headers-6.17.0-14-generic installed, asb_drm + dxgkrnl + hv_sock loaded, /dev/dxg and /dev/dri/card1 present, all four appsandbox-* services active, nvidia-smi reports the host GPU.
  • iso-patch.vcxproj + AppSandbox.sln Release|x64 build clean apart from the pre-existing warnings.

🤖 Generated with Claude Code

adihanifsdr and others added 3 commits September 3, 2026 10:01
…kernel

Refs jamesstringer90#66.

do_ubuntu_to_vhdx builds the rootfs from casper/minimal.squashfs and picks
the kernel version up from the first /boot/vmlinuz-* it sees during the
walk. On the Ubuntu 26.04 desktop ISO that layer carries the kernel; on
24.04 it does not (the kernel lives only in the minimal.standard.live
overlay). A full ingest of ubuntu-24.04.4-desktop-amd64.iso walks 85,075
files / 4.9 GiB and never sees a vmlinuz, so kernel_ver stays empty and
the bootstrap grub.cfg is written as:

    linux  /boot/vmlinuz- root=UUID=... ro ...
    initrd /boot/initrd.img-

GRUB cannot load that, the guest sits at the GRUB prompt with one vCPU
busy and ~24 MB of RAM touched, the agent never comes up, and the UI
shows "Installing Linux" forever with nothing in the log explaining why.

Two changes:

* tools/iso-patch/ubuntu_vhdx.c: after the squashfs ingest, treat an
  empty kernel version as a hard error. log_err() reports which image
  lacked /boot/vmlinuz-* and that only Ubuntu Desktop 26.04 LTS ISOs
  are supported; exit_code stays 1 so cleanup deletes the VHDX. Also
  log the detected kernel version as a STATUS line on success.

* src/backend_win/asb_core.c (run_iso_patch_ubuntu): forward iso-patch
  STATUS: and ERROR: lines into the app log. The Windows --to-vhdx path
  deliberately ignores STATUS lines because the in-guest installer logs
  are available later; the Linux build has no such fallback, so these
  lines are the only host-side record of how the disk was built.

A cheaper pre-flight on casper/minimal.manifest was considered and
dropped: the manifest lists no linux-image-* package on the 26.04 and
26.04.1 ISOs either, so it cannot tell bootable from unbootable images.
The post-ingest check reads the actual squashfs contents and only
rejects builds that would have produced an unbootable disk.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Refs jamesstringer90#66.

Ubuntu Desktop ISOs since 23.04 are "fsimage-layered". On 26.04 the base
layer (casper/minimal.squashfs) still carries /boot/vmlinuz-*, initrd and
/usr/lib/modules, so the plain ingest boots. On 24.04 it does not: the
kernel lives only in minimal.standard.live.squashfs and the initrd only
as casper/initrd, which is why a 24.04 build used to hang at GRUB
("Installing Linux" forever). Three things were needed to make 24.04 an
installable target end to end:

iso-patch (ubuntu_vhdx.c)
* The squashfs ingest is now a function (ingest_squashfs) with an
  optional include-prefix filter, so an overlay layer can be walked for
  just /boot/{vmlinuz,System.map,config}-* and /usr/lib/modules/*.
* When the base layer yields no kernel, stage_kernel_from_live_layer()
  pulls those from the live overlay, stores casper/initrd as
  /boot/initrd.img-<ver>, adds boot=local to the bootstrap grub.cfg (the
  live initrd defaults to BOOT=casper but honours boot=) plus an
  /etc/default/grub.d drop-in so update-grub keeps it, and leaves
  /etc/appsandbox-kernel-from-iso for first boot.
* First boot gains STEP 7.6: with that marker present, apt-install
  linux-image/-modules/-modules-extra for the running kernel from the
  local mirrors so dpkg owns the files and a regular initrd is
  generated. Best-effort - the ISO initrd keeps working if it cannot.
* The no-kernel error from the previous commit now only fires when the
  fallback also finds nothing.

prefetch-build-deps (prefetch_build_deps.c)
* Merge the <codename>-updates Packages index after the release one.
  Point-release ISOs (24.04.4) are -updates snapshots: the base already
  has libasound2t64 1.2.11-1ubuntu0.1, and the release pocket's
  libasound2-dev pins "= 1.2.11-1build2", so apt refused the whole build
  tool set. Later stanzas win in the hash chain, so -updates is preferred.
* Seed build-essential and dkms (the 24.04.4 pool has gcc-13 but not
  libstdc++-13-dev / libgcc-13-dev / cpp-13) and initramfs-tools +
  linux-base (needed by STEP 7.6, absent from the pool).
* Retry each download 3x with back-off; only the last attempt logs an
  ERROR. One flaky GET out of ~180 used to abort the run.
* On failure wipe the output dir. A half-populated dir left the raw
  release-pocket Packages index behind and apt in the guest happily
  resolved "= version" candidates from it - then failed on everything.

app (asb_core.c)
* detect_iso_kernel reads the release string from casper/vmlinuz's
  bzImage header (falling back to the pool scan). On 24.04 the pool
  scan returned the GA 6.8 headers while the ISO boots the HWE 6.17
  kernel, so the prefetched linux-headers did not match uname -r.
* spawn_iso_patch_prefetch captures the child's output and forwards
  ERROR: and summary STATUS: lines to the app log, so a failed prefetch
  finally says why.

Verified on Windows 11 26200 / RTX 3070 with ubuntu-24.04.4-desktop-amd64.iso
through the headless daemon: build 3.5 min (+ prefetch), first boot runs all
steps OK (apt build tools, kernel dpkg registration + initrd regen, agent
build, DKMS asb_drm + dxgkrnl against 6.17.0-14-generic), reboot, agent
online, sshState 4. Inside the guest: /dev/dxg and /dev/dri/card1 present,
all four appsandbox services active, nvidia-smi reports the host GPU.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Refs jamesstringer90#66. Follow-up to the 24.04 layered-ISO support: with the kernel
sorted, the guest came online but the display stayed black, and a Force
Stop then left it unbootable. Both turned out to be general robustness
gaps that 24.04 merely exposed first.

Black desktop
* The wsl-mesa prebuilt is built on 26.04 against LLVM 21. STEP 13 put it
  on ld.so.conf unconditionally, which shadows the distro Mesa for every
  process. On 24.04 libgallium cannot resolve libLLVM.so.21.1, so mutter
  fails to create a GBM device on asb_drm ("No GPUs found"), the Xorg
  fallback finds no screens, gdm gives up, and appsandbox-display waits
  forever for a framebuffer.
* STEP 13 now runs ldd on the extracted libgallium/libgbm and only
  activates the prebuilt when everything resolves; otherwise it removes
  /opt/wsl-mesa and logs which libraries are missing. The desktop then
  runs on the distro Mesa (llvmpipe, exactly what the compositor uses on
  26.04 as well).
* 50-appsandbox-gpu and appsandbox-gpu gain a fallback: with /dev/dxg
  present but no /opt/wsl-mesa, they select the distro Mesa's own d3d12
  gallium driver (Ubuntu builds it for WSL; libdxcore/libd3d12 come from
  the wsl-deps prefetch already on the loader path). OpenGL apps still
  reach the host GPU; Vulkan stays on lavapipe because stock
  mesa-vulkan-drivers ships no dzn ICD.

Unbootable after Force Stop
* iso-patch's ext4 writer lays the root fs down without a journal. An
  unclean stop of a running guest (Force Stop while a graceful shutdown
  was still in progress) left it "not clean with errors"; the boot-time
  fsck could not preen it, dropped to a maintenance prompt nobody can
  see, and the VM sat idle with no agent.
* fsck.repair=yes on the bootstrap cmdline and a grub.d drop-in that
  keeps it there after update-grub (plus GRUB_RECORDFAIL_TIMEOUT=5 so a
  failed boot does not park the menu for ever).
* New first-boot STEP 98 adds a journal to the mounted root with
  tune2fs -O has_journal right before the reboot, so later unclean
  stops replay a journal instead of needing a full check.

Verified so far on the 24.04.4 guest from the previous commit, with the
same changes applied offline through a WSL mount of its VHDX: e2fsck -fy
brought the fs back from 'not clean with errors', tune2fs -O has_journal
added the journal, and the boot-time journal explained the black desktop
(mutter: 'MESA-LOADER: failed to open dri: libLLVM.so.21.1', 'No GPUs
found'). A fresh 24.04 build exercising STEP 13's rollback and STEP 98
end to end is the next test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@adihanifsdr

Copy link
Copy Markdown
Author

Pushed a third commit after running the 24.04 guest for real: the kernel side was fine, but the desktop stayed black and a Force Stop then left the VM unbootable. Both are general gaps that 24.04 just exposed first:

  • Black desktop — the wsl-mesa prebuilt (26.04, LLVM 21) went onto ld.so.conf unconditionally and shadowed the distro Mesa; on 24.04 libgallium can't resolve libLLVM.so.21.1, so mutter gets "No GPUs found", Xorg finds no screens, and appsandbox-display waits for a framebuffer forever. STEP 13 now ldd-checks the prebuilt and only activates it when it resolves (otherwise removes it and says which libs are missing). 50-appsandbox-gpu / appsandbox-gpu fall back to the distro Mesa's own d3d12 driver (OpenGL still on the host GPU; Vulkan stays on lavapipe without a dzn ICD).
  • Unbootable after Force Stop — the root ext4 has no journal (iso-patch's writer), so an unclean stop left it "not clean with errors" and the boot fsck dropped to an invisible maintenance prompt. Added fsck.repair=yes (bootstrap cmdline + grub.d drop-in with GRUB_RECORDFAIL_TIMEOUT=5) and a STEP 98 that runs tune2fs -O has_journal on / before the first-boot reboot.

Diagnosed by mounting the guest VHDX in WSL and reading its journal; the offline-applied version of these fixes is running on that guest now, and a fresh 24.04 build to exercise the first-boot path end to end is next.

@adihanifsdr

Copy link
Copy Markdown
Author
image

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.

1 participant