{ pkgs ? import {} , lib ? pkgs.lib , diskImageSize ? "22G" , qemuMem ? "4G" , windowsImage ? null , autoUnattendParams ? {} , packages ? [] , impureMode ? false , baseRtc ? "2020-04-20T14:21:42" , ... }@attrs: let # qemu_test is a smaller closure only building for a single system arch qemu = pkgs.qemu_test; libguestfs = pkgs.libguestfs-with-appliance; # p7zip on >20.03 has known vulns but we have no better option p7zip = pkgs.p7zip.overrideAttrs(old: { meta = old.meta // { knownVulnerabilities = []; }; }); runQemuCommand = name: command: ( pkgs.runCommandNoCC name { buildInputs = [ p7zip qemu libguestfs ]; } ( '' if ! test -f; then echo "KVM not available, bailing out" >> /dev/stderr exit 1 fi '' + command ) ); windowsIso = if windowsImage != null then windowsImage else pkgs.fetchurl { url = "https://software-download.microsoft.com/download/sg/17763.107.101029-1455.rs5_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso"; sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a"; }; openSshServerPackage = ./openssh/server-package.cab; # pkgs.fetchurl { # name = "OpenSSH-Server-Package~31bf3856ad364e35~amd64~~.cab"; # url = "http://download.windowsupdate.com/c/msdownload/update/software/updt/2018/04/openssh-server-package~31bf3856ad364e35~amd64~~_b264949145379b61d55448ed2625916457f701ba.cab"; # sha256 = "1pzaz2i7x05ki6gq7yxh0j4c1l6r57hawl3ggkji0r83wzrmh7ps"; # }; # openSshClientPackage = pkgs.fetchurl { # name = "OpenSSH-Client-Package-31bf3856ad364e35-AMD64.cab"; # url = "http://download.windowsupdate.com/d/msdownload/update/software/updt/2018/04/openssh-client-package~31bf3856ad364e35~amd64~~_715b60a3869c393e0c03fd5683fe88c6f155ce28.cab"; # sha256 = "1rfdh2b47y27smy91g19s82cfwp8x5wg2iri95b8ndi9mplyfqdd"; # }; # Note: We're not using this one but keep around as a reference since microsoft makes it near impossible to find # URLs for these kind of things # fodIso = pkgs.fetchurl { # url = "https://software-download.microsoft.com/download/pr/17763.1.180914-1434.rs5_release_amd64fre_SERVER-FOD-PACKAGES_OEM_amd64fre_MULTI.iso"; # sha256 = "009pygycwvfkbm02zycp9zv136qc2lcljjjp0021fjd2kn3mf6k9"; # }; autounattend = import ./autounattend.nix ( attrs // { inherit pkgs; } ); bundleInstaller = pkgs.callPackage ./bundle {}; # Packages required to drive installation of other packages bootstrapPkgs = let winPkgs = import ./pkgs.nix { inherit pkgs; }; # nuget = winPkgs.makePkg { # name = "nuget-dll"; # src = ./nuget/Microsoft.PackageManagement.NuGetProvider.dll; # installScript = '' # mkdir -f "C:\Program Files\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" # cp "Microsoft.PackageManagement.NuGetProvider.dll" "C:\Program Files\PackageManagement\ProviderAssemblies\nuget\2.8.5.208" # Import-PackageProvider -Name NuGet -RequiredVersion 2.8.5.201 # ''; # }; anaconda = winPkgs.makePkg { name = "Anaconda3"; src = pkgs.fetchurl { name = "Anaconda3.exe"; url = "https://repo.anaconda.com/archive/Anaconda3-2019.03-Windows-x86_64.exe"; sha256 = "1f9icm5rwab6l1f23a70dw0qixzrl62wbglimip82h4zhxlh3jfj"; }; installScript = '' .\Anaconda3.exe /InstallationType=AllUsers /RegisterPython=0 /S /D="C:\ProgramData\Anaconda3" ''; }; in runQemuCommand "bootstrap-win-pkgs.img" '' mkdir pkgs mkdir pkgs/bootstrap mkdir pkgs/user mkdir pkgs/fod cp ${bundleInstaller} pkgs/"$(stripHash "${bundleInstaller}")" # Install optional windows features cp ${openSshServerPackage} pkgs/fod/OpenSSH-Server-Package~31bf3856ad364e35~amd64~~.cab # SSH setup script goes here because windows XML parser sucks cp ${autounattend.setupScript} pkgs/ssh-setup.ps1 # cp ${anaconda} pkgs/user/00_"$(stripHash "${anaconda}")" ${lib.concatStringsSep "\n" (builtins.map (x: ''cp ${x} pkgs/bootstrap/"$(stripHash "${x}")"'') packages)} virt-make-fs --partition --type=fat pkgs/ $out ''; installScript = pkgs.writeScript "windows-install-script" ( let qemuParams = [ "-enable-kvm" "-cpu" "host" "-smp" "$NIX_BUILD_CORES" "-m" "${qemuMem}" "-bios" "${pkgs.OVMF.fd}/FV/OVMF.fd" "-vga" "virtio" "-device" "piix3-usb-uhci" # USB root hub # USB boot "-drive" "id=win-install,file=usbimage.img,if=none,format=raw,readonly=on" "-device" "usb-storage,drive=win-install" # Output image "-drive" "file=c.img,index=0,media=disk,cache=unsafe" # "CD" drive with bootstrap pkgs "-drive" "id=virtio-win,file=${bootstrapPkgs},if=none,format=raw,readonly=on" "-device" "usb-storage,drive=virtio-win" # "CD" drive with windows features-on-demand # "-cdrom" "${fodIso}" # Set the base clock inside the VM "-rtc=$(date -Iseconds -d @${baseRtc}" ] ++ lib.optional (!impureMode) "-nographic"; in '' #!${pkgs.runtimeShell} set -euxo pipefail export PATH=${lib.makeBinPath [ p7zip qemu libguestfs ]}:$PATH # Create a bootable "USB" image # Booting in USB mode circumvents the "press any key to boot from cdrom" prompt # # Also embed the autounattend answer file in this image mkdir -p win mkdir -p win/nix-win 7z x -y ${windowsIso} -owin cp ${autounattend.autounattendXML} win/autounattend.xml virt-make-fs --partition --type=fat win/ usbimage.img rm -rf win # Qemu requires files to be rw qemu-img create -f qcow2 c.img ${diskImageSize} env NIX_BUILD_CORES="''${NIX_BUILD_CORES:4}" qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} '' ); in if impureMode then installScript else pkgs.runCommandNoCC "windows.img" {} '' ${installScript} mv c.img $out ''