Commit Graph
6 Commits
Author SHA1 Message Date
James Stringer dfd6fe475f Win-on-Mac: fix ivshmem AGENT/SSH region overlap and display readiness
Post-review fixes and release prep for the win-on-mac feature.

* asb_shm_layout.h: fix a region-sizing bug -- the AGENT (ch1) slot stride
  (0x20070) exceeded ASB_AGENT_REGION_SIZE (0x20000) by 112 bytes, so the agent
  host->guest ring overran into the adjacent SSH (ch7) region and corrupted its
  slot state under sustained traffic. Enlarge the AGENT region to 0x21000
  (downstream offsets cascade) and add per-region compile-time guards
  (n_slots*stride <= region_size) so this class of overlap fails the build.
  Host/publisher-side only -- the guest reads geometry from the published directory.

* headless.m: GET /display readiness now includes idd_ready for a Windows guest,
  matching the open precondition (asb_core_mac.m) and the Windows host
  (asb_vm_idd_ready). Before, the Mac readiness was running && agent_online only,
  so it could report ready before the VDD was up and a poll-then-open would
  spuriously 409 display_not_ready. macOS guests are unchanged.

* iso-patch-mac.m (stage): wipe the plaintext password buffer on the
  --shadowhash-file read-failure path, matching the sibling --kcpassword-file path.

* iso_patch_mac.m: point the signed Windows guest payload at the v0.1.4 release.

* vm_lifecycle.py: raise the Windows build-stall budget to 3600 s for the
  signal-less first-boot window under concurrent multi-VM load.

* tools/headless-api/README.md: document Windows guests on macOS hosts (platform
  matrix, host-fixed line, requirements heading, osType field).
2026-06-30 18:12:32 -04:00
James Stringer 303b34dab8 Win-on-Mac: encode the guest password in the daemon for all three configs
All three guest configs now obfuscate/hash the account credential in the daemon
and hand the disk-applier child only the non-plaintext result -- the plaintext
never crosses the process boundary, mirroring the Windows-on-Windows model.

  * Win-on-Mac: the daemon generates unattend.xml / setup.cmd / SetupComplete.cmd
    in-process via the shared asb_provision_* (base64 of UTF-16LE(pass+"Password"),
    the same encoder the Windows backend uses) and passes build-windows only
    --prov-dir; the plaintext --pass argv is gone. The child still detects the
    install.wim language and reports LANG:, and the daemon re-generates unattend.xml
    in-process with the detected tag -- the exact mirror of vhdx_create_thread.
  * Mac-on-Mac: the salted PBKDF2-SHA512 ShadowHash + kcpassword are computed in
    the daemon via the new shared tools/provision/mac_account_hash.{h,m}; the stage
    child takes --shadowhash-file / --kcpassword-file. --user-password-file remains
    a direct-CLI fallback.
  * Verified end-to-end on live Windows + macOS guests with a 12-character mixed
    Chinese/English password (ValidateCredentials / dscl -authonly), exercising the
    UTF-16 surrogate path.

