From 4510ae75525fe887bdb0a635618434176813336f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 15 Jun 2020 00:28:31 +0800 Subject: [PATCH] wfvm: reorganize, add demo-ssh --- artiq-fast/wfvm/default.nix | 1 + artiq-fast/wfvm/demo-image.nix | 28 ++++----- artiq-fast/wfvm/demo-ssh.nix | 11 ++++ artiq-fast/wfvm/layers/default.nix | 12 ++-- artiq-fast/wfvm/utils.nix | 92 +++++++++++++++++++++++++++ artiq-fast/wfvm/win.nix | 99 +++++++----------------------- 6 files changed, 145 insertions(+), 98 deletions(-) create mode 100644 artiq-fast/wfvm/demo-ssh.nix create mode 100644 artiq-fast/wfvm/utils.nix diff --git a/artiq-fast/wfvm/default.nix b/artiq-fast/wfvm/default.nix index e792522..7846583 100644 --- a/artiq-fast/wfvm/default.nix +++ b/artiq-fast/wfvm/default.nix @@ -3,4 +3,5 @@ { makeWindowsImage = attrs: import ./win.nix ({ inherit pkgs; } // attrs); layers = (import ./layers { inherit pkgs; }); + utils = (import ./utils.nix { inherit pkgs; }); } diff --git a/artiq-fast/wfvm/demo-image.nix b/artiq-fast/wfvm/demo-image.nix index 467d625..31a189c 100644 --- a/artiq-fast/wfvm/demo-image.nix +++ b/artiq-fast/wfvm/demo-image.nix @@ -1,9 +1,11 @@ { pkgs ? import {}, impureMode ? false }: let - win = (import ./default.nix { inherit pkgs; }); + wfvm = (import ./default.nix { inherit pkgs; }); in -win.makeWindowsImage { +wfvm.makeWindowsImage { + # Build install script & skip building iso + inherit impureMode; # Custom base iso # windowsImage = pkgs.fetchurl { @@ -11,6 +13,10 @@ win.makeWindowsImage { # sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a"; # }; + # impureShellCommands = [ + # "powershell.exe echo Hello" + # ]; + # User accounts users = { artiq = { @@ -23,23 +29,15 @@ win.makeWindowsImage { }; }; - # Build install script & skip building iso - inherit impureMode; - - # impureShellCommands = [ - # "powershell.exe echo Hello" - # ]; - - fullName = "M-Labs"; - organization = "m-labs"; - - administratorPassword = "12345"; - # Auto login defaultUser = "artiq"; + fullName = "M-Labs"; + organization = "m-labs"; + administratorPassword = "12345"; + # Imperative installation commands, to be installed incrementally - installCommands = with win.layers; [ anaconda3 msys2 msys2-packages ]; + installCommands = with wfvm.layers; [ anaconda3 msys2 msys2-packages ]; # services = { # # Enable remote management diff --git a/artiq-fast/wfvm/demo-ssh.nix b/artiq-fast/wfvm/demo-ssh.nix new file mode 100644 index 0000000..87a5148 --- /dev/null +++ b/artiq-fast/wfvm/demo-ssh.nix @@ -0,0 +1,11 @@ +{ pkgs ? import {} }: + +let + wfvm = (import ./default.nix { inherit pkgs; }); +in + wfvm.utils.wfvm-run { + name = "demo-ssh"; + image = import ./demo-image.nix { inherit pkgs; }; + display = true; + script = "${pkgs.openssh}/bin/ssh -p 2022 wfvm@localhost"; + } diff --git a/artiq-fast/wfvm/layers/default.nix b/artiq-fast/wfvm/layers/default.nix index 4a98165..a9be89c 100644 --- a/artiq-fast/wfvm/layers/default.nix +++ b/artiq-fast/wfvm/layers/default.nix @@ -10,7 +10,7 @@ }; in '' ln -s ${Anaconda3} ./Anaconda3.exe - win-put Anaconda3.exe 'C:\Users\artiq' + win-put Anaconda3.exe 'C:\Users\wfvm' echo Running Anaconda installer... win-exec 'start /wait "" .\Anaconda3.exe /S /D=%UserProfile%\Anaconda3' echo Anaconda installer finished @@ -32,8 +32,8 @@ in '' ln -s ${msys2} ./msys2.exe ln -s ${msys2-auto-install} ./auto-install.js - win-put msys2.exe 'C:\Users\artiq' - win-put auto-install.js 'C:\Users\artiq' + win-put msys2.exe 'C:\Users\wfvm' + win-put auto-install.js 'C:\Users\wfvm' 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 }' @@ -45,7 +45,7 @@ script = let msys-packages = import ./msys_packages.nix { inherit pkgs; }; msys-packages-put = pkgs.lib.strings.concatStringsSep "\n" - (map (package: ''win-put ${package} 'C:\Users\artiq\msyspackages' '') msys-packages); + (map (package: ''win-put ${package} 'C:\Users\wfvm\msyspackages' '') msys-packages); in # Windows command line is so shitty it can't even do glob expansion. Why do people use Windows? '' @@ -55,9 +55,9 @@ set MSYS=c:\msys64 set ARCH=64 set PATH=%MSYS%\usr\bin;%MSYS%\mingw%ARCH%\bin;%PATH% - bash -c "pacman -U --noconfirm C:/Users/artiq/msyspackages/*" + bash -c "pacman -U --noconfirm C:/Users/wfvm/msyspackages/*" EOF - win-put installmsyspackages.bat 'C:\Users\artiq' + win-put installmsyspackages.bat 'C:\Users\wfvm' win-exec installmsyspackages ''; }; diff --git a/artiq-fast/wfvm/utils.nix b/artiq-fast/wfvm/utils.nix new file mode 100644 index 0000000..33557b7 --- /dev/null +++ b/artiq-fast/wfvm/utils.nix @@ -0,0 +1,92 @@ +{ pkgs, baseRtc ? "2020-04-20T14:21:42", cores ? "4", qemuMem ? "4G" }: + +rec { + # qemu_test is a smaller closure only building for a single system arch + qemu = pkgs.qemu_test; + + mkQemuFlags = extraFlags: [ + "-enable-kvm" + "-cpu host" + "-smp ${cores}" + "-m ${qemuMem}" + "-bios ${pkgs.OVMF.fd}/FV/OVMF.fd" + "-vga virtio" + "-rtc base=${baseRtc}" + "-device piix3-usb-uhci" + "-device e1000,netdev=n1" + ] ++ extraFlags; + + # 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 -o ConnectTimeout=1"; + win-exec = pkgs.writeShellScriptBin "win-exec" '' + ${pkgs.sshpass}/bin/sshpass -p1234 -- \ + ${pkgs.openssh}/bin/ssh -np 2022 ${sshOpts} \ + wfvm@localhost \ + $1 + ''; + win-wait = pkgs.writeShellScriptBin "win-wait" '' + # 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" '' + echo scp windows $1 -\> $2 + ${pkgs.sshpass}/bin/sshpass -p1234 -- \ + ${pkgs.openssh}/bin/scp -P 2022 ${sshOpts} \ + $1 wfvm@localhost:$2 + ''; + + wfvm-run = { name, image, script, display ? false, isolateNetwork ? true, forwardedPorts ? [] }: + 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) "-nographic" ++ [ + "-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 -m + 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/artiq-fast/wfvm/win.nix b/artiq-fast/wfvm/win.nix index a53068f..45fd41e 100644 --- a/artiq-fast/wfvm/win.nix +++ b/artiq-fast/wfvm/win.nix @@ -1,10 +1,8 @@ { pkgs , diskImageSize ? "22G" -, qemuMem ? "4G" , windowsImage ? null , autoUnattendParams ? {} , impureMode ? false -, baseRtc ? "2020-04-20T14:21:42" , installCommands ? [] , users ? {} , ... @@ -12,8 +10,7 @@ let lib = pkgs.lib; - # qemu_test is a smaller closure only building for a single system arch - qemu = pkgs.qemu_test; + utils = import ./utils.nix { inherit pkgs; }; libguestfs = pkgs.libguestfs-with-appliance; # p7zip on >20.03 has known vulns but we have no better option @@ -24,7 +21,7 @@ let }); runQemuCommand = name: command: ( - pkgs.runCommandNoCC name { buildInputs = [ p7zip qemu libguestfs ]; } + pkgs.runCommandNoCC name { buildInputs = [ p7zip utils.qemu libguestfs ]; } ( '' if ! test -f; then @@ -45,6 +42,15 @@ let autounattend = import ./autounattend.nix ( attrs // { inherit pkgs; + users = users // { + wfvm = { + password = "1234"; + description = "WFVM Administrator"; + groups = [ + "Administrators" + ]; + }; + }; } ); @@ -66,27 +72,9 @@ let virt-make-fs --partition --type=fat pkgs/ $out ''; - mkQemuFlags = extraFlags: [ - "-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 - "-rtc base=${baseRtc}" - "-device e1000,netdev=n1" - ] ++ lib.optional (!impureMode) "-nographic" ++ extraFlags; - installScript = pkgs.writeScript "windows-install-script" ( let - qemuParams = mkQemuFlags [ + qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-nographic" ++ [ # "CD" drive with bootstrap pkgs "-drive" "id=virtio-win,file=${bootstrapPkgs},if=none,format=raw,readonly=on" @@ -102,16 +90,12 @@ let "file=c.img,index=0,media=disk,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 qemu libguestfs ]}:$PATH - - if test -z "''${NIX_BUILD_CORES+x}"; then - export NIX_BUILD_CORES=$(nproc) - fi + export PATH=${lib.makeBinPath [ p7zip utils.qemu libguestfs ]}:$PATH # Create a bootable "USB" image # Booting in USB mode circumvents the "press any key to boot from cdrom" prompt @@ -128,7 +112,7 @@ let # 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} + qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} '' ); @@ -137,36 +121,19 @@ let mv c.img $out ''; - # 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 -o ConnectTimeout=1"; - win-exec = pkgs.writeShellScriptBin "win-exec" '' - ${pkgs.sshpass}/bin/sshpass -p${users.artiq.password} -- \ - ${pkgs.openssh}/bin/ssh -np 2022 ${sshOpts} \ - artiq@localhost \ - $1 - ''; - win-put = pkgs.writeShellScriptBin "win-put" '' - echo scp windows $1 -\> $2 - ${pkgs.sshpass}/bin/sshpass -p${users.artiq.password} -- \ - ${pkgs.openssh}/bin/scp -P 2022 ${sshOpts} \ - $1 artiq@localhost:$2 - ''; - finalImage = builtins.foldl' (acc: v: pkgs.runCommandNoCC "${v.name}.img" { - buildInputs = [ - win-exec - win-put - qemu + buildInputs = with utils; [ + qemu win-wait win-exec win-put ] ++ (v.buildInputs or []); } (let script = pkgs.writeScript "${v.name}-script" v.script; - qemuParams = mkQemuFlags [ + qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-nographic" ++ [ # Output image "-drive" "file=c.img,index=0,media=disk,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 @@ -175,33 +142,11 @@ let set -m qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} & - # If the machine is not up within 10 minutes it's likely never coming up - timeout=600 + win-wait - # 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 '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 "Executing user script..." + echo "Executing script to build layer..." ${script} - echo "User script done" + echo "Layer script done" echo "Shutting down..." win-exec 'shutdown /s'