gnu: python-deepmerge: Use pyproject-build-system.
[jackhill/guix/guix.git] / gnu / bootloader.scm
CommitLineData
b09a8da4
MO
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2017 David Craven <david@craven.ch>
93ed3497 3;;; Copyright © 2017, 2020, 2022 Mathieu Othacehe <othacehe@gnu.org>
b09a8da4 4;;; Copyright © 2017 Leo Famulari <leo@famulari.name>
baf4272d 5;;; Copyright © 2019, 2021 Ludovic Courtès <ludo@gnu.org>
21acd8d6 6;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
0811d2cb 7;;; Copyright © 2022 Josselin Poiret <dev@jpoiret.xyz>
f126f23b 8;;; Copyright © 2022 Reza Alizadeh Majd <r.majd@pantherx.org>
b09a8da4
MO
9;;;
10;;; This file is part of GNU Guix.
11;;;
12;;; GNU Guix is free software; you can redistribute it and/or modify it
13;;; under the terms of the GNU General Public License as published by
14;;; the Free Software Foundation; either version 3 of the License, or (at
15;;; your option) any later version.
16;;;
17;;; GNU Guix is distributed in the hope that it will be useful, but
18;;; WITHOUT ANY WARRANTY; without even the implied warranty of
19;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;;; GNU General Public License for more details.
21;;;
22;;; You should have received a copy of the GNU General Public License
23;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
24
25(define-module (gnu bootloader)
0811d2cb
JP
26 #:use-module (gnu system file-systems)
27 #:use-module (gnu system uuid)
b09a8da4 28 #:use-module (guix discovery)
74eeb11d
S
29 #:use-module (guix gexp)
30 #:use-module (guix profiles)
b09a8da4 31 #:use-module (guix records)
baf4272d
LC
32 #:use-module (guix deprecation)
33 #:use-module ((guix ui) #:select (warn-about-load-error))
34 #:use-module (guix diagnostics)
35 #:use-module (guix i18n)
b09a8da4 36 #:use-module (srfi srfi-1)
32da9bbc 37 #:use-module (srfi srfi-34)
38 #:use-module (srfi srfi-35)
a28cfee8 39 #:use-module (ice-9 match)
8b22107e
MO
40 #:export (menu-entry
41 menu-entry?
42 menu-entry-label
43 menu-entry-device
44 menu-entry-linux
45 menu-entry-linux-arguments
46 menu-entry-initrd
1975c754 47 menu-entry-device-mount-point
21acd8d6
JN
48 menu-entry-multiboot-kernel
49 menu-entry-multiboot-arguments
50 menu-entry-multiboot-modules
52d780ea 51 menu-entry-chain-loader
8b22107e 52
a28cfee8
LC
53 menu-entry->sexp
54 sexp->menu-entry
55
8b22107e 56 bootloader
b09a8da4
MO
57 bootloader?
58 bootloader-name
59 bootloader-package
60 bootloader-installer
7feefb3b 61 bootloader-disk-image-installer
b09a8da4
MO
62 bootloader-configuration-file
63 bootloader-configuration-file-generator
64
65 bootloader-configuration
66 bootloader-configuration?
67 bootloader-configuration-bootloader
2ca982ff
MC
68 bootloader-configuration-target ;deprecated
69 bootloader-configuration-targets
b09a8da4
MO
70 bootloader-configuration-menu-entries
71 bootloader-configuration-default-entry
72 bootloader-configuration-timeout
8d058e7b 73 bootloader-configuration-keyboard-layout
b09a8da4
MO
74 bootloader-configuration-theme
75 bootloader-configuration-terminal-outputs
76 bootloader-configuration-terminal-inputs
77 bootloader-configuration-serial-unit
78 bootloader-configuration-serial-speed
f126f23b 79 bootloader-configuration-device-tree-support?
b09a8da4
MO
80
81 %bootloaders
74eeb11d
S
82 lookup-bootloader-by-name
83
84 efi-bootloader-chain))
b09a8da4
MO
85
86\f
8b22107e
MO
87;;;
88;;; Menu-entry record.
89;;;
90
91(define-record-type* <menu-entry>
92 menu-entry make-menu-entry
93 menu-entry?
94 (label menu-entry-label)
95 (device menu-entry-device ; file system uuid, label, or #f
96 (default #f))
1975c754
DM
97 (device-mount-point menu-entry-device-mount-point
98 (default #f))
21acd8d6
JN
99 (linux menu-entry-linux
100 (default #f))
8b22107e
MO
101 (linux-arguments menu-entry-linux-arguments
102 (default '())) ; list of string-valued gexps
21acd8d6
JN
103 (initrd menu-entry-initrd ; file name of the initrd as a gexp
104 (default #f))
105 (multiboot-kernel menu-entry-multiboot-kernel
106 (default #f))
107 (multiboot-arguments menu-entry-multiboot-arguments
108 (default '())) ; list of string-valued gexps
109 (multiboot-modules menu-entry-multiboot-modules
52d780ea 110 (default '())) ; list of multiboot commands, where
21acd8d6 111 ; a command is a list of <string>
52d780ea 112 (chain-loader menu-entry-chain-loader
113 (default #f))) ; string, path of efi file
8b22107e 114
32da9bbc 115(define (report-menu-entry-error menu-entry)
116 (raise
117 (condition
118 (&message
119 (message
120 (format #f (G_ "invalid menu-entry: ~a") menu-entry)))
121 (&fix-hint
122 (hint
123 (G_ "Please chose only one of:
124@enumerate
125@item direct boot by specifying fields @code{linux},
126@code{linux-arguments} and @code{linux-modules},
127@item multiboot by specifying fields @code{multiboot-kernel},
128@code{multiboot-arguments} and @code{multiboot-modules},
129@item chain-loader by specifying field @code{chain-loader}.
130@end enumerate"))))))
131
a28cfee8
LC
132(define (menu-entry->sexp entry)
133 "Return ENTRY serialized as an sexp."
0811d2cb
JP
134 (define (device->sexp device)
135 (match device
136 ((? uuid? uuid)
137 `(uuid ,(uuid-type uuid) ,(uuid->string uuid)))
138 ((? file-system-label? label)
139 `(label ,(file-system-label->string label)))
140 (_ device)))
a28cfee8 141 (match entry
52d780ea 142 (($ <menu-entry> label device mount-point
143 (? identity linux) linux-arguments (? identity initrd)
144 #f () () #f)
a28cfee8
LC
145 `(menu-entry (version 0)
146 (label ,label)
0811d2cb 147 (device ,(device->sexp device))
a28cfee8
LC
148 (device-mount-point ,mount-point)
149 (linux ,linux)
150 (linux-arguments ,linux-arguments)
21acd8d6
JN
151 (initrd ,initrd)))
152 (($ <menu-entry> label device mount-point #f () #f
52d780ea 153 (? identity multiboot-kernel) multiboot-arguments
154 multiboot-modules #f)
21acd8d6
JN
155 `(menu-entry (version 0)
156 (label ,label)
0811d2cb 157 (device ,(device->sexp device))
21acd8d6
JN
158 (device-mount-point ,mount-point)
159 (multiboot-kernel ,multiboot-kernel)
160 (multiboot-arguments ,multiboot-arguments)
52d780ea 161 (multiboot-modules ,multiboot-modules)))
162 (($ <menu-entry> label device mount-point #f () #f #f () ()
163 (? identity chain-loader))
164 `(menu-entry (version 0)
165 (label ,label)
166 (device ,(device->sexp device))
167 (device-mount-point ,mount-point)
32da9bbc 168 (chain-loader ,chain-loader)))
169 (_ (report-menu-entry-error entry))))
a28cfee8
LC
170
171(define (sexp->menu-entry sexp)
172 "Turn SEXP, an sexp as returned by 'menu-entry->sexp', into a <menu-entry>
173record."
0811d2cb
JP
174 (define (sexp->device device-sexp)
175 (match device-sexp
176 (('uuid type uuid-string)
177 (uuid uuid-string type))
178 (('label label)
179 (file-system-label label))
180 (_ device-sexp)))
a28cfee8
LC
181 (match sexp
182 (('menu-entry ('version 0)
183 ('label label) ('device device)
184 ('device-mount-point mount-point)
185 ('linux linux) ('linux-arguments linux-arguments)
186 ('initrd initrd) _ ...)
187 (menu-entry
188 (label label)
0811d2cb 189 (device (sexp->device device))
a28cfee8
LC
190 (device-mount-point mount-point)
191 (linux linux)
192 (linux-arguments linux-arguments)
21acd8d6
JN
193 (initrd initrd)))
194 (('menu-entry ('version 0)
195 ('label label) ('device device)
196 ('device-mount-point mount-point)
197 ('multiboot-kernel multiboot-kernel)
198 ('multiboot-arguments multiboot-arguments)
199 ('multiboot-modules multiboot-modules) _ ...)
200 (menu-entry
201 (label label)
0811d2cb 202 (device (sexp->device device))
21acd8d6
JN
203 (device-mount-point mount-point)
204 (multiboot-kernel multiboot-kernel)
205 (multiboot-arguments multiboot-arguments)
52d780ea 206 (multiboot-modules multiboot-modules)))
207 (('menu-entry ('version 0)
208 ('label label) ('device device)
209 ('device-mount-point mount-point)
210 ('chain-loader chain-loader) _ ...)
211 (menu-entry
212 (label label)
213 (device (sexp->device device))
214 (device-mount-point mount-point)
215 (chain-loader chain-loader)))))
a28cfee8 216
8b22107e 217\f
b09a8da4
MO
218;;;
219;;; Bootloader record.
220;;;
221
222;; The <bootloader> record contains fields expressing how the bootloader
223;; should be installed. Every bootloader in gnu/bootloader/ directory
224;; has to be described by this record.
225
226(define-record-type* <bootloader>
227 bootloader make-bootloader
228 bootloader?
229 (name bootloader-name)
230 (package bootloader-package)
231 (installer bootloader-installer)
7feefb3b
MO
232 (disk-image-installer bootloader-disk-image-installer
233 (default #f))
b09a8da4
MO
234 (configuration-file bootloader-configuration-file)
235 (configuration-file-generator bootloader-configuration-file-generator))
236
237\f
238;;;
239;;; Bootloader configuration record.
240;;;
241
242;; The <bootloader-configuration> record contains bootloader independant
243;; configuration used to fill bootloader configuration file.
244
7c414262
JP
245(define-with-syntax-properties (warn-target-field-deprecation
246 (value properties))
247 (when value
248 (warning (source-properties->location properties)
249 (G_ "the 'target' field is deprecated, please use 'targets' \
250instead~%")))
251 value)
baf4272d 252
b09a8da4
MO
253(define-record-type* <bootloader-configuration>
254 bootloader-configuration make-bootloader-configuration
255 bootloader-configuration?
93ed3497
MO
256 (bootloader
257 bootloader-configuration-bootloader) ;<bootloader>
258 (targets %bootloader-configuration-targets
259 (default #f)) ;list of strings
260 (target %bootloader-configuration-target ;deprecated
261 (default #f)
262 (sanitize warn-target-field-deprecation))
263 (menu-entries bootloader-configuration-menu-entries
264 (default '())) ;list of <menu-entry>
265 (default-entry bootloader-configuration-default-entry
266 (default 0)) ;integer
267 (timeout bootloader-configuration-timeout
268 (default 5)) ;seconds as integer
269 (keyboard-layout bootloader-configuration-keyboard-layout
270 (default #f)) ;<keyboard-layout> | #f
271 (theme bootloader-configuration-theme
272 (default #f)) ;bootloader-specific theme
273 (terminal-outputs bootloader-configuration-terminal-outputs
274 (default '(gfxterm))) ;list of symbols
275 (terminal-inputs bootloader-configuration-terminal-inputs
276 (default '())) ;list of symbols
277 (serial-unit bootloader-configuration-serial-unit
278 (default #f)) ;integer | #f
279 (serial-speed bootloader-configuration-serial-speed
280 (default #f)) ;integer | #f
281 (device-tree-support? bootloader-configuration-device-tree-support?
282 (default #t))) ;boolean
b09a8da4 283
baf4272d
LC
284(define-deprecated (bootloader-configuration-target config)
285 bootloader-configuration-targets
2ca982ff
MC
286 (%bootloader-configuration-target config))
287
288(define (bootloader-configuration-targets config)
289 (or (%bootloader-configuration-targets config)
290 ;; TODO: Remove after the deprecated 'target' field is removed.
baf4272d 291 (list (%bootloader-configuration-target config))
2ca982ff
MC
292 ;; XXX: At least the GRUB installer (see (gnu bootloader grub)) has this
293 ;; peculiar behavior of installing fonts and GRUB modules when DEVICE is #f,
294 ;; hence the default value of '(#f) rather than '().
295 (list #f)))
296
b09a8da4
MO
297\f
298;;;
299;;; Bootloaders.
300;;;
301
302(define (bootloader-modules)
303 "Return the list of bootloader modules."
304 (all-modules (map (lambda (entry)
305 `(,entry . "gnu/bootloader"))
3c0128b0
LC
306 %load-path)
307 #:warn warn-about-load-error))
b09a8da4
MO
308
309(define %bootloaders
310 ;; The list of publically-known bootloaders.
311 (delay (fold-module-public-variables (lambda (obj result)
312 (if (bootloader? obj)
313 (cons obj result)
314 result))
315 '()
316 (bootloader-modules))))
317
318(define (lookup-bootloader-by-name name)
319 "Return the bootloader called NAME."
320 (or (find (lambda (bootloader)
321 (eq? name (bootloader-name bootloader)))
322 (force %bootloaders))
323 (leave (G_ "~a: no such bootloader~%") name)))
74eeb11d 324
ede4117f 325(define (efi-bootloader-profile files bootloader-package hooks)
74eeb11d
S
326 "Creates a profile with BOOTLOADER-PACKAGE and a directory collection/ with
327links to additional FILES from the store. This collection is meant to be used
328by the bootloader installer.
329
330FILES is a list of file or directory names from the store, which will be
331symlinked into the collection/ directory. If a directory name ends with '/',
332then the directory content instead of the directory itself will be symlinked
333into the collection/ directory.
334
335FILES may contain file like objects produced by functions like plain-file,
ede4117f
S
336local-file, etc., or package contents produced with file-append.
337
338HOOKS lists additional hook functions to modify the profile."
74eeb11d
S
339 (define (bootloader-collection manifest)
340 (define build
341 (with-imported-modules '((guix build utils)
342 (ice-9 ftw)
343 (srfi srfi-1)
344 (srfi srfi-26))
345 #~(begin
346 (use-modules ((guix build utils)
347 #:select (mkdir-p strip-store-file-name))
348 ((ice-9 ftw)
349 #:select (scandir))
350 ((srfi srfi-1)
351 #:select (append-map every remove))
352 ((srfi srfi-26)
353 #:select (cut)))
354 (define (symlink-to file directory transform)
355 "Creates a symlink to FILE named (TRANSFORM FILE) in DIRECTORY."
356 (symlink file (string-append directory "/" (transform file))))
357 (define (directory-content directory)
358 "Creates a list of absolute path names inside DIRECTORY."
359 (map (lambda (name)
360 (string-append directory name))
361 (or (scandir directory (lambda (name)
362 (not (member name '("." "..")))))
363 '())))
364 (define name-ends-with-/? (cut string-suffix? "/" <>))
365 (define (name-is-store-entry? name)
366 "Return #t if NAME is a direct store entry and nothing inside."
367 (not (string-index (strip-store-file-name name) #\/)))
368 (let* ((collection (string-append #$output "/collection"))
369 (files '#$files)
370 (directories (filter name-ends-with-/? files))
371 (names-from-directories
372 (append-map (lambda (directory)
373 (directory-content directory))
374 directories))
375 (names (append names-from-directories
376 (remove name-ends-with-/? files))))
377 (mkdir-p collection)
378 (if (every file-exists? names)
379 (begin
380 (for-each (lambda (name)
381 (symlink-to name collection
382 (if (name-is-store-entry? name)
383 strip-store-file-name
384 basename)))
385 names)
386 #t)
387 #f)))))
388
389 (gexp->derivation "bootloader-collection"
390 build
391 #:local-build? #t
392 #:substitutable? #f
393 #:properties
394 `((type . profile-hook)
395 (hook . bootloader-collection))))
396
397 (profile (content (packages->manifest (list bootloader-package)))
ede4117f
S
398 (name "bootloader-profile")
399 (hooks (append (list bootloader-collection) hooks))
74eeb11d
S
400 (locales? #f)
401 (allow-collisions? #f)
402 (relative-symlinks? #f)))
403
404(define* (efi-bootloader-chain files
405 final-bootloader
406 #:key
ede4117f 407 (hooks '())
74eeb11d
S
408 installer)
409 "Define a bootloader chain with FINAL-BOOTLOADER as the final bootloader and
410certain directories and files from the store given in the list of FILES.
411
412FILES may contain file like objects produced by functions like plain-file,
413local-file, etc., or package contents produced with file-append. They will be
414collected inside a directory collection/ inside a generated bootloader profile,
415which will be passed to the INSTALLER.
416
417If a directory name in FILES ends with '/', then the directory content instead
418of the directory itself will be symlinked into the collection/ directory.
419
ede4117f
S
420The procedures in the HOOKS list can be used to further modify the bootloader
421profile. It is possible to pass a single function instead of a list.
74eeb11d
S
422
423If the INSTALLER argument is used, then this function will be called to install
ede4117f 424the bootloader. Otherwise the installer of the FINAL-BOOTLOADER will be called."
74eeb11d
S
425 (let* ((final-installer (or installer
426 (bootloader-installer final-bootloader)))
427 (profile (efi-bootloader-profile files
428 (bootloader-package final-bootloader)
ede4117f
S
429 (if (list? hooks)
430 hooks
431 (list hooks)))))
74eeb11d
S
432 (bootloader
433 (inherit final-bootloader)
434 (package profile)
435 (installer
436 #~(lambda (bootloader target mount-point)
437 (#$final-installer bootloader target mount-point)
438 (copy-recursively
439 (string-append bootloader "/collection")
440 (string-append mount-point target)
441 #:follow-symlinks? #t
442 #:log (%make-void-port "w")))))))