;;; Test for Guix System with root on LVM ;;; Copyright © 2025 (define-module (ruther tests lvm) #:use-module (ruthless bootloader grub) #:use-module (gnu bootloader) #:use-module (gnu bootloader grub) #:use-module (gnu build marionette) #:use-module (gnu packages firmware) #:use-module (gnu packages ocr) #:use-module (gnu packages virtualization) #:use-module (gnu services) #:use-module (gnu services base) #:use-module (gnu system) #:use-module (gnu system file-systems) #:use-module (gnu system mapped-devices) #:use-module (gnu system shadow) #:use-module (gnu system vm) #:use-module (gnu tests) #:use-module (gnu tests base) #:use-module (guix gexp) #:use-module (ruthless image) #:export (%test-root-lvm %test-lvm-on-luks)) ;;; ;;; OS definition with root on LVM ;;; (define %lvm-root-os ;; Operating system with root filesystem on LVM. ;; Uses vg0/root as the root logical volume. (operating-system (host-name "lvmroot") (timezone "Europe/Berlin") (locale "en_US.UTF-8") (bootloader (bootloader-configuration (bootloader grub-efi-removable-bootloader) (targets '("/boot/efi")) (terminal-outputs '(console)))) (kernel-arguments '("console=ttyS0")) (mapped-devices (list (mapped-device (source "vg0") (targets '("vg0-root")) (type lvm-device-mapping)))) (file-systems (cons* (file-system (device "/dev/mapper/vg0-root") (mount-point "/") (type "ext4") (dependencies mapped-devices)) (file-system (device (file-system-label "ESP")) (mount-point "/boot/efi") (type "vfat")) %base-file-systems)) (users (cons (user-account (name "alice") (group "users") (supplementary-groups '("wheel"))) %base-user-accounts)) (services %base-services))) ;;; ;;; Test execution ;;; (define (run-lvm-root-test) "Run the basic test suite on an OS with root on LVM." (define os (marionette-operating-system %lvm-root-os #:imported-modules '((gnu services herd) (guix combinators)))) ;; Use the generic disk image builder with LVM initializer (define image (build-disk-image os #:disk-initializer lvm-disk-initializer #:disk-size (* 2048 1024 1024) ; 2 GiB #:uefi? #t #:name "lvm-root-image")) (define vm-command #~(list (string-append #$qemu-minimal "/bin/" #$(qemu-command)) "-m" "512" "-bios" #$(file-append ovmf-x86-64 "/share/firmware/ovmf_x64.bin") "-drive" (string-append "file=" #$image ",format=qcow2,if=virtio") "-snapshot" ; Use snapshot mode since image is in read-only store "-no-reboot" #$@(if (file-exists? "/dev/kvm") '("-enable-kvm") '()) "-nographic")) (run-basic-test os vm-command "lvm-root")) ;;; ;;; System test definition ;;; (define %test-root-lvm (system-test (name "lvm-root") (description "Test basic functionality of a Guix System with root on LVM.") (value (run-lvm-root-test)))) ;;; ;;; OS definition with LVM on LUKS ;;; ;; The LUKS passphrase and UUID used during image creation. ;; These must match the values in make-lvm-on-luks-disk-initializer. (define %luks-passphrase "testpassword") (define %luks-uuid "12345678-1234-1234-1234-123456789abc") (define %lvm-on-luks-os ;; Operating system with root and home on LVM, which sits on a LUKS ;; encrypted partition. ;; ;; Mapped devices dependency chain: ;; /dev/vda3 (LUKS partition) ;; -> /dev/mapper/cryptroot (opened LUKS container) ;; -> vg0 (LVM volume group on cryptroot) ;; -> /dev/mapper/vg0-root (root LV) ;; -> /dev/mapper/vg0-home (home LV) (operating-system (host-name "crptlvm") ;; NOTE: no y, because that one the OCR does not like. (timezone "Europe/Berlin") (locale "en_US.UTF-8") (bootloader (bootloader-configuration (bootloader grub-efi-removable-bootloader) (targets '("/boot/efi")) (terminal-outputs '(console)))) ;; Note: Do not pass "console=ttyS0" so we can use our passphrase prompt ;; detection logic in 'enter-luks-passphrase' which uses OCR. ;; Mapped devices: first LUKS, then LVM on top (mapped-devices (list ;; LUKS encrypted partition (mapped-device (source (uuid %luks-uuid)) (targets '("cryptroot")) (type luks-device-mapping)) ;; LVM volume group on the LUKS container (mapped-device (source "vg0") (targets '("vg0-root" "vg0-home")) (type lvm-device-mapping)))) (file-systems (cons* (file-system (device "/dev/mapper/vg0-root") (mount-point "/") (type "ext4") (dependencies mapped-devices)) (file-system (device (file-system-label "ESP")) (mount-point "/boot/efi") (type "vfat")) (file-system (device "/dev/mapper/vg0-home") (mount-point "/home") (type "ext4") (dependencies mapped-devices)) %base-file-systems)) (users (cons (user-account (name "alice") (group "users") (supplementary-groups '("wheel"))) %base-user-accounts)) (services %base-services))) ;;; ;;; Test execution ;;; ;; Taken from the GNU Guix channel, with edits. (define (enter-luks-passphrase marionette) "Return a gexp to be inserted in the basic system test running on MARIONETTE to enter the LUKS passphrase." (let ((ocrad (file-append ocrad "/bin/ocrad"))) #~(begin (define (passphrase-prompt? text) (string-contains (pk 'screen-text text) "Enter pass")) (test-assert "enter LUKS passphrase for GRUB" (begin ;; At this point we have no choice but to use OCR to determine ;; when the passphrase should be entered. (wait-for-screen-text #$marionette passphrase-prompt? #:ocr #$ocrad #:timeout 60) (marionette-control (string-append "screendump " #$output "/post-grub-passphrase.ppm") #$marionette) (marionette-type #$(string-append %luks-passphrase "\n") #$marionette) ;; It's hard to determine what to wait for here. ;; So just wait long enough. (sleep 40))) (test-assert "enter LUKS passphrase for the initrd" (begin ;; XXX: Here we use OCR as well but we could instead use QEMU ;; '-serial stdio' and run it in an input pipe, (wait-for-screen-text #$marionette passphrase-prompt? #:ocr #$ocrad #:timeout 60) (marionette-type #$(string-append %luks-passphrase "\n") #$marionette) ;; Take a screenshot for debugging purposes. (marionette-control (string-append "screendump " #$output "/post-initrd-passphrase.ppm") #$marionette)))))) (define (run-lvm-on-luks-test) "Run the basic test suite on an OS with LVM on LUKS." (define os (marionette-operating-system %lvm-on-luks-os #:imported-modules '((gnu services herd) (guix combinators)))) ;; Use the generic disk image builder with LVM-on-LUKS initializer (define image (build-disk-image os #:disk-initializer (make-lvm-on-luks-disk-initializer #:luks-passphrase %luks-passphrase #:luks-uuid %luks-uuid) #:disk-size (* 4096 1024 1024) ; 4 GiB (need more space for LUKS + LVM) #:uefi? #t #:name "lvm-on-luks-image")) (define vm-command #~(list (string-append #$qemu-minimal "/bin/" #$(qemu-command)) "-m" "1024" ; More memory for crypto operations "-bios" #$(file-append ovmf-x86-64 "/share/firmware/ovmf_x64.bin") "-drive" (string-append "file=" #$image ",format=qcow2,if=virtio") "-snapshot" ; Use snapshot mode since image is in read-only store "-no-reboot" #$@(if (file-exists? "/dev/kvm") '("-enable-kvm") '()))) (run-basic-test os vm-command "lvm-on-luks" #:initialization enter-luks-passphrase)) ;;; ;;; System test definition ;;; (define %test-lvm-on-luks (system-test (name "lvm-on-luks") (description "Test basic functionality of a Guix System with LVM on LUKS encryption.") (value (run-lvm-on-luks-test))))