diff --git a/artiq-fast/windows/README.md b/artiq-fast/windows/README.md
index 279e967..07962a7 100644
--- a/artiq-fast/windows/README.md
+++ b/artiq-fast/windows/README.md
@@ -2,24 +2,19 @@
## Install a Windows image
+1. Adjust build.nix accordingly
+2. Run:
+
+If in impure mode
```shell
-nix-build install.nix -I artiqSrc=…/artiq
-result/bin/windows-installer.sh
+nix-build build.nix
+./result
```
+Results in a file called c.img
-Follow the instructions.
-
-## Install Anaconda to the image
-
+If in pure mode
```shell
-result/bin/anaconda-installer.sh
-```
-
-Move the image `c.img` to one of Nix' `extra-sandbox-paths` (`nix.sandboxPaths` on NixOS).
-
-
-# Running the tests manually
-
-```shell
-nix-build --pure --arg diskImage "\"…/c.img\"" -I artiqSrc=…/artiq manual-test-run.nix
+nix-build build.nix
+ls -la ./result
```
+Results in a symlink to the image in the nix store
diff --git a/artiq-fast/windows/autounattend.nix b/artiq-fast/windows/autounattend.nix
new file mode 100644
index 0000000..c72b3ae
--- /dev/null
+++ b/artiq-fast/windows/autounattend.nix
@@ -0,0 +1,295 @@
+{ pkgs
+, lib ? pkgs.lib
+, fullName
+, organization
+, administratorPassword
+, uiLanguage ? "en-US"
+, inputLocale ? "en-US"
+, userLocale ? "en-US"
+, systemLocale ? "en-US"
+, users ? {}
+, productKey ? null
+, defaultUser ? null
+, setupCommands ? []
+, timeZone ? "UTC"
+, services ? {}
+, impureMode ? false
+, impureShellCommands ? []
+, ...
+}:
+
+let
+
+ serviceCommands = lib.mapAttrsToList (
+ serviceName: attrs: "powershell Set-Service -Name ${serviceName} " + (
+ lib.concatStringsSep " " (
+ (
+ lib.mapAttrsToList (
+ n: v: if builtins.typeOf v != "bool" then "-${n} ${v}" else "-${n}"
+ )
+ ) (
+ # Always run without interaction
+ { Force = true; } // attrs
+ )
+ )
+ )
+ ) services;
+
+ # If we are running in impure mode we can also enable networked services
+ impureSetupCommands = let
+ userSSHKeys = lib.flatten (lib.mapAttrsToList (n: v: v.sshKeys or []) users);
+ keyCommands = (
+ builtins.foldl' (
+ acc: key: acc ++ [
+ ''"${key}" | Out-File C:\usersshkey.pub''
+ "ssh-add C:\usersshkey.pub"
+ ]
+ ) [] userSSHKeys
+ ) ++ [ "Remove-Item C:\usersshkey.pub" ];
+
+ in
+ if impureMode then [
+ {
+ Path = "powershell.exe Install-Module -Force OpenSSHUtils -Scope AllUsers";
+ Description = "Install Openssh.";
+ }
+ {
+ Path = "powershell.exe Start-Service ssh-agent";
+ Description = "Start the ssh-agent";
+ }
+ {
+ Path = "powershell.exe Start-Service sshd";
+ Description = "Now start the sshd service";
+ }
+ ] ++ keyCommands else [];
+
+ assertCommand = c: builtins.typeOf c == "string" || builtins.typeOf c == "set" && builtins.hasAttr "Path" c && builtins.hasAttr "Description" c;
+
+ commands = builtins.map (x: assert assertCommand x; if builtins.typeOf x == "string" then { Path = x; Description = x; } else x) (
+ [
+ {
+ Path = "powershell.exe Set-ExecutionPolicy -Force Unrestricted";
+ Description = "Allow unsigned powershell scripts.";
+ }
+ ]
+ ++ setupCommands
+ # ++ impureSetupCommands
+ ++ serviceCommands
+ ++ impureShellCommands
+ ++ [
+ {
+ Path = ''powershell.exe F:\win-bundle-installer.exe'';
+ Description = "Install any declared packages.";
+ }
+ ]
+ );
+
+ mkCommand = attrs: ''
+
+ ${lib.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: "<${n}>${v}${n}>") attrs)}
+
+ '';
+ mkCommands = commands: (
+ builtins.foldl' (
+ acc: v: rec {
+ i = acc.i + 1;
+ values = acc.values ++ [ (mkCommand (v // { Order = builtins.toString i; })) ];
+ }
+ ) {
+ i = 0;
+ values = [];
+ } commands
+ ).values;
+
+ mkUser =
+ { name
+ , password
+ , description ? ""
+ , displayName ? ""
+ , groups ? []
+ , sshKeys ? [] # Handled in scripts
+ }: ''
+
+
+ ${password}
+ true
+
+ ${description}
+ ${displayName}
+ ${builtins.concatStringsSep ";" (lib.unique ([ "Users" ] ++ groups))}
+ ${name}
+
+ '';
+
+ # Windows expects a flat list of users while we want to manage them as a set
+ flatUsers = builtins.attrValues (builtins.mapAttrs (name: s: s // { inherit name; }) users);
+
+ autounattendXML = pkgs.writeText "autounattend.xml" ''
+
+
+
+
+
+
+ D:\
+
+
+ E:\
+
+
+
+
+
+
+
+
+
+ 1
+ EFI
+ 100
+
+
+ 2
+ MSR
+ 16
+
+
+ 3
+ Primary
+ true
+
+
+
+
+ 1
+ FAT32
+
+ 1
+
+
+ 2
+ 2
+
+
+ 3
+ NTFS
+
+ C
+ 3
+
+
+ 0
+ true
+
+
+
+
+
+
+ 0
+ 3
+
+
+
+ /IMAGE/INDEX
+ 1
+
+
+
+
+
+
+
+ ${if productKey != null then "${productKey}" else ""}
+ OnError
+
+ true
+ ${fullName}
+ ${organization}
+
+
+
+
+
+ ${uiLanguage}
+
+ ${inputLocale}
+ ${systemLocale}
+ ${uiLanguage}
+ en-US
+ ${userLocale}
+
+
+
+
+
+ ${inputLocale}
+ ${systemLocale}
+ ${uiLanguage}
+ en-US
+ ${userLocale}
+
+
+
+ true
+ true
+ true
+ true
+ true
+ 1
+
+ ${timeZone}
+
+
+ ${if administratorPassword != null then ''
+
+ ${administratorPassword}
+ true
+
+ '' else ""}
+
+ ${builtins.concatStringsSep "\n" (builtins.map mkUser flatUsers)}
+
+
+
+ ${if defaultUser == null then "" else ''
+
+
+ ${(builtins.getAttr defaultUser users).password}
+ true
+
+ true
+ ${defaultUser}
+
+ ''}
+
+
+
+
+
+
+
+ ${lib.concatStringsSep "\n" (mkCommands commands)}
+
+
+
+ 0
+
+
+
+
+
+
+ false
+
+
+
+
+
+ '';
+
+in
+ # Lint and format as a sanity check
+pkgs.runCommandNoCC "autounattend.xml" {} ''
+ ${pkgs.libxml2}/bin/xmllint --format ${autounattendXML} > $out
+''
diff --git a/artiq-fast/windows/build.nix b/artiq-fast/windows/build.nix
new file mode 100644
index 0000000..28f93a8
--- /dev/null
+++ b/artiq-fast/windows/build.nix
@@ -0,0 +1,81 @@
+{ pkgs ? import {} }:
+
+let
+ win = (import ./default.nix { inherit pkgs; });
+
+in
+win.makeWindowsImage {
+
+ # Custom base iso
+ # windowsImage = 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";
+ # };
+
+ # User accounts
+ users = {
+ artiq = {
+ # sshKeys = [
+ # "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEmJW3Z+1ZNNVao2jcipQQxiEN27jtpl40fq3Je+jgir"
+ # ];
+ password = "1234";
+ # description = "Default user";
+ # displayName = "Display name";
+ groups = [
+ "Administrators"
+ ];
+ };
+ };
+
+ # Will also enable ssh
+ # These impure commands need sandbox disabled or run outside of the sandbox
+ impureMode = true;
+
+ # impureShellCommands = [
+ # "powershell.exe echo Hello"
+ # ];
+
+ fullName = "M-Labs";
+ organization = "m-labs";
+
+ administratorPassword = "12345";
+
+ # Auto login
+ defaultUser = "artiq";
+
+ # services = {
+ # # Enable remote management
+ # WinRm = {
+ # Status = "Running";
+ # PassThru = true;
+ # };
+ # };
+
+ # License key
+ # productKey = "iboughtthisone";
+
+ # Locales
+ # uiLanguage = "en-US";
+ # inputLocale = "en-US";
+ # userLocale = "en-US";
+ # systemLocale = "en-US";
+
+ # packages = [
+ # (
+ # win.pkgs.makeMSIPkg {
+ # # Note: File not in repository, it's meant as an example to subsitute
+ # name = "notepadplusplus";
+ # msi = ./Notepad++7.7.msi;
+ # # Custom cert
+ # # cert = ./notepad++-cert.cer
+ # }
+ # )
+ # (
+ # win.pkgs.makeCrossPkg {
+ # name = "hello";
+ # pkg = pkgs.pkgsCross.mingwW64.hello;
+ # }
+ # )
+ # ];
+
+}
diff --git a/artiq-fast/windows/bundle/.envrc b/artiq-fast/windows/bundle/.envrc
new file mode 100644
index 0000000..1d953f4
--- /dev/null
+++ b/artiq-fast/windows/bundle/.envrc
@@ -0,0 +1 @@
+use nix
diff --git a/artiq-fast/windows/bundle/default.nix b/artiq-fast/windows/bundle/default.nix
new file mode 100644
index 0000000..a1171ea
--- /dev/null
+++ b/artiq-fast/windows/bundle/default.nix
@@ -0,0 +1,9 @@
+{ pkgs ? import {}
+, lib ? pkgs.lib
+}:
+
+pkgs.runCommandNoCC "win-bundle-installer.exe" {} ''
+ cp ${./main.go} main.go
+ env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build
+ mv build.exe $out
+''
diff --git a/artiq-fast/windows/bundle/main.go b/artiq-fast/windows/bundle/main.go
new file mode 100644
index 0000000..a69e544
--- /dev/null
+++ b/artiq-fast/windows/bundle/main.go
@@ -0,0 +1,125 @@
+package main
+
+import (
+ "archive/tar"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+)
+
+func Untar(dst string, r io.Reader) error {
+
+ tr := tar.NewReader(r)
+
+ for {
+ header, err := tr.Next()
+
+ switch {
+
+ case err == io.EOF:
+ return nil
+ case err != nil:
+ return err
+
+ case header == nil:
+ continue
+ }
+
+ target := filepath.Join(dst, header.Name)
+
+ switch header.Typeflag {
+
+ case tar.TypeDir:
+ if _, err := os.Stat(target); err != nil {
+ if err := os.MkdirAll(target, 0755); err != nil {
+ return err
+ }
+ }
+
+ case tar.TypeReg:
+ f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
+ if err != nil {
+ return err
+ }
+
+ if _, err := io.Copy(f, tr); err != nil {
+ return err
+ }
+
+ f.Close()
+ }
+ }
+}
+
+func InstallBundle(bundlePath string) error {
+
+ reader, err := os.Open(bundlePath)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ workDir, err := ioutil.TempDir("", "bundle_install")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(workDir)
+
+ err = Untar(workDir, reader)
+ if err != nil {
+ return err
+ }
+
+ installScript := filepath.Join(workDir, "install.ps1")
+
+ cmd := exec.Command("powershell", installScript)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Dir = workDir
+ err = cmd.Run()
+
+ return err
+}
+
+func Shutdown() {
+ if err := exec.Command("cmd", "/C", "shutdown", "/s", "/f", "/t", "00").Run(); err != nil {
+ fmt.Println("Failed to initiate shutdown:", err)
+ }
+}
+
+func main() {
+
+ defer Shutdown()
+
+ // Get path relative to binary
+ baseDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var dirs = [2]string{"bootstrap", "user"}
+
+ for _, pkgDir := range dirs {
+
+ dir := filepath.Join(baseDir, pkgDir)
+
+ files, err := ioutil.ReadDir(dir)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, file := range files {
+ bundle := filepath.Join(dir, file.Name())
+ fmt.Println(fmt.Sprintf("Installing: %s", bundle))
+ err := InstallBundle(bundle)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ }
+
+}
diff --git a/artiq-fast/windows/bundle/shell.nix b/artiq-fast/windows/bundle/shell.nix
new file mode 100644
index 0000000..20c60ed
--- /dev/null
+++ b/artiq-fast/windows/bundle/shell.nix
@@ -0,0 +1,13 @@
+{ pkgs ? import {} }:
+
+pkgs.mkShell {
+
+ buildInputs = [
+ pkgs.go
+ ];
+
+ shellHook = ''
+ unset GOPATH
+ '';
+
+}
diff --git a/artiq-fast/windows/default.nix b/artiq-fast/windows/default.nix
new file mode 100644
index 0000000..74a82d1
--- /dev/null
+++ b/artiq-fast/windows/default.nix
@@ -0,0 +1,7 @@
+{ pkgs ? import {}
+}:
+
+{
+ makeWindowsImage = attrs: import ./win.nix ({ inherit pkgs; } // attrs);
+ pkgs = import ./pkgs.nix { inherit pkgs; };
+}
diff --git a/artiq-fast/windows/install.nix b/artiq-fast/windows/install.nix
index 9ac5e7a..85cc244 100644
--- a/artiq-fast/windows/install.nix
+++ b/artiq-fast/windows/install.nix
@@ -1,6 +1,7 @@
-{ pkgs ? import {},
- diskImageSize ? "22G",
- qemuMem ? "4G",
+{ pkgs ? import {}
+, diskImageSize ? "22G"
+, qemuMem ? "4G"
+,
}:
with pkgs;
@@ -33,7 +34,7 @@ let
instructions =
builtins.toFile "install.txt"
- (builtins.readFile ./install.txt);
+ (builtins.readFile ./install.txt);
in
stdenv.mkDerivation {
name = "windows-installer";
@@ -53,10 +54,13 @@ stdenv.mkDerivation {
${qemu.qemu-img} create -f qcow2 c.img ${diskImageSize}
${qemu.runQemu false [] [
- "-boot" "order=d"
- "-drive" "file=c.img,index=0,media=disk,cache=unsafe"
- "-drive" "file=$out/data/windows.iso,index=1,media=cdrom,cache=unsafe"
- ]} &
+ "-boot"
+ "order=d"
+ "-drive"
+ "file=c.img,index=0,media=disk,cache=unsafe"
+ "-drive"
+ "file=$out/data/windows.iso,index=1,media=cdrom,cache=unsafe"
+ ]} &
cat ${instructions}
wait
EOF
@@ -66,9 +70,11 @@ stdenv.mkDerivation {
set -e -m
${qemu.runQemu false [] [
- "-boot" "order=c"
- "-drive" "file=c.img,index=0,media=disk"
- ]} &
+ "-boot"
+ "order=c"
+ "-drive"
+ "file=c.img,index=0,media=disk"
+ ]} &
sleep 10
${ssh "ver"}
diff --git a/artiq-fast/windows/manual-test-run.nix b/artiq-fast/windows/manual-test-run.nix
index b04ae8d..fc90cc6 100644
--- a/artiq-fast/windows/manual-test-run.nix
+++ b/artiq-fast/windows/manual-test-run.nix
@@ -1,21 +1,24 @@
# This runs `run-test.nix` with `nix-build`
-{ pkgs ? import {},
- artiqpkgs ? import ../. { inherit pkgs; },
- diskImage ? "/opt/windows/c.img",
- qemuMem ? "2G",
- testTimeout ? 180,
+{ pkgs ? import {}
+, artiqpkgs ? import ../. { inherit pkgs; }
+, diskImage ? "/opt/windows/c.img"
+, qemuMem ? "2G"
+, testTimeout ? 180
+,
}:
with pkgs;
let
windowsRunner = overrides:
- import ./run-test.nix ({
- inherit pkgs diskImage qemuMem testTimeout;
- sipycoPkg = artiqpkgs.conda-sipyco;
- artiqPkg = artiqpkgs.conda-artiq;
- } // overrides);
+ import ./run-test.nix (
+ {
+ inherit pkgs diskImage qemuMem testTimeout;
+ sipycoPkg = artiqpkgs.conda-sipyco;
+ artiqPkg = artiqpkgs.conda-artiq;
+ } // overrides
+ );
in
stdenv.mkDerivation {
diff --git a/artiq-fast/windows/pkgs.nix b/artiq-fast/windows/pkgs.nix
new file mode 100644
index 0000000..03a71a4
--- /dev/null
+++ b/artiq-fast/windows/pkgs.nix
@@ -0,0 +1,110 @@
+{ pkgs ? import {}
+, lib ? pkgs.lib
+}:
+
+/*
+
+This file creates a simple custom simple bundle format containing
+a powershell script plus any required executables and assets.
+
+These are assets that are only handled in the pure build steps.
+
+Impure packages are installed in _another_ step that runs impurely outside of
+the Nix sandbox.
+
+*/
+
+let
+
+ makeBundle =
+ { name
+ , bundle
+ }: pkgs.runCommandNoCC "${name}-archive.tar" {} ''
+ cp -r -L ${bundle} build
+ tar -cpf $out -C build .
+ '';
+
+
+in
+rec {
+
+ /*
+ Make a custom install bundle
+ */
+ makePkg =
+ { name
+ , src
+ , installScript
+ }: let
+ installScript_ = pkgs.writeText "${name}-install-script" installScript;
+
+ bundle = pkgs.runCommandNoCC "${name}-bundle" {} ''
+ mkdir build
+ ln -s ${src} build/"$(stripHash "${src}")"
+ ln -s ${installScript_} build/install.ps1
+ mv build $out
+ '';
+ in
+ makeBundle {
+ inherit name bundle;
+ };
+
+
+ /*
+ Make an install bundle from a .msi
+ */
+ makeMSIPkg =
+ { name
+ , msi
+ , cert ? null
+ , ADDLOCAL ? []
+ , preInstall ? ""
+ , postInstall ? ""
+ }: let
+ installScript = pkgs.writeText "${name}-install-script" ''
+ ${preInstall}
+ ${if cert != null then "certutil.exe -f -addstore TrustedPublisher cert.cer" else ""}
+ msiexec.exe /i .\${name}.msi ${if ADDLOCAL != [] then "ADDLOCAL=" else ""}${lib.concatStringsSep "," ADDLOCAL}
+ ${postInstall}
+ '';
+
+ bundle = pkgs.runCommandNoCC "${name}-bundle" {} ''
+ mkdir build
+ ln -s ${msi} build/${name}.msi
+ ${if cert != null then "ln -s ${cert} build/cert.cer" else ""}
+ ln -s ${installScript} build/install.ps1
+ mv build $out
+ '';
+ in
+ makeBundle {
+ inherit name bundle;
+ };
+
+ /*
+ Nix cross-built packages
+ */
+ makeCrossPkg =
+ { name
+ , pkg
+ , destination ? ''C:\Program Files\${name}\''
+ , preInstall ? ""
+ , postInstall ? ""
+ }: let
+ installScript = pkgs.writeText "${name}-install-script" ''
+ ${preInstall}
+ Copy-Item pkg -Destination "${destination}"
+ ${postInstall}
+ '';
+
+ bundle = pkgs.runCommandNoCC "${name}-bundle" {} ''
+ mkdir -p build/pkg
+ ln -s ${pkg} build/pkg
+ ln -s ${installScript} build/install.ps1
+ mv build $out
+ '';
+ in
+ makeBundle {
+ inherit name bundle;
+ };
+
+}
diff --git a/artiq-fast/windows/qemu.nix b/artiq-fast/windows/qemu.nix
index e575f0d..fc26017 100644
--- a/artiq-fast/windows/qemu.nix
+++ b/artiq-fast/windows/qemu.nix
@@ -1,8 +1,9 @@
-{ pkgs,
- diskImage,
- qemuMem,
- sshUser ? "user",
- sshPassword ? "user",
+{ pkgs
+, diskImage
+, qemuMem
+, sshUser ? "user"
+, sshPassword ? "user"
+,
}:
with pkgs;
@@ -18,18 +19,26 @@ let
# use socat instead of `tcp:…` to allow multiple connections
guestfwds =
builtins.concatStringsSep ""
- (map ({ listenAddr, targetAddr, port }:
- ",guestfwd=tcp:${listenAddr}:${toString port}-cmd:${socat}/bin/socat\\ -\\ tcp:${targetAddr}:${toString port}"
- ) forwardedPorts);
+ (
+ map (
+ { listenAddr, targetAddr, port }:
+ ",guestfwd=tcp:${listenAddr}:${toString port}-cmd:${socat}/bin/socat\\ -\\ tcp:${targetAddr}:${toString port}"
+ ) forwardedPorts
+ );
args = [
"-enable-kvm"
- "-m" qemuMem
- "-bios" "${OVMF.fd}/FV/OVMF.fd"
- "-netdev" "user,id=n1,net=192.168.1.0/24,restrict=${restrict},hostfwd=tcp::2022-:22${guestfwds}"
- "-device" "e1000,netdev=n1"
+ "-m"
+ qemuMem
+ "-bios"
+ "${OVMF.fd}/FV/OVMF.fd"
+ "-netdev"
+ "user,id=n1,net=192.168.1.0/24,restrict=${restrict},hostfwd=tcp::2022-:22${guestfwds}"
+ "-device"
+ "e1000,netdev=n1"
];
argStr = builtins.concatStringsSep " " (args ++ extraArgs);
- in "${qemu_kvm}/bin/qemu-system-x86_64 ${argStr}";
+ in
+ "${qemu_kvm}/bin/qemu-system-x86_64 ${argStr}";
# Pass empty config file to prevent ssh from failing to create ~/.ssh
sshOpts = "-F /dev/null -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=\$TMP/known_hosts";
@@ -47,7 +56,7 @@ let
${openssh}/bin/scp -P 2022 ${sshOpts} \
"${src}" "${sshUser}@localhost:${target}"
'';
-
+
in
{
inherit qemu-img runQemu ssh sshWithQuotes scp;
diff --git a/artiq-fast/windows/redhat-cert.cer b/artiq-fast/windows/redhat-cert.cer
new file mode 100644
index 0000000..7fb3357
--- /dev/null
+++ b/artiq-fast/windows/redhat-cert.cer
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIE1jCCA76gAwIBAgIQXRDLGOs6eQCHg6t0d/nTGTANBgkqhkiG9w0BAQsFADCB
+hDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w
+HQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTUwMwYDVQQDEyxTeW1hbnRl
+YyBDbGFzcyAzIFNIQTI1NiBDb2RlIFNpZ25pbmcgQ0EgLSBHMjAeFw0xODExMjcw
+MDAwMDBaFw0yMjAxMjUyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRcwFQYDVQQIDA5O
+b3J0aCBDYXJvbGluYTEQMA4GA1UEBwwHUmFsZWlnaDEWMBQGA1UECgwNUmVkIEhh
+dCwgSW5jLjEWMBQGA1UEAwwNUmVkIEhhdCwgSW5jLjCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAN6tLWiLXZXnYDRc6y9qeQrnN59qP5xutjQ4AHZY/m9E
+aNMRzKOONgalW6YTQRrW6emIscqlweRzvDnrF4hv/u/SfIq16XLqdViL0tZjmFWY
+hijbtFP1cjEZNeS47m2YnQgTpTsKmZ5A66/oiqzg8ogNbxxilUOojQ+rjzhwsvfJ
+AgnaGhOMeR81ca2YsgzFX3Ywf7iy6A/CtjHIOh78wcwR0MaJW6QvOhOaClVhHGtq
+8yIUA7k/3k8sCC4xIxci2UqFOXopw0EUvd/xnc5by8m7LYdDO048sOM0lASt2d4P
+KniOvUkU/LpqiFSYo/6272j+KRBDYCW2IgPCK5HWlZMCAwEAAaOCAV0wggFZMAkG
+A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6
+Ly9yYi5zeW1jYi5jb20vcmIuY3JsMGEGA1UdIARaMFgwVgYGZ4EMAQQBMEwwIwYI
+KwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwICMBkM
+F2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMBMGA1UdJQQMMAoGCCsGAQUFBwMDMFcG
+CCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0cDovL3JiLnN5bWNkLmNvbTAm
+BggrBgEFBQcwAoYaaHR0cDovL3JiLnN5bWNiLmNvbS9yYi5jcnQwHwYDVR0jBBgw
+FoAU1MAGIknrOUvdk+JcobhHdglyA1gwHQYDVR0OBBYEFG9GZUQmGAU3flEwvkNB
+0Dhx23xpMA0GCSqGSIb3DQEBCwUAA4IBAQBX36ARUohDOhdV52T3imb+YRVdlm4k
+9eX4mtE/Z+3vTuQGeCKgRFo10w94gQrRCRCQdfeyRsJHSvYFbgdGf+NboOxX2MDQ
+F9ARGw6DmIezVvNJCnngv19ULo1VrDDH9tySafmb1PFjkYwcl8a/i2MWQqM/erne
+y9aHFHGiWiGfWu8GWc1fmnZdG0LjlzLWn+zvYKmRE30v/Hb8rRhXpEAUUvaB4tNo
+8ahQCl00nEBsr7tNKLabf9OfxXLp3oiMRfzWLBG4TavH5gWS5MgXBiP6Wxidf93v
+MkM3kaYRRj+33lHdchapyKtWzgvhHa8kjDBB5oOXYhc08zqbfMpf9vNm
+-----END CERTIFICATE-----
diff --git a/artiq-fast/windows/run-test.nix b/artiq-fast/windows/run-test.nix
index 6293d2e..46932e1 100644
--- a/artiq-fast/windows/run-test.nix
+++ b/artiq-fast/windows/run-test.nix
@@ -1,10 +1,11 @@
-{ pkgs,
- sipycoPkg,
- artiqPkg,
- diskImage ? "/opt/windows/c.img",
- qemuMem ? "2G",
- testTimeout ? 600,
- testCommand ? "python -m unittest discover -v sipyco.test && python -m unittest discover -v artiq.test",
+{ pkgs
+, sipycoPkg
+, artiqPkg
+, diskImage ? "/opt/windows/c.img"
+, qemuMem ? "2G"
+, testTimeout ? 600
+, testCommand ? "python -m unittest discover -v sipyco.test && python -m unittest discover -v artiq.test"
+,
}:
with pkgs;
@@ -22,11 +23,13 @@ let
condaEnv = "artiq-env";
tcpPorts = [ 1380 1381 1382 1383 ];
forwardedPorts =
- map (port: {
- listenAddr = "192.168.1.50";
- targetAddr = "192.168.1.50";
- inherit port;
- }) tcpPorts;
+ map (
+ port: {
+ listenAddr = "192.168.1.50";
+ targetAddr = "192.168.1.50";
+ inherit port;
+ }
+ ) tcpPorts;
in
stdenv.mkDerivation {
@@ -44,12 +47,16 @@ stdenv.mkDerivation {
# +1 day from last modification of the disk image
CLOCK=$(date -Is -d @$(expr $(stat -c %Y ${diskImage}) + 86400))
${qemu.runQemu true forwardedPorts [
- "-boot" "order=c"
- "-snapshot"
- "-drive" "file=${diskImage},index=0,media=disk,cache=unsafe"
- "-rtc" "base=\\$CLOCK"
- "-display" "none"
- ]} &
+ "-boot"
+ "order=c"
+ "-snapshot"
+ "-drive"
+ "file=${diskImage},index=0,media=disk,cache=unsafe"
+ "-rtc"
+ "base=\\$CLOCK"
+ "-display"
+ "none"
+ ]} &
echo "Wait for Windows to boot"
sleep 30
diff --git a/artiq-fast/windows/win.nix b/artiq-fast/windows/win.nix
new file mode 100644
index 0000000..d5ff4cc
--- /dev/null
+++ b/artiq-fast/windows/win.nix
@@ -0,0 +1,153 @@
+{ pkgs ? import {}
+, lib ? pkgs.lib
+, diskImageSize ? "22G"
+, qemuMem ? "4G"
+, windowsImage ? null
+, autoUnattendParams ? {}
+, packages ? []
+, impureMode ? false
+, ...
+}@attrs:
+
+let
+ # qemu_test is a smaller closure only building for a single system arch
+ qemu = pkgs.qemu_test;
+ libguestfs = pkgs.libguestfs-with-appliance.override {
+ inherit qemu;
+ };
+
+ runQemuCommand = name: command: (
+ pkgs.runCommandNoCC name { buildInputs = [ pkgs.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";
+ };
+
+ 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; };
+
+ # Get updates from https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/
+ virtioWin = winPkgs.makeMSIPkg {
+ name = "virtio-win";
+ cert = ./redhat-cert.cer;
+ msi = pkgs.fetchurl {
+ url = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.173-2/virtio-win-gt-x64.msi";
+ sha256 = "0gmxw45fh22kxil1h2d42gxsri9diqfl7rdsxw2r261vvxrmrlq2";
+ };
+ ADDLOCAL = [
+ "FE_balloon_driver"
+ "FE_network_driver"
+ "FE_pvpanic_driver"
+ "FE_qemufwcfg_driver"
+ "FE_qemupciserial_driver"
+ "FE_qxl_driver"
+ "FE_spice_driver"
+ "FE_viorng_driver"
+ "FE_vioscsi_driver"
+ "FE_vioserial_driver"
+ "FE_viostor_driver"
+ ];
+ };
+
+ autohotkey = winPkgs.makePkg {
+ name = "autohotkey";
+ src = pkgs.fetchurl {
+ url = "https://www.autohotkey.com/download/1.0/AutoHotkey104805_Install.exe";
+ sha256 = "1f87w9g7f7dr0fq212vmg3zmabxw2013cf2i85zxdllyqbkw64a3";
+ };
+ installScript = ''
+ .\AutoHotkey104805_Install.exe /S
+ '';
+ };
+
+ in
+ runQemuCommand "bootstrap-win-pkgs.img" ''
+ mkdir pkgs
+ mkdir pkgs/bootstrap
+ mkdir pkgs/user
+
+ cp ${autohotkey} pkgs/bootstrap/"$(stripHash "${autohotkey}")"
+ cp ${bundleInstaller} pkgs/"$(stripHash "${bundleInstaller}")"
+
+ ${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"
+ ] ++ lib.optional (!impureMode) "-nographic";
+ in
+ ''
+ #!${pkgs.runtimeShell}
+ set -exuo pipefail
+ export PATH=${lib.makeBinPath [ pkgs.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} 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
+''