iso-patch builds an arm64 Ubuntu VHDX: the grub/shim/EFI, apt, dxcore and
mesa paths are selected from the build arch at compile time via the new
target_arch.h, and the arm64 serial console is ttyAMA0 (PL011) not ttyS0.
x64 behavior is unchanged. (Windows-on-ARM support landed in the previous
commit.)
Known issue: an arm64 Ubuntu 26.04 LTS guest boots very slowly on this
platform and is not usable in practice yet — Windows is the supported
arm64 guest for now. The 26.04 generic kernel is built
CONFIG_BPF_JIT_ALWAYS_ON (+RODATA_FULL), so systemd's constant BPF churn
JIT-allocs and frees executable memory, and each free issues a broadcast
cross-CPU TLB/cache-maintenance op (TLBI ..IS). arm64 has no Hyper-V
paravirt TLB flush, so the hypervisor traps every one and the cost scales
with vCPU count; the host's experimental Insider Hyper-V services them
slowly. Fewer vCPUs or a stable Hyper-V build mitigate it.
Also trim the previous commit's ARM code comments down to the essential
architectural differences.
- C1/C2/H7/H8/H9 engine/squashfs.c: validate block_size (non-zero, power
of two, == 1<<block_log, <= 1 MiB); bound walk_directory to the dir
stream; bound the inode block_sizes[] list and each per-block write to
the output buffer.
- C6/H6 engine/ext4.c: cap inodes_per_group at BLOCK_SIZE*8 and size the
inode table by whole blocks so the bitmap/table buffers can't overrun.
- C7 + M8-sibling iso-patch.c: bound qcow2 l1_size (reject under/oversize,
avoid 32-bit length overflow) and propagate IStream::Read failures.
- H10/H11 prefetch_build_deps.c: reject an over-long Filename and clamp
WriteFile lengths derived from snprintf return values.
- L17 prefetch_wsl_deps.c: route download/extract/copy failures through a
cleanup label that removes the unique temp directory.
- M10/M11 ubuntu_vhdx.c: abort (don't finalize a partial VHDX) when the
squashfs walk reports errors; stop and join workers if a thread fails
to start so they can't touch the freed stack-local pipeline.
The resized differencing-create helper was only used by the removed
cloud-image flow (it stretched a small cached cloud-image base to the
user-requested disk size). Confirmed zero callers repo-wide. Delete the
definition (disk_util.c) + declaration (disk_util.h) and drop the comment
reference in iso-patch.c's raw_to_vhdx. The plain vhdx_create_differencing
stays — still used by the template + snapshot paths. Both binaries rebuild
clean.
Line-by-line verification of comments against the code they document,
across the Linux creation path and the guest agent. Comment text only —
no code or log-string changes.
prefetch_build_deps.c: header still described the old Packages.gz +
tar.exe gunzip + atomic-rename-staging design; rewrite to match the
actual Packages.xz + in-process xz-embedded decode + direct-to-out_dir
flow, and correct the "~125 KiB" figure.
appsandbox-agent.c: gdm is a system user with UID <1000, not in the
1000-65534 range — reword the uid_is_real_user rationale; the netplan
"detect the active renderer" comment actually probes the NetworkManager
service, reword accordingly.
appsandbox-display.c: cursor-cadence comment said 30 fps (TARGET_FPS is
60); card-selection comment claimed a connected-output check on the
driver-name priority loop (that check is only in the legacy fallback).
appsandbox-audio.c: wire-protocol doc block said bits=16/block_align=4
(the stream is 32-bit / 8-byte align); snd-aloop subdev comment.
Verified accurate (no change): prefetch_repo.c, prefetch_wsl_deps.c,
appsandbox-clipboard.c, appsandbox-input.c, and the Linux comments in
hcs_vm.c (ComPorts/VideoMonitor/Secure Boot) and vm_agent.c (set_ip,
os_shutdown).
Delete the abandoned cloud-image/cidata Linux install path (zero live
callers — linux_create_thread builds the VHDX directly from the ISO):
- disk_util.c: drop iso_create_resources_ubuntu, generate_autoinstall_*,
generate_network_config, generate_ubuntu_setup_sh, find_repo_root +
copy helpers, the DEAD_ #if0 block, and the entire qcow2/VHD/inflate/
cloud-image converter incl. ensure_ubuntu_cloud_image_cached
(4492 -> 2315 lines). Shared helpers kept (create_iso_from_dir,
remove_staging_dir, unix_password_hash, stage_linux_agent_and_extras).
- disk_util.h: drop the two now-dead prototypes.
Rename the guest first-boot identifier poc-firstboot -> appsandbox-firstboot
(service/script/marker) in ubuntu_vhdx.c and reword stale "POC gpt.c"/
"f361def"/"old flow"/"subiquity"/"cidata" comment references to describe
the current direct-ISO->VHDX flow.
Fix factually-wrong comments found in a line-by-line audit:
- asb_core.c: NAT comment claimed DHCP; it's a static IP baked into the
HCN endpoint + pushed by the agent. SHA-512-crypt / Secure Boot /
create-path comments reworded off the removed flow.
- appsandbox-agent.c: gdm UID-range claim (gdm is <1000); netplan
"renderer detect" actually probes the NetworkManager service.
- appsandbox-display.c: cursor cadence said 30fps (TARGET_FPS is 60);
card-selection "connected output" claim corrected (driver-name match).
- appsandbox-audio.c: wire-proto doc said 16-bit/4-align (it's 32/8);
snd-aloop subdev comment.
Move the internal IMPLEMENTATION_PLAN.md design doc out of the repo
(it now lives in the gitignored docs/).
Linux guest provisioning now mirrors the host's regional settings instead
of hardcoding en_US / us / America/New_York / "ubuntu":
- detect_host_locale_settings() reads the current Windows user's locale
(GetUserDefaultLocaleName), keyboard (GetKeyboardLayoutList) and
timezone (GetDynamicTimeZoneInformation), translating each to the
Linux equivalent. Language is gated to the 8 ISO-preinstalled
languages (de/en/es/fr/it/pt/ru/zh) and falls back to en_US.UTF-8
otherwise; keyboard + timezone always match the host (every XKB
layout and IANA zone ships in the squashfs offline).
- VM name flows through as the guest hostname (mirrors Windows
ComputerName), staged as /etc/appsandbox-hostname.
- All four values staged as marker files; firstboot STEP 2/3/4 read
them with safe fallbacks and STEP 17 echoes the applied values.
Create-VM UI validation is now per-guest-OS, keyed consistently off
osType across validateVmName / validateUsername / validatePassword:
- Linux hostname: lowercase, RFC-1123 charset
- Linux username: ^[a-z_][a-z0-9_-]*$, <=32 chars
- Linux password: all chars allowed (host $6$ hash path), <=255 bytes
- default name lowercases to "myappsandbox" when Linux is selected
- OS Type moved above VM Name in the modal; .field-warn now wraps so
long messages don't break the modal layout; added admin-pass-warn span
IDD audio: drop the unused GetMixFormat call that returned
AUDCLNT_E_UNSUPPORTED_FORMAT on some AMD endpoints and needlessly killed
the audio path. renderfmt is built from the guest header and Initialize
runs with AUTOCONVERTPCM, so the host mix format was never used.
Replace the prebuilt-ELF/prebuilt-.ko shipping model with three
in-process prefetchers that populate per-VM staging from authoritative
sources at create time:
--prefetch-repo GitHub tarball (agent + DKMS sources, units)
--prefetch-build-deps archive.ubuntu.com Packages closure walk
(xz-embedded for Packages.xz, BCrypt SHA-256
verify, synthetic Packages file for offline apt)
--prefetch-wsl-deps Microsoft NuGet libd3d12*/libdxcore.so
The Ubuntu guest's firstboot script then builds the agent from source
(make + make install), installs both kernel modules via DKMS, and
optionally enables openssh-server — all offline, against an isolated
apt sources dir pinned to /opt/appsandbox/local-apt{,-extras}.
xz-embedded gained CRC64 support (XZ_USE_CRC64 + xz_crc64.c compiled in)
because Ubuntu's Packages.xz uses 0x04/CRC64 in its stream check; without
this the decoder bails at the first block header. Decompression also
streams to disk in 4 MiB chunks instead of buffering the whole ~50 MiB
output in RAM.
Linux VM credentials now flow from the create-VM modal:
- host hashes admin_pass into glibc $6$ SHA-512 (unix_password_hash,
same path Windows autounattend uses); plaintext is wiped immediately
- two marker files staged into the rootfs:
/etc/appsandbox-admin-user (UTF-8 username)
/etc/appsandbox-admin-hash (consumed and wiped by STEP 5)
- firstboot useradd + usermod -p so /etc/shadow gets the hash directly
- gdm autologin + GNOME OOBE skip now use $ASB_USER, not hardcoded test
- SSH gated on /etc/appsandbox-ssh-enabled marker; STEP 7.5 installs
openssh-server from local-apt-extras, enables ssh.service + ssh.socket,
opens ufw
ComPorts/com1 restored as a Linux-only debug pipe — systemd's
console output goes to \.\pipe\<vm>.com1 so the host can see firstboot
progress without an SSH session into the guest.
STEP 17 verification now resolves the asb_drm card by walking
/sys/class/drm/card*/device/driver instead of hardcoding card0 — with
efifb=off and asb-evict-simpledrm.service, asb_drm lands as card1.
iso-patch now stages the ISO's pool/main + dists/ tree into the VHDX
at /opt/appsandbox/local-apt/ (~296 MiB). firstboot writes an apt
source pointing at file:/opt/appsandbox/local-apt and runs apt update
against only the local list — dkms + build-essential +
linux-headers-$(uname -r) install with zero network.
Firstboot script hardened to match the old subiquity-late-commands
flow at commit f361def:
- /etc/modules-load.d/dxgkrnl.conf written so dxgkrnl auto-loads
across reboots (was missing — GPU-PV silently broke after reboot)
- STEP 12 DKMS checks all three .ko locations (extra/, updates/,
updates/dkms/, updates/dxgkrnl/), tail-40 of make.log on failure
- STEP 16 /etc/default/grub.d/99-appsandbox-no-efifb.cfg with
video=efifb:off video=simplefb:off + update-grub
- STEP 17 end-of-firstboot verification block: OK/MISSING/state
for every critical artifact (binaries, units, modules-load.d,
modprobe.d, .ko, /dev nodes, ldconfig, Vulkan ICD, grub drop-in,
local apt source)
SKIP_PREBUILT_KO=1 left ON in STEP 11 so DKMS path is exercised
end-to-end against the new local apt mirror.
iso-patch.exe gains a new --ubuntu-to-vhdx mode that produces a fully
installed, bootable Ubuntu VHDX in one shot:
- vendored xz-embedded + custom ext4 writer + squashfs reader
- mounts the ISO, partitions GPT (ESP + Linux root), streams
casper/minimal.squashfs into a freshly-built ext4 rootfs through
a producer/N-decompress-worker/single-writer pipeline
- installs signed shim + grub from the ISO's pool .debs onto ESP,
writes a redirect grub.cfg pointing at the rootfs by UUID
- plants a defensive one-shot first-boot systemd unit that runs
grub-install + update-grub + AppSandbox guest setup (agent ELFs,
kernel modules, systemd units, wsl-mesa, wsl-deps, ldconfig,
no-sleep dconf, etc.) and reboots
linux_create_thread now spawns iso-patch with --ubuntu-to-vhdx
and a --stage manifest covering the full extras tree (reuses
stage_linux_agent_and_extras to populate a staging dir then walks
it). image_path is cleared post-build so the ISO isn't re-attached
to the runtime VM. ComPorts/com1 named pipe and the do_connect_rdp
window path are dropped (IDD is the only live display path now).
VideoMonitor stays unconditional (vmwp.exe crashes 0xC0000005
without it) — but with no ConnectionOptions/named pipe, matching
master.
JS frontend: "Installing Linux" branch added so the spinner label
no longer says "Installing Windows" for Linux VMs. Progress %
now tracks ingest fraction (n_processed / total_inodes) mapped
into 10..75%, with post-ingest phases at 82..99%.
Ubuntu 26.04 LTS guest end-to-end:
- cidata-driven autoinstall pipeline (subiquity user-data + nested
cloud-init bootcmd) in disk_util.c. Static NAT IP, ssh-via-vsock
marker, serial-debug kernel cmdline injected into ESP grub.cfg by
iso-patch.exe. Five separate firstboot pipeline fixes get the VM
to GDM unattended on Hyper-V Gen 2.
- Linux guest stack under tools/linux/:
- appsandbox-agent (vsock :1 control + clipboard helper spawn)
- appsandbox-audio (snd-aloop -> vsock :4)
- appsandbox-display (KMS framebuffer capture -> vsock :2)
- appsandbox-input (uinput injection <- vsock :3)
- appsandbox-clipboard (XCB selection bridge <-> vsock :5/:6)
Helper binaries staged via release/resources/linux/.
- asb_drm DRM module: virtual single-CRTC display sink the host
captures over vsock. Platform-bus, DRIVER_GEM/MODESET/ATOMIC/RENDER,
linear shmem framebuffers. asb-evict-simpledrm.service unbinds the
EFI simple-framebuffer before display-manager so mutter binds
asb_drm as the sole DRM card.
- dxgkrnl vendored byte-identical from microsoft/WSL2-Linux-Kernel
linux-msft-wsl-6.18.y with our DKMS/out-of-tree shim (dxgkrnl_compat.h
+ Kbuild + dkms.conf). Autoinstall late-commands dkms-builds against
every /target/lib/modules/<kver> so kernel-version drift between ISO
and post-update reboot still leaves a working .ko.
- wsl-mesa custom build (Mesa 25.3 + d3d12 gallium + microsoft-
experimental dzn Vulkan). Installs to /opt/wsl-mesa parallel to
Ubuntu's stock Mesa. Prebuilt tarball for ubuntu-26.04-amd64
shipped under tools/linux/wsl-mesa/prebuilt/ (21 MB).
- Per-VM GPU acceleration policy:
- /etc/systemd/user-environment-generators/50-appsandbox-gpu
emits GALLIUM_DRIVER=d3d12 + LD_LIBRARY_PATH + VK_DRIVER_FILES
into the user@1000 systemd-user environment when /dev/dxg and
/opt/wsl-mesa are present. Inherited by every app-*.scope a
.desktop launch creates.
- /etc/systemd/user/org.gnome.Shell@.service.d/no-gpu.conf
UnsetEnvironment= strips these for the compositor — mutter
stays on llvmpipe so asb_drm scanout works (the d3d12 pipe has
no GBM compatibility with asb_drm).
- /usr/local/bin/appsandbox-gpu wrapper for terminal-launched
opt-in GPU acceleration (probes /dev/dxg; no-op on no-GPU VMs).
- Host changes:
- vm_display_idd.c, vm_clipboard.c, vm_agent.c, vm_ssh_proxy.c,
asb_display.c: hcs_service_guid(os_type, port) so the same code
paths reach the per-OS service identifiers (HV_GUID for Windows,
vsock template for Linux).
- asb_core.c: linux_create_thread builds an empty VHDX, cidata
ISO with extras (agent ELFs, dxgkrnl src, asb_drm src, wsl-mesa
tarball, wsl-deps libs), and boots the desktop ISO + cidata.
- hcs_vm.c: HostLxssLib Plan9 share appended for Linux VMs;
VideoMonitor + ComPorts retained for serial debug.
- gpu_enum.c/h: gpu_append_lxsslib_share helper.
- vm_clipboard.c file-drop support (CF_HDROP / x-special/gnome-
copied-files round-trip).
- vm_display_idd.c: 32-bit S32_LE PCM audio path matches the
Linux daemon's snd-aloop capture format.
- iso-patch.c gains a patch_ubuntu_esp_grub_for_debug pass that
rewrites /EFI/ubuntu/grub.cfg via the FAT32 ESP, plus a qcow2->
vhdx converter with a RFC 1951 DEFLATE inflater for compressed
cluster handling.
- wsl-deps: Microsoft's libd3d12.so / libd3d12core.so / libdxcore.so
fetched on demand from the public NuGet feed
pkgs.dev.azure.com/shine-oss/wsl (no auth). Staged at
/opt/appsandbox/wsl-deps on the guest and put on /etc/ld.so.conf.d
rather than /usr/lib/wsl/lib (which is overlaid by the agent's
9P mount).
UI: web/{app.js,index.html} expose Linux VM creation + GPU selection,
distribution dropdown for cached Ubuntu ISOs, and the ISO picker for
custom images.
set nat as default network, set IP address automatically when in NAT mode, don't open the rdp display on win11 home, fix a bug where display would open again at shutdown if it was closed