1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016, 2017 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
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.
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.
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/>.
19 (define-module (gnu tests install)
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
45 ;;; Test the installation of GuixSD using the documented approach at the
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))
55 (host-name "liberigilo")
56 (timezone "Europe/Paris")
57 (locale "en_US.UTF-8")
59 (bootloader (grub-configuration (device "/dev/vdb")))
60 (kernel-arguments '("console=ttyS0"))
61 (file-systems (cons (file-system
67 (users (cons (user-account
69 (comment "Bob's sister")
71 (supplementary-groups '("wheel" "audio" "video"))
72 (home-directory "/home/alice"))
74 (services (cons (service marionette-service-type
75 (marionette-configuration
76 (imported-modules '((gnu services herd)
77 (guix combinators)))))
80 (define (operating-system-with-current-guix os)
81 "Return a variant of OS that uses the current Guix."
84 (services (modify-services (operating-system-user-services os)
85 (guix-service-type config =>
88 (guix (current-guix))))))))
90 (define (operating-system-with-gc-roots os roots)
91 "Return a variant of OS where ROOTS are registered as GC roots."
94 (services (cons (service gc-root-service-type roots)
95 (operating-system-user-services os)))))
98 (define MiB (expt 2 20))
100 (define %simple-installation-script
101 ;; Shell script of a simple installation.
107 export GUIX_BUILD_OPTIONS=--no-grafts
109 parted --script /dev/vdb mklabel gpt \\
110 mkpart primary ext2 1M 3M \\
111 mkpart primary ext2 3M 1G \\
114 mkfs.ext4 -L my-root /dev/vdb2
117 herd start cow-store /mnt
119 cp /etc/target-config.scm /mnt/etc/config.scm
120 guix system init /mnt/etc/config.scm /mnt --no-substitutes
124 (define* (run-install target-os target-os-source
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
132 (inherit (operating-system-with-current-guix
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."
142 (mlet* %store-monad ((_ (set-grafting #f))
143 (system (current-system))
144 (target (operating-system-derivation target-os))
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
150 (image (system-disk-image
151 (operating-system-with-gc-roots
153 #:disk-image-size (* 1500 MiB))))
155 (with-imported-modules '((guix build utils)
156 (gnu build marionette))
158 (use-modules (guix build utils)
159 (gnu build marionette))
161 (set-path-environment-variable "PATH" '("bin")
162 (list #$qemu-minimal))
164 (system* "qemu-img" "create" "-f" "qcow2"
165 #$output #$(number->string target-size))
169 (cons (which #$(qemu-command system))
170 (cons* "-no-reboot" "-m" "800"
172 (string-append "file=" #$image
173 ",if=virtio,readonly")
175 (string-append "file=" #$output ",if=virtio")
176 (if (file-exists? "/dev/kvm")
180 (pk 'uname (marionette-eval '(uname) marionette))
183 (marionette-eval '(begin
184 (use-modules (gnu services herd))
188 (marionette-eval '(call-with-output-file "/etc/target-config.scm"
190 (write '#$target-os-source port)))
193 (exit (marionette-eval '(zero? (system #$script))
196 (gexp->derivation "installation" install)))
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
207 "create" "-f" "qcow2"
209 (string-append "backing_file=" image)
211 (error "failed to create writable QEMU image" image))
213 (chmod "disk.img" #o644)
214 `(,(string-append #$qemu-minimal "/bin/"
215 #$(qemu-command system))
216 ,@(if (file-exists? "/dev/kvm")
219 "-no-reboot" "-m" #$(number->string memory-size)
220 "-drive" "file=disk.img,if=virtio")))))
222 (define %test-installed-os
224 (name "installed-os")
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.")
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
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))
245 (host-name "liberigilo")
246 (timezone "Europe/Paris")
247 (locale "en_US.utf8")
249 (bootloader (grub-configuration (device "/dev/vdb")))
250 (kernel-arguments '("console=ttyS0"))
251 (file-systems (cons* (file-system
260 (mount-point "/home")
263 (users (cons* (user-account
266 (home-directory "/home/alice"))
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)))))
278 (define %test-separate-home-os
280 (name "separate-home-os")
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>).")
286 (mlet* %store-monad ((image (run-install %separate-home-os
287 %separate-home-os-source
289 %simple-installation-script))
290 (command (qemu-command/writable-image image)))
291 (run-basic-test %separate-home-os command "separate-home-os")))))
295 ;;; Separate /gnu/store partition.
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))
303 (host-name "liberigilo")
304 (timezone "Europe/Paris")
305 (locale "en_US.UTF-8")
307 (bootloader (grub-configuration (device "/dev/vdb")))
308 (kernel-arguments '("console=ttyS0"))
309 (file-systems (cons* (file-system
320 (users %base-user-accounts)
321 (services (cons (service marionette-service-type
322 (marionette-configuration
323 (imported-modules '((gnu services herd)
324 (guix combinators)))))
327 (define %separate-store-installation-script
328 ;; Installation with a separate /gnu partition.
334 export GUIX_BUILD_OPTIONS=--no-grafts
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 \\
342 mkfs.ext4 -L root-fs /dev/vdb2
343 mkfs.ext4 -L store-fs /dev/vdb3
346 mount /dev/vdb3 /mnt/gnu
348 herd start cow-store /mnt
350 cp /etc/target-config.scm /mnt/etc/config.scm
351 guix system init /mnt/etc/config.scm /mnt --no-substitutes
355 (define %test-separate-store-os
357 (name "separate-store-os")
359 "Test basic functionality of an OS installed like one would do by hand,
360 where /gnu lives on a separate partition.")
362 (mlet* %store-monad ((image (run-install %separate-store-os
363 %separate-store-os-source
365 %separate-store-installation-script))
366 (command (qemu-command/writable-image image)))
367 (run-basic-test %separate-store-os command "separate-store-os")))))
371 ;;; RAID root device.
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))
379 (host-name "raidified")
380 (timezone "Europe/Paris")
381 (locale "en_US.utf8")
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")
390 (mapped-devices (list (mapped-device
391 (source (list "/dev/vda2" "/dev/vda3"))
393 (type raid-device-mapping))))
394 (file-systems (cons (file-system
399 (dependencies mapped-devices))
401 (users %base-user-accounts)
402 (services (cons (service marionette-service-type
403 (marionette-configuration
404 (imported-modules '((gnu services herd)
405 (guix combinators)))))
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
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 \\
424 mdadm --create /dev/md0 --verbose --level=stripe --raid-devices=2 \\
426 mkfs.ext4 -L root-fs /dev/md0
429 herd start cow-store /mnt
431 cp /etc/target-config.scm /mnt/etc/config.scm
432 guix system init /mnt/etc/config.scm /mnt --no-substitutes
436 (define %test-raid-root-os
438 (name "raid-root-os")
440 "Test functionality of an OS installed with a RAID root partition managed
443 (mlet* %store-monad ((image (run-install %raid-root-os
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")))))
454 ;;; LUKS-encrypted root file system.
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))
462 (host-name "liberigilo")
463 (timezone "Europe/Paris")
464 (locale "en_US.UTF-8")
466 (bootloader (grub-configuration (device "/dev/vdb")))
468 ;; Note: Do not pass "console=ttyS0" so we can use our passphrase prompt
469 ;; detection logic in 'enter-luks-passphrase'.
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")
481 (users (cons (user-account
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)))))
493 (define %encrypted-root-installation-script
494 ;; Shell script of a simple installation.
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 \\
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
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
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")))
526 (define (passphrase-prompt? text)
527 (string-contains (pk 'screen-text text) "Enter pass"))
529 (define (bios-boot-screen? text)
530 ;; Return true if TEXT corresponds to the boot screen, before GRUB's
532 (string-prefix? "SeaBIOS" text))
534 (test-assert "enter LUKS passphrase for GRUB"
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?
540 (marionette-type "thepassphrase\n" #$marionette)
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?)
549 (test-assert "enter LUKS passphrase for the initrd"
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?
556 (marionette-type "thepassphrase\n" #$marionette)
558 ;; Take a screenshot for debugging purposes.
559 (marionette-control (string-append "screendump " #$output
560 "/post-initrd-passphrase.ppm")
563 (define %test-encrypted-os
565 (name "encrypted-root-os")
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.")
571 (mlet* %store-monad ((image (run-install %encrypted-root-os
572 %encrypted-root-os-source
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)))))
581 ;;; Btrfs root file system.
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))
589 (host-name "liberigilo")
590 (timezone "Europe/Paris")
591 (locale "en_US.UTF-8")
593 (bootloader (grub-configuration (device "/dev/vdb")))
594 (kernel-arguments '("console=ttyS0"))
595 (file-systems (cons (file-system
601 (users (cons (user-account
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)))))
613 (define %btrfs-root-installation-script
614 ;; Shell script of a simple installation.
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 \\
627 mkfs.btrfs -L my-root /dev/vdb2
629 btrfs subvolume create /mnt/home
630 herd start cow-store /mnt
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
638 (define %test-btrfs-root-os
640 (name "btrfs-root-os")
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.")
646 (mlet* %store-monad ((image (run-install %btrfs-root-os
647 %btrfs-root-os-source
649 %btrfs-root-installation-script))
650 (command (qemu-command/writable-image image)))
651 (run-basic-test %btrfs-root-os command "btrfs-root-os")))))
653 ;;; install.scm ends here