Compare commits
2 commits
015714f713
...
89a0673f54
| Author | SHA1 | Date | |
|---|---|---|---|
| 89a0673f54 | |||
| ebfb10b3b3 |
6 changed files with 175 additions and 48 deletions
118
flake.nix
118
flake.nix
|
|
@ -29,16 +29,19 @@
|
||||||
echo " vmix build --image <path> [--generalize key=val,...] [--out-link PATH]"
|
echo " vmix build --image <path> [--generalize key=val,...] [--out-link PATH]"
|
||||||
echo " vmix copy --image <path> [--generalize key=val,...] --to-disk /dev/sdX"
|
echo " vmix copy --image <path> [--generalize key=val,...] --to-disk /dev/sdX"
|
||||||
echo " vmix copy --image <path> [--generalize key=val,...] --to-remote-disk user@host:/dev/sdX"
|
echo " vmix copy --image <path> [--generalize key=val,...] --to-remote-disk user@host:/dev/sdX"
|
||||||
|
echo " vmix run <qcow2-file> [--mem 4096] [--smp 4] [--snapshot]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Commands:"
|
echo "Commands:"
|
||||||
echo " build Build a vmix image (optionally generalized)"
|
echo " build Build a vmix image (optionally generalized)"
|
||||||
echo " copy Build a vmix image and write it to a disk"
|
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 ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --image PATH Image path in vmixLib (e.g. windows.images.win10.laptop)"
|
echo " --image PATH Image path in vmixLib (e.g. windows.images.win10.laptop)"
|
||||||
echo " --generalize KEY=VAL,... Generalize with comma-separated options:"
|
echo " --generalize KEY=VAL,... Finalize image with comma-separated options:"
|
||||||
echo " username=User password= hostname=WIN-PC"
|
echo " username=User password= hostname=PC"
|
||||||
echo " timezone=UTC bgColor=8e8cd8"
|
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-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 " --to-remote-disk SSH:DEV Stream to remote disk via SSH and expand partitions"
|
||||||
echo " e.g. root@10.10.10.100:/dev/sda"
|
echo " e.g. root@10.10.10.100:/dev/sda"
|
||||||
|
|
@ -63,15 +66,67 @@
|
||||||
COMMAND="$1"; shift
|
COMMAND="$1"; shift
|
||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
build|copy) ;;
|
build|copy|run) ;;
|
||||||
--help|-h) usage ;;
|
--help|-h) usage ;;
|
||||||
*) echo "Unknown command: $COMMAND"; usage ;;
|
*) echo "Unknown command: $COMMAND"; usage ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# --- run command ---
|
||||||
|
if [[ "$COMMAND" == "run" ]]; then
|
||||||
|
[[ ''${#} -lt 1 ]] && { echo "Error: vmix run <qcow2-file-or-image-name> [--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=""
|
IMAGE_NAME=""
|
||||||
GENERALIZE=""
|
GENERALIZE=""
|
||||||
TO_DISK=""
|
TO_DISK=""
|
||||||
TO_REMOTE_DISK=""
|
TO_REMOTE_DISK=""
|
||||||
|
YES=false
|
||||||
OUT_LINK="./result"
|
OUT_LINK="./result"
|
||||||
|
|
||||||
while [[ ''${#} -gt 0 ]]; do
|
while [[ ''${#} -gt 0 ]]; do
|
||||||
|
|
@ -80,6 +135,7 @@
|
||||||
--generalize) GENERALIZE="$2"; shift 2 ;;
|
--generalize) GENERALIZE="$2"; shift 2 ;;
|
||||||
--to-disk) TO_DISK="$2"; shift 2 ;;
|
--to-disk) TO_DISK="$2"; shift 2 ;;
|
||||||
--to-remote-disk) TO_REMOTE_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 ;;
|
--out-link) OUT_LINK="$2"; shift 2 ;;
|
||||||
--help|-h) usage ;;
|
--help|-h) usage ;;
|
||||||
*) echo "Unknown option: $1"; usage ;;
|
*) echo "Unknown option: $1"; usage ;;
|
||||||
|
|
@ -101,8 +157,14 @@
|
||||||
for pair in "''${PAIRS[@]}"; do
|
for pair in "''${PAIRS[@]}"; do
|
||||||
key="''${pair%%=*}"
|
key="''${pair%%=*}"
|
||||||
val="''${pair#*=}"
|
val="''${pair#*=}"
|
||||||
|
if [[ "$val" == "true" || "$val" == "false" ]]; then
|
||||||
|
NIX_ARGS+="$key = $val; "
|
||||||
|
else
|
||||||
NIX_ARGS+="$key = \"$val\"; "
|
NIX_ARGS+="$key = \"$val\"; "
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
# CLI delay-oobe-run → nix delayOobeRun
|
||||||
|
NIX_ARGS="''${NIX_ARGS//delay-oobe-run/delayOobeRun}"
|
||||||
GENERALIZE_EXPR=".generalize { $NIX_ARGS }"
|
GENERALIZE_EXPR=".generalize { $NIX_ARGS }"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -152,17 +214,24 @@
|
||||||
echo "Target: $TO_DISK ($DISK_SIZE_GB GB)"
|
echo "Target: $TO_DISK ($DISK_SIZE_GB GB)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "WARNING: This will DESTROY all data on $TO_DISK"
|
echo "WARNING: This will DESTROY all data on $TO_DISK"
|
||||||
|
if [[ "$YES" != "true" ]]; then
|
||||||
read -rp "Continue? [y/N] " confirm
|
read -rp "Continue? [y/N] " confirm
|
||||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
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"
|
${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"
|
${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%
|
${pkgs.parted}/bin/parted -s "$TO_DISK" resizepart 3 100%
|
||||||
|
|
||||||
if [[ "$TO_DISK" == *nvme* ]] || [[ "$TO_DISK" == *mmcblk* ]]; then
|
if [[ "$TO_DISK" == *nvme* ]] || [[ "$TO_DISK" == *mmcblk* ]]; then
|
||||||
|
|
@ -171,7 +240,7 @@
|
||||||
WIN_PART="''${TO_DISK}3"
|
WIN_PART="''${TO_DISK}3"
|
||||||
fi
|
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"
|
${pkgs.ntfs3g}/bin/ntfsresize --force --no-action "$WIN_PART"
|
||||||
echo "y" | ${pkgs.ntfs3g}/bin/ntfsresize --force "$WIN_PART"
|
echo "y" | ${pkgs.ntfs3g}/bin/ntfsresize --force "$WIN_PART"
|
||||||
|
|
||||||
|
|
@ -192,19 +261,28 @@
|
||||||
echo "Disk: $REMOTE_DISK"
|
echo "Disk: $REMOTE_DISK"
|
||||||
echo ""
|
echo ""
|
||||||
echo "WARNING: This will DESTROY all data on $REMOTE_HOST:$REMOTE_DISK"
|
echo "WARNING: This will DESTROY all data on $REMOTE_HOST:$REMOTE_DISK"
|
||||||
|
if [[ "$YES" != "true" ]]; then
|
||||||
read -rp "Continue? [y/N] " confirm
|
read -rp "Continue? [y/N] " confirm
|
||||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { echo "Aborted."; exit 0; }
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "[1/4] Streaming image to remote disk..."
|
echo "[1/5] Wiping partition table..."
|
||||||
${pkgs.qemu}/bin/qemu-img convert -f qcow2 -O raw "$IMAGE_FILE" /dev/stdout \
|
ssh "$REMOTE_HOST" "sgdisk --zap-all $REMOTE_DISK"
|
||||||
| ssh "$REMOTE_HOST" "dd of=$REMOTE_DISK bs=4M status=progress oflag=direct"
|
|
||||||
|
|
||||||
echo "[2/4] Fixing GPT backup header..."
|
echo "[2/5] Streaming image to remote disk..."
|
||||||
ssh "$REMOTE_HOST" "sgdisk -e $REMOTE_DISK"
|
NBD_SOCK="/tmp/vmix-nbd-$$.sock"
|
||||||
|
${pkgs.qemu}/bin/qemu-nbd --read-only -f qcow2 -k "$NBD_SOCK" "$IMAGE_FILE" &
|
||||||
echo "[3/4] Expanding Windows partition (partition 3) to fill disk..."
|
NBD_PID=$!
|
||||||
ssh "$REMOTE_HOST" "parted -s $REMOTE_DISK resizepart 3 100%"
|
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
|
if [[ "$REMOTE_DISK" == *nvme* ]] || [[ "$REMOTE_DISK" == *mmcblk* ]]; then
|
||||||
REMOTE_WIN_PART="''${REMOTE_DISK}p3"
|
REMOTE_WIN_PART="''${REMOTE_DISK}p3"
|
||||||
|
|
@ -212,8 +290,14 @@
|
||||||
REMOTE_WIN_PART="''${REMOTE_DISK}3"
|
REMOTE_WIN_PART="''${REMOTE_DISK}3"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[4/4] Expanding NTFS filesystem on $REMOTE_WIN_PART..."
|
echo "[3/5] Fixing GPT backup header..."
|
||||||
ssh "$REMOTE_HOST" "echo y | ntfsresize --force $REMOTE_WIN_PART"
|
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 ""
|
||||||
echo "Done. $REMOTE_HOST:$REMOTE_DISK is ready to boot."
|
echo "Done. $REMOTE_HOST:$REMOTE_DISK is ready to boot."
|
||||||
|
|
|
||||||
|
|
@ -80,31 +80,36 @@
|
||||||
chmod +w vars.fd
|
chmod +w vars.fd
|
||||||
|
|
||||||
VMIX_DISPLAY="-nographic"
|
VMIX_DISPLAY="-nographic"
|
||||||
${lib.optionalString (displayArg != null) ''VMIX_DISPLAY="${displayArg}"''}
|
${lib.optionalString (vncDisplay != null) ''VMIX_DISPLAY="-vnc ${vncDisplay}"''}
|
||||||
${lib.optionalString (displayArg == null) ''
|
${lib.optionalString (vncDisplay == null) ''
|
||||||
VMIX_DF=$(ls -t /tmp/.vmix-display-* 2>/dev/null | head -1)
|
VMIX_DF=$(ls -t /tmp/.vmix-display-* 2>/dev/null | head -1)
|
||||||
if [ -n "$VMIX_DF" ]; then
|
if [ -n "$VMIX_DF" ]; then
|
||||||
export DISPLAY=$(cat "$VMIX_DF")
|
export DISPLAY=$(cat "$VMIX_DF")
|
||||||
|
export HOME=$(mktemp -d)
|
||||||
|
export XDG_RUNTIME_DIR=$HOME
|
||||||
|
export SDL_VIDEODRIVER=x11
|
||||||
VMIX_DISPLAY="-display sdl"
|
VMIX_DISPLAY="-display sdl"
|
||||||
fi
|
fi
|
||||||
''}
|
''}
|
||||||
|
|
||||||
echo "=== vmix: booting Audit Mode for ${name} ==="
|
echo "=== vmix: booting Audit Mode for ${name} ==="
|
||||||
timeout 1800 qemu-system-x86_64 \
|
QEMU_ARGS="-accel kvm -m ${toString memSize} -smp ${toString smp} -cpu host -machine type=q35 \
|
||||||
$VMIX_DISPLAY \
|
|
||||||
-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,readonly=on,file=${pkgs.OVMF.fd}/FV/OVMF_CODE.fd \
|
||||||
-drive if=pflash,format=raw,file=vars.fd \
|
-drive if=pflash,format=raw,file=vars.fd \
|
||||||
-rtc base=localtime,clock=host \
|
-rtc base=localtime,clock=host \
|
||||||
-device qemu-xhci -device usb-tablet \
|
-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 \
|
-drive file=${resultImg},format=qcow2,if=virtio \
|
||||||
${cdromArgs} \
|
${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 ==="
|
echo "=== vmix: audit script ${name} complete ==="
|
||||||
'';
|
'';
|
||||||
|
|
|
||||||
|
|
@ -51,28 +51,33 @@ let
|
||||||
VMIX_DF=$(ls -t /tmp/.vmix-display-* 2>/dev/null | head -1)
|
VMIX_DF=$(ls -t /tmp/.vmix-display-* 2>/dev/null | head -1)
|
||||||
if [ -n "$VMIX_DF" ]; then
|
if [ -n "$VMIX_DF" ]; then
|
||||||
export DISPLAY=$(cat "$VMIX_DF")
|
export DISPLAY=$(cat "$VMIX_DF")
|
||||||
|
export HOME=$(mktemp -d)
|
||||||
|
export XDG_RUNTIME_DIR=$HOME
|
||||||
|
export SDL_VIDEODRIVER=x11
|
||||||
VMIX_DISPLAY="-display sdl"
|
VMIX_DISPLAY="-display sdl"
|
||||||
fi
|
fi
|
||||||
''}
|
''}
|
||||||
|
|
||||||
# Windows installs unattended, reboots into Audit Mode,
|
# Windows installs unattended, reboots into Audit Mode,
|
||||||
# deletes cached Autounattend, shuts down → QEMU exits.
|
# deletes cached Autounattend, shuts down → QEMU exits.
|
||||||
timeout 3600 qemu-system-x86_64 \
|
QEMU_ARGS="-accel kvm -m ${toString memSize} -smp ${toString smp} -cpu host -machine type=q35 \
|
||||||
$VMIX_DISPLAY \
|
|
||||||
-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,readonly=on,file=${pkgs.OVMF.fd}/FV/OVMF_CODE.fd \
|
||||||
-drive if=pflash,format=raw,file=vars.fd \
|
-drive if=pflash,format=raw,file=vars.fd \
|
||||||
-rtc base=localtime,clock=host \
|
-rtc base=localtime,clock=host \
|
||||||
-device qemu-xhci -device usb-tablet \
|
-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=disk.qcow2,format=qcow2,if=virtio \
|
||||||
-drive file=${iso},media=cdrom,readonly=on \
|
-drive file=${iso},media=cdrom,readonly=on \
|
||||||
-drive file=${drivers.virtio-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 ==="
|
echo "=== vmix: ${name} install complete ==="
|
||||||
mv disk.qcow2 $out
|
mv disk.qcow2 $out
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ in rec {
|
||||||
vcppRuntimes = import ./essentials/vcpp-runtimes.nix args;
|
vcppRuntimes = import ./essentials/vcpp-runtimes.nix args;
|
||||||
bestPerformance = import ./essentials/best-performance.nix args;
|
bestPerformance = import ./essentials/best-performance.nix args;
|
||||||
clearFileAssociations = import ./essentials/clear-file-associations.nix args;
|
clearFileAssociations = import ./essentials/clear-file-associations.nix args;
|
||||||
|
virtioDrivers = import ./essentials/virtio-drivers.nix args;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Applications
|
# Applications
|
||||||
|
|
@ -35,7 +36,7 @@ in rec {
|
||||||
# Default file associations policy
|
# Default file associations policy
|
||||||
defaultApps = import ./default-apps.nix args;
|
defaultApps = import ./default-apps.nix args;
|
||||||
|
|
||||||
# Generalize (sysprep + OOBE)
|
# Generalize (sysprep + OOBE). Pass seal=true for hardware deployment.
|
||||||
generalize = import ./generalize.nix args;
|
generalize = import ./generalize.nix args;
|
||||||
|
|
||||||
# Offline registry templates
|
# Offline registry templates
|
||||||
|
|
@ -59,10 +60,10 @@ in rec {
|
||||||
reg.performanceTweaks
|
reg.performanceTweaks
|
||||||
apps.edgeWebview
|
apps.edgeWebview
|
||||||
apps.thorium
|
apps.thorium
|
||||||
apps.sandboxie
|
|
||||||
apps.sevenZip
|
apps.sevenZip
|
||||||
apps.vlc
|
apps.vlc
|
||||||
apps.imageGlass
|
apps.imageGlass
|
||||||
|
essentials.virtioDrivers # needed for network during Office download
|
||||||
apps.office
|
apps.office
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
18
lib/images/windows/templates/essentials/virtio-drivers.nix
Normal file
18
lib/images/windows/templates/essentials/virtio-drivers.nix
Normal file
|
|
@ -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
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,9 @@ in
|
||||||
timezone ? "UTC",
|
timezone ? "UTC",
|
||||||
# Desktop background solid color as hex string (e.g. "8e8cd8")
|
# Desktop background solid color as hex string (e.g. "8e8cd8")
|
||||||
bgColor ? null,
|
bgColor ? null,
|
||||||
|
# delayOobeRun = true: sysprep only, OOBE + activation on real hardware
|
||||||
|
# delayOobeRun = false: sysprep + OOBE + activation in build VM
|
||||||
|
delayOobeRun ? false,
|
||||||
}: let
|
}: let
|
||||||
# Convert "8e8cd8" hex to "142 140 216" decimal RGB for Windows registry
|
# Convert "8e8cd8" hex to "142 140 216" decimal RGB for Windows registry
|
||||||
hexToRgbStr = hex: let
|
hexToRgbStr = hex: let
|
||||||
|
|
@ -50,6 +53,12 @@ in
|
||||||
reg add "HKCU\Control Panel\Desktop" /v WallpaperStyle /t REG_SZ /d "0" /f
|
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)
|
:: 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
|
:: 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"
|
powershell -Command "Get-AppxPackage *MicrosoftEdge* | Remove-AppxPackage -ErrorAction SilentlyContinue"
|
||||||
|
|
@ -74,7 +83,7 @@ in
|
||||||
del /q C:\vmix-audit-script.cmd 2>nul
|
del /q C:\vmix-audit-script.cmd 2>nul
|
||||||
del /q C:\vmix-audit-wrapper.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
|
del /q C:\post-oobe.cmd 2>nul
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
@ -145,16 +154,21 @@ in
|
||||||
</unattend>
|
</unattend>
|
||||||
'';
|
'';
|
||||||
in {
|
in {
|
||||||
name = "generalize";
|
name = if delayOobeRun then "generalize-delay-oobe" else "generalize";
|
||||||
uploads = [
|
uploads = [
|
||||||
{ source = oobeXml; dest = "/oobe-unattend.xml"; }
|
{ source = oobeXml; dest = "/oobe-unattend.xml"; }
|
||||||
{ source = postOobeScript; dest = "/post-oobe.cmd"; }
|
{ source = postOobeScript; dest = "/post-oobe.cmd"; }
|
||||||
{ source = masScript; dest = "/MAS_AIO.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 = ''
|
auditScript = ''
|
||||||
@echo off
|
@echo off
|
||||||
C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /reboot /quiet /unattend:C:\oobe-unattend.xml
|
:: 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