macOS: persistent home lives at /Volumes/vmix-home/<user> (no mount over /Users)
macOS refuses to mount a volume over /Users (firmlink), and a failing fstab entry also suppresses the automount. Point NFSHomeDirectory at the automounted volume instead. Profile: Dock entries need tile-type/file URL; wait for the wallpaper store before shutting down. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XsESshRCoBoUVWV9qKURUF
This commit is contained in:
parent
0f9373263d
commit
e241364ce9
4 changed files with 23 additions and 17 deletions
|
|
@ -154,11 +154,13 @@ net, sound, balloon, vsock — QEMU's modern virtio-pci devices work as-is:
|
||||||
for every `shares.<name>` beyond the first. `vmix run --macos --share DIR`.
|
for every `shares.<name>` beyond the first. `vmix run --macos --share DIR`.
|
||||||
(9p does not automount on macOS; the Linux `-virtfs` path is not used.)
|
(9p does not automount on macOS; the Linux `-virtfs` path is not used.)
|
||||||
* **ephemeral OS disk + persistent home**: `generalize { persistHome = true; }`
|
* **ephemeral OS disk + persistent home**: `generalize { persistHome = true; }`
|
||||||
adds `LABEL=vmix-home /Users apfs rw 0 2` to the image's fstab. The host
|
gives the account its home directory on an APFS volume labelled `vmix-home`
|
||||||
provides a virtio-blk disk (`macos.homeDisk` in the module, `--home FILE` in
|
(`NFSHomeDirectory = /Volumes/vmix-home/<user>`; macOS refuses mounts over
|
||||||
the CLI: qcow2/raw file or zvol) that `formatVolume` formats as APFS
|
`/Users`, which is a firmlink). The host provides a virtio-blk disk
|
||||||
`vmix-home` by booting the PE for ~35 s on first use. macOS mounts it at
|
(`macos.homeDisk` in the module, `--home FILE` in the CLI: qcow2/raw file or
|
||||||
`/Users` before login, home directories are created there; the OS disk can run
|
zvol) that `formatVolume` formats as APFS `vmix-home` by booting the PE for
|
||||||
|
~35 s on first use; diskarbitrationd mounts it before login and loginwindow
|
||||||
|
creates the home directory there on first login. The OS disk can then run
|
||||||
with `snapshot=on` (`disks.os.persist = false`).
|
with `snapshot=on` (`disks.os.persist = false`).
|
||||||
* **SPICE**: `-vga vmware` (or `std`) is kept as the display device — macOS has
|
* **SPICE**: `-vga vmware` (or `std`) is kept as the display device — macOS has
|
||||||
no QXL/virtio-gpu driver; USB redirection channels work as for other guests
|
no QXL/virtio-gpu driver; USB redirection channels work as for other guests
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@
|
||||||
uuid ? null,
|
uuid ? null,
|
||||||
mac ? null,
|
mac ? null,
|
||||||
seed ? "${hostname}-${username}",
|
seed ? "${hostname}-${username}",
|
||||||
# mount an APFS volume labelled vmix-home (a virtio-blk/AHCI disk the host
|
# keep the user's home on an APFS volume labelled vmix-home (a virtio-blk disk
|
||||||
# provides, formatted by the PE on first start) at /Users: ephemeral OS disk,
|
# the host provides, formatted by the PE on first start). macOS refuses mounts
|
||||||
# persistent home directories
|
# over /Users (firmlink), so the volume automounts at /Volumes/<label> and the
|
||||||
|
# account's home directory lives there: ephemeral OS disk, persistent home.
|
||||||
persistHome ? false,
|
persistHome ? false,
|
||||||
homeVolumeLabel ? "vmix-home",
|
homeVolumeLabel ? "vmix-home",
|
||||||
# no desktop widgets for the created user (Sonoma+)
|
# no desktop widgets for the created user (Sonoma+)
|
||||||
|
|
@ -61,6 +62,7 @@ in
|
||||||
${lib.optionalString (!delayOobeRun) ''
|
${lib.optionalString (!delayOobeRun) ''
|
||||||
# --- user account (admin), created directly in the local directory node
|
# --- user account (admin), created directly in the local directory node
|
||||||
U="${username}"; HOME_DIR="$DATA/Users/$U"
|
U="${username}"; HOME_DIR="$DATA/Users/$U"
|
||||||
|
${lib.optionalString persistHome ''HOME_PATH="/Volumes/${homeVolumeLabel}/$U"''}
|
||||||
if ! D -read "/Local/Default/Users/$U" >/dev/null 2>&1; then
|
if ! D -read "/Local/Default/Users/$U" >/dev/null 2>&1; then
|
||||||
UID_NEW=$(D -list /Local/Default/Users UniqueID | awk '$2 >= 501 && $2 < 1000 && $2 > m {m = $2} END {print (m ? m + 1 : 501)}')
|
UID_NEW=$(D -list /Local/Default/Users UniqueID | awk '$2 >= 501 && $2 < 1000 && $2 > m {m = $2} END {print (m ? m + 1 : 501)}')
|
||||||
D -create "/Local/Default/Users/$U" || pe_fail "dscl create user"
|
D -create "/Local/Default/Users/$U" || pe_fail "dscl create user"
|
||||||
|
|
@ -68,7 +70,7 @@ in
|
||||||
D -create "/Local/Default/Users/$U" RealName ${lib.escapeShellArg fullName}
|
D -create "/Local/Default/Users/$U" RealName ${lib.escapeShellArg fullName}
|
||||||
D -create "/Local/Default/Users/$U" UniqueID "$UID_NEW"
|
D -create "/Local/Default/Users/$U" UniqueID "$UID_NEW"
|
||||||
D -create "/Local/Default/Users/$U" PrimaryGroupID 20
|
D -create "/Local/Default/Users/$U" PrimaryGroupID 20
|
||||||
D -create "/Local/Default/Users/$U" NFSHomeDirectory "/Users/$U"
|
D -create "/Local/Default/Users/$U" NFSHomeDirectory "${if persistHome then "/Volumes/${homeVolumeLabel}/$U" else "/Users/$U"}"
|
||||||
if ! D -passwd "/Local/Default/Users/$U" ${lib.escapeShellArg password}; then
|
if ! D -passwd "/Local/Default/Users/$U" ${lib.escapeShellArg password}; then
|
||||||
echo "vmix: WARNING: could not set the requested password, using '${tempPassword}'"
|
echo "vmix: WARNING: could not set the requested password, using '${tempPassword}'"
|
||||||
D -passwd "/Local/Default/Users/$U" "${tempPassword}" || pe_fail "dscl passwd"
|
D -passwd "/Local/Default/Users/$U" "${tempPassword}" || pe_fail "dscl passwd"
|
||||||
|
|
@ -132,11 +134,9 @@ in
|
||||||
pe_plist_set "$KT" keyboardtype.1-1575-0 integer 40
|
pe_plist_set "$KT" keyboardtype.1-1575-0 integer 40
|
||||||
|
|
||||||
${lib.optionalString persistHome ''
|
${lib.optionalString persistHome ''
|
||||||
# --- home directories on the host-provided persistent volume (fstab by label;
|
# --- persistent home: the seeded home directory on the Data volume is the
|
||||||
# diskarbitrationd mounts it at /Users when a volume named ${homeVolumeLabel} exists)
|
# template loginwindow copies to /Volumes/${homeVolumeLabel}/$U at first login
|
||||||
F="$DATA/private/etc/fstab"
|
# (the volume is automounted by diskarbitrationd before the login)
|
||||||
grep -q "LABEL=${homeVolumeLabel}" "$F" 2>/dev/null || echo "LABEL=${homeVolumeLabel} /Users apfs rw 0 2" >> "$F"
|
|
||||||
chmod 644 "$F"; chown 0:0 "$F"
|
|
||||||
''}
|
''}
|
||||||
|
|
||||||
# --- use the whole (possibly grown) disk
|
# --- use the whole (possibly grown) disk
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ rec {
|
||||||
{ source = desktoppr; name = "desktoppr.pkg"; }
|
{ source = desktoppr; name = "desktoppr.pkg"; }
|
||||||
];
|
];
|
||||||
bootScript = ''
|
bootScript = ''
|
||||||
|
set -x
|
||||||
[ -n "$CONSOLE_USER" ] || { echo "vmix: profile needs a logged-in user (generalize with autoLogon)"; exit 1; }
|
[ -n "$CONSOLE_USER" ] || { echo "vmix: profile needs a logged-in user (generalize with autoLogon)"; exit 1; }
|
||||||
H=$(dscl . -read "/Users/$CONSOLE_USER" NFSHomeDirectory | awk '{print $2}')
|
H=$(dscl . -read "/Users/$CONSOLE_USER" NFSHomeDirectory | awk '{print $2}')
|
||||||
D() { as_user defaults write "$@"; }
|
D() { as_user defaults write "$@"; }
|
||||||
|
|
@ -39,11 +40,14 @@ rec {
|
||||||
mkdir -p "/Library/Desktop Pictures"; cp "$V/wallpaper".* "$W"; chmod 644 "$W"
|
mkdir -p "/Library/Desktop Pictures"; cp "$V/wallpaper".* "$W"; chmod 644 "$W"
|
||||||
installer -pkg "$V/desktoppr.pkg" -target / >/dev/null || echo "vmix: WARNING: desktoppr install failed"
|
installer -pkg "$V/desktoppr.pkg" -target / >/dev/null || echo "vmix: WARNING: desktoppr install failed"
|
||||||
as_user /usr/local/bin/desktoppr "$W" || echo "vmix: WARNING: could not set the wallpaper"
|
as_user /usr/local/bin/desktoppr "$W" || echo "vmix: WARNING: could not set the wallpaper"
|
||||||
|
# the wallpaper store is written asynchronously; give it time before the shutdown
|
||||||
|
sleep 15
|
||||||
|
echo "vmix: wallpaper now: $(as_user /usr/local/bin/desktoppr 2>/dev/null)"
|
||||||
''}
|
''}
|
||||||
${lib.optionalString (dockApps != null) ''
|
${lib.optionalString (dockApps != null) ''
|
||||||
D com.apple.dock persistent-apps -array
|
D com.apple.dock persistent-apps -array
|
||||||
${lib.concatMapStringsSep "\n" (a: ''
|
${lib.concatMapStringsSep "\n" (a: ''
|
||||||
D com.apple.dock persistent-apps -array-add "<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>${a}</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>"
|
D com.apple.dock persistent-apps -array-add "<dict><key>tile-type</key><string>file-tile</string><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>file://${a}/</string><key>_CFURLStringType</key><integer>15</integer></dict></dict></dict>"
|
||||||
'') dockApps}
|
'') dockApps}
|
||||||
''}
|
''}
|
||||||
${lib.optionalString dockAutohide ''D com.apple.dock autohide -bool true''}
|
${lib.optionalString dockAutohide ''D com.apple.dock autohide -bool true''}
|
||||||
|
|
@ -52,7 +56,7 @@ rec {
|
||||||
then ''D -g AppleInterfaceStyle Dark''
|
then ''D -g AppleInterfaceStyle Dark''
|
||||||
else ''as_user defaults delete -g AppleInterfaceStyle 2>/dev/null || true'')}
|
else ''as_user defaults delete -g AppleInterfaceStyle 2>/dev/null || true'')}
|
||||||
as_user killall Dock Finder WindowManager 2>/dev/null || true
|
as_user killall Dock Finder WindowManager 2>/dev/null || true
|
||||||
sleep 3
|
sleep 10
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ with lib;
|
||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = "Persistent home volume: a host disk image attached as virtio-blk, formatted APFS with label `label` by the PE on first start. The image must be generalized with persistHome = true (fstab mounts it at /Users), which makes the OS disk safely ephemeral (disks.os.persist = false).";
|
description = "Persistent home volume: a host disk image attached as virtio-blk, formatted APFS with label `label` by the PE on first start. The image must be generalized with persistHome = true (the user's home is /Volumes/<label>/<user>), which makes the OS disk safely ephemeral (disks.os.persist = false).";
|
||||||
};
|
};
|
||||||
file = mkOption {
|
file = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue