1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2017 Tobias Geerinckx-Rice <me@tobias.gr>
5 ;;; This file is part of GNU Guix.
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;;; GNU General Public License for more details.
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20 (define-module (gnu tests install)
22 #:use-module (gnu bootloader extlinux)
23 #:use-module (gnu tests)
24 #:use-module (gnu tests base)
25 #:use-module (gnu system)
26 #:use-module (gnu system install)
27 #:use-module (gnu system vm)
28 #:use-module ((gnu build vm) #:select (qemu-command))
29 #:use-module (gnu packages bootloaders)
30 #:use-module (gnu packages ocr)
31 #:use-module (gnu packages package-management)
32 #:use-module (gnu packages virtualization)
33 #:use-module (guix store)
34 #:use-module (guix monads)
35 #:use-module (guix packages)
36 #:use-module (guix grafts)
37 #:use-module (guix gexp)
38 #:use-module (guix utils)
39 #:export (%test-installed-os
40 %test-installed-extlinux-os
41 %test-iso-image-installer
42 %test-separate-store-os
43 %test-separate-home-os
45 %test-encrypted-root-os
50 ;;; Test the installation of Guix using the documented approach at the
55 (define-os-with-source (%minimal-os %minimal-os-source)
56 ;; The OS we want to install.
57 (use-modules (gnu) (gnu tests) (srfi srfi-1))
60 (host-name "liberigilo")
61 (timezone "Europe/Paris")
62 (locale "en_US.UTF-8")
64 (bootloader (bootloader-configuration
65 (bootloader grub-bootloader)
67 (kernel-arguments '("console=ttyS0"))
68 (file-systems (cons (file-system
69 (device (file-system-label "my-root"))
73 (users (cons (user-account
75 (comment "Bob's sister")
77 (supplementary-groups '("wheel" "audio" "video"))
78 (home-directory "/home/alice"))
80 (services (cons (service marionette-service-type
81 (marionette-configuration
82 (imported-modules '((gnu services herd)
83 (guix combinators)))))
86 (define (operating-system-add-packages os packages)
87 "Append PACKAGES to OS packages list."
90 (packages (append packages (operating-system-packages os)))))
92 (define-os-with-source (%minimal-extlinux-os
93 %minimal-extlinux-os-source)
94 (use-modules (gnu) (gnu tests) (gnu bootloader extlinux)
98 (host-name "liberigilo")
99 (timezone "Europe/Paris")
100 (locale "en_US.UTF-8")
102 (bootloader (bootloader-configuration
103 (bootloader extlinux-bootloader-gpt)
104 (target "/dev/vdb")))
105 (kernel-arguments '("console=ttyS0"))
106 (file-systems (cons (file-system
107 (device (file-system-label "my-root"))
111 (services (cons (service marionette-service-type
112 (marionette-configuration
113 (imported-modules '((gnu services herd)
114 (guix combinators)))))
117 (define (operating-system-with-current-guix os)
118 "Return a variant of OS that uses the current Guix."
121 (services (modify-services (operating-system-user-services os)
122 (guix-service-type config =>
125 (guix (current-guix))))))))
127 (define (operating-system-with-gc-roots os roots)
128 "Return a variant of OS where ROOTS are registered as GC roots."
132 ;; We use this procedure for the installation OS, which already defines GC
133 ;; roots. Add ROOTS to those.
134 (services (cons (simple-service 'extra-root
135 gc-root-service-type roots)
136 (operating-system-user-services os)))))
139 (define MiB (expt 2 20))
141 (define %simple-installation-script
142 ;; Shell script of a simple installation.
148 export GUIX_BUILD_OPTIONS=--no-grafts
150 parted --script /dev/vdb mklabel gpt \\
151 mkpart primary ext2 1M 3M \\
152 mkpart primary ext2 3M 1.2G \\
155 mkfs.ext4 -L my-root /dev/vdb2
158 herd start cow-store /mnt
160 cp /etc/target-config.scm /mnt/etc/config.scm
161 guix system init /mnt/etc/config.scm /mnt --no-substitutes
165 (define %extlinux-gpt-installation-script
166 ;; Shell script of a simple installation.
167 ;; As syslinux 6.0.3 does not handle 64bits ext4 partitions,
168 ;; we make sure to pass -O '^64bit' to mkfs.
174 export GUIX_BUILD_OPTIONS=--no-grafts
176 parted --script /dev/vdb mklabel gpt \\
177 mkpart ext2 1M 1.2G \\
179 mkfs.ext4 -L my-root -O '^64bit' /dev/vdb1
182 herd start cow-store /mnt
184 cp /etc/target-config.scm /mnt/etc/config.scm
185 guix system init /mnt/etc/config.scm /mnt --no-substitutes
189 (define* (run-install target-os target-os-source
191 (script %simple-installation-script)
193 (os (marionette-operating-system
195 ;; Since the image has no network access, use the
196 ;; current Guix so the store items we need are in
197 ;; the image and add packages provided.
198 (inherit (operating-system-add-packages
199 (operating-system-with-current-guix
202 (kernel-arguments '("console=ttyS0")))
203 #:imported-modules '((gnu services herd)
204 (guix combinators))))
205 (installation-disk-image-file-system-type "ext4")
206 (target-size (* 2200 MiB)))
207 "Run SCRIPT (a shell script following the system installation procedure) in
208 OS to install TARGET-OS. Return a VM image of TARGET-SIZE bytes containing
209 the installed system. The packages specified in PACKAGES will be appended to
210 packages defined in installation-os."
212 (mlet* %store-monad ((_ (set-grafting #f))
213 (system (current-system))
214 (target (operating-system-derivation target-os))
216 ;; Since the installation system has no network access,
217 ;; we cheat a little bit by adding TARGET to its GC
218 ;; roots. This way, we know 'guix system init' will
220 (image (system-disk-image
221 (operating-system-with-gc-roots
223 #:disk-image-size 'guess
225 installation-disk-image-file-system-type)))
227 (with-imported-modules '((guix build utils)
228 (gnu build marionette))
230 (use-modules (guix build utils)
231 (gnu build marionette))
233 (set-path-environment-variable "PATH" '("bin")
234 (list #$qemu-minimal))
236 (system* "qemu-img" "create" "-f" "qcow2"
237 #$output #$(number->string target-size))
241 `(,(which #$(qemu-command system))
245 ((string=? "ext4" installation-disk-image-file-system-type)
247 ,(string-append "file=" #$image
248 ",if=virtio,readonly")))
249 ((string=? "iso9660" installation-disk-image-file-system-type)
250 #~("-cdrom" #$image))
253 "unsupported installation-disk-image-file-system-type:"
254 installation-disk-image-file-system-type)))
256 ,(string-append "file=" #$output ",if=virtio")
257 ,@(if (file-exists? "/dev/kvm")
261 (pk 'uname (marionette-eval '(uname) marionette))
264 (marionette-eval '(begin
265 (use-modules (gnu services herd))
269 (marionette-eval '(call-with-output-file "/etc/target-config.scm"
271 (write '#$target-os-source port)))
274 (exit (marionette-eval '(zero? (system #$script))
277 (gexp->derivation "installation" install)))
279 (define* (qemu-command/writable-image image #:key (memory-size 256))
280 "Return as a monadic value the command to run QEMU on a writable copy of
281 IMAGE, a disk image. The QEMU VM is has access to MEMORY-SIZE MiB of RAM."
282 (mlet %store-monad ((system (current-system)))
283 (return #~(let ((image #$image))
284 ;; First we need a writable copy of the image.
285 (format #t "creating writable image from '~a'...~%" image)
286 (unless (zero? (system* #+(file-append qemu-minimal
288 "create" "-f" "qcow2"
290 (string-append "backing_file=" image)
292 (error "failed to create writable QEMU image" image))
294 (chmod "disk.img" #o644)
295 `(,(string-append #$qemu-minimal "/bin/"
296 #$(qemu-command system))
297 ,@(if (file-exists? "/dev/kvm")
300 "-no-reboot" "-m" #$(number->string memory-size)
301 "-drive" "file=disk.img,if=virtio")))))
303 (define %test-installed-os
305 (name "installed-os")
307 "Test basic functionality of an OS installed like one would do by hand.
308 This test is expensive in terms of CPU and storage usage since we need to
309 build (current-guix) and then store a couple of full system images.")
311 (mlet* %store-monad ((image (run-install %minimal-os %minimal-os-source))
312 (command (qemu-command/writable-image image)))
313 (run-basic-test %minimal-os command
316 (define %test-installed-extlinux-os
318 (name "installed-extlinux-os")
320 "Test basic functionality of an OS booted with an extlinux bootloader. As
321 per %test-installed-os, this test is expensive in terms of CPU and storage.")
323 (mlet* %store-monad ((image (run-install %minimal-extlinux-os
324 %minimal-extlinux-os-source
328 %extlinux-gpt-installation-script))
329 (command (qemu-command/writable-image image)))
330 (run-basic-test %minimal-extlinux-os command
331 "installed-extlinux-os")))))
335 ;;; Installation through an ISO image.
338 (define-os-with-source (%minimal-os-on-vda %minimal-os-on-vda-source)
339 ;; The OS we want to install.
340 (use-modules (gnu) (gnu tests) (srfi srfi-1))
343 (host-name "liberigilo")
344 (timezone "Europe/Paris")
345 (locale "en_US.UTF-8")
347 (bootloader (bootloader-configuration
348 (bootloader grub-bootloader)
349 (target "/dev/vda")))
350 (kernel-arguments '("console=ttyS0"))
351 (file-systems (cons (file-system
352 (device (file-system-label "my-root"))
356 (users (cons (user-account
358 (comment "Bob's sister")
360 (supplementary-groups '("wheel" "audio" "video"))
361 (home-directory "/home/alice"))
362 %base-user-accounts))
363 (services (cons (service marionette-service-type
364 (marionette-configuration
365 (imported-modules '((gnu services herd)
366 (guix combinators)))))
369 (define %simple-installation-script-for-/dev/vda
370 ;; Shell script of a simple installation.
376 export GUIX_BUILD_OPTIONS=--no-grafts
378 parted --script /dev/vda mklabel gpt \\
379 mkpart primary ext2 1M 3M \\
380 mkpart primary ext2 3M 1.2G \\
383 mkfs.ext4 -L my-root /dev/vda2
386 herd start cow-store /mnt
388 cp /etc/target-config.scm /mnt/etc/config.scm
389 guix system init /mnt/etc/config.scm /mnt --no-substitutes
393 (define %test-iso-image-installer
395 (name "iso-image-installer")
399 (mlet* %store-monad ((image (run-install
401 %minimal-os-on-vda-source
403 %simple-installation-script-for-/dev/vda
404 #:installation-disk-image-file-system-type
406 (command (qemu-command/writable-image image)))
407 (run-basic-test %minimal-os-on-vda command name)))))
414 (define-os-with-source (%separate-home-os %separate-home-os-source)
415 ;; The OS we want to install.
416 (use-modules (gnu) (gnu tests) (srfi srfi-1))
419 (host-name "liberigilo")
420 (timezone "Europe/Paris")
421 (locale "en_US.utf8")
423 (bootloader (bootloader-configuration
424 (bootloader grub-bootloader)
425 (target "/dev/vdb")))
426 (kernel-arguments '("console=ttyS0"))
427 (file-systems (cons* (file-system
428 (device (file-system-label "my-root"))
433 (mount-point "/home")
436 (users (cons* (user-account
439 (home-directory "/home/alice"))
443 (home-directory "/home/charlie"))
444 %base-user-accounts))
445 (services (cons (service marionette-service-type
446 (marionette-configuration
447 (imported-modules '((gnu services herd)
448 (guix combinators)))))
451 (define %test-separate-home-os
453 (name "separate-home-os")
455 "Test basic functionality of an installed OS with a separate /home
456 partition. In particular, home directories must be correctly created (see
457 <https://bugs.gnu.org/21108>).")
459 (mlet* %store-monad ((image (run-install %separate-home-os
460 %separate-home-os-source
462 %simple-installation-script))
463 (command (qemu-command/writable-image image)))
464 (run-basic-test %separate-home-os command "separate-home-os")))))
468 ;;; Separate /gnu/store partition.
471 (define-os-with-source (%separate-store-os %separate-store-os-source)
472 ;; The OS we want to install.
473 (use-modules (gnu) (gnu tests) (srfi srfi-1))
476 (host-name "liberigilo")
477 (timezone "Europe/Paris")
478 (locale "en_US.UTF-8")
480 (bootloader (bootloader-configuration
481 (bootloader grub-bootloader)
482 (target "/dev/vdb")))
483 (kernel-arguments '("console=ttyS0"))
484 (file-systems (cons* (file-system
485 (device (file-system-label "root-fs"))
489 (device (file-system-label "store-fs"))
493 (users %base-user-accounts)
494 (services (cons (service marionette-service-type
495 (marionette-configuration
496 (imported-modules '((gnu services herd)
497 (guix combinators)))))
500 (define %separate-store-installation-script
501 ;; Installation with a separate /gnu partition.
507 export GUIX_BUILD_OPTIONS=--no-grafts
509 parted --script /dev/vdb mklabel gpt \\
510 mkpart primary ext2 1M 3M \\
511 mkpart primary ext2 3M 100M \\
512 mkpart primary ext2 100M 1.2G \\
515 mkfs.ext4 -L root-fs /dev/vdb2
516 mkfs.ext4 -L store-fs /dev/vdb3
519 mount /dev/vdb3 /mnt/gnu
521 herd start cow-store /mnt
523 cp /etc/target-config.scm /mnt/etc/config.scm
524 guix system init /mnt/etc/config.scm /mnt --no-substitutes
528 (define %test-separate-store-os
530 (name "separate-store-os")
532 "Test basic functionality of an OS installed like one would do by hand,
533 where /gnu lives on a separate partition.")
535 (mlet* %store-monad ((image (run-install %separate-store-os
536 %separate-store-os-source
538 %separate-store-installation-script))
539 (command (qemu-command/writable-image image)))
540 (run-basic-test %separate-store-os command "separate-store-os")))))
544 ;;; RAID root device.
547 (define-os-with-source (%raid-root-os %raid-root-os-source)
548 ;; An OS whose root partition is a RAID partition.
549 (use-modules (gnu) (gnu tests))
552 (host-name "raidified")
553 (timezone "Europe/Paris")
554 (locale "en_US.utf8")
556 (bootloader (bootloader-configuration
557 (bootloader grub-bootloader)
558 (target "/dev/vdb")))
559 (kernel-arguments '("console=ttyS0"))
561 ;; Add a kernel module for RAID-0 (aka. "stripe").
562 (initrd-modules (cons "raid0" %base-initrd-modules))
564 (mapped-devices (list (mapped-device
565 (source (list "/dev/vda2" "/dev/vda3"))
567 (type raid-device-mapping))))
568 (file-systems (cons (file-system
569 (device (file-system-label "root-fs"))
572 (dependencies mapped-devices))
574 (users %base-user-accounts)
575 (services (cons (service marionette-service-type
576 (marionette-configuration
577 (imported-modules '((gnu services herd)
578 (guix combinators)))))
581 (define %raid-root-installation-script
582 ;; Installation with a separate /gnu partition. See
583 ;; <https://raid.wiki.kernel.org/index.php/RAID_setup> for more on RAID and
590 export GUIX_BUILD_OPTIONS=--no-grafts
591 parted --script /dev/vdb mklabel gpt \\
592 mkpart primary ext2 1M 3M \\
593 mkpart primary ext2 3M 600M \\
594 mkpart primary ext2 600M 1200M \\
597 mdadm --create /dev/md0 --verbose --level=stripe --raid-devices=2 \\
599 mkfs.ext4 -L root-fs /dev/md0
602 herd start cow-store /mnt
604 cp /etc/target-config.scm /mnt/etc/config.scm
605 guix system init /mnt/etc/config.scm /mnt --no-substitutes
609 (define %test-raid-root-os
611 (name "raid-root-os")
613 "Test functionality of an OS installed with a RAID root partition managed
616 (mlet* %store-monad ((image (run-install %raid-root-os
619 %raid-root-installation-script
620 #:target-size (* 1300 MiB)))
621 (command (qemu-command/writable-image image)))
622 (run-basic-test %raid-root-os
623 `(,@command) "raid-root-os")))))
627 ;;; LUKS-encrypted root file system.
630 (define-os-with-source (%encrypted-root-os %encrypted-root-os-source)
631 ;; The OS we want to install.
632 (use-modules (gnu) (gnu tests) (srfi srfi-1))
635 (host-name "liberigilo")
636 (timezone "Europe/Paris")
637 (locale "en_US.UTF-8")
639 (bootloader (bootloader-configuration
640 (bootloader grub-bootloader)
641 (target "/dev/vdb")))
643 ;; Note: Do not pass "console=ttyS0" so we can use our passphrase prompt
644 ;; detection logic in 'enter-luks-passphrase'.
646 (mapped-devices (list (mapped-device
647 (source (uuid "12345678-1234-1234-1234-123456789abc"))
648 (target "the-root-device")
649 (type luks-device-mapping))))
650 (file-systems (cons (file-system
651 (device "/dev/mapper/the-root-device")
655 (users (cons (user-account
658 (home-directory "/home/charlie")
659 (supplementary-groups '("wheel" "audio" "video")))
660 %base-user-accounts))
661 (services (cons (service marionette-service-type
662 (marionette-configuration
663 (imported-modules '((gnu services herd)
664 (guix combinators)))))
667 (define %encrypted-root-installation-script
668 ;; Shell script of a simple installation.
674 export GUIX_BUILD_OPTIONS=--no-grafts
675 ls -l /run/current-system/gc-roots
676 parted --script /dev/vdb mklabel gpt \\
677 mkpart primary ext2 1M 3M \\
678 mkpart primary ext2 3M 1.2G \\
681 echo -n thepassphrase | \\
682 cryptsetup luksFormat --uuid=12345678-1234-1234-1234-123456789abc -q /dev/vdb2 -
683 echo -n thepassphrase | \\
684 cryptsetup open --type luks --key-file - /dev/vdb2 the-root-device
685 mkfs.ext4 -L my-root /dev/mapper/the-root-device
686 mount LABEL=my-root /mnt
687 herd start cow-store /mnt
689 cp /etc/target-config.scm /mnt/etc/config.scm
690 guix system build /mnt/etc/config.scm
691 guix system init /mnt/etc/config.scm /mnt --no-substitutes
695 (define (enter-luks-passphrase marionette)
696 "Return a gexp to be inserted in the basic system test running on MARIONETTE
697 to enter the LUKS passphrase."
698 (let ((ocrad (file-append ocrad "/bin/ocrad")))
700 (define (passphrase-prompt? text)
701 (string-contains (pk 'screen-text text) "Enter pass"))
703 (define (bios-boot-screen? text)
704 ;; Return true if TEXT corresponds to the boot screen, before GRUB's
706 (string-prefix? "SeaBIOS" text))
708 (test-assert "enter LUKS passphrase for GRUB"
710 ;; At this point we have no choice but to use OCR to determine
711 ;; when the passphrase should be entered.
712 (wait-for-screen-text #$marionette passphrase-prompt?
714 (marionette-type "thepassphrase\n" #$marionette)
716 ;; Now wait until we leave the boot screen. This is necessary so
717 ;; we can then be sure we match the "Enter passphrase" prompt from
718 ;; 'cryptsetup', in the initrd.
719 (wait-for-screen-text #$marionette (negate bios-boot-screen?)
723 (test-assert "enter LUKS passphrase for the initrd"
725 ;; XXX: Here we use OCR as well but we could instead use QEMU
726 ;; '-serial stdio' and run it in an input pipe,
727 (wait-for-screen-text #$marionette passphrase-prompt?
730 (marionette-type "thepassphrase\n" #$marionette)
732 ;; Take a screenshot for debugging purposes.
733 (marionette-control (string-append "screendump " #$output
734 "/post-initrd-passphrase.ppm")
737 (define %test-encrypted-root-os
739 (name "encrypted-root-os")
741 "Test basic functionality of an OS installed like one would do by hand.
742 This test is expensive in terms of CPU and storage usage since we need to
743 build (current-guix) and then store a couple of full system images.")
745 (mlet* %store-monad ((image (run-install %encrypted-root-os
746 %encrypted-root-os-source
748 %encrypted-root-installation-script))
749 (command (qemu-command/writable-image image)))
750 (run-basic-test %encrypted-root-os command "encrypted-root-os"
751 #:initialization enter-luks-passphrase)))))
755 ;;; Btrfs root file system.
758 (define-os-with-source (%btrfs-root-os %btrfs-root-os-source)
759 ;; The OS we want to install.
760 (use-modules (gnu) (gnu tests) (srfi srfi-1))
763 (host-name "liberigilo")
764 (timezone "Europe/Paris")
765 (locale "en_US.UTF-8")
767 (bootloader (bootloader-configuration
768 (bootloader grub-bootloader)
769 (target "/dev/vdb")))
770 (kernel-arguments '("console=ttyS0"))
771 (file-systems (cons (file-system
772 (device (file-system-label "my-root"))
776 (users (cons (user-account
779 (home-directory "/home/charlie")
780 (supplementary-groups '("wheel" "audio" "video")))
781 %base-user-accounts))
782 (services (cons (service marionette-service-type
783 (marionette-configuration
784 (imported-modules '((gnu services herd)
785 (guix combinators)))))
788 (define %btrfs-root-installation-script
789 ;; Shell script of a simple installation.
795 export GUIX_BUILD_OPTIONS=--no-grafts
796 ls -l /run/current-system/gc-roots
797 parted --script /dev/vdb mklabel gpt \\
798 mkpart primary ext2 1M 3M \\
799 mkpart primary ext2 3M 2G \\
802 mkfs.btrfs -L my-root /dev/vdb2
804 btrfs subvolume create /mnt/home
805 herd start cow-store /mnt
807 cp /etc/target-config.scm /mnt/etc/config.scm
808 guix system build /mnt/etc/config.scm
809 guix system init /mnt/etc/config.scm /mnt --no-substitutes
813 (define %test-btrfs-root-os
815 (name "btrfs-root-os")
817 "Test basic functionality of an OS installed like one would do by hand.
818 This test is expensive in terms of CPU and storage usage since we need to
819 build (current-guix) and then store a couple of full system images.")
821 (mlet* %store-monad ((image (run-install %btrfs-root-os
822 %btrfs-root-os-source
824 %btrfs-root-installation-script))
825 (command (qemu-command/writable-image image)))
826 (run-basic-test %btrfs-root-os command "btrfs-root-os")))))
828 ;;; install.scm ends here