Fix Windows CUDA and OpenCL adapter identity handling and provision NVIDIA Optical Flow and OptiX runtimes through the existing GPU copy path. Include the new shims in the existing build, signing and packaging pipeline.
Prioritize the Linux WSL library directory so CUDA loads from SSH sessions and background processes.
Add POST/DELETE/GET /vms/{name}/display plus the open_display/close_display/display_status/display_ready SDK methods, so a script can open and close a VM's display window like the GUI's Connect button.
Readiness is a latched flag, not a frame-channel probe: connecting to the guest frame service is itself becoming the display's single consumer, so polling it would steal the slot and blank the display. The guest agent reports the display driver state over the agent channel (idd_status) and the host latches it as idd_ready; asb_vm_idd_ready = running && agent_online && idd_ready. The Linux agent reports idd_status:ok once the user session is up (Mutter compositing).
The display window is daemon-owned (per-VM table keyed by unique_id), gated on a visible window station, and self-tears-down on the X button; displayOpen is read from the live window state, so it is honest however the window closed.
Headless mode: `appsandbox.exe --headless` runs a single-owner daemon that hosts
the core over a loopback HTTP/JSON API (http.sys) with a host.json discovery
file. A dependency-free Python SDK, examples, and a self-contained regression
suite live under tools/headless-api/ (PLAN.md = design rationale).
The API mirrors the GUI: create/start/shutdown/stop/delete/edit, snapshots and
branches (incl. base branches via the new asb_snap_base_branch_count), templates,
sshInfo, an SSE /v1/events stream, GUI-mirrored input validation, a startup
fail-fast (admin / Win11 build / VirtualMachinePlatform), and a single-instance
mutex shared with the GUI so only one of GUI/headless runs at a time.
SSH key deploy: sshDeployKey (requires sshEnabled) has the core generate an
AppSandbox ed25519 keypair once and the guest agent write the public key into
authorized_keys -- Windows administrators_authorized_keys (+ ACL), Linux/macOS
~/.ssh -- reporting a new ssh_key_deployed status (sshState 4). The GUI SSH
button and the SDK use the key for password-less login. Verified end-to-end on
Windows.
The Linux guest builds its agent/driver source from this branch's GitHub repo;
asb_core.c points the prefetch at "headless-api" (revert to "main" before merge,
see PLAN.md).
- LICENSE: MIT, Copyright (c) 2026 James Stringer.
- THIRD-PARTY-NOTICES.md: scopes the MIT grant around bundled third-party
components, documenting each one's license/copyright:
dxgkrnl (Microsoft WSL2 GPU driver) GPL-2.0; iso-patch/engine/xz 0BSD;
wsl-mesa prebuilt (Mesa 25.3.6) MIT; WDF SDK, WebView2 SDK, DevCon (Microsoft).
- tools/linux/dxgkrnl/COPYING: verbatim GPL-2.0 text shipped with the GPL'd
driver source.
- tools/linux/wsl-mesa/prebuilt/MESA-LICENSE.txt: Mesa's headline MIT license,
shipped next to the prebuilt binary.
- README: license section points to LICENSE + THIRD-PARTY-NOTICES.
- Info.plist: copyright string now names the holder and notes MIT.
The asb_drm Linux kernel module keeps its existing dual (GPL-2.0 OR MIT)
licensing; vendored sources keep their own SPDX headers.
- C10 appsandbox-clipboard.c: the host->guest INCR accumulator doubled
accum_cap *before* realloc, so on realloc failure it memcpy'd into the
un-enlarged buffer (heap overflow). Grow into a temp, commit the cap
only on a successful realloc, and append only when the buffer has room.
- L11 appsandbox-clipboard.c: reject FORMAT_LIST frames with data_size<4
before reading the 4-byte count (OOB read; Linux analog of H4/C9).
- L18 appsandbox-clipboard.c: host->guest folder paste flattened — the
text/uri-list builder listed every nested file and no directory, so the
file manager pasted the files flat with no folder. List top-level items
only (a top-level dir contributes its own URI; nested entries none),
guarded on the dir actually existing.
- L12 appsandbox-display.c: use-after-free reading p->plane_id after
drmModeFreePlane(p) in cursor_init; log the cached cur->plane_id.
- L13 appsandbox-audio.c: idle silence paced at 10ms < the ~21.3ms packet
duration, overrunning the host render ring; pace at the true packet
duration (FRAMES_PER_PACKET * 1000000000 / SAMPLE_RATE).
Built in-VM and validated on a live Ubuntu VM (folder/sub-item paste;
audio/display/clipboard daemons healthy after redeploy).
The cloud-image install flow is gone; reword the last comments that still
referenced it:
- asb_core.c: "Linux cloud-image creates" -> "Linux ISO creates"
- disk_util.c: drop the "Ubuntu cloud-image parent ~2.5 GB" rationale
from vhdx_create_differencing_resized (now describes the generic
inherit-then-resize behavior)
- disk_util.h: stage_linux_agent_and_extras doc no longer says "same
content the cidata builder used to ship"
- appsandbox-agent.c: "autoinstall lets the user pick" -> "the create-VM
modal lets the user pick"
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/).
appsandbox-display / -audio / -input installed SIGINT/SIGTERM via
signal(), which on glibc carries BSD SA_RESTART semantics. With the
listen socket blocking and no SO_RCVTIMEO, a SIGTERM at poweroff ran
the handler (set g_stop=1) but the kernel auto-restarted the blocking
accept() instead of returning EINTR — so the existing
`if (errno == EINTR) continue;` re-check never fired and the daemon sat
blocked in accept(). systemd then waited the full TimeoutStopSec (~90s)
per unit before SIGKILL, which presented as the VM hanging at shutdown.
Reconnecting the IDD display made accept() return a real connection, the
loop re-evaluated g_stop, and shutdown finally completed — the tell.
Switch all three to sigaction() with sa_flags=0 (no SA_RESTART), exactly
matching appsandbox-agent.c, which already shuts down promptly for this
reason. accept() now returns EINTR on SIGTERM and the loop exits cleanly.
- systemd-user environment generator and appsandbox-gpu wrapper both
append /usr/lib/wsl/lib to LD_LIBRARY_PATH. ldconfig has the .so.1
entries cached, but the 9P mount is read-only so the unversioned
symlinks ldconfig wants to create can't be written. dlopen() of
unversioned names (libcuda.so, libnvidia-encode.so, libnvcuvid.so)
needs the path on LD_LIBRARY_PATH — without it ffmpeg's h264_nvenc /
h264_cuvid plugins fail with 'Cannot load libcuda.so.1'.
- setup.sh: symlink /usr/lib/wsl/lib/nvidia-smi to /usr/local/bin so
the bare command works without env tweaks. Created unconditionally
because the symlink target is resolved at exec time — the file
appears under the agent's 9P mount on first boot regardless of when
the symlink was created.
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.