~ruther/ruthless-guix

ref: 7e69ff0f45dea7ab8f2fdc0a5c033767fcc0169c ruthless-guix/modules/ruther/tests/lvm.scm -rw-r--r-- 9.2 KiB
7e69ff0f — Rutherther feat: add LVM on LUKS test 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
;;; 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))))