vm: Print the label and UUID of partitions.
[jackhill/guix/guix.git] / gnu / system / vm.scm
CommitLineData
04086015 1;;; GNU Guix --- Functional package management for GNU
61b94b8c 2;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
944d2b17 3;;; Copyright © 2016 Christopher Allan Webber <cwebber@dustycloud.org>
2ca712bd 4;;; Copyright © 2016, 2017 Leo Famulari <leo@famulari.name>
07f812c4 5;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
ecf5d537 6;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com>
8c9bf294 7;;; Copyright © 2018 Chris Marusich <cmmarusich@gmail.com>
04086015
LC
8;;;
9;;; This file is part of GNU Guix.
10;;;
11;;; GNU Guix is free software; you can redistribute it and/or modify it
12;;; under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation; either version 3 of the License, or (at
14;;; your option) any later version.
15;;;
16;;; GNU Guix is distributed in the hope that it will be useful, but
17;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24(define-module (gnu system vm)
93d44bd8 25 #:use-module (guix config)
a335f6fc 26 #:use-module (guix docker)
04086015 27 #:use-module (guix store)
02100028 28 #:use-module (guix gexp)
04086015
LC
29 #:use-module (guix derivations)
30 #:use-module (guix packages)
d9f0a237 31 #:use-module (guix monads)
fcf63cf8 32 #:use-module (guix records)
239c6e27 33 #:use-module (guix modules)
a335f6fc 34 #:use-module (guix scripts pack)
00e39b2e 35 #:use-module (guix utils)
dffc5ab5
LC
36 #:use-module (guix hash)
37 #:use-module (guix base32)
fcf63cf8 38
548f7a8f 39 #:use-module ((gnu build vm)
66670cf3 40 #:select (qemu-command))
bdb36958 41 #:use-module (gnu packages base)
862e38d5 42 #:use-module (gnu packages bootloaders)
be1033a3 43 #:use-module (gnu packages cdrom)
a335f6fc 44 #:use-module (gnu packages compression)
1b89a66e 45 #:use-module (gnu packages guile)
a335f6fc 46 #:autoload (gnu packages gnupg) (libgcrypt)
bdb36958 47 #:use-module (gnu packages gawk)
1b89a66e 48 #:use-module (gnu packages bash)
4f62d8d6 49 #:use-module (gnu packages less)
59132b80 50 #:use-module (gnu packages virtualization)
cc4a2aeb 51 #:use-module (gnu packages disk)
5b16ff09 52 #:use-module (gnu packages zile)
04086015 53 #:use-module (gnu packages linux)
30f25b03 54 #:use-module (gnu packages package-management)
04086015
LC
55 #:use-module ((gnu packages make-bootstrap)
56 #:select (%guile-static-stripped))
9de46ffb 57 #:use-module (gnu packages admin)
0ded70f3 58
9121ce55 59 #:use-module (gnu bootloader)
9b396c0c 60 #:use-module (gnu bootloader grub)
0ded70f3 61 #:use-module (gnu system shadow)
6e828634 62 #:use-module (gnu system pam)
735c6dd7 63 #:use-module (gnu system linux-initrd)
b09a8da4 64 #:use-module (gnu bootloader)
c5df1839 65 #:use-module (gnu system file-systems)
033adfe7 66 #:use-module (gnu system)
db4fdc04 67 #:use-module (gnu services)
9b336338 68 #:use-module (gnu system uuid)
0ded70f3 69
ca85d7bc 70 #:use-module (srfi srfi-1)
04086015 71 #:use-module (srfi srfi-26)
5f7fe1c5 72 #:use-module (rnrs bytevectors)
04086015 73 #:use-module (ice-9 match)
0ded70f3 74
04086015 75 #:export (expression->derivation-in-linux-vm
aedb72fb 76 qemu-image
e9f693d0 77 virtualized-operating-system
fd3bfc44 78 system-qemu-image
fcf63cf8 79
fd3bfc44 80 system-qemu-image/shared-store
1e77fedb 81 system-qemu-image/shared-store-script
ed419fa0 82 system-disk-image
a335f6fc 83 system-docker-image
ed419fa0
LC
84
85 virtual-machine
86 virtual-machine?))
04086015
LC
87
88\f
89;;; Commentary:
90;;;
91;;; Tools to evaluate build expressions within virtual machines.
92;;;
93;;; Code:
94
83bcd0b8 95(define %linux-vm-file-systems
8c9bf294
CM
96 ;; File systems mounted for 'derivation-in-linux-vm'. These are shared with
97 ;; the host over 9p.
83bcd0b8
LC
98 (list (file-system
99 (mount-point (%store-prefix))
100 (device "store")
101 (type "9p")
102 (needed-for-boot? #t)
3c05b4bc
LC
103 (options "trans=virtio")
104 (check? #f))
83bcd0b8
LC
105 (file-system
106 (mount-point "/xchg")
107 (device "xchg")
108 (type "9p")
109 (needed-for-boot? #t)
3c05b4bc 110 (options "trans=virtio")
8c9bf294
CM
111 (check? #f))
112 (file-system
113 (mount-point "/tmp")
114 (device "tmp")
115 (type "9p")
116 (needed-for-boot? #t)
117 (options "trans=virtio")
3c05b4bc 118 (check? #f))))
83bcd0b8 119
d9f0a237 120(define* (expression->derivation-in-linux-vm name exp
04086015 121 #:key
2455085a 122 (system (%current-system))
04086015 123 (linux linux-libre)
735c6dd7 124 initrd
06da1a6b 125 (qemu qemu-minimal)
04086015 126 (env-vars '())
04086015
LC
127 (guile-for-build
128 (%guile-for-build))
129
8d033e3e 130 (single-file-output? #f)
04086015 131 (make-disk-image? #f)
ca85d7bc 132 (references-graphs #f)
defa1b9b 133 (memory-size 256)
c4a74364 134 (disk-image-format "qcow2")
a8ac4f08 135 (disk-image-size 'guess))
735c6dd7 136 "Evaluate EXP in a QEMU virtual machine running LINUX with INITRD (a
8d033e3e
LC
137derivation). The virtual machine runs with MEMORY-SIZE MiB of memory. In the
138virtual machine, EXP has access to all its inputs from the store; it should
139put its output file(s) in the '/xchg' directory.
140
141If SINGLE-FILE-OUTPUT? is true, copy a single file from '/xchg' to OUTPUT.
142Otherwise, copy the contents of /xchg to a new directory OUTPUT.
04086015 143
c4a74364
LC
144When MAKE-DISK-IMAGE? is true, then create a QEMU disk image of type
145DISK-IMAGE-FORMAT (e.g., 'qcow2' or 'raw'), of DISK-IMAGE-SIZE bytes and
a8ac4f08
LC
146return it. When DISK-IMAGE-SIZE is 'guess, estimate the image size based
147based on the size of the closure of REFERENCES-GRAPHS.
ca85d7bc
LC
148
149When REFERENCES-GRAPHS is true, it must be a list of file name/store path
150pairs, as for `derivation'. The files containing the reference graphs are
151made available under the /xchg CIFS share."
d9f0a237 152 (mlet* %store-monad
fd129893 153 ((user-builder (gexp->file "builder-in-linux-vm" exp))
02100028 154 (loader (gexp->file "linux-vm-loader"
fd129893 155 #~(primitive-load #$user-builder)))
bdb36958 156 (coreutils -> (canonical-package coreutils))
d4254711 157 (initrd (if initrd ; use the default initrd?
735c6dd7 158 (return initrd)
060238ae 159 (base-initrd %linux-vm-file-systems
248db51c 160 #:on-error 'backtrace
0d275f4a 161 #:linux linux
5a3716ae 162 #:linux-modules %base-initrd-modules
6c1df081 163 #:qemu-networking? #t))))
1aa0033b
LC
164
165 (define builder
166 ;; Code that launches the VM that evaluates EXP.
239c6e27
LC
167 (with-imported-modules (source-module-closure '((guix build utils)
168 (gnu build vm)))
4ee96a79
LC
169 #~(begin
170 (use-modules (guix build utils)
171 (gnu build vm))
172
a8ac4f08
LC
173 (let* ((inputs '#$(list qemu coreutils))
174 (linux (string-append #$linux "/"
175 #$(system-linux-image-file-name)))
176 (initrd (string-append #$initrd "/initrd"))
177 (loader #$loader)
178 (graphs '#$(match references-graphs
179 (((graph-files . _) ...) graph-files)
180 (_ #f)))
181 (size #$(if (eq? 'guess disk-image-size)
182 #~(+ (* 70 (expt 2 20)) ;ESP
183 (estimated-partition-size graphs))
184 disk-image-size)))
4ee96a79
LC
185
186 (set-path-environment-variable "PATH" '("bin") inputs)
187
188 (load-in-linux-vm loader
189 #:output #$output
190 #:linux linux #:initrd initrd
191 #:memory-size #$memory-size
192 #:make-disk-image? #$make-disk-image?
8d033e3e 193 #:single-file-output? #$single-file-output?
acf54bca
MO
194 ;; FIXME: ‘target-arm32?’ may not operate on
195 ;; the right system/target values. Rewrite
196 ;; using ‘let-system’ when available.
197 #:target-arm32? #$(target-arm32?)
4ee96a79 198 #:disk-image-format #$disk-image-format
a8ac4f08 199 #:disk-image-size size
4ee96a79 200 #:references-graphs graphs)))))
1aa0033b
LC
201
202 (gexp->derivation name builder
203 ;; TODO: Require the "kvm" feature.
204 #:system system
205 #:env-vars env-vars
1aa0033b
LC
206 #:guile-for-build guile-for-build
207 #:references-graphs references-graphs)))
d9f0a237 208
be1033a3
DM
209(define* (iso9660-image #:key
210 (name "iso9660-image")
acc0f6bb
DM
211 file-system-label
212 file-system-uuid
be1033a3
DM
213 (system (%current-system))
214 (qemu qemu-minimal)
215 os-drv
216 bootcfg-drv
217 bootloader
e375d3fa 218 register-closures?
be1033a3
DM
219 (inputs '()))
220 "Return a bootable, stand-alone iso9660 image.
221
222INPUTS is a list of inputs (as for packages)."
223 (expression->derivation-in-linux-vm
224 name
225 (with-imported-modules (source-module-closure '((gnu build vm)
226 (guix build utils)))
227 #~(begin
228 (use-modules (gnu build vm)
229 (guix build utils))
230
231 (let ((inputs
232 '#$(append (list qemu parted e2fsprogs dosfstools xorriso)
233 (map canonical-package
e375d3fa
CB
234 (list sed grep coreutils findutils gawk))
235 (if register-closures? (list guix) '())))
be1033a3 236
e375d3fa
CB
237
238 (graphs '#$(match inputs
239 (((names . _) ...)
240 names)))
be1033a3
DM
241 ;; This variable is unused but allows us to add INPUTS-TO-COPY
242 ;; as inputs.
243 (to-register
244 '#$(map (match-lambda
245 ((name thing) thing)
246 ((name thing output) `(,thing ,output)))
247 inputs)))
248
249 (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
250 (make-iso9660-image #$(bootloader-package bootloader)
251 #$bootcfg-drv
252 #$os-drv
acc0f6bb 253 "/xchg/guixsd.iso"
e375d3fa
CB
254 #:register-closures? #$register-closures?
255 #:closures graphs
acc0f6bb 256 #:volume-id #$file-system-label
9b336338
LC
257 #:volume-uuid #$(and=> file-system-uuid
258 uuid-bytevector))
be1033a3
DM
259 (reboot))))
260 #:system system
261 #:make-disk-image? #f
8d033e3e 262 #:single-file-output? #t
be1033a3
DM
263 #:references-graphs inputs))
264
d9f0a237 265(define* (qemu-image #:key
04086015
LC
266 (name "qemu-image")
267 (system (%current-system))
06da1a6b 268 (qemu qemu-minimal)
a8ac4f08 269 (disk-image-size 'guess)
c4a74364 270 (disk-image-format "qcow2")
03ddfaf5 271 (file-system-type "ext4")
ef9fc40d 272 file-system-label
fd3b4b98 273 file-system-uuid
9121ce55
MO
274 os-drv
275 bootcfg-drv
276 bootloader
150e20dd 277 (register-closures? #t)
150e20dd
LC
278 (inputs '())
279 copy-inputs?)
c4a74364 280 "Return a bootable, stand-alone QEMU image of type DISK-IMAGE-FORMAT (e.g.,
ef9fc40d
LC
281'qcow2' or 'raw'), with a root partition of type FILE-SYSTEM-TYPE.
282Optionally, FILE-SYSTEM-LABEL can be specified as the volume name for the root
fd3b4b98
LC
283partition; likewise FILE-SYSTEM-UUID, if true, specifies the UUID of the root
284partition (a UUID object).
285
286The returned image is a full disk image that runs OS-DERIVATION,
f2c403ea
LC
287with a GRUB installation that uses GRUB-CONFIGURATION as its configuration
288file (GRUB-CONFIGURATION must be the name of a file in the VM.)
93d44bd8 289
150e20dd
LC
290INPUTS is a list of inputs (as for packages). When COPY-INPUTS? is true, copy
291all of INPUTS into the image being built. When REGISTER-CLOSURES? is true,
292register INPUTS in the store database of the image so that Guix can be used in
b4140694 293the image."
b53833b2
LC
294 (expression->derivation-in-linux-vm
295 name
e2248203
MO
296 (with-imported-modules (source-module-closure '((gnu build bootloader)
297 (gnu build vm)
239c6e27 298 (guix build utils)))
fd129893 299 #~(begin
e2248203
MO
300 (use-modules (gnu build bootloader)
301 (gnu build vm)
a8ac4f08 302 (guix build utils)
4307397b
MO
303 (srfi srfi-26)
304 (ice-9 binary-ports))
1aa0033b 305
fd129893 306 (let ((inputs
4d415f0c 307 '#$(append (list qemu parted e2fsprogs dosfstools)
fd129893
LC
308 (map canonical-package
309 (list sed grep coreutils findutils gawk))
310 (if register-closures? (list guix) '())))
1aa0033b 311
fd129893
LC
312 ;; This variable is unused but allows us to add INPUTS-TO-COPY
313 ;; as inputs.
314 (to-register
315 '#$(map (match-lambda
316 ((name thing) thing)
317 ((name thing output) `(,thing ,output)))
318 inputs)))
1aa0033b 319
fd129893 320 (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
1aa0033b 321
fd129893
LC
322 (let* ((graphs '#$(match inputs
323 (((names . _) ...)
324 names)))
325 (initialize (root-partition-initializer
326 #:closures graphs
327 #:copy-closures? #$copy-inputs?
328 #:register-closures? #$register-closures?
9121ce55 329 #:system-directory #$os-drv))
a8ac4f08 330 (root-size #$(if (eq? 'guess disk-image-size)
0c75a4de
CB
331 #~(max
332 ;; Minimum 20 MiB root size
333 (* 20 (expt 2 20))
334 (estimated-partition-size
335 (map (cut string-append "/xchg/" <>)
336 graphs)))
a8ac4f08
LC
337 (- disk-image-size
338 (* 50 (expt 2 20)))))
00e39b2e
MO
339 (partitions
340 (append
341 (list (partition
342 (size root-size)
343 (label #$file-system-label)
344 (uuid #$(and=> file-system-uuid
345 uuid-bytevector))
346 (file-system #$file-system-type)
347 (flags '(boot))
348 (initializer initialize)))
349 ;; Append a small EFI System Partition for use with UEFI
04bbd072 350 ;; bootloaders if we are not targeting ARM because UEFI
00e39b2e
MO
351 ;; support in U-Boot is experimental.
352 ;;
353 ;; FIXME: ‘target-arm32?’ may be not operate on the right
354 ;; system/target values. Rewrite using ‘let-system’ when
355 ;; available.
356 (if #$(target-arm32?)
357 '()
358 (list (partition
359 ;; The standalone grub image is about 10MiB, but
360 ;; leave some room for custom or multiple images.
361 (size (* 40 (expt 2 20)))
362 (label "GNU-ESP") ;cosmetic only
363 ;; Use "vfat" here since this property is used
364 ;; when mounting. The actual FAT-ness is based
162a1374 365 ;; on file system size (16 in this case).
00e39b2e
MO
366 (file-system "vfat")
367 (flags '(esp))))))))
fd129893
LC
368 (initialize-hard-disk "/dev/vda"
369 #:partitions partitions
ecf5d537 370 #:grub-efi #$grub-efi
9121ce55
MO
371 #:bootloader-package
372 #$(bootloader-package bootloader)
373 #:bootcfg #$bootcfg-drv
374 #:bootcfg-location
375 #$(bootloader-configuration-file bootloader)
376 #:bootloader-installer
377 #$(bootloader-installer bootloader))
fd129893 378 (reboot)))))
b53833b2
LC
379 #:system system
380 #:make-disk-image? #t
381 #:disk-image-size disk-image-size
382 #:disk-image-format disk-image-format
383 #:references-graphs inputs))
04086015 384
a335f6fc
CM
385(define* (system-docker-image os
386 #:key
387 (name "guixsd-docker-image")
388 register-closures?)
389 "Build a docker image. OS is the desired <operating-system>. NAME is the
390base name to use for the output file. When REGISTER-CLOSURES? is not #f,
391register the closure of OS with Guix in the resulting Docker image. This only
392makes sense when you want to build a GuixSD Docker image that has Guix
393installed inside of it. If you don't need Guix (e.g., your GuixSD Docker
394image just contains a web server that is started by the Shepherd), then you
395should set REGISTER-CLOSURES? to #f."
396 (define not-config?
397 (match-lambda
398 (('guix 'config) #f)
399 (('guix rest ...) #t)
400 (('gnu rest ...) #t)
401 (rest #f)))
402
403 (define config
404 ;; (guix config) module for consumption by (guix gcrypt).
405 (scheme-file "gcrypt-config.scm"
406 #~(begin
407 (define-module (guix config)
408 #:export (%libgcrypt))
409
410 ;; XXX: Work around <http://bugs.gnu.org/15602>.
411 (eval-when (expand load eval)
412 (define %libgcrypt
413 #+(file-append libgcrypt "/lib/libgcrypt"))))))
414 (mlet %store-monad ((os-drv (operating-system-derivation os #:container? #t))
415 (name -> (string-append name ".tar.gz"))
416 (graph -> "system-graph"))
417 (define build
418 (with-imported-modules `(,@(source-module-closure '((guix docker)
419 (guix build utils)
420 (gnu build vm))
421 #:select? not-config?)
422 (guix build store-copy)
423 ((guix config) => ,config))
424 #~(begin
425 ;; Guile-JSON is required by (guix docker).
426 (add-to-load-path
427 (string-append #+guile-json "/share/guile/site/"
428 (effective-version)))
429 (use-modules (guix docker)
430 (guix build utils)
431 (gnu build vm)
432 (srfi srfi-19)
433 (guix build store-copy))
434
435 (let* ((inputs '#$(append (list tar)
436 (if register-closures?
437 (list guix)
438 '())))
439 ;; This initializer requires elevated privileges that are
440 ;; not normally available in the build environment (e.g.,
441 ;; it needs to create device nodes). In order to obtain
442 ;; such privileges, we run it as root in a VM.
443 (initialize (root-partition-initializer
444 #:closures '(#$graph)
445 #:register-closures? #$register-closures?
446 #:system-directory #$os-drv
447 ;; De-duplication would fail due to
448 ;; cross-device link errors, so don't do it.
449 #:deduplicate? #f))
450 ;; Even as root in a VM, the initializer would fail due to
451 ;; lack of privileges if we use a root-directory that is on
452 ;; a file system that is shared with the host (e.g., /tmp).
453 (root-directory "/guixsd-system-root"))
454 (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
455 (mkdir root-directory)
456 (initialize root-directory)
457 (build-docker-image
458 (string-append "/xchg/" #$name) ;; The output file.
459 (cons* root-directory
460 (call-with-input-file (string-append "/xchg/" #$graph)
461 read-reference-graph))
462 #$os-drv
463 #:compressor '(#+(file-append gzip "/bin/gzip") "-9n")
464 #:creation-time (make-time time-utc 0 1)
465 #:transformations `((,root-directory -> "")))))))
466 (expression->derivation-in-linux-vm
467 name
468 ;; The VM's initrd Guile doesn't support dlopen, but our "build" gexp
469 ;; needs to be run by a Guile that can dlopen libgcrypt. The following
470 ;; hack works around that problem by putting the "build" gexp into an
471 ;; executable script (created by program-file) which, when executed, will
472 ;; run using a Guile that supports dlopen. That way, the VM's initrd
473 ;; Guile can just execute it via invoke, without using dlopen. See:
474 ;; https://lists.gnu.org/archive/html/guix-devel/2017-10/msg00233.html
475 (with-imported-modules `((guix build utils))
476 #~(begin
477 (use-modules (guix build utils))
478 ;; If we use execl instead of invoke here, the VM will crash with a
479 ;; kernel panic.
480 (invoke #$(program-file "build-docker-image" build))))
481 #:make-disk-image? #f
482 #:single-file-output? #t
483 #:references-graphs `((,graph ,os-drv)))))
484
04086015
LC
485\f
486;;;
1e77fedb 487;;; VM and disk images.
04086015
LC
488;;;
489
5f7fe1c5
LC
490(define* (operating-system-uuid os #:optional (type 'dce))
491 "Compute UUID object with a deterministic \"UUID\" for OS, of the given
492TYPE (one of 'iso9660 or 'dce). Return a UUID object."
493 (if (eq? type 'iso9660)
494 (let ((pad (compose (cut string-pad <> 2 #\0)
495 number->string))
496 (h (hash (operating-system-services os) 3600)))
497 (bytevector->uuid
498 (string->iso9660-uuid
499 (string-append "1970-01-01-"
500 (pad (hash (operating-system-host-name os) 24)) "-"
501 (pad (quotient h 60)) "-"
502 (pad (modulo h 60)) "-"
503 (pad (hash (operating-system-file-systems os) 100))))
504 'iso9660))
505 (bytevector->uuid
506 (uint-list->bytevector
507 (list (hash file-system-type
b1a30793 508 (- (expt 2 32) 1))
5f7fe1c5 509 (hash (operating-system-host-name os)
b1a30793 510 (- (expt 2 32) 1))
5f7fe1c5 511 (hash (operating-system-services os)
b1a30793 512 (- (expt 2 32) 1))
5f7fe1c5 513 (hash (operating-system-file-systems os)
b1a30793 514 (- (expt 2 32) 1)))
5f7fe1c5
LC
515 (endianness little)
516 4)
517 type)))
518
1e77fedb
LC
519(define* (system-disk-image os
520 #:key
56ef7fcc 521 (name "disk-image")
1e77fedb
LC
522 (file-system-type "ext4")
523 (disk-image-size (* 900 (expt 2 20)))
524 (volatile? #t))
525 "Return the derivation of a disk image of DISK-IMAGE-SIZE bytes of the
526system described by OS. Said image can be copied on a USB stick as is. When
527VOLATILE? is true, the root file system is made volatile; this is useful
528to USB sticks meant to be read-only."
651de2bd
DM
529 (define normalize-label
530 ;; ISO labels are all-caps (case-insensitive), but since
531 ;; 'find-partition-by-label' is case-sensitive, make it all-caps here.
532 (if (string=? "iso9660" file-system-type)
533 string-upcase
534 identity))
5f7fe1c5 535
10ace2c4 536 (define root-label
5f7fe1c5 537 ;; Volume name of the root file system.
0862b954 538 (normalize-label "GuixSD_image"))
10ace2c4 539
5f7fe1c5
LC
540 (define root-uuid
541 ;; UUID of the root file system, computed in a deterministic fashion.
542 ;; This is what we use to locate the root file system so it has to be
543 ;; different from the user's own file system UUIDs.
544 (operating-system-uuid os
545 (if (string=? file-system-type "iso9660")
546 'iso9660
547 'dce)))
548
1e77fedb
LC
549 (define file-systems-to-keep
550 (remove (lambda (fs)
551 (string=? (file-system-mount-point fs) "/"))
552 (operating-system-file-systems os)))
553
554 (let ((os (operating-system (inherit os)
932e1f92 555 ;; Since this is meant to be used on real hardware, don't
493c245b
LC
556 ;; install QEMU networking or anything like that. Assume USB
557 ;; mass storage devices (usb-storage.ko) are available.
52ac153e 558 (initrd (lambda (file-systems . rest)
b8e77811
MO
559 (apply (operating-system-initrd os)
560 file-systems
52ac153e 561 #:volatile-root? #t
52ac153e 562 rest)))
1e77fedb 563
cf189709
DM
564 (bootloader (if (string=? "iso9660" file-system-type)
565 (bootloader-configuration
566 (inherit (operating-system-bootloader os))
567 (bootloader grub-mkrescue-bootloader))
568 (operating-system-bootloader os)))
569
1e77fedb
LC
570 ;; Force our own root file system.
571 (file-systems (cons (file-system
572 (mount-point "/")
5f7fe1c5
LC
573 (device root-uuid)
574 (title 'uuid)
1e77fedb
LC
575 (type file-system-type))
576 file-systems-to-keep)))))
577
578 (mlet* %store-monad ((os-drv (operating-system-derivation os))
c76b3046 579 (bootcfg (operating-system-bootcfg os)))
be1033a3
DM
580 (if (string=? "iso9660" file-system-type)
581 (iso9660-image #:name name
acc0f6bb 582 #:file-system-label root-label
5f7fe1c5 583 #:file-system-uuid root-uuid
be1033a3 584 #:os-drv os-drv
b069111f 585 #:register-closures? #t
be1033a3
DM
586 #:bootcfg-drv bootcfg
587 #:bootloader (bootloader-configuration-bootloader
588 (operating-system-bootloader os))
589 #:inputs `(("system" ,os-drv)
590 ("bootcfg" ,bootcfg)))
591 (qemu-image #:name name
592 #:os-drv os-drv
593 #:bootcfg-drv bootcfg
594 #:bootloader (bootloader-configuration-bootloader
595 (operating-system-bootloader os))
596 #:disk-image-size disk-image-size
597 #:disk-image-format "raw"
4138e782 598 #:file-system-type file-system-type
be1033a3 599 #:file-system-label root-label
5f7fe1c5 600 #:file-system-uuid root-uuid
be1033a3
DM
601 #:copy-inputs? #t
602 #:register-closures? #t
603 #:inputs `(("system" ,os-drv)
604 ("bootcfg" ,bootcfg)))))))
1e77fedb 605
0b14d1d7 606(define* (system-qemu-image os
66f23d66
LC
607 #:key
608 (file-system-type "ext4")
609 (disk-image-size (* 900 (expt 2 20))))
610 "Return the derivation of a freestanding QEMU image of DISK-IMAGE-SIZE bytes
611of the GNU system as described by OS."
1eeccc2f
LC
612 (define file-systems-to-keep
613 ;; Keep only file systems other than root and not normally bound to real
614 ;; devices.
615 (remove (lambda (fs)
616 (let ((target (file-system-mount-point fs))
617 (source (file-system-device fs)))
618 (or (string=? target "/")
619 (string-prefix? "/dev/" source))))
620 (operating-system-file-systems os)))
621
61b94b8c
LC
622 (define root-uuid
623 ;; UUID of the root file system.
624 (operating-system-uuid os
625 (if (string=? file-system-type "iso9660")
626 'iso9660
627 'dce)))
628
629
66f23d66 630 (let ((os (operating-system (inherit os)
eac026e5 631 ;; Assume we have an initrd with the whole QEMU shebang.
e84d8b30 632
61b94b8c
LC
633 ;; Force our own root file system. Refer to it by UUID so that
634 ;; it works regardless of how the image is used ("qemu -hda",
635 ;; Xen, etc.).
1eeccc2f 636 (file-systems (cons (file-system
66f23d66 637 (mount-point "/")
61b94b8c
LC
638 (device root-uuid)
639 (title 'uuid)
1eeccc2f
LC
640 (type file-system-type))
641 file-systems-to-keep)))))
66f23d66
LC
642 (mlet* %store-monad
643 ((os-drv (operating-system-derivation os))
c76b3046 644 (bootcfg (operating-system-bootcfg os)))
9121ce55
MO
645 (qemu-image #:os-drv os-drv
646 #:bootcfg-drv bootcfg
647 #:bootloader (bootloader-configuration-bootloader
648 (operating-system-bootloader os))
66f23d66
LC
649 #:disk-image-size disk-image-size
650 #:file-system-type file-system-type
61b94b8c 651 #:file-system-uuid root-uuid
b4140694 652 #:inputs `(("system" ,os-drv)
07f812c4 653 ("bootcfg" ,bootcfg))
150e20dd 654 #:copy-inputs? #t))))
04086015 655
fcf63cf8
LC
656\f
657;;;
658;;; VMs that share file systems with the host.
659;;;
660
96ffa27b
LC
661(define (file-system->mount-tag fs)
662 "Return a 9p mount tag for host file system FS."
dffc5ab5
LC
663 ;; QEMU mount tags must be ASCII, at most 31-byte long, cannot contain
664 ;; slashes, and cannot start with '_'. Compute an identifier that
665 ;; corresponds to the rules.
96ffa27b 666 (string-append "TAG"
dffc5ab5
LC
667 (string-drop (bytevector->base32-string
668 (sha1 (string->utf8 fs)))
669 4)))
96ffa27b 670
fcf63cf8
LC
671(define (mapping->file-system mapping)
672 "Return a 9p file system that realizes MAPPING."
673 (match mapping
674 (($ <file-system-mapping> source target writable?)
675 (file-system
676 (mount-point target)
677 (device (file-system->mount-tag source))
678 (type "9p")
679 (flags (if writable? '() '(read-only)))
e0d96774 680 (options "trans=virtio,cache=loose")
fcf63cf8
LC
681 (check? #f)
682 (create-mount-point? #t)))))
683
909de139 684(define* (virtualized-operating-system os mappings #:optional (full-boot? #f))
83bcd0b8 685 "Return an operating system based on OS suitable for use in a virtualized
fcf63cf8
LC
686environment with the store shared with the host. MAPPINGS is a list of
687<file-system-mapping> to realize in the virtualized OS."
688 (define user-file-systems
689 ;; Remove file systems that conflict with those added below, or that are
690 ;; normally bound to real devices.
691 (remove (lambda (fs)
692 (let ((target (file-system-mount-point fs))
693 (source (file-system-device fs)))
694 (or (string=? target (%store-prefix))
695 (string=? target "/")
29824d80 696 (and (eq? 'device (file-system-title fs))
f00515b4
LC
697 (string-prefix? "/dev/" source))
698
699 ;; Labels and UUIDs are necessarily invalid in the VM.
700 (and (file-system-mount? fs)
701 (or (eq? 'label (file-system-title fs))
702 (eq? 'uuid (file-system-title fs))
703 (uuid? source))))))
fcf63cf8
LC
704 (operating-system-file-systems os)))
705
909de139
DC
706 (define virtual-file-systems
707 (cons (file-system
708 (mount-point "/")
709 (device "/dev/vda1")
710 (type "ext4"))
711
712 (append (map mapping->file-system mappings)
713 user-file-systems)))
714
83bcd0b8 715 (operating-system (inherit os)
9b396c0c
LC
716
717 ;; XXX: Until we run QEMU with UEFI support (with the OVMF firmware),
718 ;; force the traditional i386/BIOS method.
719 ;; See <https://bugs.gnu.org/28768>.
720 (bootloader (bootloader-configuration
721 (bootloader grub-bootloader)
722 (target "/dev/vda")))
723
52ac153e 724 (initrd (lambda (file-systems . rest)
b8e77811
MO
725 (apply (operating-system-initrd os)
726 file-systems
52ac153e 727 #:volatile-root? #t
52ac153e 728 rest)))
65fb4515
LC
729
730 ;; Disable swap.
731 (swap-devices '())
732
909de139
DC
733 ;; XXX: When FULL-BOOT? is true, do not add a 9p mount for /gnu/store
734 ;; since that would lead the bootloader config to look for the kernel and
735 ;; initrd in it.
736 (file-systems (if full-boot?
737 virtual-file-systems
738 (cons
739 (file-system
740 (inherit (mapping->file-system %store-mapping))
741 (needed-for-boot? #t))
742 virtual-file-systems)))))
83bcd0b8 743
fd3bfc44 744(define* (system-qemu-image/shared-store
0b14d1d7 745 os
6aa260af
LC
746 #:key
747 full-boot?
4c0416ae 748 (disk-image-size (* (if full-boot? 500 30) (expt 2 20))))
fd3bfc44 749 "Return a derivation that builds a QEMU image of OS that shares its store
6aa260af
LC
750with the host.
751
752When FULL-BOOT? is true, return an image that does a complete boot sequence,
753bootloaded included; thus, make a disk image that contains everything the
754bootloader refers to: OS kernel, initrd, bootloader data, etc."
755 (mlet* %store-monad ((os-drv (operating-system-derivation os))
c76b3046 756 (bootcfg (operating-system-bootcfg os)))
6aa260af 757 ;; XXX: When FULL-BOOT? is true, we end up creating an image that contains
07f812c4 758 ;; BOOTCFG and all its dependencies, including the output of OS-DRV.
6aa260af
LC
759 ;; This is more than needed (we only need the kernel, initrd, GRUB for its
760 ;; font, and the background image), but it's hard to filter that.
9121ce55
MO
761 (qemu-image #:os-drv os-drv
762 #:bootcfg-drv bootcfg
763 #:bootloader (bootloader-configuration-bootloader
764 (operating-system-bootloader os))
150e20dd 765 #:disk-image-size disk-image-size
6aa260af 766 #:inputs (if full-boot?
07f812c4 767 `(("bootcfg" ,bootcfg))
6aa260af 768 '())
150e20dd
LC
769
770 ;; XXX: Passing #t here is too slow, so let it off by default.
771 #:register-closures? #f
6aa260af 772 #:copy-inputs? full-boot?)))
fd3bfc44 773
96ffa27b
LC
774(define* (common-qemu-options image shared-fs)
775 "Return the a string-value gexp with the common QEMU options to boot IMAGE,
776with '-virtfs' options for the host file systems listed in SHARED-FS."
26a076ed 777
96ffa27b 778 (define (virtfs-option fs)
26a076ed
DC
779 #~(format #f "-virtfs local,path=~s,security_model=none,mount_tag=~s"
780 #$fs #$(file-system->mount-tag fs)))
96ffa27b 781
26a076ed 782 #~(;; Only enable kvm if we see /dev/kvm exists.
944d2b17
CAW
783 ;; This allows users without hardware virtualization to still use these
784 ;; commands.
26a076ed
DC
785 #$@(if (file-exists? "/dev/kvm")
786 '("-enable-kvm")
787 '())
788
789 "-no-reboot"
790 "-net nic,model=virtio"
2ca712bd
LF
791 "-object" "rng-random,filename=/dev/urandom,id=guixsd-vm-rng"
792 "-device" "virtio-rng-pci,rng=guixsd-vm-rng"
26a076ed
DC
793
794 #$@(map virtfs-option shared-fs)
795 "-vga std"
796 (format #f "-drive file=~a,if=virtio,cache=writeback,werror=report,readonly"
ebfb71d4 797 #$image)))
3c1f0e3b 798
ab11f0be
LC
799(define* (system-qemu-image/shared-store-script os
800 #:key
801 (qemu qemu)
802 (graphic? #t)
ebfb71d4 803 (memory-size 256)
fcf63cf8 804 (mappings '())
6aa260af
LC
805 full-boot?
806 (disk-image-size
9a1bfe76 807 (* (if full-boot? 500 70)
ed419fa0
LC
808 (expt 2 20)))
809 (options '()))
fd3bfc44 810 "Return a derivation that builds a script to run a virtual machine image of
ebfb71d4
JN
811OS that shares its store with the host. The virtual machine runs with
812MEMORY-SIZE MiB of memory.
6aa260af 813
fcf63cf8
LC
814MAPPINGS is a list of <file-system-mapping> specifying mapping of host file
815systems into the guest.
816
6aa260af
LC
817When FULL-BOOT? is true, the returned script runs everything starting from the
818bootloader; otherwise it directly starts the operating system kernel. The
819DISK-IMAGE-SIZE parameter specifies the size in bytes of the root disk image;
820it is mostly useful when FULL-BOOT? is true."
909de139 821 (mlet* %store-monad ((os -> (virtualized-operating-system os mappings full-boot?))
6aa260af
LC
822 (os-drv (operating-system-derivation os))
823 (image (system-qemu-image/shared-store
824 os
825 #:full-boot? full-boot?
826 #:disk-image-size disk-image-size)))
26a076ed 827 (define kernel-arguments
83071b05
DM
828 #~(list #$@(if graphic? #~() #~("console=ttyS0"))
829 #+@(operating-system-kernel-arguments os os-drv "/dev/vda1")))
26a076ed
DC
830
831 (define qemu-exec
832 #~(list (string-append #$qemu "/bin/" #$(qemu-command (%current-system)))
833 #$@(if full-boot?
834 #~()
835 #~("-kernel" #$(operating-system-kernel-file os)
836 "-initrd" #$(file-append os-drv "/initrd")
837 (format #f "-append ~s"
838 (string-join #$kernel-arguments " "))))
839 #$@(common-qemu-options image
840 (map file-system-mapping-source
ebfb71d4 841 (cons %store-mapping mappings)))
ed419fa0
LC
842 "-m " (number->string #$memory-size)
843 #$@options))
26a076ed 844
fd3bfc44 845 (define builder
02100028
LC
846 #~(call-with-output-file #$output
847 (lambda (port)
26a076ed
DC
848 (format port "#!~a~% exec ~a \"$@\"~%"
849 #$(file-append bash "/bin/sh")
850 (string-join #$qemu-exec " "))
02100028
LC
851 (chmod port #o555))))
852
853 (gexp->derivation "run-vm.sh" builder)))
fd3bfc44 854
ed419fa0
LC
855\f
856;;;
857;;; High-level abstraction.
858;;;
859
860(define-record-type* <virtual-machine> %virtual-machine
861 make-virtual-machine
862 virtual-machine?
863 (operating-system virtual-machine-operating-system) ;<operating-system>
864 (qemu virtual-machine-qemu ;<package>
865 (default qemu))
866 (graphic? virtual-machine-graphic? ;Boolean
867 (default #f))
868 (memory-size virtual-machine-memory-size ;integer (MiB)
869 (default 256))
eb152070
CB
870 (disk-image-size virtual-machine-disk-image-size ;integer (bytes)
871 (default 'guess))
ed419fa0
LC
872 (port-forwardings virtual-machine-port-forwardings ;list of integer pairs
873 (default '())))
874
875(define-syntax virtual-machine
876 (syntax-rules ()
877 "Declare a virtual machine running the specified OS, with the given
878options."
879 ((_ os) ;shortcut
880 (%virtual-machine (operating-system os)))
881 ((_ fields ...)
882 (%virtual-machine fields ...))))
883
884(define (port-forwardings->qemu-options forwardings)
885 "Return the QEMU option for the given port FORWARDINGS as a string, where
886FORWARDINGS is a list of host-port/guest-port pairs."
887 (string-join
888 (map (match-lambda
889 ((host-port . guest-port)
890 (string-append "hostfwd=tcp::"
891 (number->string host-port)
892 "-:" (number->string guest-port))))
893 forwardings)
894 ","))
895
896(define-gexp-compiler (virtual-machine-compiler (vm <virtual-machine>)
897 system target)
898 ;; XXX: SYSTEM and TARGET are ignored.
899 (match vm
eb152070 900 (($ <virtual-machine> os qemu graphic? memory-size disk-image-size ())
ed419fa0
LC
901 (system-qemu-image/shared-store-script os
902 #:qemu qemu
903 #:graphic? graphic?
eb152070
CB
904 #:memory-size memory-size
905 #:disk-image-size
906 disk-image-size))
907 (($ <virtual-machine> os qemu graphic? memory-size disk-image-size
908 forwardings)
ed419fa0
LC
909 (let ((options
910 `("-net" ,(string-append
911 "user,"
912 (port-forwardings->qemu-options forwardings)))))
913 (system-qemu-image/shared-store-script os
914 #:qemu qemu
915 #:graphic? graphic?
916 #:memory-size memory-size
eb152070
CB
917 #:disk-image-size
918 disk-image-size
ed419fa0
LC
919 #:options options)))))
920
04086015 921;;; vm.scm ends here