1{ stdenv, writeScript, vmTools, makeInitrd
2, samba, vde2, openssh, socat, netcat-gnu, coreutils, gnugrep, gzip
3}:
4
5{ sshKey
6, qemuArgs ? []
7, command ? "sync"
8, suspendTo ? null
9, resumeFrom ? null
10, installMode ? false
11}:
12
13with stdenv.lib;
14
15let
16 preInitScript = writeScript "preinit.sh" ''
17 #!${vmTools.initrdUtils}/bin/ash -e
18 export PATH=${vmTools.initrdUtils}/bin
19 mount -t proc none /proc
20 mount -t sysfs none /sys
21 for arg in $(cat /proc/cmdline); do
22 if [ "x''${arg#command=}" != "x$arg" ]; then
23 command="''${arg#command=}"
24 fi
25 done
26
27 for i in $(cat ${modulesClosure}/insmod-list); do
28 insmod $i
29 done
30
31 mkdir -p /dev /fs
32
33 mount -t tmpfs none /dev
34 mknod /dev/null c 1 3
35 mknod /dev/zero c 1 5
36 mknod /dev/random c 1 8
37 mknod /dev/urandom c 1 9
38 mknod /dev/tty c 5 0
39
40 ifconfig lo up
41 ifconfig eth0 up 192.168.0.2
42
43 mount -t tmpfs none /fs
44 mkdir -p /fs/nix/store /fs/xchg /fs/dev /fs/sys /fs/proc /fs/etc /fs/tmp
45
46 mount -o bind /dev /fs/dev
47 mount -t sysfs none /fs/sys
48 mount -t proc none /fs/proc
49
50 mount -t 9p \
51 -o trans=virtio,version=9p2000.L,cache=loose \
52 store /fs/nix/store
53
54 mount -t 9p \
55 -o trans=virtio,version=9p2000.L,cache=loose \
56 xchg /fs/xchg
57
58 echo root:x:0:0::/root:/bin/false > /fs/etc/passwd
59
60 set +e
61 chroot /fs $command $out
62 echo $? > /fs/xchg/in-vm-exit
63
64 poweroff -f
65 '';
66
67 initrd = makeInitrd {
68 contents = singleton {
69 object = preInitScript;
70 symlink = "/init";
71 };
72 };
73
74 loopForever = "while :; do ${coreutils}/bin/sleep 1; done";
75
76 initScript = writeScript "init.sh" (''
77 #!${stdenv.shell}
78 ${coreutils}/bin/cp -L "${sshKey}" /ssh.key
79 ${coreutils}/bin/chmod 600 /ssh.key
80 '' + (if installMode then ''
81 echo -n "Waiting for Windows installation to finish..."
82 while ! ${netcat-gnu}/bin/netcat -z 192.168.0.1 22; do
83 echo -n .
84 # Print a dot every 10 seconds only to shorten line length.
85 ${coreutils}/bin/sleep 10
86 done
87 ${coreutils}/bin/touch /xchg/waiting_done
88 echo " success."
89 # Loop forever, because this VM is going to be killed.
90 ${loopForever}
91 '' else ''
92 ${coreutils}/bin/mkdir -p /etc/samba /etc/samba/private \
93 /var/lib/samba /var/log /var/run
94 ${coreutils}/bin/cat > /etc/samba/smb.conf <<CONFIG
95 [global]
96 security = user
97 map to guest = Bad User
98 guest account = root
99 workgroup = cygwin
100 netbios name = controller
101 server string = %h
102 log level = 1
103 max log size = 1000
104 log file = /var/log/samba.log
105
106 [nixstore]
107 path = /nix/store
108 writable = yes
109 guest ok = yes
110
111 [xchg]
112 path = /xchg
113 writable = yes
114 guest ok = yes
115 CONFIG
116
117 ${samba}/sbin/nmbd -D
118 ${samba}/sbin/smbd -D
119
120 echo -n "Waiting for Windows VM to become available..."
121 while ! ${netcat-gnu}/bin/netcat -z 192.168.0.1 22; do
122 echo -n .
123 ${coreutils}/bin/sleep 1
124 done
125 ${coreutils}/bin/touch /xchg/waiting_done
126 echo " success."
127
128 ${openssh}/bin/ssh \
129 -o UserKnownHostsFile=/dev/null \
130 -o StrictHostKeyChecking=no \
131 -i /ssh.key \
132 -l Administrator \
133 192.168.0.1 -- ${lib.escapeShellArg command}
134 '') + optionalString (suspendTo != null) ''
135 ${coreutils}/bin/touch /xchg/suspend_now
136 ${loopForever}
137 '');
138
139 kernelAppend = concatStringsSep " " [
140 "panic=1"
141 "loglevel=4"
142 "console=tty1"
143 "console=ttyS0"
144 "command=${initScript}"
145 ];
146
147 controllerQemuArgs = concatStringsSep " " (maybeKvm64 ++ [
148 "-pidfile $CTRLVM_PIDFILE"
149 "-nographic"
150 "-no-reboot"
151 "-virtfs local,path=/nix/store,security_model=none,mount_tag=store"
152 "-virtfs local,path=$XCHG_DIR,security_model=none,mount_tag=xchg"
153 "-kernel ${modulesClosure.kernel}/bzImage"
154 "-initrd ${initrd}/initrd"
155 "-append \"${kernelAppend}\""
156 "-net nic,vlan=0,macaddr=52:54:00:12:01:02,model=virtio"
157 "-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
158 ]);
159
160 maybeKvm64 = optional (stdenv.system == "x86_64-linux") "-cpu kvm64";
161
162 cygwinQemuArgs = concatStringsSep " " (maybeKvm64 ++ [
163 "-monitor unix:$MONITOR_SOCKET,server,nowait"
164 "-pidfile $WINVM_PIDFILE"
165 "-nographic"
166 "-net nic,vlan=0,macaddr=52:54:00:12:01:01"
167 "-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
168 "-rtc base=2010-01-01,clock=vm"
169 ] ++ qemuArgs ++ optionals (resumeFrom != null) [
170 "-incoming 'exec: ${gzip}/bin/gzip -c -d \"${resumeFrom}\"'"
171 ]);
172
173 modulesClosure = overrideDerivation vmTools.modulesClosure (o: {
174 rootModules = o.rootModules ++ singleton "virtio_net";
175 });
176
177 preVM = ''
178 (set; declare -p) > saved-env
179 XCHG_DIR="$(${coreutils}/bin/mktemp -d nix-vm.XXXXXXXXXX --tmpdir)"
180 ${coreutils}/bin/mv saved-env "$XCHG_DIR/"
181
182 eval "$preVM"
183
184 QEMU_VDE_SOCKET="$(pwd)/vde.ctl"
185 MONITOR_SOCKET="$(pwd)/monitor"
186 WINVM_PIDFILE="$(pwd)/winvm.pid"
187 CTRLVM_PIDFILE="$(pwd)/ctrlvm.pid"
188 ${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" --dirmode 0700 &
189 echo 'alive?' | ${socat}/bin/socat - \
190 UNIX-CONNECT:$QEMU_VDE_SOCKET/ctl,retry=20
191 '';
192
193 vmExec = ''
194 ${vmTools.qemuProg} ${controllerQemuArgs} &
195 ${vmTools.qemuProg} ${cygwinQemuArgs} &
196 echo -n "Waiting for VMs to start up..."
197 timeout=60
198 while ! test -e "$WINVM_PIDFILE" -a -e "$CTRLVM_PIDFILE"; do
199 timeout=$(($timeout - 1))
200 echo -n .
201 if test $timeout -le 0; then
202 echo " timed out."
203 exit 1
204 fi
205 ${coreutils}/bin/sleep 1
206 done
207 echo " done."
208 '';
209
210 checkDropOut = ''
211 if ! test -e "$XCHG_DIR/waiting_done" &&
212 ! kill -0 $(< "$WINVM_PIDFILE"); then
213 echo "Windows VM has dropped out early, bailing out!" >&2
214 exit 1
215 fi
216 '';
217
218 toMonitor = "${socat}/bin/socat - UNIX-CONNECT:$MONITOR_SOCKET";
219
220 postVM = if suspendTo != null then ''
221 while ! test -e "$XCHG_DIR/suspend_now"; do
222 ${checkDropOut}
223 ${coreutils}/bin/sleep 1
224 done
225 ${toMonitor} <<CMD
226 stop
227 migrate_set_speed 4095m
228 migrate "exec:${gzip}/bin/gzip -c > '${suspendTo}'"
229 CMD
230 echo -n "Waiting for memory dump to finish..."
231 while ! echo info migrate | ${toMonitor} | \
232 ${gnugrep}/bin/grep -qi '^migration *status: *complete'; do
233 ${coreutils}/bin/sleep 1
234 echo -n .
235 done
236 echo " done."
237 echo quit | ${toMonitor}
238 wait $(< "$WINVM_PIDFILE")
239 eval "$postVM"
240 exit 0
241 '' else if installMode then ''
242 wait $(< "$WINVM_PIDFILE")
243 eval "$postVM"
244 exit 0
245 '' else ''
246 while kill -0 $(< "$CTRLVM_PIDFILE"); do
247 ${checkDropOut}
248 done
249 if ! test -e "$XCHG_DIR/in-vm-exit"; then
250 echo "Virtual machine didn't produce an exit code."
251 exit 1
252 fi
253 eval "$postVM"
254 exit $(< "$XCHG_DIR/in-vm-exit")
255 '';
256
257in writeScript "run-cygwin-vm.sh" ''
258 #!${stdenv.shell} -e
259 ${preVM}
260 ${vmExec}
261 ${postVM}
262''