forked from mirrors/nixpkgs
vm/windows: Split install into several stages.
These stages are in particular:
* Install of the bare Windows VM with Cygwin and shut down.
* Boot up the same VM again without the installation media and dump the
VMs memory to state.gz.
* Resume from state.gz and build whatever we want to build.
Every single stage involves a new "controller", which is more like an
abstraction on the Nix side that constructs the madness described in
276b72fb93
.
Signed-off-by: aszlig <aszlig@redmoonstudios.org>
This commit is contained in:
parent
5105e7f0bf
commit
c731467e2c
3 changed files with 239 additions and 154 deletions
173
pkgs/build-support/vm/windows/controller/default.nix
Normal file
173
pkgs/build-support/vm/windows/controller/default.nix
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
{ sshKey
|
||||||
|
, qemuArgs ? []
|
||||||
|
, command ? "sync"
|
||||||
|
, suspendTo ? null
|
||||||
|
, resumeFrom ? null
|
||||||
|
, installMode ? false
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (import <nixpkgs> {}) lib stdenv writeScript vmTools makeInitrd;
|
||||||
|
inherit (import <nixpkgs> {}) samba vde2 busybox openssh;
|
||||||
|
inherit (import <nixpkgs> {}) socat netcat coreutils gzip;
|
||||||
|
|
||||||
|
preInitScript = writeScript "preinit.sh" ''
|
||||||
|
#!${vmTools.initrdUtils}/bin/ash -e
|
||||||
|
export PATH=${vmTools.initrdUtils}/bin
|
||||||
|
mount -t proc none /proc
|
||||||
|
mount -t sysfs none /sys
|
||||||
|
for arg in $(cat /proc/cmdline); do
|
||||||
|
if [ "x''${arg#command=}" != "x$arg" ]; then
|
||||||
|
command="''${arg#command=}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in $(cat ${modulesClosure}/insmod-list); do
|
||||||
|
insmod $i
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p /tmp /dev
|
||||||
|
mknod /dev/null c 1 3
|
||||||
|
mknod /dev/zero c 1 5
|
||||||
|
mknod /dev/random c 1 8
|
||||||
|
mknod /dev/urandom c 1 9
|
||||||
|
mknod /dev/tty c 5 0
|
||||||
|
|
||||||
|
ifconfig lo up
|
||||||
|
ifconfig eth0 up 192.168.0.2
|
||||||
|
|
||||||
|
mkdir -p /nix/store /etc /var/run /var/log
|
||||||
|
|
||||||
|
cat > /etc/passwd <<PASSWD
|
||||||
|
root:x:0:0::/root:/bin/false
|
||||||
|
nobody:x:65534:65534::/var/empty:/bin/false
|
||||||
|
PASSWD
|
||||||
|
|
||||||
|
mount -t 9p \
|
||||||
|
-o trans=virtio,version=9p2000.L,msize=262144,cache=loose \
|
||||||
|
store /nix/store
|
||||||
|
|
||||||
|
exec "$command"
|
||||||
|
'';
|
||||||
|
|
||||||
|
initrd = makeInitrd {
|
||||||
|
contents = lib.singleton {
|
||||||
|
object = preInitScript;
|
||||||
|
symlink = "/init";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
initScript = writeScript "init.sh" (''
|
||||||
|
#!${stdenv.shell}
|
||||||
|
${coreutils}/bin/mkdir -p /etc/samba /etc/samba/private /var/lib/samba
|
||||||
|
${coreutils}/bin/cat > /etc/samba/smb.conf <<CONFIG
|
||||||
|
[global]
|
||||||
|
security = user
|
||||||
|
map to guest = Bad User
|
||||||
|
workgroup = cygwin
|
||||||
|
netbios name = controller
|
||||||
|
server string = %h
|
||||||
|
log level = 1
|
||||||
|
max log size = 1000
|
||||||
|
log file = /var/log/samba.log
|
||||||
|
|
||||||
|
[nixstore]
|
||||||
|
path = /nix/store
|
||||||
|
read only = no
|
||||||
|
guest ok = yes
|
||||||
|
CONFIG
|
||||||
|
|
||||||
|
${samba}/sbin/nmbd -D
|
||||||
|
${samba}/sbin/smbd -D
|
||||||
|
${coreutils}/bin/cp -L "${sshKey}" /ssh.key
|
||||||
|
${coreutils}/bin/chmod 600 /ssh.key
|
||||||
|
'' + (if installMode then ''
|
||||||
|
echo -n "Waiting for Windows installation to finish..."
|
||||||
|
while ! ${netcat}/bin/netcat -z 192.168.0.1 22; do
|
||||||
|
echo -n .
|
||||||
|
# Print a dot every 10 seconds only to shorten line length.
|
||||||
|
${coreutils}/bin/sleep 10
|
||||||
|
done
|
||||||
|
echo " success."
|
||||||
|
# Loop forever, because this VM is going to be killed.
|
||||||
|
while :; do ${coreutils}/bin/sleep 1; done
|
||||||
|
'' else ''
|
||||||
|
echo -n "Waiting for Windows VM to become available..."
|
||||||
|
while ! ${netcat}/bin/netcat -z 192.168.0.1 22; do
|
||||||
|
echo -n .
|
||||||
|
${coreutils}/bin/sleep 1
|
||||||
|
done
|
||||||
|
echo " success."
|
||||||
|
|
||||||
|
${openssh}/bin/ssh \
|
||||||
|
-o UserKnownHostsFile=/dev/null \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-i /ssh.key \
|
||||||
|
-l Administrator \
|
||||||
|
192.168.0.1 -- "${command}"
|
||||||
|
|
||||||
|
${busybox}/sbin/poweroff -f
|
||||||
|
''));
|
||||||
|
|
||||||
|
kernelAppend = lib.concatStringsSep " " [
|
||||||
|
"panic=1"
|
||||||
|
"loglevel=4"
|
||||||
|
"console=tty1"
|
||||||
|
"console=ttyS0"
|
||||||
|
"command=${initScript}"
|
||||||
|
];
|
||||||
|
|
||||||
|
controllerQemuArgs = lib.concatStringsSep " " (maybeKvm64 ++ [
|
||||||
|
"-nographic"
|
||||||
|
"-no-reboot"
|
||||||
|
"-virtfs local,path=/nix/store,security_model=none,mount_tag=store"
|
||||||
|
"-kernel ${modulesClosure.kernel}/bzImage"
|
||||||
|
"-initrd ${initrd}/initrd"
|
||||||
|
"-append \"${kernelAppend}\""
|
||||||
|
"-net nic,vlan=0,macaddr=52:54:00:12:01:02,model=virtio"
|
||||||
|
"-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
|
||||||
|
]);
|
||||||
|
|
||||||
|
maybeKvm64 = lib.optional (stdenv.system == "x86_64-linux") "-cpu kvm64";
|
||||||
|
|
||||||
|
cygwinQemuArgs = lib.concatStringsSep " " (maybeKvm64 ++ [
|
||||||
|
"-monitor unix:$MONITOR_SOCKET,server,nowait"
|
||||||
|
"-nographic"
|
||||||
|
"-net nic,vlan=0,macaddr=52:54:00:12:01:01"
|
||||||
|
"-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
|
||||||
|
"-rtc base=2010-01-01,clock=vm"
|
||||||
|
] ++ qemuArgs ++ lib.optionals (resumeFrom != null) [
|
||||||
|
"-incoming 'exec: ${gzip}/bin/gzip -c -d \"${resumeFrom}\"'"
|
||||||
|
]);
|
||||||
|
|
||||||
|
modulesClosure = lib.overrideDerivation vmTools.modulesClosure (o: {
|
||||||
|
rootModules = o.rootModules ++ lib.singleton "virtio_net";
|
||||||
|
});
|
||||||
|
|
||||||
|
preVM = ''
|
||||||
|
QEMU_VDE_SOCKET="$(pwd)/vde.ctl"
|
||||||
|
MONITOR_SOCKET="$(pwd)/monitor"
|
||||||
|
${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" &
|
||||||
|
'';
|
||||||
|
|
||||||
|
vmExec = if installMode then ''
|
||||||
|
${vmTools.qemuProg} ${controllerQemuArgs} &
|
||||||
|
${vmTools.qemuProg} ${cygwinQemuArgs}
|
||||||
|
'' else ''
|
||||||
|
${vmTools.qemuProg} ${cygwinQemuArgs} &
|
||||||
|
${vmTools.qemuProg} ${controllerQemuArgs}
|
||||||
|
'' + lib.optionalString (suspendTo != null) ''
|
||||||
|
${socat}/bin/socat - UNIX-CONNECT:$MONITOR_SOCKET <<CMD
|
||||||
|
stop
|
||||||
|
migrate_set_speed 4095m
|
||||||
|
migrate "exec:${gzip}/bin/gzip -c > '${suspendTo}'"
|
||||||
|
quit
|
||||||
|
CMD
|
||||||
|
wait %%
|
||||||
|
'';
|
||||||
|
|
||||||
|
in writeScript "run-cygwin-vm.sh" ''
|
||||||
|
#!${stdenv.shell} -e
|
||||||
|
${preVM}
|
||||||
|
${vmExec}
|
||||||
|
''
|
|
@ -1,160 +1,49 @@
|
||||||
with import <nixpkgs> {};
|
|
||||||
|
|
||||||
with import <nixpkgs/nixos/lib/build-vms.nix> {
|
|
||||||
inherit system;
|
|
||||||
minimal = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let
|
let
|
||||||
|
inherit (import <nixpkgs> {}) lib stdenv requireFile writeText qemu;
|
||||||
|
|
||||||
winISO = /path/to/iso/XXX;
|
winISO = /path/to/iso/XXX;
|
||||||
|
|
||||||
base = import ./install {
|
installedVM = import ./install {
|
||||||
isoFile = winISO;
|
isoFile = winISO;
|
||||||
productKey = "XXX";
|
productKey = "XXX";
|
||||||
};
|
};
|
||||||
|
|
||||||
maybeKvm64 = lib.optional (stdenv.system == "x86_64-linux") "-cpu kvm64";
|
runInVM = img: attrs: import ./controller (attrs // {
|
||||||
|
inherit (installedVM) sshKey;
|
||||||
cygwinQemuArgs = lib.concatStringsSep " " (maybeKvm64 ++ [
|
qemuArgs = attrs.qemuArgs or [] ++ [
|
||||||
"-monitor unix:$MONITOR_SOCKET,server,nowait"
|
"-boot order=c"
|
||||||
"-nographic"
|
"-drive file=${img},index=0,media=disk"
|
||||||
"-boot order=c,once=d"
|
];
|
||||||
"-drive file=${base.floppy},readonly,index=0,if=floppy"
|
|
||||||
"-drive file=winvm.img,index=0,media=disk"
|
|
||||||
"-drive file=${winISO},index=1,media=cdrom"
|
|
||||||
"-drive file=${base.iso}/iso/cd.iso,index=2,media=cdrom"
|
|
||||||
"-net nic,vlan=0,macaddr=52:54:00:12:01:01"
|
|
||||||
"-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
|
|
||||||
"-rtc base=2010-01-01,clock=vm"
|
|
||||||
]);
|
|
||||||
|
|
||||||
modulesClosure = lib.overrideDerivation vmTools.modulesClosure (o: {
|
|
||||||
rootModules = o.rootModules ++ lib.singleton "virtio_net";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
controllerQemuArgs = cmd: let
|
runAndSuspend = runInVM "winvm.img" {
|
||||||
preInitScript = writeScript "preinit.sh" ''
|
suspendTo = "state.gz";
|
||||||
#!${vmTools.initrdUtils}/bin/ash -e
|
};
|
||||||
export PATH=${vmTools.initrdUtils}/bin
|
|
||||||
mount -t proc none /proc
|
|
||||||
mount -t sysfs none /sys
|
|
||||||
for arg in $(cat /proc/cmdline); do
|
|
||||||
if [ "x''${arg#command=}" != "x$arg" ]; then
|
|
||||||
command="''${arg#command=}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for i in $(cat ${modulesClosure}/insmod-list); do
|
|
||||||
insmod $i
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir -p /tmp /dev
|
|
||||||
mknod /dev/null c 1 3
|
|
||||||
mknod /dev/zero c 1 5
|
|
||||||
mknod /dev/random c 1 8
|
|
||||||
mknod /dev/urandom c 1 9
|
|
||||||
mknod /dev/tty c 5 0
|
|
||||||
|
|
||||||
ifconfig lo up
|
|
||||||
ifconfig eth0 up 192.168.0.2
|
|
||||||
|
|
||||||
mkdir -p /nix/store /etc /var/run /var/log
|
|
||||||
|
|
||||||
cat > /etc/passwd <<PASSWD
|
|
||||||
root:x:0:0::/root:/bin/false
|
|
||||||
nobody:x:65534:65534::/var/empty:/bin/false
|
|
||||||
PASSWD
|
|
||||||
|
|
||||||
mount -t 9p \
|
|
||||||
-o trans=virtio,version=9p2000.L,msize=262144,cache=loose \
|
|
||||||
store /nix/store
|
|
||||||
|
|
||||||
exec "$command"
|
|
||||||
'';
|
|
||||||
initrd = makeInitrd {
|
|
||||||
contents = lib.singleton {
|
|
||||||
object = preInitScript;
|
|
||||||
symlink = "/init";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
initScript = writeScript "init.sh" ''
|
|
||||||
#!${stdenv.shell}
|
|
||||||
${coreutils}/bin/mkdir -p /etc/samba /etc/samba/private /var/lib/samba
|
|
||||||
${coreutils}/bin/cat > /etc/samba/smb.conf <<CONFIG
|
|
||||||
[global]
|
|
||||||
security = user
|
|
||||||
map to guest = Bad User
|
|
||||||
workgroup = cygwin
|
|
||||||
netbios name = controller
|
|
||||||
server string = %h
|
|
||||||
log level = 1
|
|
||||||
max log size = 1000
|
|
||||||
log file = /var/log/samba.log
|
|
||||||
|
|
||||||
[nixstore]
|
|
||||||
path = /nix/store
|
|
||||||
read only = no
|
|
||||||
guest ok = yes
|
|
||||||
CONFIG
|
|
||||||
|
|
||||||
${samba}/sbin/nmbd -D
|
|
||||||
${samba}/sbin/smbd -D
|
|
||||||
${coreutils}/bin/cp -L "${base.sshKey}" /ssh.key
|
|
||||||
${coreutils}/bin/chmod 600 /ssh.key
|
|
||||||
|
|
||||||
echo -n "Waiting for Windows VM to become ready"
|
|
||||||
while ! ${netcat}/bin/netcat -z 192.168.0.1 22; do
|
|
||||||
echo -n .
|
|
||||||
${coreutils}/bin/sleep 1
|
|
||||||
done
|
|
||||||
echo " ready."
|
|
||||||
|
|
||||||
${openssh}/bin/ssh \
|
|
||||||
-o UserKnownHostsFile=/dev/null \
|
|
||||||
-o StrictHostKeyChecking=no \
|
|
||||||
-i /ssh.key \
|
|
||||||
-l Administrator \
|
|
||||||
192.168.0.1 -- "${cmd}"
|
|
||||||
|
|
||||||
${busybox}/sbin/poweroff -f
|
|
||||||
'';
|
|
||||||
kernelAppend = lib.concatStringsSep " " [
|
|
||||||
"panic=1"
|
|
||||||
"loglevel=4"
|
|
||||||
"console=tty1"
|
|
||||||
"console=ttyS0"
|
|
||||||
"command=${initScript}"
|
|
||||||
];
|
|
||||||
in lib.concatStringsSep " " (maybeKvm64 ++ [
|
|
||||||
"-nographic"
|
|
||||||
"-no-reboot"
|
|
||||||
"-virtfs local,path=/nix/store,security_model=none,mount_tag=store"
|
|
||||||
"-kernel ${modulesClosure.kernel}/bzImage"
|
|
||||||
"-initrd ${initrd}/initrd"
|
|
||||||
"-append \"${kernelAppend}\""
|
|
||||||
"-net nic,vlan=0,macaddr=52:54:00:12:01:02,model=virtio"
|
|
||||||
"-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
|
|
||||||
]);
|
|
||||||
|
|
||||||
bootstrap = stdenv.mkDerivation {
|
|
||||||
name = "windown-vm";
|
|
||||||
|
|
||||||
|
suspendedVM = stdenv.mkDerivation {
|
||||||
|
name = "cygwin-suspended-vm";
|
||||||
buildCommand = ''
|
buildCommand = ''
|
||||||
${qemu}/bin/qemu-img create -f qcow2 winvm.img 2G
|
${qemu}/bin/qemu-img create \
|
||||||
QEMU_VDE_SOCKET="$(pwd)/vde.ctl"
|
-b "${installedVM}/disk.img" \
|
||||||
MONITOR_SOCKET="$(pwd)/monitor"
|
-f qcow2 winvm.img
|
||||||
${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" &
|
${runAndSuspend}
|
||||||
${vmTools.qemuProg} ${cygwinQemuArgs} &
|
|
||||||
${vmTools.qemuProg} ${controllerQemuArgs "sync"}
|
|
||||||
|
|
||||||
ensureDir "$out"
|
ensureDir "$out"
|
||||||
${socat}/bin/socat - UNIX-CONNECT:$MONITOR_SOCKET <<CMD
|
|
||||||
stop
|
|
||||||
migrate_set_speed 4095m
|
|
||||||
migrate "exec:${gzip}/bin/gzip -c > '$out/state.gz'"
|
|
||||||
CMD
|
|
||||||
cp winvm.img "$out/disk.img"
|
cp winvm.img "$out/disk.img"
|
||||||
|
cp state.gz "$out/state.gz"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
in bootstrap
|
resumeAndRun = command: runInVM "${suspendedVM}/disk.img" {
|
||||||
|
resumeFrom = "${suspendedVM}/state.gz";
|
||||||
|
qemuArgs = lib.singleton "-snapshot";
|
||||||
|
inherit command;
|
||||||
|
};
|
||||||
|
|
||||||
|
runFromSuspended = command: stdenv.mkDerivation {
|
||||||
|
name = "cygwin-vm-run";
|
||||||
|
buildCommand = ''
|
||||||
|
${resumeAndRun command}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
in runFromSuspended "uname -a"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (import <nixpkgs> {}) lib stdenv runCommand openssh;
|
inherit (import <nixpkgs> {}) lib stdenv runCommand openssh qemu;
|
||||||
|
|
||||||
bootstrapAfterLogin = runCommand "bootstrap.sh" {} ''
|
bootstrapAfterLogin = runCommand "bootstrap.sh" {} ''
|
||||||
cat > "$out" <<EOF
|
cat > "$out" <<EOF
|
||||||
|
@ -12,11 +12,11 @@ let
|
||||||
$(cat "${cygwinSshKey}/key.pub")
|
$(cat "${cygwinSshKey}/key.pub")
|
||||||
PUBKEY
|
PUBKEY
|
||||||
ssh-host-config -y -c 'binmode ntsec' -w dummy
|
ssh-host-config -y -c 'binmode ntsec' -w dummy
|
||||||
cygrunsrv -S sshd
|
|
||||||
|
|
||||||
net use S: '\\192.168.0.2\nixstore'
|
net use S: '\\192.168.0.2\nixstore'
|
||||||
mkdir -p /nix/store
|
mkdir -p /nix/store
|
||||||
mount -o bind /cygdrives/s /nix/store
|
echo "/cygdrives/s /nix/store none bind 0 0" >> /etc/fstab
|
||||||
|
shutdown -s now
|
||||||
EOF
|
EOF
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
@ -28,10 +28,16 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
packages = [ "openssh" ];
|
sshKey = "${cygwinSshKey}/key";
|
||||||
|
|
||||||
in {
|
packages = [ "openssh" "shutdown" ];
|
||||||
iso = import ../cygwin-iso {
|
|
||||||
|
instfloppy = import ./unattended-image.nix {
|
||||||
|
cygwinPackages = packages;
|
||||||
|
inherit productKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
cygiso = import ../cygwin-iso {
|
||||||
inherit packages;
|
inherit packages;
|
||||||
extraContents = lib.singleton {
|
extraContents = lib.singleton {
|
||||||
source = bootstrapAfterLogin;
|
source = bootstrapAfterLogin;
|
||||||
|
@ -39,10 +45,27 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
floppy = import ./unattended-image.nix {
|
installController = import ../controller {
|
||||||
cygwinPackages = packages;
|
inherit sshKey;
|
||||||
inherit productKey;
|
installMode = true;
|
||||||
|
qemuArgs = [
|
||||||
|
"-boot order=c,once=d"
|
||||||
|
"-drive file=${instfloppy},readonly,index=0,if=floppy"
|
||||||
|
"-drive file=winvm.img,index=0,media=disk"
|
||||||
|
"-drive file=${isoFile},index=1,media=cdrom"
|
||||||
|
"-drive file=${cygiso}/iso/cd.iso,index=2,media=cdrom"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
sshKey = "${cygwinSshKey}/key";
|
in stdenv.mkDerivation {
|
||||||
|
name = "cygwin-base-vm";
|
||||||
|
buildCommand = ''
|
||||||
|
${qemu}/bin/qemu-img create -f qcow2 winvm.img 2G
|
||||||
|
${installController}
|
||||||
|
ensureDir "$out"
|
||||||
|
cp winvm.img "$out/disk.img"
|
||||||
|
'';
|
||||||
|
passthru = {
|
||||||
|
inherit sshKey;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue