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