Skip to content

Linux create: cache the prefetches and show progress while downloading - #124

Closed
adihanifsdr wants to merge 5 commits into
jamesstringer90:mainfrom
adihanifsdr:feat/prefetch-cache
Closed

Linux create: cache the prefetches and show progress while downloading#124
adihanifsdr wants to merge 5 commits into
jamesstringer90:mainfrom
adihanifsdr:feat/prefetch-cache

Conversation

@adihanifsdr

Copy link
Copy Markdown

Stacked on #123 (it builds on the prefetch output capture added there).

Problem

Every Linux create re-downloads the same three things from scratch, one file at a time, before iso-patch even starts: the repo tarball from GitHub (~80 MB), the apt build-deps closure (~180 .debs, ~120 MB) and the wsl-deps NuGet package. On a normal connection that is 5–7 minutes during which the UI sits at "Building Disk (0%)" and looks hung — the disk build itself takes ~90 s.

Change

  • Prefetch cache at %ProgramData%\AppSandbox\cache\prefetch\<key>\ (repo-<branch>, build-deps-<codename>-<kver>, wsl-deps). A fresh entry is copied into the staging dir instead of downloading. Entries get a .cache-ok marker only after a complete run and expire by age (repo daily, build-deps weekly, wsl-deps monthly), so a moved branch or refreshed -updates pocket is still picked up. APPSANDBOX_NO_PREFETCH_CACHE=1 bypasses it; deleting the directory does too.
  • Progress while downloading: the three prefetches own 1..9% of the build bar, advanced per downloaded .deb (from iso-patch's closure = N packages line). run_iso_patch_ubuntu keeps the bar monotonic through iso-patch's own early 2/4/6% steps.

Second and later creates for the same release go from ~7 min of prefetch to a few seconds of copying. No change to what ends up in the guest: the cache holds exactly what the prefetch wrote.

🤖 Generated with Claude Code

adihanifsdr and others added 5 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>
Every Linux create re-downloaded the same three things from scratch,
sequentially, before iso-patch even started: the repo tarball from
GitHub (~80 MB), the apt build-deps closure (~180 .debs, ~120 MB) and the
wsl-deps NuGet package. That is 5-7 minutes during which the UI sits at
"Building Disk (0%)" and looks hung - the disk build itself takes ~90 s.

* %ProgramData%\AppSandbox\cache\prefetch\<key>\ now keeps each prefetch
  result (repo-<branch>, build-deps-<codename>-<kver>, wsl-deps). A
  fresh entry is copied into the staging dir instead of downloading;
  entries carry a .cache-ok marker written only after a complete run and
  expire by age (repo daily, build-deps weekly, wsl-deps monthly), so a
  moved branch or a refreshed -updates pocket is still picked up.
  APPSANDBOX_NO_PREFETCH_CACHE=1 bypasses it; deleting the dir does too.
* spawn_iso_patch_prefetch reports build progress: the three prefetches
  own 1..9% of the bar, advanced per downloaded .deb using the
  "closure = N packages" line, so the bar moves during the downloads.
  run_iso_patch_ubuntu keeps the bar monotonic through iso-patch's own
  early 2/4/6% steps.

Second and later creates for the same release go from ~7 min of
prefetch to a few seconds of copying.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Follow-up to the prefetch cache: make the first (uncached) download fast
and stop calling it "Building Disk".

* prefetch-build-deps downloads the closure on 6 connections at once
  (a small worker pool over the closure list; first failure stops the
  rest). archive.ubuntu.com and most mirrors cap per-connection
  throughput well below the link, so this is where the minutes went.
* The app passes --mirror http://<cc>.archive.ubuntu.com/ubuntu, Ubuntu's
  per-country alias derived from the Windows region (the same choice the
  Ubuntu installer makes); APPSANDBOX_APT_MIRROR overrides it. iso-patch
  falls back to the main archive if the alias is unusable, so a bad guess
  costs one failed request, not the build.
* VmInstance.vhdx_step (already there, never used) now carries the phase
  name: "Downloading sources", "Downloading packages 42/182",
  "Downloading GPU libraries", then iso-patch's own step names from its
  PROGRESS:<pct>:<step> lines ("Building rootfs", ...). ui.c emits it as
  vhdxStep and the status cell shows it instead of the generic
  "Building Disk (0%)" that sat there for minutes and looked hung.

Measured from Indonesia against a 24.04 closure (182 .debs): one
connection to archive.ubuntu.com took >10 min; 6 connections to
id.archive.ubuntu.com take 27 s.

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

Copy link
Copy Markdown
Author

Second commit: makes the first (uncached) download fast and fixes the label.

  • 6 parallel connections for the .deb closure (small worker pool; first failure stops the rest).
  • Country mirror: the app passes --mirror http://<cc>.archive.ubuntu.com/ubuntu from the Windows region (what the Ubuntu installer does); APPSANDBOX_APT_MIRROR overrides; iso-patch falls back to the main archive if the alias is unusable.
  • Phase label: the never-used VmInstance.vhdx_step now carries "Downloading sources" / "Downloading packages 42/182" / "Downloading GPU libraries" and then iso-patch's own PROGRESS:<pct>:<step> names; ui.c emits it as vhdxStep and the status cell shows it instead of "Building Disk (0%)".

Measured from Indonesia against a 24.04 closure (182 debs): one connection to archive.ubuntu.com took >10 min; 6 connections to id.archive.ubuntu.com take 27 s.

@adihanifsdr adihanifsdr closed this Sep 3, 2026
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