diff --git a/README.md b/README.md index 4b8e9c8..9dbd617 100644 --- a/README.md +++ b/README.md @@ -28,21 +28,14 @@ How to use Install a Windows image ----------------------- -1. Adjust demo-image.nix accordingly +1. Adjust demo-image in ``flake.nix`` accordingly 2. Run: -If in impure mode ```shell -nix-build demo-image.nix +nix build .#demo-image ./result ``` -Results in a file called c.img -If in pure mode -```shell -nix-build demo-image.nix -ls -la ./result -``` Results in a symlink to the image in the nix store @@ -52,3 +45,5 @@ Impure/pure mode Sometimes it can be useful to build the image _outside_ of the Nix sandbox for debugging purposes. For this purpose we have an attribute called `impureMode` which outputs the shell script used by Nix inside the sandbox to build the image. + +When building an image with flakes, use ``nix build .#demo-image-impure`` instead. \ No newline at end of file diff --git a/bundle/default.nix b/bundle/default.nix deleted file mode 100644 index 16a0fed..0000000 --- a/bundle/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ pkgs }: - -pkgs.runCommandNoCC "win-bundle-installer.exe" {} '' - mkdir bundle - cd bundle - cp ${./go.mod} go.mod - cp ${./main.go} main.go - env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build - mv bundle.exe $out -'' diff --git a/bundle/shell.nix b/bundle/shell.nix deleted file mode 100644 index 20c60ed..0000000 --- a/bundle/shell.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ pkgs ? import {} }: - -pkgs.mkShell { - - buildInputs = [ - pkgs.go - ]; - - shellHook = '' - unset GOPATH - ''; - -} diff --git a/flake.nix b/flake.nix index 3d82254..e655aee 100644 --- a/flake.nix +++ b/flake.nix @@ -622,7 +622,18 @@ ) ); }; - # ============ + # /autounattend ============ + + # bundle + bundleInstaller = pkgs.runCommandNoCC "win-bundle-installer.exe" {} '' + mkdir bundle + cd bundle + cp ${./bundle/go.mod} go.mod + cp ${./bundle/main.go} main.go + env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build + mv bundle.exe $out + ''; + # /bundle =========== # makeWindowsImage makeWindowsImage = { diskImageSize ? "70G", windowsImage ? null, autoUnattendParams ? {} @@ -683,16 +694,6 @@ }; }; - # bundle - bundleInstaller = pkgs.runCommandNoCC "win-bundle-installer.exe" {} '' - mkdir bundle - cd bundle - cp ${./bundle/go.mod} go.mod - cp ${./bundle/main.go} main.go - env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build - mv bundle.exe $out - ''; - # Packages required to drive installation of other packages bootstrapPkgs = runQemuCommand "bootstrap-win-pkgs.img" '' @@ -704,7 +705,7 @@ cp ${openSshServerPackage} pkgs/OpenSSH-Win64.zip # SSH setup script goes here because windows XML parser sucks - cp ${./install-ssh.ps1} pkgs/install-ssh.ps1 + cp ${self}/install-ssh.ps1 pkgs/install-ssh.ps1 cp ${autounattend.setupScript} pkgs/setup.ps1 virt-make-fs --partition --type=fat pkgs/ $out diff --git a/wfvm/install-ssh.ps1 b/install-ssh.ps1 similarity index 100% rename from wfvm/install-ssh.ps1 rename to install-ssh.ps1 diff --git a/layers/default.nix b/layers/default.nix deleted file mode 100644 index a9068a6..0000000 --- a/layers/default.nix +++ /dev/null @@ -1,178 +0,0 @@ -{ pkgs }: -let - wfvm = import ../. { inherit pkgs; }; -in -{ - anaconda3 = { - name = "Anaconda3"; - script = let - Anaconda3 = pkgs.fetchurl { - name = "Anaconda3.exe"; - url = "https://repo.anaconda.com/archive/Anaconda3-2021.05-Windows-x86_64.exe"; - sha256 = "1lpk7k4gydyk524z1nk4rrninrwi20g2ias2njc9w0a40hwl5nwk"; - }; - in - '' - ln -s ${Anaconda3} ./Anaconda3.exe - win-put Anaconda3.exe . - echo Running Anaconda installer... - win-exec 'start /wait "" .\Anaconda3.exe /S /D=%UserProfile%\Anaconda3' - echo Anaconda installer finished - ''; - }; - msys2 = { - name = "MSYS2"; - buildInputs = [ pkgs.expect ]; - script = let - msys2 = pkgs.fetchurl { - name = "msys2.exe"; - url = "https://github.com/msys2/msys2-installer/releases/download/2020-06-02/msys2-x86_64-20200602.exe"; - sha256 = "1mswlfybvk42vdr4r85dypgkwhrp5ff47gcbxgjqwq86ym44xzd4"; - }; - msys2-auto-install = pkgs.fetchurl { - url = "https://raw.githubusercontent.com/msys2/msys2-installer/7b4b35f65904d03399d5dfb8fc4e5729b0b4d81f/auto-install.js"; - sha256 = "17fq1xprbs00j8wb4m0w1x4dvb48qb5hwa3zx77snlhw8226d81y"; - }; - in '' - ln -s ${msys2} ./msys2.exe - ln -s ${msys2-auto-install} ./auto-install.js - win-put msys2.exe . - win-put auto-install.js . - echo Running MSYS2 installer... - # work around MSYS2 installer bug that prevents it from closing at the end of unattended install - expect -c 'set timeout 600; spawn win-exec ".\\msys2.exe --script auto-install.js -v InstallPrefix=C:\\msys64"; expect FinishedPageCallback { close }' - echo MSYS2 installer finished - ''; - }; - msys2-packages = msys-packages: { - name = "MSYS2-packages"; - script = let - msys-packages-put = pkgs.lib.strings.concatStringsSep "\n" - (map (package: ''win-put ${package} 'msyspackages' '') msys-packages); - in - # Windows command line is so shitty it can't even do glob expansion. Why do people use Windows? - '' - win-exec 'mkdir msyspackages' - ${msys-packages-put} - cat > installmsyspackages.bat << EOF - set MSYS=c:\msys64 - set ARCH=64 - set PATH=%MSYS%\usr\bin;%MSYS%\mingw%ARCH%\bin;%PATH% - bash -c "pacman -U --noconfirm C:/Users/wfvm/msyspackages/*" - EOF - win-put installmsyspackages.bat . - win-exec installmsyspackages - ''; - }; - msvc = { - # Those instructions are vaguely correct: - # https://docs.microsoft.com/en-us/visualstudio/install/create-an-offline-installation-of-visual-studio?view=vs-2019 - name = "MSVC"; - script = let - bootstrapper = pkgs.fetchurl { - name = "RESTRICTDIST-vs_Community.exe"; - url = "https://aka.ms/vs/16/release/vs_community.exe"; - sha256 = "0b3csxz0qsafnvc0d74ywfpralwz8chv4zf9k07akpm8lp8ycgq0"; - }; - # This touchy-feely "community" piece of trash seems deliberately crafted to break Wine, so we use the VM to run it. - download-vs = wfvm.utils.wfvm-run { - name = "download-vs"; - image = wfvm.makeWindowsImage { }; - isolateNetwork = false; - script = - '' - ln -s ${bootstrapper} vs_Community.exe - ${wfvm.utils.win-put}/bin/win-put vs_Community.exe - rm vs_Community.exe - ${wfvm.utils.win-exec}/bin/win-exec "vs_Community.exe --quiet --norestart --layout c:\vslayout --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --lang en-US" - ${wfvm.utils.win-get}/bin/win-get /c:/vslayout - ''; - }; - cache = pkgs.stdenv.mkDerivation { - name = "RESTRICTDIST-vs"; - - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; - outputHash = "0ic3jvslp2y9v8yv9mfr2mafkvj2q5frmcyhmlbxj71si1x3kpag"; - - phases = [ "buildPhase" ]; - buildInputs = [ download-vs ]; - buildPhase = - '' - mkdir $out - cd $out - wfvm-run-download-vs - ''; - }; - in - '' - ln -s ${cache}/vslayout vslayout - win-put vslayout /c:/ - echo "Running Visual Studio installer" - win-exec "cd \vslayout && start /wait vs_Community.exe --passive --wait && echo %errorlevel%" - ''; - }; - # You need to run the IDE at least once or else most of the Visual Studio trashware won't actually work. - # With the /ResetSettings flag, it will actually start without pestering you about opening a Microsoft account. - msvc-ide-unbreak = { - name = "MSVC-ide-unbreak"; - script = - '' - win-exec 'cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE" && devenv /ResetSettings' - sleep 40 - ''; - }; - # Disable the Windows firewall - disable-firewall = { - name = "disable-firewall"; - script = '' - echo Disabling firewall - win-exec "netsh advfirewall set allprofiles state off" - ''; - }; - # Disable automatic power management which causes the machine to go - # into standby after periods without mouse wiggling. - disable-autosleep = { - name = "disable-autosleep"; - script = '' - echo Disabling autosleep - win-exec "powercfg /x -hibernate-timeout-ac 0" - win-exec "powercfg /x -hibernate-timeout-dc 0" - win-exec "powercfg /x -disk-timeout-ac 0" - win-exec "powercfg /x -disk-timeout-dc 0" - win-exec "powercfg /x -monitor-timeout-ac 0" - win-exec "powercfg /x -monitor-timeout-dc 0" - win-exec "powercfg /x -standby-timeout-ac 0" - win-exec "powercfg /x -standby-timeout-dc 0" - ''; - }; - # Turn off automatic locking of idle user sessions - disable-autolock = { - name = "disable-autolock"; - script = '' - echo Disabling autolock - win-exec "reg add HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\Personalization /v NoLockScreen /t REG_DWORD /d 1" - ''; - }; - # Don't let Windows start completely rewriting gigabytes of disk - # space. Defragmentation increases the size of our qcow layers - # needlessly. - disable-scheduled-defrag = { - name = "disable-scheduled-defrag"; - script = '' - echo Disabling scheduled defragmentation service - win-exec 'schtasks /Change /DISABLE /TN "\Microsoft\Windows\Defrag\ScheduledDefrag"' - ''; - }; - - # Chain together layers that are quick to run so that the VM does - # not have to be started/shutdown for each. - collapseLayers = scripts: { - name = pkgs.lib.concatMapStringsSep "-" ({ name, ... }: name) scripts; - script = builtins.concatStringsSep "\n" ( - map ({ script, ... }: script) scripts - ); - buildInputs = - builtins.concatMap ({ buildInputs ? [], ... }: buildInputs) scripts; - }; -} diff --git a/wfvm/default.nix b/wfvm/default.nix deleted file mode 100644 index 7846583..0000000 --- a/wfvm/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ pkgs }: - -{ - makeWindowsImage = attrs: import ./win.nix ({ inherit pkgs; } // attrs); - layers = (import ./layers { inherit pkgs; }); - utils = (import ./utils.nix { inherit pkgs; }); -} diff --git a/wfvm/demo-image.nix b/wfvm/demo-image.nix deleted file mode 100644 index a928de1..0000000 --- a/wfvm/demo-image.nix +++ /dev/null @@ -1,72 +0,0 @@ -{ pkgs ? import {}, impureMode ? false }: - -let - wfvm = (import ./default.nix { inherit pkgs; }); -in -wfvm.makeWindowsImage { - # Build install script & skip building iso - inherit impureMode; - - # Custom base iso - # windowsImage = pkgs.requireFile rec { - # name = "Win10_21H1_English_x64.iso"; - # sha256 = "1sl51lnx4r6ckh5fii7m2hi15zh8fh7cf7rjgjq9kacg8hwyh4b9"; - # message = "Get ${name} from https://www.microsoft.com/en-us/software-download/windows10ISO"; - # }; - - # impureShellCommands = [ - # "powershell.exe echo Hello" - # ]; - - # User accounts - # users = { - # artiq = { - # password = "1234"; - # # description = "Default user"; - # # displayName = "Display name"; - # groups = [ - # "Administrators" - # ]; - # }; - # }; - - # Auto login - # defaultUser = "artiq"; - - # fullName = "M-Labs"; - # organization = "m-labs"; - # administratorPassword = "12345"; - - # Imperative installation commands, to be installed incrementally - installCommands = - if impureMode - then [] - else with wfvm.layers; [ - (collapseLayers [ - disable-autosleep - disable-autolock - disable-firewall - ]) - anaconda3 msys2 msvc msvc-ide-unbreak - ]; - - # services = { - # # Enable remote management - # WinRm = { - # Status = "Running"; - # PassThru = true; - # }; - # }; - - # License key (required) - # productKey = throw "Search the f* web" - imageSelection = "Windows 10 Pro"; - - - # Locales - # uiLanguage = "en-US"; - # inputLocale = "en-US"; - # userLocale = "en-US"; - # systemLocale = "en-US"; - -} diff --git a/wfvm/demo-ssh.nix b/wfvm/demo-ssh.nix deleted file mode 100644 index 47c60e4..0000000 --- a/wfvm/demo-ssh.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ pkgs ? import {} }: - -let - wfvm = (import ./default.nix { inherit pkgs; }); -in - wfvm.utils.wfvm-run { - name = "demo-ssh"; - image = import ./demo-image.nix { inherit pkgs; }; - isolateNetwork = false; - script = '' - ${pkgs.sshpass}/bin/sshpass -p1234 -- ${pkgs.openssh}/bin/ssh -p 2022 wfvm@localhost -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null - ''; - } diff --git a/wfvm/utils.nix b/wfvm/utils.nix deleted file mode 100644 index 9039682..0000000 --- a/wfvm/utils.nix +++ /dev/null @@ -1,108 +0,0 @@ -{ pkgs, baseRtc ? "2020-04-20T14:21:42", cores ? "4", qemuMem ? "4G", efi ? true }: - -rec { - # qemu_test is a smaller closure only building for a single system arch - qemu = pkgs.qemu; - - mkQemuFlags = extraFlags: [ - "-enable-kvm" - "-cpu host" - "-smp ${cores}" - "-m ${qemuMem}" - "-M q35" - "-vga qxl" - "-rtc base=${baseRtc}" - "-device qemu-xhci" - "-device virtio-net-pci,netdev=n1" - ] ++ pkgs.lib.optionals efi [ - "-bios ${pkgs.OVMF.fd}/FV/OVMF.fd" - ] ++ extraFlags; - - # Pass empty config file to prevent ssh from failing to create ~/.ssh - sshOpts = "-F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=1"; - win-exec = pkgs.writeShellScriptBin "win-exec" '' - set -e - ${pkgs.sshpass}/bin/sshpass -p1234 -- \ - ${pkgs.openssh}/bin/ssh -np 2022 ${sshOpts} \ - wfvm@localhost \ - $1 - ''; - win-wait = pkgs.writeShellScriptBin "win-wait" '' - set -e - - # If the machine is not up within 10 minutes it's likely never coming up - timeout=600 - - # Wait for VM to be accessible - sleep 20 - echo "Waiting for SSH..." - while true; do - if test "$timeout" -eq 0; then - echo "SSH connection timed out" - exit 1 - fi - - output=$(${win-exec}/bin/win-exec 'echo|set /p="Ran command"' || echo "") - if test "$output" = "Ran command"; then - break - fi - - echo "Retrying in 1 second, timing out in $timeout seconds" - - ((timeout=$timeout-1)) - - sleep 1 - done - echo "SSH OK" - ''; - win-put = pkgs.writeShellScriptBin "win-put" '' - set -e - echo win-put $1 -\> $2 - ${pkgs.sshpass}/bin/sshpass -p1234 -- \ - ${pkgs.openssh}/bin/sftp -r -P 2022 ${sshOpts} \ - wfvm@localhost -b- << EOF - cd $2 - put $1 - EOF - ''; - win-get = pkgs.writeShellScriptBin "win-get" '' - set -e - echo win-get $1 - ${pkgs.sshpass}/bin/sshpass -p1234 -- \ - ${pkgs.openssh}/bin/sftp -r -P 2022 ${sshOpts} \ - wfvm@localhost:$1 . - ''; - - wfvm-run = { name, image, script, display ? false, isolateNetwork ? true, forwardedPorts ? [], fakeRtc ? true }: - let - restrict = - if isolateNetwork - then "on" - else "off"; - # use socat instead of `tcp:...` to allow multiple connections - guestfwds = - builtins.concatStringsSep "" - (map ({ listenAddr, targetAddr, port }: - ",guestfwd=tcp:${listenAddr}:${toString port}-cmd:${pkgs.socat}/bin/socat\\ -\\ tcp:${targetAddr}:${toString port}" - ) forwardedPorts); - qemuParams = mkQemuFlags (pkgs.lib.optional (!display) "-display none" ++ pkgs.lib.optional (!fakeRtc) "-rtc base=localtime" ++ [ - "-drive" - "file=${image},index=0,media=disk,cache=unsafe" - "-snapshot" - "-netdev user,id=n1,net=192.168.1.0/24,restrict=${restrict},hostfwd=tcp::2022-:22${guestfwds}" - ]); - in pkgs.writeShellScriptBin "wfvm-run-${name}" '' - set -e -m - ${qemu}/bin/qemu-system-x86_64 ${pkgs.lib.concatStringsSep " " qemuParams} & - - ${win-wait}/bin/win-wait - - ${script} - - echo "Shutting down..." - ${win-exec}/bin/win-exec 'shutdown /s' - echo "Waiting for VM to terminate..." - fg - echo "Done" - ''; -} diff --git a/wfvm/win.nix b/wfvm/win.nix deleted file mode 100644 index 0e153e4..0000000 --- a/wfvm/win.nix +++ /dev/null @@ -1,195 +0,0 @@ -{ pkgs -, diskImageSize ? "70G" -, windowsImage ? null -, autoUnattendParams ? {} -, impureMode ? false -, installCommands ? [] -, users ? {} -# autounattend always installs index 1, so this default is backward-compatible -, imageSelection ? "Windows 10 Pro" -, efi ? true -, ... -}@attrs: - -let - lib = pkgs.lib; - utils = import ./utils.nix { inherit pkgs efi; }; - 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 utils.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.requireFile rec { - name = "Win10_21H1_English_x64.iso"; - sha256 = "1sl51lnx4r6ckh5fii7m2hi15zh8fh7cf7rjgjq9kacg8hwyh4b9"; - message = "Get ${name} from https://www.microsoft.com/en-us/software-download/windows10ISO"; - }; - - # stable as of 2021-04-08 - virtioWinIso = pkgs.fetchurl { - url = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.185-2/virtio-win-0.1.185.iso"; - sha256 = "11n3kjyawiwacmi3jmfmn311g9xvfn6m0ccdwnjxw1brzb4kqaxg"; - }; - - openSshServerPackage = pkgs.fetchurl { - url = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/V8.6.0.0p1-Beta/OpenSSH-Win64.zip"; - sha256 = "1dw6n054r0939501dpxfm7ghv21ihmypdx034van8cl21gf1b4lz"; - }; - - autounattend = import ./autounattend.nix ( - attrs // { - inherit pkgs; - users = users // { - wfvm = { - password = "1234"; - description = "WFVM Administrator"; - groups = [ - "Administrators" - ]; - }; - }; - } - ); - - bundleInstaller = pkgs.callPackage ./bundle {}; - - # Packages required to drive installation of other packages - bootstrapPkgs = - runQemuCommand "bootstrap-win-pkgs.img" '' - 7z x -y ${virtioWinIso} -opkgs/virtio - - cp ${bundleInstaller} pkgs/"$(stripHash "${bundleInstaller}")" - - # Install optional windows features - cp ${openSshServerPackage} pkgs/OpenSSH-Win64.zip - - # SSH setup script goes here because windows XML parser sucks - cp ${./install-ssh.ps1} pkgs/install-ssh.ps1 - cp ${autounattend.setupScript} pkgs/setup.ps1 - - virt-make-fs --partition --type=fat pkgs/ $out - ''; - - installScript = pkgs.writeScript "windows-install-script" ( - let - qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-display none" ++ [ - # "CD" drive with bootstrap pkgs - "-drive" - "id=virtio-win,file=${bootstrapPkgs},if=none,format=raw,readonly=on" - "-device" - "usb-storage,drive=virtio-win" - # USB boot - "-drive" - "id=win-install,file=${if efi then "usb" else "cd"}image.img,if=none,format=raw,readonly=on,media=${if efi then "disk" else "cdrom"}" - "-device" - "usb-storage,drive=win-install" - # Output image - "-drive" - "file=c.img,index=0,media=disk,if=virtio,cache=unsafe" - # Network - "-netdev user,id=n1,net=192.168.1.0/24,restrict=on" - ]); - in - '' - #!${pkgs.runtimeShell} - set -euxo pipefail - export PATH=${lib.makeBinPath [ p7zip utils.qemu libguestfs pkgs.wimlib ]}:$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 - - # Split image so it fits in FAT32 partition - wimsplit win/sources/install.wim win/sources/install.swm 4090 - rm win/sources/install.wim - - cp ${autounattend.autounattendXML} win/autounattend.xml - - ${if efi then '' - virt-make-fs --partition --type=fat win/ usbimage.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 cdimage.img win/ - ''} - rm -rf win - - # Qemu requires files to be rw - qemu-img create -f qcow2 c.img ${diskImageSize} - qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} - '' - ); - - baseImage = pkgs.runCommandNoCC "RESTRICTDIST-windows.img" {} '' - ${installScript} - mv c.img $out - ''; - - finalImage = builtins.foldl' (acc: v: pkgs.runCommandNoCC "RESTRICTDIST-${v.name}.img" { - buildInputs = with utils; [ - qemu win-wait win-exec win-put - ] ++ (v.buildInputs or []); - } (let - script = pkgs.writeScript "${v.name}-script" v.script; - qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-display none" ++ [ - # Output image - "-drive" - "file=c.img,index=0,media=disk,if=virtio,cache=unsafe" - # Network - enable SSH forwarding - "-netdev user,id=n1,net=192.168.1.0/24,restrict=on,hostfwd=tcp::2022-:22" - ]); - - in '' - # Create an image referencing the previous image in the chain - qemu-img create -f qcow2 -b ${acc} c.img - - set -m - qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} & - - win-wait - - echo "Executing script to build layer..." - ${script} - echo "Layer script done" - - echo "Shutting down..." - win-exec 'shutdown /s' - echo "Waiting for VM to terminate..." - fg - echo "Done" - - mv c.img $out - '')) baseImage ( - [ - { - name = "DisablePasswordExpiry"; - script = '' - win-exec 'wmic UserAccount set PasswordExpires=False' - ''; - } - ] ++ - installCommands - ); - -in - -# impureMode is meant for debugging the base image, not the full incremental build process -if !(impureMode) then finalImage else assert installCommands == []; installScript