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")))
79 (services (cons (service marionette-service-type
80 (marionette-configuration
81 (imported-modules '((gnu services herd)
82 (guix combinators)))))
85 (define (operating-system-add-packages os packages)
86 "Append PACKAGES to OS packages list."
89 (packages (append packages (operating-system-packages os)))))
91 (define-os-with-source (%minimal-extlinux-os
92 %minimal-extlinux-os-source)
93 (use-modules (gnu) (gnu tests) (gnu bootloader extlinux)
97 (host-name "liberigilo")
98 (timezone "Europe/Paris")
99 (locale "en_US.UTF-8")
101 (bootloader (bootloader-configuration
102 (bootloader extlinux-bootloader-gpt)
103 (target "/dev/vdb")))
104 (kernel-arguments '("console=ttyS0"))
105 (file-systems (cons (file-system
106 (device (file-system-label "my-root"))
110 (services (cons (service marionette-service-type
111 (marionette-configuration
112 (imported-modules '((gnu services herd)
113 (guix combinators)))))
116 (define (operating-system-with-current-guix os)
117 "Return a variant of OS that uses the current Guix."
120 (services (modify-services (operating-system-user-services os)
121 (guix-service-type config =>
124 (guix (current-guix))))))))
127 (define MiB (expt 2 20))
129 (define %simple-installation-script
130 ;; Shell script of a simple installation.
136 export GUIX_BUILD_OPTIONS=--no-grafts
138 parted --script /dev/vdb mklabel gpt \\
139 mkpart primary ext2 1M 3M \\
140 mkpart primary ext2 3M 1.2G \\
143 mkfs.ext4 -L my-root /dev/vdb2
146 herd start cow-store /mnt
148 cp /etc/target-config.scm /mnt/etc/config.scm
149 guix system init /mnt/etc/config.scm /mnt --no-substitutes
153 (define %extlinux-gpt-installation-script
154 ;; Shell script of a simple installation.
155 ;; As syslinux 6.0.3 does not handle 64bits ext4 partitions,
156 ;; we make sure to pass -O '^64bit' to mkfs.
162 export GUIX_BUILD_OPTIONS=--no-grafts
164 parted --script /dev/vdb mklabel gpt \\
165 mkpart ext2 1M 1.2G \\
167 mkfs.ext4 -L my-root -O '^64bit' /dev/vdb1
170 herd start cow-store /mnt
172 cp /etc/target-config.scm /mnt/etc/config.scm
173 guix system init /mnt/etc/config.scm /mnt --no-substitutes
177 (define* (run-install target-os target-os-source
179 (script %simple-installation-script)
181 (os (marionette-operating-system
183 ;; Since the image has no network access, use the
184 ;; current Guix so the store items we need are in
185 ;; the image and add packages provided.
186 (inherit (operating-system-add-packages
187 (operating-system-with-current-guix
190 (kernel-arguments '("console=ttyS0")))
191 #:imported-modules '((gnu services herd)
192 (guix combinators))))
193 (installation-disk-image-file-system-type "ext4")
194 (target-size (* 2200 MiB)))
195 "Run SCRIPT (a shell script following the system installation procedure) in
196 OS to install TARGET-OS. Return a VM image of TARGET-SIZE bytes containing
197 the installed system. The packages specified in PACKAGES will be appended to
198 packages defined in installation-os."
200 (mlet* %store-monad ((_ (set-grafting #f))
201 (system (current-system))
202 (target (operating-system-derivation target-os))
204 ;; Since the installation system has no network access,
205 ;; we cheat a little bit by adding TARGET to its GC
206 ;; roots. This way, we know 'guix system init' will
208 (image (system-disk-image
209 (operating-system-with-gc-roots
211 #:disk-image-size 'guess
213 installation-disk-image-file-system-type)))
215 (with-imported-modules '((guix build utils)
216 (gnu build marionette))
218 (use-modules (guix build utils)
219 (gnu build marionette))
221 (set-path-environment-variable "PATH" '("bin")
222 (list #$qemu-minimal))
224 (system* "qemu-img" "create" "-f" "qcow2"
225 #$output #$(number->string target-size))
229 `(,(which #$(qemu-command system))
233 ((string=? "ext4" installation-disk-image-file-system-type)
235 ,(string-append "file=" #$image
236 ",if=virtio,readonly")))
237 ((string=? "iso9660" installation-disk-image-file-system-type)
238 #~("-cdrom" #$image))
241 "unsupported installation-disk-image-file-system-type:"
242 installation-disk-image-file-system-type)))
244 ,(string-append "file=" #$output ",if=virtio")
245 ,@(if (file-exists? "/dev/kvm")
249 (pk 'uname (marionette-eval '(uname) marionette))
252 (marionette-eval '(begin
253 (use-modules (gnu services herd))
257 (marionette-eval '(call-with-output-file "/etc/target-config.scm"
259 (write '#$target-os-source port)))
262 (exit (marionette-eval '(zero? (system #$script))
265 (gexp->derivation "installation" install)))
267 (define* (qemu-command/writable-image image #:key (memory-size 256))
268 "Return as a monadic value the command to run QEMU on a writable copy of
269 IMAGE, a disk image. The QEMU VM is has access to MEMORY-SIZE MiB of RAM."
270 (mlet %store-monad ((system (current-system)))
271 (return #~(let ((image #$image))
272 ;; First we need a writable copy of the image.
273 (format #t "creating writable image from '~a'...~%" image)
274 (unless (zero? (system* #+(file-append qemu-minimal
276 "create" "-f" "qcow2"
278 (string-append "backing_file=" image)
280 (error "failed to create writable QEMU image" image))
282 (chmod "disk.img" #o644)
283 `(,(string-append #$qemu-minimal "/bin/"
284 #$(qemu-command system))
285 ,@(if (file-exists? "/dev/kvm")
288 "-no-reboot" "-m" #$(number->string memory-size)
289 "-drive" "file=disk.img,if=virtio")))))
291 (define %test-installed-os
293 (name "installed-os")
295 "Test basic functionality of an OS installed like one would do by hand.
296 This test is expensive in terms of CPU and storage usage since we need to
297 build (current-guix) and then store a couple of full system images.")
299 (mlet* %store-monad ((image (run-install %minimal-os %minimal-os-source))
300 (command (qemu-command/writable-image image)))
301 (run-basic-test %minimal-os command
304 (define %test-installed-extlinux-os
306 (name "installed-extlinux-os")
308 "Test basic functionality of an OS booted with an extlinux bootloader. As
309 per %test-installed-os, this test is expensive in terms of CPU and storage.")
311 (mlet* %store-monad ((image (run-install %minimal-extlinux-os
312 %minimal-extlinux-os-source
316 %extlinux-gpt-installation-script))
317 (command (qemu-command/writable-image image)))
318 (run-basic-test %minimal-extlinux-os command
319 "installed-extlinux-os")))))
323 ;;; Installation through an ISO image.
326 (define-os-with-source (%minimal-os-on-vda %minimal-os-on-vda-source)
327 ;; The OS we want to install.
328 (use-modules (gnu) (gnu tests) (srfi srfi-1))
331 (host-name "liberigilo")
332 (timezone "Europe/Paris")
333 (locale "en_US.UTF-8")
335 (bootloader (bootloader-configuration
336 (bootloader grub-bootloader)
337 (target "/dev/vda")))
338 (kernel-arguments '("console=ttyS0"))
339 (file-systems (cons (file-system
340 (device (file-system-label "my-root"))
344 (users (cons (user-account
346 (comment "Bob's sister")
348 (supplementary-groups '("wheel" "audio" "video")))
349 %base-user-accounts))
350 (services (cons (service marionette-service-type
351 (marionette-configuration
352 (imported-modules '((gnu services herd)
353 (guix combinators)))))
356 (define %simple-installation-script-for-/dev/vda
357 ;; Shell script of a simple installation.
363 export GUIX_BUILD_OPTIONS=--no-grafts
365 parted --script /dev/vda mklabel gpt \\
366 mkpart primary ext2 1M 3M \\
367 mkpart primary ext2 3M 1.2G \\
370 mkfs.ext4 -L my-root /dev/vda2
373 herd start cow-store /mnt
375 cp /etc/target-config.scm /mnt/etc/config.scm
376 guix system init /mnt/etc/config.scm /mnt --no-substitutes
380 (define %test-iso-image-installer
382 (name "iso-image-installer")
386 (mlet* %store-monad ((image (run-install
388 %minimal-os-on-vda-source
390 %simple-installation-script-for-/dev/vda
391 #:installation-disk-image-file-system-type
393 (command (qemu-command/writable-image image)))
394 (run-basic-test %minimal-os-on-vda command name)))))
401 (define-os-with-source (%separate-home-os %separate-home-os-source)
402 ;; The OS we want to install.
403 (use-modules (gnu) (gnu tests) (srfi srfi-1))
406 (host-name "liberigilo")
407 (timezone "Europe/Paris")
408 (locale "en_US.utf8")
410 (bootloader (bootloader-configuration
411 (bootloader grub-bootloader)
412 (target "/dev/vdb")))
413 (kernel-arguments '("console=ttyS0"))
414 (file-systems (cons* (file-system
415 (device (file-system-label "my-root"))
420 (mount-point "/home")
423 (users (cons* (user-account
429 %base-user-accounts))
430 (services (cons (service marionette-service-type
431 (marionette-configuration
432 (imported-modules '((gnu services herd)
433 (guix combinators)))))
436 (define %test-separate-home-os
438 (name "separate-home-os")
440 "Test basic functionality of an installed OS with a separate /home
441 partition. In particular, home directories must be correctly created (see
442 <https://bugs.gnu.org/21108>).")
444 (mlet* %store-monad ((image (run-install %separate-home-os
445 %separate-home-os-source
447 %simple-installation-script))
448 (command (qemu-command/writable-image image)))
449 (run-basic-test %separate-home-os command "separate-home-os")))))
453 ;;; Separate /gnu/store partition.
456 (define-os-with-source (%separate-store-os %separate-store-os-source)
457 ;; The OS we want to install.
458 (use-modules (gnu) (gnu tests) (srfi srfi-1))
461 (host-name "liberigilo")
462 (timezone "Europe/Paris")
463 (locale "en_US.UTF-8")
465 (bootloader (bootloader-configuration
466 (bootloader grub-bootloader)
467 (target "/dev/vdb")))
468 (kernel-arguments '("console=ttyS0"))
469 (file-systems (cons* (file-system
470 (device (file-system-label "root-fs"))
474 (device (file-system-label "store-fs"))
478 (users %base-user-accounts)
479 (services (cons (service marionette-service-type
480 (marionette-configuration
481 (imported-modules '((gnu services herd)
482 (guix combinators)))))
485 (define %separate-store-installation-script
486 ;; Installation with a separate /gnu partition.
492 export GUIX_BUILD_OPTIONS=--no-grafts
494 parted --script /dev/vdb mklabel gpt \\
495 mkpart primary ext2 1M 3M \\
496 mkpart primary ext2 3M 400M \\
497 mkpart primary ext2 400M 2.1G \\
500 mkfs.ext4 -L root-fs /dev/vdb2
501 mkfs.ext4 -L store-fs /dev/vdb3
504 mount /dev/vdb3 /mnt/gnu
507 herd start cow-store /mnt
509 cp /etc/target-config.scm /mnt/etc/config.scm
510 guix system init /mnt/etc/config.scm /mnt --no-substitutes
514 (define %test-separate-store-os
516 (name "separate-store-os")
518 "Test basic functionality of an OS installed like one would do by hand,
519 where /gnu lives on a separate partition.")
521 (mlet* %store-monad ((image (run-install %separate-store-os
522 %separate-store-os-source
524 %separate-store-installation-script))
525 (command (qemu-command/writable-image image)))
526 (run-basic-test %separate-store-os command "separate-store-os")))))
530 ;;; RAID root device.
533 (define-os-with-source (%raid-root-os %raid-root-os-source)
534 ;; An OS whose root partition is a RAID partition.
535 (use-modules (gnu) (gnu tests))
538 (host-name "raidified")
539 (timezone "Europe/Paris")
540 (locale "en_US.utf8")
542 (bootloader (bootloader-configuration
543 (bootloader grub-bootloader)
544 (target "/dev/vdb")))
545 (kernel-arguments '("console=ttyS0"))
547 ;; Add a kernel module for RAID-0 (aka. "stripe").
548 (initrd-modules (cons "raid0" %base-initrd-modules))
550 (mapped-devices (list (mapped-device
551 (source (list "/dev/vda2" "/dev/vda3"))
553 (type raid-device-mapping))))
554 (file-systems (cons (file-system
555 (device (file-system-label "root-fs"))
558 (dependencies mapped-devices))
560 (users %base-user-accounts)
561 (services (cons (service marionette-service-type
562 (marionette-configuration
563 (imported-modules '((gnu services herd)
564 (guix combinators)))))
567 (define %raid-root-installation-script
568 ;; Installation with a separate /gnu partition. See
569 ;; <https://raid.wiki.kernel.org/index.php/RAID_setup> for more on RAID and
576 export GUIX_BUILD_OPTIONS=--no-grafts
577 parted --script /dev/vdb mklabel gpt \\
578 mkpart primary ext2 1M 3M \\
579 mkpart primary ext2 3M 600M \\
580 mkpart primary ext2 600M 1200M \\
583 mdadm --create /dev/md0 --verbose --level=stripe --raid-devices=2 \\
585 mkfs.ext4 -L root-fs /dev/md0
588 herd start cow-store /mnt
590 cp /etc/target-config.scm /mnt/etc/config.scm
591 guix system init /mnt/etc/config.scm /mnt --no-substitutes
595 (define %test-raid-root-os
597 (name "raid-root-os")
599 "Test functionality of an OS installed with a RAID root partition managed
602 (mlet* %store-monad ((image (run-install %raid-root-os
605 %raid-root-installation-script
606 #:target-size (* 1300 MiB)))
607 (command (qemu-command/writable-image image)))
608 (run-basic-test %raid-root-os
609 `(,@command) "raid-root-os")))))
613 ;;; LUKS-encrypted root file system.
616 (define-os-with-source (%encrypted-root-os %encrypted-root-os-source)
617 ;; The OS we want to install.
618 (use-modules (gnu) (gnu tests) (srfi srfi-1))
621 (host-name "liberigilo")
622 (timezone "Europe/Paris")
623 (locale "en_US.UTF-8")
625 (bootloader (bootloader-configuration
626 (bootloader grub-bootloader)
627 (target "/dev/vdb")))
629 ;; Note: Do not pass "console=ttyS0" so we can use our passphrase prompt
630 ;; detection logic in 'enter-luks-passphrase'.
632 (mapped-devices (list (mapped-device
633 (source (uuid "12345678-1234-1234-1234-123456789abc"))
634 (target "the-root-device")
635 (type luks-device-mapping))))
636 (file-systems (cons (file-system
637 (device "/dev/mapper/the-root-device")
641 (users (cons (user-account
644 (supplementary-groups '("wheel" "audio" "video")))
645 %base-user-accounts))
646 (services (cons (service marionette-service-type
647 (marionette-configuration
648 (imported-modules '((gnu services herd)
649 (guix combinators)))))
652 (define %encrypted-root-installation-script
653 ;; Shell script of a simple installation.
659 export GUIX_BUILD_OPTIONS=--no-grafts
660 ls -l /run/current-system/gc-roots
661 parted --script /dev/vdb mklabel gpt \\
662 mkpart primary ext2 1M 3M \\
663 mkpart primary ext2 3M 1.2G \\
666 echo -n thepassphrase | \\
667 cryptsetup luksFormat --uuid=12345678-1234-1234-1234-123456789abc -q /dev/vdb2 -
668 echo -n thepassphrase | \\
669 cryptsetup open --type luks --key-file - /dev/vdb2 the-root-device
670 mkfs.ext4 -L my-root /dev/mapper/the-root-device
671 mount LABEL=my-root /mnt
672 herd start cow-store /mnt
674 cp /etc/target-config.scm /mnt/etc/config.scm
675 guix system build /mnt/etc/config.scm
676 guix system init /mnt/etc/config.scm /mnt --no-substitutes
680 (define (enter-luks-passphrase marionette)
681 "Return a gexp to be inserted in the basic system test running on MARIONETTE
682 to enter the LUKS passphrase."
683 (let ((ocrad (file-append ocrad "/bin/ocrad")))
685 (define (passphrase-prompt? text)
686 (string-contains (pk 'screen-text text) "Enter pass"))
688 (define (bios-boot-screen? text)
689 ;; Return true if TEXT corresponds to the boot screen, before GRUB's
691 (string-prefix? "SeaBIOS" text))
693 (test-assert "enter LUKS passphrase for GRUB"
695 ;; At this point we have no choice but to use OCR to determine
696 ;; when the passphrase should be entered.
697 (wait-for-screen-text #$marionette passphrase-prompt?
699 (marionette-type "thepassphrase\n" #$marionette)
701 ;; Now wait until we leave the boot screen. This is necessary so
702 ;; we can then be sure we match the "Enter passphrase" prompt from
703 ;; 'cryptsetup', in the initrd.
704 (wait-for-screen-text #$marionette (negate bios-boot-screen?)
708 (test-assert "enter LUKS passphrase for the initrd"
710 ;; XXX: Here we use OCR as well but we could instead use QEMU
711 ;; '-serial stdio' and run it in an input pipe,
712 (wait-for-screen-text #$marionette passphrase-prompt?
715 (marionette-type "thepassphrase\n" #$marionette)
717 ;; Take a screenshot for debugging purposes.
718 (marionette-control (string-append "screendump " #$output
719 "/post-initrd-passphrase.ppm")
722 (define %test-encrypted-root-os
724 (name "encrypted-root-os")
726 "Test basic functionality of an OS installed like one would do by hand.
727 This test is expensive in terms of CPU and storage usage since we need to
728 build (current-guix) and then store a couple of full system images.")
730 (mlet* %store-monad ((image (run-install %encrypted-root-os
731 %encrypted-root-os-source
733 %encrypted-root-installation-script))
734 (command (qemu-command/writable-image image)))
735 (run-basic-test %encrypted-root-os command "encrypted-root-os"
736 #:initialization enter-luks-passphrase)))))
740 ;;; Btrfs root file system.
743 (define-os-with-source (%btrfs-root-os %btrfs-root-os-source)
744 ;; The OS we want to install.
745 (use-modules (gnu) (gnu tests) (srfi srfi-1))
748 (host-name "liberigilo")
749 (timezone "Europe/Paris")
750 (locale "en_US.UTF-8")
752 (bootloader (bootloader-configuration
753 (bootloader grub-bootloader)
754 (target "/dev/vdb")))
755 (kernel-arguments '("console=ttyS0"))
756 (file-systems (cons (file-system
757 (device (file-system-label "my-root"))
761 (users (cons (user-account
764 (supplementary-groups '("wheel" "audio" "video")))
765 %base-user-accounts))
766 (services (cons (service marionette-service-type
767 (marionette-configuration
768 (imported-modules '((gnu services herd)
769 (guix combinators)))))
772 (define %btrfs-root-installation-script
773 ;; Shell script of a simple installation.
779 export GUIX_BUILD_OPTIONS=--no-grafts
780 ls -l /run/current-system/gc-roots
781 parted --script /dev/vdb mklabel gpt \\
782 mkpart primary ext2 1M 3M \\
783 mkpart primary ext2 3M 2G \\
786 mkfs.btrfs -L my-root /dev/vdb2
788 btrfs subvolume create /mnt/home
789 herd start cow-store /mnt
791 cp /etc/target-config.scm /mnt/etc/config.scm
792 guix system build /mnt/etc/config.scm
793 guix system init /mnt/etc/config.scm /mnt --no-substitutes
797 (define %test-btrfs-root-os
799 (name "btrfs-root-os")
801 "Test basic functionality of an OS installed like one would do by hand.
802 This test is expensive in terms of CPU and storage usage since we need to
803 build (current-guix) and then store a couple of full system images.")
805 (mlet* %store-monad ((image (run-install %btrfs-root-os
806 %btrfs-root-os-source
808 %btrfs-root-installation-script))
809 (command (qemu-command/writable-image image)))
810 (run-basic-test %btrfs-root-os command "btrfs-root-os")))))
812 ;;; install.scm ends here