nixfiles/lib/windows/windows.nix

234 lines
8.2 KiB
Nix
Raw Normal View History

2024-01-08 07:49:51 +03:00
{ pkgs, ... }@inputs:
let
utils = import ./utils.nix inputs;
autounattend = import ./autounattend.nix inputs;
in
rec {
makeUnattendedImage =
{ windowsIso
, name ? "windows"
, params ? { }
,
}:
let
efi = params.uefi or true;
autounattendXml = pkgs.writeText "autounattend.xml"
(autounattend.mkAutoUnattend (params));
in
pkgs.runCommand "unattended-${name}.img" { buildInputs = [ pkgs.p7zip pkgs.guestfs-tools pkgs.wimlib ]; } ''
#!${pkgs.runtimeShell}
set -euxo pipefail
mkdir -p win
7z x -y ${windowsIso} -owin
# Split image so it fits in FAT32 partition
wimsplit win/sources/install.wim win/sources/install.swm 4070
rm win/sources/install.wim
cp ${autounattendXml} win/autounattend.xml
${if efi then ''
virt-make-fs --partition --type=fat win/ image.img
'' else ''
${pkgs.cdrkit}/bin/mkisofs -iso-level 4 -l -R -udf -D -b boot/etfsboot.com -no-emul-boot -boot-load-size 8 -hide boot.catalog -eltorito-alt-boot -o image.img win/
''}
rm -rf win
mv image.img $out
'';
makeBootstrapImage =
{ virtioWinIso ? utils.virtioWinIso
, additionalFiles ? [ ]
,
}: pkgs.runCommand "win-bootstrap.img" { buildInputs = [ pkgs.p7zip pkgs.guestfs-tools ]; } ''
#!${pkgs.runtimeShell}
set -euxo pipefail
mkdir -p pkgs;
# Extract virtio drivers
7z x -y ${virtioWinIso} -opkgs/virtio
# Extract additional files
${
builtins.concatStringsSep "\n" (
builtins.map
({ name, path }:
if builtins.readFileType path == "directory" then
"cp -r ${path} pkgs/${name}"
else
"cp ${path} pkgs/${name}"
)
additionalFiles
)
}
virt-make-fs --partition --type=fat pkgs/ $out
'';
makeBaseImage =
{ windowsIso
, virtioWinIso ? utils.virtioWinIso
, diskImageSize ? "80G"
, additionalFiles ? [ ]
, name ? "windows"
, unattendedParams ? { }
, qemuOptions ? { }
, postInstallScript ? null
, preLoginScript ? null
, tapName ? "tap0"
, bridgeName ? "br0"
,
}: pkgs.runCommand "system-${name}.img" { buildInputs = [ pkgs.qemu ]; } (
let
efi = qemuOptions.efi or true;
enableTpm = qemuOptions.enableTpm or true;
postInstallScriptFile =
if postInstallScript == null then null
else pkgs.writeText "post-install.bat" postInstallScript;
preLoginScriptFile =
if preLoginScript == null then null
else pkgs.writeText "pre-login.bat" preLoginScript;
installerImage = makeUnattendedImage {
inherit windowsIso name;
params = unattendedParams // {
driverPaths = [
"D:\\"
"E:\\"
"C:\\virtio\\amd64\\w11"
"C:\\virtio\\NetKVM\\w11\\amd64"
"C:\\virtio\\viogpudo\\w11\\amd64"
] ++ (unattendedParams.driverPaths or [ ]);
afterInstallCommands =
(unattendedParams.afterInstallCommands or [ ])
++ (if postInstallScript == null then [ ] else [
"cmd /C FOR %i IN (c d e f g h i j k l m n o p q r s t u v w x y z) DO IF EXIST %i:\\post-install.bat START /WAIT %i:\\post-install.bat %i"
]);
beforeFirstLoginCommands =
(unattendedParams.beforeFirstLoginCommands or [ ])
++ (if preLoginScriptFile == null then [
"shutdown /s /t 0"
] else [
"cmd /C FOR %i IN (c d e f g h i j k l m n o p q r s t u v w x y z) DO IF EXIST %i:\\pre-login.bat START /WAIT %i:\\pre-login.bat %i"
]);
};
};
bootstrapImage = makeBootstrapImage {
inherit virtioWinIso;
additionalFiles =
additionalFiles ++
(if postInstallScriptFile == null then [ ] else [{
name = "post-install.bat";
path = postInstallScriptFile;
}]) ++
(if preLoginScriptFile == null then [ ] else [{
name = "pre-login.bat";
path = preLoginScriptFile;
}]);
};
qemuParams = utils.mkQemuFlags (qemuOptions // {
vnc = qemuOptions.vnc or ":1";
extraFlags = [
# "CD" drive with bootstrap pkgs
"-drive id=virtio-win,file=${bootstrapImage},if=none,format=raw,readonly=on"
"-device usb-storage,drive=virtio-win"
# USB boot (installer)
"-drive id=win-install,file=${installerImage},if=none,format=raw,readonly=on,media=${if efi then "disk" else "cdrom"}"
"-device usb-storage,drive=win-install"
# Output image (installed OS)
"-object iothread,id=thread-scsi"
"-device virtio-scsi-pci,bus=pcie.0,num_queues=8,iothread=thread-scsi,id=scsi"
"-blockdev driver=qcow2,file.driver=file,file.filename=output.img,file.aio=io_uring,discard=unmap,detect-zeroes=unmap,read-only=off,cache.direct=on,node-name=drive-system"
"-device scsi-hd,drive=drive-system,bus=scsi.0,rotation_rate=1,physical_block_size=512,logical_block_size=512,id=scsi-drive-system"
# "-drive file=output.img,index=0,media=disk,cache=unsafe"
"-monitor stdio"
"-device virtio-net-pci,netdev=n1"
"-netdev user,id=n1,net=192.168.1.0/24,restrict=on"
] ++ (qemuOptions.extraFlags or [ ]);
tap = null;
});
in
''
#!${pkgs.runtimeShell}
set -euxo pipefail
${if enableTpm then utils.tpmStartCommands else ""}
qemu-img create -f qcow2 output.img ${diskImageSize}
qemu-system-x86_64 ${builtins.concatStringsSep " " qemuParams}
${if enableTpm then utils.tpmStopCommands else ""}
mv output.img $out
''
);
makeSystemdService =
{ systemImage
, name ? "windows"
, tapName ? "tap-${name}"
, bridgeName ? "br0"
, userImagePath ? "/etc/vms/user-disks/"
, userImageSize ? "10G"
, qemuOptions ? { }
, tempDir ? "/tmp"
,
}:
let
enableTpm = qemuOptions.enableTpm or true;
userImage = "${userImagePath}${name}.img";
sockPath = "/run/qemu-${name}.mon.sock";
qemuParams = utils.mkQemuFlags (qemuOptions // {
extraFlags = [
"-object iothread,id=thread-scsi"
"-device virtio-scsi-pci,bus=pcie.0,num_queues=8,iothread=thread-scsi,id=scsi"
# System image (installed OS)
"-blockdev driver=qcow2,file.driver=file,file.filename=system.img,file.aio=io_uring,discard=unmap,detect-zeroes=unmap,read-only=off,cache.direct=on,node-name=drive-system"
"-device scsi-hd,drive=drive-system,bus=scsi.0,rotation_rate=1,physical_block_size=512,logical_block_size=512,id=scsi-drive-system"
# User image (persistent data)
"-blockdev driver=qcow2,file.driver=file,file.filename=${userImage},file.aio=io_uring,discard=unmap,detect-zeroes=unmap,read-only=off,cache.direct=on,node-name=drive-user"
"-device scsi-hd,drive=drive-user,bus=scsi.0,rotation_rate=1,physical_block_size=512,logical_block_size=512,id=scsi-drive-user"
# "-drive file=output.img,index=0,media=disk,cache=unsafe"
"-monitor unix:${sockPath},server,nowait"
] ++ (qemuOptions.extraFlags or [ ]);
tap = tapName;
});
in
{
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig.PrivateTmp = true;
script = ''
set -euxo pipefail
cd ${tempDir}
cp ${systemImage} system.img
if ! test -f ${userImage}; then
mkdir -p ${userImagePath}
${pkgs.qemu}/bin/qemu-img create -f qcow2 ${userImage} ${userImageSize}
fi
${if enableTpm then utils.tpmStartCommands else ""}
${utils.tapStartCommands tapName bridgeName}
${pkgs.qemu}/bin/qemu-system-x86_64 ${builtins.concatStringsSep " " qemuParams}
${if enableTpm then utils.tpmStopCommands else ""}
${utils.tapStopCommands tapName}
'';
preStop = ''
echo 'system_powerdown' | ${pkgs.socat}/bin/socat - UNIX-CONNECT:${sockPath}
sleep 10
'';
};
}