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:
aszlig 2014-02-15 23:23:47 +01:00
parent 5105e7f0bf
commit c731467e2c
No known key found for this signature in database
GPG Key ID: D0EBD0EC8C2DC961
3 changed files with 239 additions and 154 deletions

View 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}
''

View File

@ -1,160 +1,49 @@
with import <nixpkgs> {};
with import <nixpkgs/nixos/lib/build-vms.nix> {
inherit system;
minimal = false;
};
let
inherit (import <nixpkgs> {}) lib stdenv requireFile writeText qemu;
winISO = /path/to/iso/XXX;
base = import ./install {
installedVM = import ./install {
isoFile = winISO;
productKey = "XXX";
};
maybeKvm64 = lib.optional (stdenv.system == "x86_64-linux") "-cpu kvm64";
cygwinQemuArgs = lib.concatStringsSep " " (maybeKvm64 ++ [
"-monitor unix:$MONITOR_SOCKET,server,nowait"
"-nographic"
"-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";
runInVM = img: attrs: import ./controller (attrs // {
inherit (installedVM) sshKey;
qemuArgs = attrs.qemuArgs or [] ++ [
"-boot order=c"
"-drive file=${img},index=0,media=disk"
];
});
controllerQemuArgs = cmd: let
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 "${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";
runAndSuspend = runInVM "winvm.img" {
suspendTo = "state.gz";
};
suspendedVM = stdenv.mkDerivation {
name = "cygwin-suspended-vm";
buildCommand = ''
${qemu}/bin/qemu-img create -f qcow2 winvm.img 2G
QEMU_VDE_SOCKET="$(pwd)/vde.ctl"
MONITOR_SOCKET="$(pwd)/monitor"
${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" &
${vmTools.qemuProg} ${cygwinQemuArgs} &
${vmTools.qemuProg} ${controllerQemuArgs "sync"}
${qemu}/bin/qemu-img create \
-b "${installedVM}/disk.img" \
-f qcow2 winvm.img
${runAndSuspend}
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 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"

View File

@ -3,7 +3,7 @@
}:
let
inherit (import <nixpkgs> {}) lib stdenv runCommand openssh;
inherit (import <nixpkgs> {}) lib stdenv runCommand openssh qemu;
bootstrapAfterLogin = runCommand "bootstrap.sh" {} ''
cat > "$out" <<EOF
@ -12,11 +12,11 @@ let
$(cat "${cygwinSshKey}/key.pub")
PUBKEY
ssh-host-config -y -c 'binmode ntsec' -w dummy
cygrunsrv -S sshd
net use S: '\\192.168.0.2\nixstore'
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
'';
@ -28,10 +28,16 @@ let
'';
};
packages = [ "openssh" ];
sshKey = "${cygwinSshKey}/key";
in {
iso = import ../cygwin-iso {
packages = [ "openssh" "shutdown" ];
instfloppy = import ./unattended-image.nix {
cygwinPackages = packages;
inherit productKey;
};
cygiso = import ../cygwin-iso {
inherit packages;
extraContents = lib.singleton {
source = bootstrapAfterLogin;
@ -39,10 +45,27 @@ in {
};
};
floppy = import ./unattended-image.nix {
cygwinPackages = packages;
inherit productKey;
installController = import ../controller {
inherit sshKey;
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;
};
}