Also in this checkpoint of accumulated win-on-mac work:

  * agent: report_idd_status re-runs on the device-check loop (force-on-connect +
    on-change) so display readiness tracks the live, self-healed VDD state instead
    of a single one-shot sample.
  * disk builder (tools/iso-patch-mac/engine): WIM reader gains the install-image
    default-language reader behind the language auto-detect; win_disk locale/apply
    refinements.
  * display + transport: idd_display Metal render + clipboard-reader paths;
    asb_ivshmem_transport slot-reclaim hardening.
  * housekeeping: remove the dev-only transport test harness (tools/transport/test/*);
    update headless-api test scripts; drop changelog-style comments.
2026-06-29 20:50:32 -04:00
James Stringer 9b9b92f053 Add the display window to the macOS headless daemon; retire the plan docs
Bring macOS to parity with the Windows display feature: open/close/poll the VM's
display window over the API (POST/DELETE/GET /v1/vms/{name}/display, plus
displayOpen on every status object), so a script can show the same view the GUI's
Connect button does. Validated live on Apple silicon.

The GUI already creates the per-VM VZVirtualMachineView window on the Running
transition (gated off under --headless); the daemon now opens it on an explicit
request after two gates:
  - A-gate: asb_mac_have_gui_session() (CGSessionCopyCurrentDictionary /
    kCGSessionOnConsoleKey) -- a daemon with no console Aqua session (launchd /
    SSH) refuses with 409 no_display instead of spawning an invisible window;
  - readiness: running && agent_online. macOS has no frame channel or display
    driver (the view binds the in-process VM framebuffer directly), so there is
    nothing to probe -- unlike the Windows/Linux idd_status latch.
New core API asb_mac_open_display / asb_mac_close_display (asb_core_mac.m); new
codes BACKEND_ERR_NO_DISPLAY / BACKEND_ERR_NOT_READY (asb_types.h).

run_headless swaps the bare CFRunLoopRun() for [NSApp run] (Accessory policy)
ONLY in a console GUI session -- the window needs the window server; otherwise it
keeps the bare CFRunLoop so the daemon still runs headless over SSH/launchd. Both
loops drain the main dispatch queue, so every VZ op and the SIGINT/SIGTERM sources
are unaffected; cleanup_and_exit exit()s directly, so no quit-path change. The
spike the design flagged as unverifiable without a Mac was confirmed here: VZ
main-queue dispatches still fire under [NSApp run], and windows open, take focus,
and deliver windowWillClose:.

Close tracking avoids a mid-close use-after-free: VzDisplayWindow gains a
userClosed flag set in windowWillClose:; displayOpen reads display && !userClosed,
and open_display reaps a self-(X)-closed window on the next request (off the WM
close stack), mirroring the Windows reap. showDisplay also calls
activateIgnoringOtherApps: so the Accessory daemon's window comes to the front.

tests: vm_lifecycle.py now runs the display open/close check on macOS too (it
self-skips on 409 no_display or a Linux guest whose agent predates idd_status).
README documents the macOS display support (matrix row, the console-session
A-gate, and the running && agentOnline readiness).

Remove tools/headless-api/PLAN.md and display-plan.md: these were the design /
decision logs for the headless port and the display feature, both now shipped and
validated. Their dangling references in README.md and headless.m comments are
cleaned up in the same change.
2026-06-11 20:27:52 -04:00
James Stringer 889ccbc3a1 Implement headless display open/close with agent-reported readiness
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.
2026-06-11 18:14:13 -04:00
James Stringer 929ad69f89 Make the headless SDK and test suite cross-platform; update docs
asb.py: resolve the support directory, daemon launch hint, and SSH key path per
platform (%ProgramData%\AppSandbox on Windows, ~/Library/Application Support/
AppSandbox on macOS) so the same SDK drives either daemon from its host.json.

tests: the suite is now capability-gated and runs on either host. helpers.py
adds IS_MAC, the per-platform VM/disk paths, the macOS IPSW location, a key-auth
login helper (chmod on mac), and wait_progress -- a stall-based wait that watches
the VM's reported progress/status instead of a fixed deadline, so a slow install
or IPSW download doesn't false-fail. run_all.py launches the macOS suite as two
concurrent VMs (one downloading a fresh IPSW, one from cache) sized 4096 MB / 2
cores / 64 GB, with a leftover sweep and IPSW housekeeping. The lifecycle and
host/validation tests assert 501 for snapshots/templates on macOS and run the
Windows-only template build only where supported.

docs: README gains a platform-support matrix (which guest OSes and host features
each platform supports) and the macOS sudo/SUDO_USER launch model alongside the
Windows instructions. PLAN.md records the macOS port, the Windows
cleanup-on-exit fix, the guest-agent threading fix, and the startup fail-fast /
GUI polish.
2026-06-10 19:00:04 -04:00
James Stringer 4087baa4bf Add headless --headless daemon, HTTP/JSON API + Python SDK, and SSH key deploy
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).
2026-06-09 20:12:18 -04:00