diff --git a/flake.nix b/flake.nix index 2dbd6de..c8c027f 100644 --- a/flake.nix +++ b/flake.nix @@ -29,16 +29,19 @@ echo " vmix build --image [--generalize key=val,...] [--out-link PATH]" echo " vmix copy --image [--generalize key=val,...] --to-disk /dev/sdX" echo " vmix copy --image [--generalize key=val,...] --to-remote-disk user@host:/dev/sdX" + echo " vmix run [--mem 4096] [--smp 4] [--snapshot]" echo "" echo "Commands:" echo " build Build a vmix image (optionally generalized)" echo " copy Build a vmix image and write it to a disk" + echo " run Boot a qcow2 image with QEMU (SDL if DISPLAY available)" echo "" echo "Options:" echo " --image PATH Image path in vmixLib (e.g. windows.images.win10.laptop)" - echo " --generalize KEY=VAL,... Generalize with comma-separated options:" - echo " username=User password= hostname=WIN-PC" + echo " --generalize KEY=VAL,... Finalize image with comma-separated options:" + echo " username=User password= hostname=PC" echo " timezone=UTC bgColor=8e8cd8" + echo " delay-oobe-run=true (OOBE + activation on real hardware)" echo " --to-disk DEVICE Write to local disk and expand partitions" echo " --to-remote-disk SSH:DEV Stream to remote disk via SSH and expand partitions" echo " e.g. root@10.10.10.100:/dev/sda" @@ -63,11 +66,62 @@ COMMAND="$1"; shift case "$COMMAND" in - build|copy) ;; + build|copy|run) ;; --help|-h) usage ;; *) echo "Unknown command: $COMMAND"; usage ;; esac + # --- run command --- + if [[ "$COMMAND" == "run" ]]; then + [[ ''${#} -lt 1 ]] && { echo "Error: vmix run [--mem 4096] [--smp 4]"; exit 1; } + RUN_INPUT="$1"; shift + RUN_MEM=4096 + RUN_SMP=4 + while [[ ''${#} -gt 0 ]]; do + case "$1" in + --mem) RUN_MEM="$2"; shift 2 ;; + --smp) RUN_SMP="$2"; shift 2 ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac + done + + RUN_IMAGE="$RUN_INPUT" + [[ ! -f "$RUN_IMAGE" ]] && { echo "Error: file not found: $RUN_IMAGE"; exit 1; } + + VMIX_DISPLAY="-nographic" + if [[ -n "''${DISPLAY:-}" ]]; then + VMIX_DISPLAY="-display sdl" + fi + + cp ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd /tmp/vmix-run-vars-$$.fd + chmod +w /tmp/vmix-run-vars-$$.fd + trap 'rm -f /tmp/vmix-run-vars-$$.fd' EXIT + + echo "=== vmix run ===" + echo "Image: $RUN_IMAGE" + echo "Memory: $RUN_MEM MB" + echo "CPUs: $RUN_SMP" + echo "Display: $VMIX_DISPLAY" + echo "" + + exec ${pkgs.qemu}/bin/qemu-system-x86_64 \ + $VMIX_DISPLAY \ + -accel kvm \ + -m "$RUN_MEM" \ + -smp "$RUN_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=/tmp/vmix-run-vars-$$.fd \ + -rtc base=localtime,clock=host \ + -device qemu-xhci -device usb-tablet \ + -drive file="$RUN_IMAGE",format=qcow2,if=virtio,snapshot=on \ + -nic user,model=virtio-net-pci \ + -device virtio-serial-pci \ + -chardev spicevmc,id=vdagent,debug=0,name=vdagent \ + -device virtserialport,chardev=vdagent,name=com.redhat.spice.0 + fi + IMAGE_NAME="" GENERALIZE="" TO_DISK="" @@ -101,8 +155,14 @@ for pair in "''${PAIRS[@]}"; do key="''${pair%%=*}" val="''${pair#*=}" - NIX_ARGS+="$key = \"$val\"; " + if [[ "$val" == "true" || "$val" == "false" ]]; then + NIX_ARGS+="$key = $val; " + else + NIX_ARGS+="$key = \"$val\"; " + fi done + # CLI delay-oobe-run → nix delayOobeRun + NIX_ARGS="''${NIX_ARGS//delay-oobe-run/delayOobeRun}" GENERALIZE_EXPR=".generalize { $NIX_ARGS }" fi diff --git a/lib/images/windows/helpers/customizeImage.nix b/lib/images/windows/helpers/customizeImage.nix index 08b8a25..4e1e363 100644 --- a/lib/images/windows/helpers/customizeImage.nix +++ b/lib/images/windows/helpers/customizeImage.nix @@ -85,6 +85,8 @@ 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 VMIX_DISPLAY="-display sdl" fi ''} @@ -101,7 +103,6 @@ -drive if=pflash,format=raw,file=vars.fd \ -rtc base=localtime,clock=host \ -device qemu-xhci -device usb-tablet \ - -global ICH9-LMB.disable_s3=1 -global ICH9-LMB.disable_s4=1 \ -drive file=${resultImg},format=qcow2,if=virtio \ ${cdromArgs} \ -nic user,model=virtio-net-pci diff --git a/lib/images/windows/helpers/makeImage.nix b/lib/images/windows/helpers/makeImage.nix index 67084b1..5cc1ac6 100644 --- a/lib/images/windows/helpers/makeImage.nix +++ b/lib/images/windows/helpers/makeImage.nix @@ -51,6 +51,8 @@ let 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 VMIX_DISPLAY="-display sdl" fi ''} @@ -68,7 +70,6 @@ let -drive if=pflash,format=raw,file=vars.fd \ -rtc base=localtime,clock=host \ -device qemu-xhci -device usb-tablet \ - -global ICH9-LMB.disable_s3=1 -global ICH9-LMB.disable_s4=1 \ -drive file=disk.qcow2,format=qcow2,if=virtio \ -drive file=${iso},media=cdrom,readonly=on \ -drive file=${drivers.virtio-iso},media=cdrom,readonly=on \ diff --git a/lib/images/windows/templates/default.nix b/lib/images/windows/templates/default.nix index c13e39c..07ea447 100644 --- a/lib/images/windows/templates/default.nix +++ b/lib/images/windows/templates/default.nix @@ -19,6 +19,7 @@ in rec { vcppRuntimes = import ./essentials/vcpp-runtimes.nix args; bestPerformance = import ./essentials/best-performance.nix args; clearFileAssociations = import ./essentials/clear-file-associations.nix args; + virtioDrivers = import ./essentials/virtio-drivers.nix args; }; # Applications @@ -35,7 +36,7 @@ in rec { # Default file associations policy defaultApps = import ./default-apps.nix args; - # Generalize (sysprep + OOBE) + # Generalize (sysprep + OOBE). Pass seal=true for hardware deployment. generalize = import ./generalize.nix args; # Offline registry templates @@ -59,10 +60,10 @@ in rec { reg.performanceTweaks apps.edgeWebview apps.thorium - apps.sandboxie apps.sevenZip apps.vlc apps.imageGlass + essentials.virtioDrivers # needed for network during Office download apps.office ]; }; diff --git a/lib/images/windows/templates/essentials/virtio-drivers.nix b/lib/images/windows/templates/essentials/virtio-drivers.nix new file mode 100644 index 0000000..dbc9fe5 --- /dev/null +++ b/lib/images/windows/templates/essentials/virtio-drivers.nix @@ -0,0 +1,18 @@ +# Install VirtIO drivers only (no guest agent or SPICE) +# Used during build for network access, not needed on real hardware +{ drivers, ... }: +{ + name = "virtio-drv"; + cdroms = [ drivers.virtio-iso ]; + auditScript = '' + @echo off + if exist D:\cert\virtio_win_cert.cer ( + certutil -addstore TrustedPublisher D:\cert\virtio_win_cert.cer + ) + :: Install drivers via pnputil (network, storage, balloon, serial) + for %%d in (NetKVM vioinput viostor vioscsi Balloon vioserial) do ( + if exist "D:\%%d\w10\amd64" pnputil /add-driver "D:\%%d\w10\amd64\*.inf" /install 2>nul + if exist "D:\%%d\w11\amd64" pnputil /add-driver "D:\%%d\w11\amd64\*.inf" /install 2>nul + ) + ''; +} diff --git a/lib/images/windows/templates/generalize.nix b/lib/images/windows/templates/generalize.nix index efe1cc8..807e1c4 100644 --- a/lib/images/windows/templates/generalize.nix +++ b/lib/images/windows/templates/generalize.nix @@ -19,6 +19,9 @@ in timezone ? "UTC", # Desktop background solid color as hex string (e.g. "8e8cd8") bgColor ? null, + # delayOobeRun = true: sysprep only, OOBE + activation on real hardware + # delayOobeRun = false: sysprep + OOBE + activation in build VM + delayOobeRun ? false, }: let # Convert "8e8cd8" hex to "142 140 216" decimal RGB for Windows registry hexToRgbStr = hex: let @@ -74,7 +77,7 @@ in del /q C:\vmix-audit-script.cmd 2>nul del /q C:\vmix-audit-wrapper.cmd 2>nul - shutdown /s /t 5 /c "vmix generalize complete" + ${if delayOobeRun then "" else "shutdown /s /t 5 /c \"vmix generalize complete\""} del /q C:\post-oobe.cmd 2>nul ''; @@ -145,16 +148,17 @@ in ''; in { - name = "generalize"; + name = if delayOobeRun then "sealed" else "generalize"; uploads = [ { source = oobeXml; dest = "/oobe-unattend.xml"; } { source = postOobeScript; dest = "/post-oobe.cmd"; } { source = masScript; dest = "/MAS_AIO.cmd"; } ]; - # Sysprep reboots into OOBE within the same QEMU session + # delayOobeRun: sysprep + shutdown — OOBE runs on real hardware + # generalize: sysprep + reboot into OOBE in the same QEMU session auditScript = '' @echo off - C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /reboot /quiet /unattend:C:\oobe-unattend.xml + C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe ${if delayOobeRun then "/shutdown" else "/reboot"} /quiet /unattend:C:\oobe-unattend.xml ''; }