macOS: guest agent, online templates, virtio-fs shares, persistent home volume
Apple's built-in QEMU guest agent (AppleQEMUGuestAgent, launched by launchd when a virtio console port org.qemu.guest_agent.0 appears; guest-exec as root) is attached by vmix run --macos and the NixOS module. AppleVirtIO.kext on x86 Tahoe drives virtio-fs, block, console, input, net — verified in QEMU. - customizeImage: `bootScript` — online step through the guest agent (driver mode qga): boot the image, run the script as root with the VMIX volume, shut down through the agent. `as_user` runs commands in the logged-in session. - templates.software: pkg/app (offline in the PE), script/homebrew (online). - templates.profile.settings: widgets, wallpaper (pinned desktoppr — Apple Events need TCC consent that a headless session cannot give), dock apps, autohide, dark mode, hidden files. - generalize: persistHome (fstab LABEL=vmix-home /Users), hideWidgets offline. - formatVolume: formats a blank disk image as APFS by booting the PE (~35 s); idempotent. - NixOS module: macos.guestAgent (/run/vmix/qga-<name>.sock), shares via virtiofsd + vhost-user-fs (Apple automount tag for the first share, others mounted through the agent), macos.homeDisk (created + formatted on first start, virtio-blk), SPICE keeps -vga vmware for macOS. - CLI: vmix run --macos --share DIR --home FILE --qga PATH. - qemu.nix helpers; README section. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XsESshRCoBoUVWV9qKURUF
This commit is contained in:
parent
50ad8d521c
commit
0f9373263d
13 changed files with 600 additions and 19 deletions
56
lib/images/macos/helpers/formatVolume.nix
Normal file
56
lib/images/macos/helpers/formatVolume.nix
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Host-side script that formats a blank disk image as an APFS volume with a
|
||||
# given label by booting the image's PE headless for ~30 s (Linux cannot write
|
||||
# APFS). Used for the persistent home volume (`generalize { persistHome = true; }`
|
||||
# mounts LABEL=<label> at /Users) by the NixOS module and `vmix run --home`.
|
||||
# ${formatVolume { inherit image; label = "vmix-home"; }} <disk-file> <raw|qcow2>
|
||||
# The disk is found inside the PE as the only one without a partition table.
|
||||
{ pkgs, lib, qemu, makeVmixVolume, makeBootDisk, vmDriver, ... }:
|
||||
{ image, label ? "vmix-home", memSize ? 4096 }:
|
||||
let
|
||||
pe = image.pe or (throw "vmix: image ${image.name} carries no PE");
|
||||
bootDisk = makeBootDisk { name = "${label}-format"; esp = image.opencore; bootArgs = "keepsyms=1 serial=3"; scanPolicy = 66051; };
|
||||
runScript = pkgs.writeText "format-${label}.sh" ''
|
||||
. /Volumes/VMIX/pe-lib.sh
|
||||
LIST=$(diskutil list)
|
||||
# idempotent: a run.sh re-run (e.g. after a guest reboot) finds the volume done
|
||||
if echo "$LIST" | grep -q "APFS Volume ${label} "; then echo "vmix: volume '${label}' already exists"; exit 0; fi
|
||||
# blank disk: a whole-disk line followed by no partition entries
|
||||
TARGET=$(echo "$LIST" | awk '/^\/dev\/disk[0-9]+ / {d=$1; n=0; next} /^ +[0-9]+:/ {n++} /^$/ {if (d != "" && n <= 1) print d; d=""} END {if (d != "" && n <= 1) print d}' | grep -vE "synthesized" | head -1)
|
||||
[ -n "$TARGET" ] || { echo "$LIST"; pe_fail "no blank disk found"; }
|
||||
echo "vmix: formatting $TARGET as APFS '${label}'"
|
||||
diskutil eraseDisk APFS "${label}" GPT "$TARGET" || pe_fail "eraseDisk $TARGET"
|
||||
diskutil unmount "/Volumes/${label}" >/dev/null 2>&1 || true
|
||||
'';
|
||||
vmixVol = makeVmixVolume {
|
||||
name = "format-${label}";
|
||||
files = [ { source = runScript; name = "run.sh"; } { source = ../guest/pe-lib.sh; name = "pe-lib.sh"; } ];
|
||||
};
|
||||
driverPython = pkgs.python3.withPackages (p: [ p.pillow ]);
|
||||
in
|
||||
pkgs.writeShellScript "vmix-format-${label}" ''
|
||||
set -eu
|
||||
DISK="$1"; FMT="''${2:-qcow2}"
|
||||
T=$(mktemp -d /tmp/vmix-format-XXXXXX)
|
||||
cleanup() { if [ "''${OK:-0}" = 1 ]; then rm -rf "$T"; else echo "vmix: logs kept in $T"; fi; }
|
||||
trap cleanup EXIT
|
||||
cp ${vmixVol} "$T/vmix.img"; chmod +w "$T/vmix.img"
|
||||
${pkgs.qemu}/bin/qemu-img create -q -f qcow2 -F raw -b ${pe} "$T/pe.qcow2"
|
||||
${pkgs.qemu}/bin/qemu-img create -q -f qcow2 -F raw -b ${bootDisk}/boot.img "$T/ocboot.qcow2"
|
||||
cp ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd "$T/vars.fd"; chmod +w "$T/vars.fd"
|
||||
echo "vmix: formatting $DISK as APFS '${label}' (PE boot)"
|
||||
${driverPython}/bin/python3 ${vmDriver} --mode pe --name "format-${label}" --debug-dir "$T/debug" --timeout 600 \
|
||||
--serial-log "$T/serial.log" -- \
|
||||
${pkgs.qemu}/bin/qemu-system-x86_64 -display none \
|
||||
${qemu.machineArgs { smp = 2; inherit memSize; }} \
|
||||
${qemu.firmwareArgs "$T/vars.fd"} \
|
||||
${qemu.serialArgs "$T/serial.log"} \
|
||||
${qemu.sataDrive { id = "opencore"; port = 0; file = "$T/ocboot.qcow2"; }} \
|
||||
${qemu.sataDrive { id = "pe"; port = 1; file = "$T/pe.qcow2"; }} \
|
||||
${qemu.sataDrive { id = "vmix"; port = 2; file = "$T/vmix.img"; format = "raw"; }} \
|
||||
${qemu.virtioBlkArgs { id = "target"; file = "$DISK"; format = "$FMT"; }} \
|
||||
>/dev/null
|
||||
STATUS=$(${pkgs.libguestfs-with-appliance}/bin/guestfish --ro -a "$T/vmix.img" -m /dev/sda1 cat /vmix-run.status 2>/dev/null | tr -d '[:space:]' || true)
|
||||
[ "$STATUS" = "0" ] || { echo "vmix: formatting failed (status '$STATUS')"; ${pkgs.libguestfs-with-appliance}/bin/guestfish --ro -a "$T/vmix.img" -m /dev/sda1 cat /vmix-run.log 2>/dev/null | tail -20; exit 1; }
|
||||
OK=1
|
||||
echo "vmix: $DISK formatted"
|
||||
''
|
||||
Loading…
Add table
Add a link
Reference in a new issue