Merge branch 'master' into python-tests
[jackhill/guix/guix.git] / gnu / tests / install.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016, 2017 Ludovic Courtès <ludo@gnu.org>
3 ;;;
4 ;;; This file is part of GNU Guix.
5 ;;;
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
10 ;;;
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
15 ;;;
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19 (define-module (gnu tests install)
20 #:use-module (gnu)
21 #:use-module (gnu tests)
22 #:use-module (gnu tests base)
23 #:use-module (gnu system)
24 #:use-module (gnu system install)
25 #:use-module (gnu system vm)
26 #:use-module ((gnu build vm) #:select (qemu-command))
27 #:use-module (gnu packages ocr)
28 #:use-module (gnu packages qemu)
29 #:use-module (gnu packages package-management)
30 #:use-module (guix store)
31 #:use-module (guix monads)
32 #:use-module (guix packages)
33 #:use-module (guix grafts)
34 #:use-module (guix gexp)
35 #:use-module (guix utils)
36 #:export (%test-installed-os
37 %test-separate-store-os
38 %test-separate-home-os
39 %test-raid-root-os
40 %test-encrypted-os
41 %test-btrfs-root-os))
42
43 ;;; Commentary:
44 ;;;
45 ;;; Test the installation of GuixSD using the documented approach at the
46 ;;; command line.
47 ;;;
48 ;;; Code:
49
50 (define-os-with-source (%minimal-os %minimal-os-source)
51 ;; The OS we want to install.
52 (use-modules (gnu) (gnu tests) (srfi srfi-1))
53
54 (operating-system
55 (host-name "liberigilo")
56 (timezone "Europe/Paris")
57 (locale "en_US.UTF-8")
58
59 (bootloader (grub-configuration (device "/dev/vdb")))
60 (kernel-arguments '("console=ttyS0"))
61 (file-systems (cons (file-system
62 (device "my-root")
63 (title 'label)
64 (mount-point "/")
65 (type "ext4"))
66 %base-file-systems))
67 (users (cons (user-account
68 (name "alice")
69 (comment "Bob's sister")
70 (group "users")
71 (supplementary-groups '("wheel" "audio" "video"))
72 (home-directory "/home/alice"))
73 %base-user-accounts))
74 (services (cons (service marionette-service-type
75 (marionette-configuration
76 (imported-modules '((gnu services herd)
77 (guix combinators)))))
78 %base-services))))
79
80 (define (operating-system-with-current-guix os)
81 "Return a variant of OS that uses the current Guix."
82 (operating-system
83 (inherit os)
84 (services (modify-services (operating-system-user-services os)
85 (guix-service-type config =>
86 (guix-configuration
87 (inherit config)
88 (guix (current-guix))))))))
89
90 (define (operating-system-with-gc-roots os roots)
91 "Return a variant of OS where ROOTS are registered as GC roots."
92 (operating-system
93 (inherit os)
94 (services (cons (service gc-root-service-type roots)
95 (operating-system-user-services os)))))
96
97 \f
98 (define MiB (expt 2 20))
99
100 (define %simple-installation-script
101 ;; Shell script of a simple installation.
102 "\
103 . /etc/profile
104 set -e -x
105 guix --version
106
107 export GUIX_BUILD_OPTIONS=--no-grafts
108 guix build isc-dhcp
109 parted --script /dev/vdb mklabel gpt \\
110 mkpart primary ext2 1M 3M \\
111 mkpart primary ext2 3M 1G \\
112 set 1 boot on \\
113 set 1 bios_grub on
114 mkfs.ext4 -L my-root /dev/vdb2
115 mount /dev/vdb2 /mnt
116 df -h /mnt
117 herd start cow-store /mnt
118 mkdir /mnt/etc
119 cp /etc/target-config.scm /mnt/etc/config.scm
120 guix system init /mnt/etc/config.scm /mnt --no-substitutes
121 sync
122 reboot\n")
123
124 (define* (run-install target-os target-os-source
125 #:key
126 (script %simple-installation-script)
127 (os (marionette-operating-system
128 ;; Since the image has no network access, use the
129 ;; current Guix so the store items we need are in
130 ;; the image.
131 (operating-system
132 (inherit (operating-system-with-current-guix
133 installation-os))
134 (kernel-arguments '("console=ttyS0")))
135 #:imported-modules '((gnu services herd)
136 (guix combinators))))
137 (target-size (* 1200 MiB)))
138 "Run SCRIPT (a shell script following the GuixSD installation procedure) in
139 OS to install TARGET-OS. Return a VM image of TARGET-SIZE bytes containing
140 the installed system."
141
142 (mlet* %store-monad ((_ (set-grafting #f))
143 (system (current-system))
144 (target (operating-system-derivation target-os))
145
146 ;; Since the installation system has no network access,
147 ;; we cheat a little bit by adding TARGET to its GC
148 ;; roots. This way, we know 'guix system init' will
149 ;; succeed.
150 (image (system-disk-image
151 (operating-system-with-gc-roots
152 os (list target))
153 #:disk-image-size (* 1500 MiB))))
154 (define install
155 (with-imported-modules '((guix build utils)
156 (gnu build marionette))
157 #~(begin
158 (use-modules (guix build utils)
159 (gnu build marionette))
160
161 (set-path-environment-variable "PATH" '("bin")
162 (list #$qemu-minimal))
163
164 (system* "qemu-img" "create" "-f" "qcow2"
165 #$output #$(number->string target-size))
166
167 (define marionette
168 (make-marionette
169 (cons (which #$(qemu-command system))
170 (cons* "-no-reboot" "-m" "800"
171 "-drive"
172 (string-append "file=" #$image
173 ",if=virtio,readonly")
174 "-drive"
175 (string-append "file=" #$output ",if=virtio")
176 (if (file-exists? "/dev/kvm")
177 '("-enable-kvm")
178 '())))))
179
180 (pk 'uname (marionette-eval '(uname) marionette))
181
182 ;; Wait for tty1.
183 (marionette-eval '(begin
184 (use-modules (gnu services herd))
185 (start 'term-tty1))
186 marionette)
187
188 (marionette-eval '(call-with-output-file "/etc/target-config.scm"
189 (lambda (port)
190 (write '#$target-os-source port)))
191 marionette)
192
193 (exit (marionette-eval '(zero? (system #$script))
194 marionette)))))
195
196 (gexp->derivation "installation" install)))
197
198 (define* (qemu-command/writable-image image #:key (memory-size 256))
199 "Return as a monadic value the command to run QEMU on a writable copy of
200 IMAGE, a disk image. The QEMU VM is has access to MEMORY-SIZE MiB of RAM."
201 (mlet %store-monad ((system (current-system)))
202 (return #~(let ((image #$image))
203 ;; First we need a writable copy of the image.
204 (format #t "creating writable image from '~a'...~%" image)
205 (unless (zero? (system* #+(file-append qemu-minimal
206 "/bin/qemu-img")
207 "create" "-f" "qcow2"
208 "-o"
209 (string-append "backing_file=" image)
210 "disk.img"))
211 (error "failed to create writable QEMU image" image))
212
213 (chmod "disk.img" #o644)
214 `(,(string-append #$qemu-minimal "/bin/"
215 #$(qemu-command system))
216 ,@(if (file-exists? "/dev/kvm")
217 '("-enable-kvm")
218 '())
219 "-no-reboot" "-m" #$(number->string memory-size)
220 "-drive" "file=disk.img,if=virtio")))))
221
222 (define %test-installed-os
223 (system-test
224 (name "installed-os")
225 (description
226 "Test basic functionality of an OS installed like one would do by hand.
227 This test is expensive in terms of CPU and storage usage since we need to
228 build (current-guix) and then store a couple of full system images.")
229 (value
230 (mlet* %store-monad ((image (run-install %minimal-os %minimal-os-source))
231 (command (qemu-command/writable-image image)))
232 (run-basic-test %minimal-os command
233 "installed-os")))))
234
235 \f
236 ;;;
237 ;;; Separate /home.
238 ;;;
239
240 (define-os-with-source (%separate-home-os %separate-home-os-source)
241 ;; The OS we want to install.
242 (use-modules (gnu) (gnu tests) (srfi srfi-1))
243
244 (operating-system
245 (host-name "liberigilo")
246 (timezone "Europe/Paris")
247 (locale "en_US.utf8")
248
249 (bootloader (grub-configuration (device "/dev/vdb")))
250 (kernel-arguments '("console=ttyS0"))
251 (file-systems (cons* (file-system
252 (device "my-root")
253 (title 'label)
254 (mount-point "/")
255 (type "ext4"))
256 (file-system
257 (device "none")
258 (title 'device)
259 (type "tmpfs")
260 (mount-point "/home")
261 (type "tmpfs"))
262 %base-file-systems))
263 (users (cons* (user-account
264 (name "alice")
265 (group "users")
266 (home-directory "/home/alice"))
267 (user-account
268 (name "charlie")
269 (group "users")
270 (home-directory "/home/charlie"))
271 %base-user-accounts))
272 (services (cons (service marionette-service-type
273 (marionette-configuration
274 (imported-modules '((gnu services herd)
275 (guix combinators)))))
276 %base-services))))
277
278 (define %test-separate-home-os
279 (system-test
280 (name "separate-home-os")
281 (description
282 "Test basic functionality of an installed OS with a separate /home
283 partition. In particular, home directories must be correctly created (see
284 <https://bugs.gnu.org/21108>).")
285 (value
286 (mlet* %store-monad ((image (run-install %separate-home-os
287 %separate-home-os-source
288 #:script
289 %simple-installation-script))
290 (command (qemu-command/writable-image image)))
291 (run-basic-test %separate-home-os command "separate-home-os")))))
292
293 \f
294 ;;;
295 ;;; Separate /gnu/store partition.
296 ;;;
297
298 (define-os-with-source (%separate-store-os %separate-store-os-source)
299 ;; The OS we want to install.
300 (use-modules (gnu) (gnu tests) (srfi srfi-1))
301
302 (operating-system
303 (host-name "liberigilo")
304 (timezone "Europe/Paris")
305 (locale "en_US.UTF-8")
306
307 (bootloader (grub-configuration (device "/dev/vdb")))
308 (kernel-arguments '("console=ttyS0"))
309 (file-systems (cons* (file-system
310 (device "root-fs")
311 (title 'label)
312 (mount-point "/")
313 (type "ext4"))
314 (file-system
315 (device "store-fs")
316 (title 'label)
317 (mount-point "/gnu")
318 (type "ext4"))
319 %base-file-systems))
320 (users %base-user-accounts)
321 (services (cons (service marionette-service-type
322 (marionette-configuration
323 (imported-modules '((gnu services herd)
324 (guix combinators)))))
325 %base-services))))
326
327 (define %separate-store-installation-script
328 ;; Installation with a separate /gnu partition.
329 "\
330 . /etc/profile
331 set -e -x
332 guix --version
333
334 export GUIX_BUILD_OPTIONS=--no-grafts
335 guix build isc-dhcp
336 parted --script /dev/vdb mklabel gpt \\
337 mkpart primary ext2 1M 3M \\
338 mkpart primary ext2 3M 100M \\
339 mkpart primary ext2 100M 1G \\
340 set 1 boot on \\
341 set 1 bios_grub on
342 mkfs.ext4 -L root-fs /dev/vdb2
343 mkfs.ext4 -L store-fs /dev/vdb3
344 mount /dev/vdb2 /mnt
345 mkdir /mnt/gnu
346 mount /dev/vdb3 /mnt/gnu
347 df -h /mnt
348 herd start cow-store /mnt
349 mkdir /mnt/etc
350 cp /etc/target-config.scm /mnt/etc/config.scm
351 guix system init /mnt/etc/config.scm /mnt --no-substitutes
352 sync
353 reboot\n")
354
355 (define %test-separate-store-os
356 (system-test
357 (name "separate-store-os")
358 (description
359 "Test basic functionality of an OS installed like one would do by hand,
360 where /gnu lives on a separate partition.")
361 (value
362 (mlet* %store-monad ((image (run-install %separate-store-os
363 %separate-store-os-source
364 #:script
365 %separate-store-installation-script))
366 (command (qemu-command/writable-image image)))
367 (run-basic-test %separate-store-os command "separate-store-os")))))
368
369 \f
370 ;;;
371 ;;; RAID root device.
372 ;;;
373
374 (define-os-with-source (%raid-root-os %raid-root-os-source)
375 ;; An OS whose root partition is a RAID partition.
376 (use-modules (gnu) (gnu tests))
377
378 (operating-system
379 (host-name "raidified")
380 (timezone "Europe/Paris")
381 (locale "en_US.utf8")
382
383 (bootloader (grub-configuration (device "/dev/vdb")))
384 (kernel-arguments '("console=ttyS0"))
385 (initrd (lambda (file-systems . rest)
386 ;; Add a kernel module for RAID-0 (aka. "stripe").
387 (apply base-initrd file-systems
388 #:extra-modules '("raid0")
389 rest)))
390 (mapped-devices (list (mapped-device
391 (source (list "/dev/vda2" "/dev/vda3"))
392 (target "/dev/md0")
393 (type raid-device-mapping))))
394 (file-systems (cons (file-system
395 (device "root-fs")
396 (title 'label)
397 (mount-point "/")
398 (type "ext4")
399 (dependencies mapped-devices))
400 %base-file-systems))
401 (users %base-user-accounts)
402 (services (cons (service marionette-service-type
403 (marionette-configuration
404 (imported-modules '((gnu services herd)
405 (guix combinators)))))
406 %base-services))))
407
408 (define %raid-root-installation-script
409 ;; Installation with a separate /gnu partition. See
410 ;; <https://raid.wiki.kernel.org/index.php/RAID_setup> for more on RAID and
411 ;; mdadm.
412 "\
413 . /etc/profile
414 set -e -x
415 guix --version
416
417 export GUIX_BUILD_OPTIONS=--no-grafts
418 parted --script /dev/vdb mklabel gpt \\
419 mkpart primary ext2 1M 3M \\
420 mkpart primary ext2 3M 600M \\
421 mkpart primary ext2 600M 1200M \\
422 set 1 boot on \\
423 set 1 bios_grub on
424 mdadm --create /dev/md0 --verbose --level=stripe --raid-devices=2 \\
425 /dev/vdb2 /dev/vdb3
426 mkfs.ext4 -L root-fs /dev/md0
427 mount /dev/md0 /mnt
428 df -h /mnt
429 herd start cow-store /mnt
430 mkdir /mnt/etc
431 cp /etc/target-config.scm /mnt/etc/config.scm
432 guix system init /mnt/etc/config.scm /mnt --no-substitutes
433 sync
434 reboot\n")
435
436 (define %test-raid-root-os
437 (system-test
438 (name "raid-root-os")
439 (description
440 "Test functionality of an OS installed with a RAID root partition managed
441 by 'mdadm'.")
442 (value
443 (mlet* %store-monad ((image (run-install %raid-root-os
444 %raid-root-os-source
445 #:script
446 %raid-root-installation-script
447 #:target-size (* 1300 MiB)))
448 (command (qemu-command/writable-image image)))
449 (run-basic-test %raid-root-os
450 `(,@command) "raid-root-os")))))
451
452 \f
453 ;;;
454 ;;; LUKS-encrypted root file system.
455 ;;;
456
457 (define-os-with-source (%encrypted-root-os %encrypted-root-os-source)
458 ;; The OS we want to install.
459 (use-modules (gnu) (gnu tests) (srfi srfi-1))
460
461 (operating-system
462 (host-name "liberigilo")
463 (timezone "Europe/Paris")
464 (locale "en_US.UTF-8")
465
466 (bootloader (grub-configuration (device "/dev/vdb")))
467
468 ;; Note: Do not pass "console=ttyS0" so we can use our passphrase prompt
469 ;; detection logic in 'enter-luks-passphrase'.
470
471 (mapped-devices (list (mapped-device
472 (source (uuid "12345678-1234-1234-1234-123456789abc"))
473 (target "the-root-device")
474 (type luks-device-mapping))))
475 (file-systems (cons (file-system
476 (device "/dev/mapper/the-root-device")
477 (title 'device)
478 (mount-point "/")
479 (type "ext4"))
480 %base-file-systems))
481 (users (cons (user-account
482 (name "charlie")
483 (group "users")
484 (home-directory "/home/charlie")
485 (supplementary-groups '("wheel" "audio" "video")))
486 %base-user-accounts))
487 (services (cons (service marionette-service-type
488 (marionette-configuration
489 (imported-modules '((gnu services herd)
490 (guix combinators)))))
491 %base-services))))
492
493 (define %encrypted-root-installation-script
494 ;; Shell script of a simple installation.
495 "\
496 . /etc/profile
497 set -e -x
498 guix --version
499
500 export GUIX_BUILD_OPTIONS=--no-grafts
501 ls -l /run/current-system/gc-roots
502 parted --script /dev/vdb mklabel gpt \\
503 mkpart primary ext2 1M 3M \\
504 mkpart primary ext2 3M 1G \\
505 set 1 boot on \\
506 set 1 bios_grub on
507 echo -n thepassphrase | \\
508 cryptsetup luksFormat --uuid=12345678-1234-1234-1234-123456789abc -q /dev/vdb2 -
509 echo -n thepassphrase | \\
510 cryptsetup open --type luks --key-file - /dev/vdb2 the-root-device
511 mkfs.ext4 -L my-root /dev/mapper/the-root-device
512 mount LABEL=my-root /mnt
513 herd start cow-store /mnt
514 mkdir /mnt/etc
515 cp /etc/target-config.scm /mnt/etc/config.scm
516 guix system build /mnt/etc/config.scm
517 guix system init /mnt/etc/config.scm /mnt --no-substitutes
518 sync
519 reboot\n")
520
521 (define (enter-luks-passphrase marionette)
522 "Return a gexp to be inserted in the basic system test running on MARIONETTE
523 to enter the LUKS passphrase."
524 (let ((ocrad (file-append ocrad "/bin/ocrad")))
525 #~(begin
526 (define (passphrase-prompt? text)
527 (string-contains (pk 'screen-text text) "Enter pass"))
528
529 (define (bios-boot-screen? text)
530 ;; Return true if TEXT corresponds to the boot screen, before GRUB's
531 ;; menu.
532 (string-prefix? "SeaBIOS" text))
533
534 (test-assert "enter LUKS passphrase for GRUB"
535 (begin
536 ;; At this point we have no choice but to use OCR to determine
537 ;; when the passphrase should be entered.
538 (wait-for-screen-text #$marionette passphrase-prompt?
539 #:ocrad #$ocrad)
540 (marionette-type "thepassphrase\n" #$marionette)
541
542 ;; Now wait until we leave the boot screen. This is necessary so
543 ;; we can then be sure we match the "Enter passphrase" prompt from
544 ;; 'cryptsetup', in the initrd.
545 (wait-for-screen-text #$marionette (negate bios-boot-screen?)
546 #:ocrad #$ocrad
547 #:timeout 20)))
548
549 (test-assert "enter LUKS passphrase for the initrd"
550 (begin
551 ;; XXX: Here we use OCR as well but we could instead use QEMU
552 ;; '-serial stdio' and run it in an input pipe,
553 (wait-for-screen-text #$marionette passphrase-prompt?
554 #:ocrad #$ocrad
555 #:timeout 60)
556 (marionette-type "thepassphrase\n" #$marionette)
557
558 ;; Take a screenshot for debugging purposes.
559 (marionette-control (string-append "screendump " #$output
560 "/post-initrd-passphrase.ppm")
561 #$marionette))))))
562
563 (define %test-encrypted-os
564 (system-test
565 (name "encrypted-root-os")
566 (description
567 "Test basic functionality of an OS installed like one would do by hand.
568 This test is expensive in terms of CPU and storage usage since we need to
569 build (current-guix) and then store a couple of full system images.")
570 (value
571 (mlet* %store-monad ((image (run-install %encrypted-root-os
572 %encrypted-root-os-source
573 #:script
574 %encrypted-root-installation-script))
575 (command (qemu-command/writable-image image)))
576 (run-basic-test %encrypted-root-os command "encrypted-root-os"
577 #:initialization enter-luks-passphrase)))))
578
579 \f
580 ;;;
581 ;;; Btrfs root file system.
582 ;;;
583
584 (define-os-with-source (%btrfs-root-os %btrfs-root-os-source)
585 ;; The OS we want to install.
586 (use-modules (gnu) (gnu tests) (srfi srfi-1))
587
588 (operating-system
589 (host-name "liberigilo")
590 (timezone "Europe/Paris")
591 (locale "en_US.UTF-8")
592
593 (bootloader (grub-configuration (device "/dev/vdb")))
594 (kernel-arguments '("console=ttyS0"))
595 (file-systems (cons (file-system
596 (device "my-root")
597 (title 'label)
598 (mount-point "/")
599 (type "btrfs"))
600 %base-file-systems))
601 (users (cons (user-account
602 (name "charlie")
603 (group "users")
604 (home-directory "/home/charlie")
605 (supplementary-groups '("wheel" "audio" "video")))
606 %base-user-accounts))
607 (services (cons (service marionette-service-type
608 (marionette-configuration
609 (imported-modules '((gnu services herd)
610 (guix combinators)))))
611 %base-services))))
612
613 (define %btrfs-root-installation-script
614 ;; Shell script of a simple installation.
615 "\
616 . /etc/profile
617 set -e -x
618 guix --version
619
620 export GUIX_BUILD_OPTIONS=--no-grafts
621 ls -l /run/current-system/gc-roots
622 parted --script /dev/vdb mklabel gpt \\
623 mkpart primary ext2 1M 3M \\
624 mkpart primary ext2 3M 1G \\
625 set 1 boot on \\
626 set 1 bios_grub on
627 mkfs.btrfs -L my-root /dev/vdb2
628 mount /dev/vdb2 /mnt
629 btrfs subvolume create /mnt/home
630 herd start cow-store /mnt
631 mkdir /mnt/etc
632 cp /etc/target-config.scm /mnt/etc/config.scm
633 guix system build /mnt/etc/config.scm
634 guix system init /mnt/etc/config.scm /mnt --no-substitutes
635 sync
636 reboot\n")
637
638 (define %test-btrfs-root-os
639 (system-test
640 (name "btrfs-root-os")
641 (description
642 "Test basic functionality of an OS installed like one would do by hand.
643 This test is expensive in terms of CPU and storage usage since we need to
644 build (current-guix) and then store a couple of full system images.")
645 (value
646 (mlet* %store-monad ((image (run-install %btrfs-root-os
647 %btrfs-root-os-source
648 #:script
649 %btrfs-root-installation-script))
650 (command (qemu-command/writable-image image)))
651 (run-basic-test %btrfs-root-os command "btrfs-root-os")))))
652
653 ;;; install.scm ends here