SDL fallback, partition fix, lz4 remote streaming, delay-oobe-run
SDL display: - try SDL, auto-fallback to headless if it fails (no crash) - SDL_VIDEODRIVER=x11 to avoid wayland socket path issues - suppress XDG_RUNTIME_DIR warnings Disk copy: - zap-all before writing to clear old partition tables - delete recovery partition (4) before resizing partition 3 - use parted resizepart (preserves partition GUID for BCD) - remote: nix-shell for sgdisk/parted/ntfsresize on target - remote: lz4 compression for faster streaming - remote: pv progress bar with disk size - -y/--yes flag to skip confirmation prompt Generalize: - delay-oobe-run=true defers OOBE + activation to real hardware - clean cached Autounattend from Windows\Panther before sysprep - taskkill sysprep.exe on first login (CopyProfile artifact) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ebfb10b3b3
commit
89a0673f54
4 changed files with 79 additions and 37 deletions
60
flake.nix
60
flake.nix
|
|
@ -126,6 +126,7 @@
|
|||
GENERALIZE=""
|
||||
TO_DISK=""
|
||||
TO_REMOTE_DISK=""
|
||||
YES=false
|
||||
OUT_LINK="./result"
|
||||
|
||||
while [[ ''${#} -gt 0 ]]; do
|
||||
|
|
@ -134,6 +135,7 @@
|
|||
--generalize) GENERALIZE="$2"; shift 2 ;;
|
||||
--to-disk) TO_DISK="$2"; shift 2 ;;
|
||||
--to-remote-disk) TO_REMOTE_DISK="$2"; shift 2 ;;
|
||||
-y|--yes) YES=true; shift ;;
|
||||
--out-link) OUT_LINK="$2"; shift 2 ;;
|
||||
--help|-h) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
|
|
@ -212,17 +214,24 @@
|
|||
echo "Target: $TO_DISK ($DISK_SIZE_GB GB)"
|
||||
echo ""
|
||||
echo "WARNING: This will DESTROY all data on $TO_DISK"
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
||||
if [[ "$YES" != "true" ]]; then
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[1/4] Writing image to disk..."
|
||||
echo "[1/5] Wiping partition table..."
|
||||
${pkgs.gptfdisk}/bin/sgdisk --zap-all "$TO_DISK"
|
||||
|
||||
echo "[2/5] Writing image to disk..."
|
||||
${pkgs.qemu}/bin/qemu-img convert -p -S 4k -f qcow2 -O raw "$IMAGE_FILE" "$TO_DISK"
|
||||
|
||||
echo "[2/4] Fixing GPT backup header..."
|
||||
echo "[3/5] Fixing GPT backup header..."
|
||||
${pkgs.gptfdisk}/bin/sgdisk -e "$TO_DISK"
|
||||
# delete recovery partition if present, then resize Windows partition
|
||||
${pkgs.gptfdisk}/bin/sgdisk -d 4 "$TO_DISK" 2>/dev/null || true
|
||||
|
||||
echo "[3/4] Expanding Windows partition (partition 3) to fill disk..."
|
||||
echo "[4/5] Expanding Windows partition (partition 3)..."
|
||||
${pkgs.parted}/bin/parted -s "$TO_DISK" resizepart 3 100%
|
||||
|
||||
if [[ "$TO_DISK" == *nvme* ]] || [[ "$TO_DISK" == *mmcblk* ]]; then
|
||||
|
|
@ -231,7 +240,7 @@
|
|||
WIN_PART="''${TO_DISK}3"
|
||||
fi
|
||||
|
||||
echo "[4/4] Expanding NTFS filesystem on $WIN_PART..."
|
||||
echo "[5/5] Expanding NTFS filesystem on $WIN_PART..."
|
||||
${pkgs.ntfs3g}/bin/ntfsresize --force --no-action "$WIN_PART"
|
||||
echo "y" | ${pkgs.ntfs3g}/bin/ntfsresize --force "$WIN_PART"
|
||||
|
||||
|
|
@ -252,19 +261,28 @@
|
|||
echo "Disk: $REMOTE_DISK"
|
||||
echo ""
|
||||
echo "WARNING: This will DESTROY all data on $REMOTE_HOST:$REMOTE_DISK"
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
||||
if [[ "$YES" != "true" ]]; then
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[1/4] Streaming image to remote disk..."
|
||||
${pkgs.qemu}/bin/qemu-img convert -f qcow2 -O raw "$IMAGE_FILE" /dev/stdout \
|
||||
| ssh "$REMOTE_HOST" "dd of=$REMOTE_DISK bs=4M status=progress oflag=direct"
|
||||
echo "[1/5] Wiping partition table..."
|
||||
ssh "$REMOTE_HOST" "sgdisk --zap-all $REMOTE_DISK"
|
||||
|
||||
echo "[2/4] Fixing GPT backup header..."
|
||||
ssh "$REMOTE_HOST" "sgdisk -e $REMOTE_DISK"
|
||||
|
||||
echo "[3/4] Expanding Windows partition (partition 3) to fill disk..."
|
||||
ssh "$REMOTE_HOST" "parted -s $REMOTE_DISK resizepart 3 100%"
|
||||
echo "[2/5] Streaming image to remote disk..."
|
||||
NBD_SOCK="/tmp/vmix-nbd-$$.sock"
|
||||
${pkgs.qemu}/bin/qemu-nbd --read-only -f qcow2 -k "$NBD_SOCK" "$IMAGE_FILE" &
|
||||
NBD_PID=$!
|
||||
trap "kill $NBD_PID 2>/dev/null; rm -f $NBD_SOCK" EXIT
|
||||
while [ ! -S "$NBD_SOCK" ]; do sleep 0.1; done
|
||||
DISK_SIZE=$(${pkgs.libnbd}/bin/nbdinfo --size "nbd+unix:///?socket=$NBD_SOCK")
|
||||
${pkgs.libnbd}/bin/nbdcopy --request-size=4194304 "nbd+unix:///?socket=$NBD_SOCK" - \
|
||||
| ${pkgs.pv}/bin/pv -s "$DISK_SIZE" \
|
||||
| ${pkgs.lib.getBin pkgs.lz4}/bin/lz4 -1 - \
|
||||
| ssh "$REMOTE_HOST" "nix-shell -p lz4 --run 'lz4 -d - - | dd of=$REMOTE_DISK bs=4M iflag=fullblock oflag=direct conv=sparse'"
|
||||
kill $NBD_PID 2>/dev/null || true
|
||||
rm -f "$NBD_SOCK"
|
||||
|
||||
if [[ "$REMOTE_DISK" == *nvme* ]] || [[ "$REMOTE_DISK" == *mmcblk* ]]; then
|
||||
REMOTE_WIN_PART="''${REMOTE_DISK}p3"
|
||||
|
|
@ -272,8 +290,14 @@
|
|||
REMOTE_WIN_PART="''${REMOTE_DISK}3"
|
||||
fi
|
||||
|
||||
echo "[4/4] Expanding NTFS filesystem on $REMOTE_WIN_PART..."
|
||||
ssh "$REMOTE_HOST" "echo y | ntfsresize --force $REMOTE_WIN_PART"
|
||||
echo "[3/5] Fixing GPT backup header..."
|
||||
ssh "$REMOTE_HOST" "nix-shell -p gptfdisk --run 'sgdisk -e $REMOTE_DISK && sgdisk -d 4 $REMOTE_DISK 2>/dev/null || true'"
|
||||
|
||||
echo "[4/5] Expanding Windows partition (partition 3)..."
|
||||
ssh "$REMOTE_HOST" "nix-shell -p parted --run 'parted -s $REMOTE_DISK resizepart 3 100%'"
|
||||
|
||||
echo "[5/5] Expanding NTFS filesystem on $REMOTE_WIN_PART..."
|
||||
ssh "$REMOTE_HOST" "nix-shell -p ntfs3g --run 'echo y | ntfsresize --force $REMOTE_WIN_PART'"
|
||||
|
||||
echo ""
|
||||
echo "Done. $REMOTE_HOST:$REMOTE_DISK is ready to boot."
|
||||
|
|
|
|||
|
|
@ -80,32 +80,36 @@
|
|||
chmod +w vars.fd
|
||||
|
||||
VMIX_DISPLAY="-nographic"
|
||||
${lib.optionalString (displayArg != null) ''VMIX_DISPLAY="${displayArg}"''}
|
||||
${lib.optionalString (displayArg == null) ''
|
||||
${lib.optionalString (vncDisplay != null) ''VMIX_DISPLAY="-vnc ${vncDisplay}"''}
|
||||
${lib.optionalString (vncDisplay == null) ''
|
||||
VMIX_DF=$(ls -t /tmp/.vmix-display-* 2>/dev/null | head -1)
|
||||
if [ -n "$VMIX_DF" ]; then
|
||||
export DISPLAY=$(cat "$VMIX_DF")
|
||||
export HOME=$(mktemp -d)
|
||||
export XDG_RUNTIME_DIR=$HOME
|
||||
export SDL_VIDEODRIVER=x11
|
||||
VMIX_DISPLAY="-display sdl"
|
||||
fi
|
||||
''}
|
||||
|
||||
echo "=== vmix: booting Audit Mode for ${name} ==="
|
||||
timeout 1800 qemu-system-x86_64 \
|
||||
$VMIX_DISPLAY \
|
||||
-accel kvm \
|
||||
-m ${toString memSize} \
|
||||
-smp ${toString smp} \
|
||||
-cpu host \
|
||||
-machine type=q35 \
|
||||
QEMU_ARGS="-accel kvm -m ${toString memSize} -smp ${toString smp} -cpu host -machine type=q35 \
|
||||
-drive if=pflash,format=raw,readonly=on,file=${pkgs.OVMF.fd}/FV/OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,file=vars.fd \
|
||||
-rtc base=localtime,clock=host \
|
||||
-device qemu-xhci -device usb-tablet \
|
||||
-drive file=${resultImg},format=qcow2,if=virtio \
|
||||
${cdromArgs} \
|
||||
-nic user,model=virtio-net-pci
|
||||
-nic user,model=virtio-net-pci"
|
||||
|
||||
timeout 1800 qemu-system-x86_64 $VMIX_DISPLAY $QEMU_ARGS || \
|
||||
if [[ "$VMIX_DISPLAY" == "-display sdl" ]]; then
|
||||
echo "=== vmix: SDL failed, retrying headless ==="
|
||||
cp ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd vars.fd && chmod +w vars.fd
|
||||
timeout 1800 qemu-system-x86_64 -nographic $QEMU_ARGS
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== vmix: audit script ${name} complete ==="
|
||||
'';
|
||||
|
|
|
|||
|
|
@ -53,19 +53,14 @@ let
|
|||
export DISPLAY=$(cat "$VMIX_DF")
|
||||
export HOME=$(mktemp -d)
|
||||
export XDG_RUNTIME_DIR=$HOME
|
||||
export SDL_VIDEODRIVER=x11
|
||||
VMIX_DISPLAY="-display sdl"
|
||||
fi
|
||||
''}
|
||||
|
||||
# Windows installs unattended, reboots into Audit Mode,
|
||||
# deletes cached Autounattend, shuts down → QEMU exits.
|
||||
timeout 3600 qemu-system-x86_64 \
|
||||
$VMIX_DISPLAY \
|
||||
-accel kvm \
|
||||
-m ${toString memSize} \
|
||||
-smp ${toString smp} \
|
||||
-cpu host \
|
||||
-machine type=q35 \
|
||||
QEMU_ARGS="-accel kvm -m ${toString memSize} -smp ${toString smp} -cpu host -machine type=q35 \
|
||||
-drive if=pflash,format=raw,readonly=on,file=${pkgs.OVMF.fd}/FV/OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,file=vars.fd \
|
||||
-rtc base=localtime,clock=host \
|
||||
|
|
@ -73,7 +68,16 @@ let
|
|||
-drive file=disk.qcow2,format=qcow2,if=virtio \
|
||||
-drive file=${iso},media=cdrom,readonly=on \
|
||||
-drive file=${drivers.virtio-iso},media=cdrom,readonly=on \
|
||||
-nic user,model=virtio-net-pci
|
||||
-nic user,model=virtio-net-pci"
|
||||
|
||||
timeout 3600 qemu-system-x86_64 $VMIX_DISPLAY $QEMU_ARGS || \
|
||||
if [[ "$VMIX_DISPLAY" == "-display sdl" ]]; then
|
||||
echo "=== vmix: SDL failed, retrying headless ==="
|
||||
cp ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd vars.fd && chmod +w vars.fd
|
||||
timeout 3600 qemu-system-x86_64 -nographic $QEMU_ARGS
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== vmix: ${name} install complete ==="
|
||||
mv disk.qcow2 $out
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ in
|
|||
reg add "HKCU\Control Panel\Desktop" /v WallpaperStyle /t REG_SZ /d "0" /f
|
||||
''}
|
||||
|
||||
:: Kill sysprep if it was triggered via CopyProfile'd startup entries
|
||||
taskkill /f /im sysprep.exe 2>nul
|
||||
:: Clean any leftover RunOnce/Run entries from audit phase
|
||||
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /v "vmixAudit" /f 2>nul
|
||||
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "vmixAudit" /f 2>nul
|
||||
|
||||
:: Remove Edge AppxPackage for current user (runs in user context during OOBE)
|
||||
:: The app is already removed on one of the templates but a ghost appx entry remains that can only be deleted at the user level
|
||||
powershell -Command "Get-AppxPackage *MicrosoftEdge* | Remove-AppxPackage -ErrorAction SilentlyContinue"
|
||||
|
|
@ -148,7 +154,7 @@ in
|
|||
</unattend>
|
||||
'';
|
||||
in {
|
||||
name = if delayOobeRun then "sealed" else "generalize";
|
||||
name = if delayOobeRun then "generalize-delay-oobe" else "generalize";
|
||||
uploads = [
|
||||
{ source = oobeXml; dest = "/oobe-unattend.xml"; }
|
||||
{ source = postOobeScript; dest = "/post-oobe.cmd"; }
|
||||
|
|
@ -158,6 +164,10 @@ in {
|
|||
# generalize: sysprep + reboot into OOBE in the same QEMU session
|
||||
auditScript = ''
|
||||
@echo off
|
||||
:: Remove cached Autounattend from initial install (contains Audit Mode reseal)
|
||||
del /q C:\Windows\Panther\unattend.xml 2>nul
|
||||
del /q C:\Windows\Panther\Unattend\unattend.xml 2>nul
|
||||
del /q C:\Windows\System32\Sysprep\Panther\unattend.xml 2>nul
|
||||
C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe ${if delayOobeRun then "/shutdown" else "/reboot"} /quiet /unattend:C:\oobe-unattend.xml
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue