aboutsummaryrefslogtreecommitdiff
path: root/gnu/bootloader.scm
blob: d1c72c0c8540bad59587be3e4abc9328968701a6 (about) (plain)
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
    > 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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
    ;;; GNU Guix --- Functional package management for GNU
    ;;; Copyright © 2017 David Craven <david@craven.ch>
    ;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
    ;;; Copyright © 2017 Leo Famulari <leo@famulari.name>
    ;;; Copyright © 2019, 2021 Ludovic Courtès <ludo@gnu.org>
    ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
    ;;;
    ;;; This file is part of GNU Guix.
    ;;;
    ;;; GNU Guix is free software; you can redistribute it and/or modify it
    ;;; under the terms of the GNU General Public License as published by
    ;;; the Free Software Foundation; either version 3 of the License, or (at
    ;;; your option) any later version.
    ;;;
    ;;; GNU Guix is distributed in the hope that it will be useful, but
    ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
    ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ;;; GNU General Public License for more details.
    ;;;
    ;;; You should have received a copy of the GNU General Public License
    ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
    
    (define-module (gnu bootloader)
      #:use-module (guix discovery)
      #:use-module (guix gexp)
      #:use-module (guix profiles)
      #:use-module (guix records)
      #:use-module (guix deprecation)
      #:use-module ((guix ui) #:select (warn-about-load-error))
      #:use-module (guix diagnostics)
      #:use-module (guix i18n)
      #:use-module (srfi srfi-1)
      #:use-module (ice-9 match)
      #:export (menu-entry
                menu-entry?
                menu-entry-label
                menu-entry-device
                menu-entry-linux
                menu-entry-linux-arguments
                menu-entry-initrd
                menu-entry-device-mount-point
                menu-entry-multiboot-kernel
                menu-entry-multiboot-arguments
                menu-entry-multiboot-modules
    
                menu-entry->sexp
                sexp->menu-entry
    
                bootloader
                bootloader?
                bootloader-name
                bootloader-package
                bootloader-installer
                bootloader-disk-image-installer
                bootloader-configuration-file
                bootloader-configuration-file-generator
    
                bootloader-configuration
                bootloader-configuration?
                bootloader-configuration-bootloader
                bootloader-configuration-target ;deprecated
                bootloader-configuration-targets
                bootloader-configuration-menu-entries
                bootloader-configuration-default-entry
                bootloader-configuration-timeout
                bootloader-configuration-keyboard-layout
                bootloader-configuration-theme
                bootloader-configuration-terminal-outputs
                bootloader-configuration-terminal-inputs
                bootloader-configuration-serial-unit
                bootloader-configuration-serial-speed
                bootloader-configuration-additional-configuration
    
                %bootloaders
                lookup-bootloader-by-name
    
                efi-bootloader-chain))
    
    
    ;;;
    ;;; Menu-entry record.
    ;;;
    
    (define-record-type* <menu-entry>
      menu-entry make-menu-entry
      menu-entry?
      (label           menu-entry-label)
      (device          menu-entry-device       ; file system uuid, label, or #f
                       (default #f))
      (device-mount-point menu-entry-device-mount-point
                       (default #f))
      (linux           menu-entry-linux
                       (default #f))
      (linux-arguments menu-entry-linux-arguments
                       (default '()))          ; list of string-valued gexps
      (initrd          menu-entry-initrd       ; file name of the initrd as a gexp
                       (default #f))
      (multiboot-kernel menu-entry-multiboot-kernel
                        (default #f))
      (multiboot-arguments menu-entry-multiboot-arguments
                           (default '()))      ; list of string-valued gexps
      (multiboot-modules menu-entry-multiboot-modules
                         (default '())))       ; list of multiboot commands, where
                                               ; a command is a list of <string>
    
    (define (menu-entry->sexp entry)
      "Return ENTRY serialized as an sexp."
      (match entry
        (($ <menu-entry> label device mount-point linux linux-arguments initrd #f
                         ())
         `(menu-entry (version 0)
                      (label ,label)
                      (device ,device)
                      (device-mount-point ,mount-point)
                      (linux ,linux)
                      (linux-arguments ,linux-arguments)
                      (initrd ,initrd)))
        (($ <menu-entry> label device mount-point #f () #f
                         multiboot-kernel multiboot-arguments multiboot-modules)
         `(menu-entry (version 0)
                      (label ,label)
                      (device ,device)
                      (device-mount-point ,mount-point)
                      (multiboot-kernel ,multiboot-kernel)
                      (multiboot-arguments ,multiboot-arguments)
                      (multiboot-modules ,multiboot-modules)))))
    
    (define (sexp->menu-entry sexp)
      "Turn SEXP, an sexp as returned by 'menu-entry->sexp', into a <menu-entry>
    record."
      (match sexp
        (('menu-entry ('version 0)
                      ('label label) ('device device)
                      ('device-mount-point mount-point)
                      ('linux linux) ('linux-arguments linux-arguments)
                      ('initrd initrd) _ ...)
         (menu-entry
          (label label)
          (device device)
          (device-mount-point mount-point)
          (linux linux)
          (linux-arguments linux-arguments)
          (initrd initrd)))
        (('menu-entry ('version 0)
                      ('label label) ('device device)
                      ('device-mount-point mount-point)
                      ('multiboot-kernel multiboot-kernel)
                      ('multiboot-arguments multiboot-arguments)
                      ('multiboot-modules multiboot-modules) _ ...)
         (menu-entry
          (label label)
          (device device)
          (device-mount-point mount-point)
          (multiboot-kernel multiboot-kernel)
          (multiboot-arguments multiboot-arguments)
          (multiboot-modules multiboot-modules)))))
    
    
    ;;;
    ;;; Bootloader record.
    ;;;
    
    ;; The <bootloader> record contains fields expressing how the bootloader
    ;; should be installed. Every bootloader in gnu/bootloader/ directory
    ;; has to be described by this record.
    
    (define-record-type* <bootloader>
      bootloader make-bootloader
      bootloader?
      (name                            bootloader-name)
      (package                         bootloader-package)
      (installer                       bootloader-installer)
      (disk-image-installer            bootloader-disk-image-installer
                                       (default #f))
      (configuration-file              bootloader-configuration-file)
      (configuration-file-generator    bootloader-configuration-file-generator))
    
    
    ;;;
    ;;; Bootloader configuration record.
    ;;;
    
    ;; The <bootloader-configuration> record contains bootloader independant
    ;; configuration used to fill bootloader configuration file.
    
    (define-syntax-rule (warn-target-field-deprecation value)
      (%warn-target-field-deprecation value (current-source-location)))
    
    (define-record-type* <bootloader-configuration>
      bootloader-configuration make-bootloader-configuration
      bootloader-configuration?
      (bootloader         bootloader-configuration-bootloader) ;<bootloader>
      (targets            %bootloader-configuration-targets    ;list of strings
                          (default #f))
      (target             %bootloader-configuration-target ;deprecated
                          (default #f) (sanitize warn-target-field-deprecation))
      (menu-entries       bootloader-configuration-menu-entries ;list of <menu-entry>
                          (default '()))
      (default-entry      bootloader-configuration-default-entry ;integer
                          (default 0))
      (timeout            bootloader-configuration-timeout ;seconds as integer
                          (default 5))
      (keyboard-layout    bootloader-configuration-keyboard-layout ;<keyboard-layout> | #f
                          (default #f))
      (theme              bootloader-configuration-theme ;bootloader-specific theme
                          (default #f))
      (terminal-outputs   bootloader-configuration-terminal-outputs ;list of symbols
                          (default '(gfxterm)))
      (terminal-inputs    bootloader-configuration-terminal-inputs ;list of symbols
                          (default '()))
      (serial-unit        bootloader-configuration-serial-unit ;integer | #f
                          (default #f))
      (serial-speed       bootloader-configuration-serial-speed ;integer | #f
                          (default #f)))
    
    (define (%warn-target-field-deprecation value location)
      (when value
        (warning (source-properties->location location)
                 (G_ "the 'target' field is deprecated, please use 'targets' \
    instead~%")))
      value)
    
    (define-deprecated (bootloader-configuration-target config)
      bootloader-configuration-targets
      (%bootloader-configuration-target config))
    
    (define (bootloader-configuration-targets config)
      (or (%bootloader-configuration-targets config)
          ;; TODO: Remove after the deprecated 'target' field is removed.
          (list (%bootloader-configuration-target config))
          ;; XXX: At least the GRUB installer (see (gnu bootloader grub)) has this
          ;; peculiar behavior of installing fonts and GRUB modules when DEVICE is #f,
          ;; hence the default value of '(#f) rather than '().
          (list #f)))
    
    
    ;;;
    ;;; Bootloaders.
    ;;;
    
    (define (bootloader-modules)
      "Return the list of bootloader modules."
      (all-modules (map (lambda (entry)
                          `(,entry . "gnu/bootloader"))
                        %load-path)
                   #:warn warn-about-load-error))
    
    (define %bootloaders
      ;; The list of publically-known bootloaders.
      (delay (fold-module-public-variables (lambda (obj result)
                                             (if (bootloader? obj)
                                                 (cons obj result)
                                                 result))
                                           '()
                                           (bootloader-modules))))
    
    (define (lookup-bootloader-by-name name)
      "Return the bootloader called NAME."
      (or (find (lambda (bootloader)
                  (eq? name (bootloader-name bootloader)))
                (force %bootloaders))
          (leave (G_ "~a: no such bootloader~%") name)))
    
    (define (efi-bootloader-profile files bootloader-package hooks)
      "Creates a profile with BOOTLOADER-PACKAGE and a directory collection/ with
    links to additional FILES from the store.  This collection is meant to be used
    by the bootloader installer.
    
    FILES is a list of file or directory names from the store, which will be
    symlinked into the collection/ directory.  If a directory name ends with '/',
    then the directory content instead of the directory itself will be symlinked
    into the collection/ directory.
    
    FILES may contain file like objects produced by functions like plain-file,
    local-file, etc., or package contents produced with file-append.
    
    HOOKS lists additional hook functions to modify the profile."
      (define (bootloader-collection manifest)
        (define build
            (with-imported-modules '((guix build utils)
                                     (ice-9 ftw)
                                     (srfi srfi-1)
                                     (srfi srfi-26))
              #~(begin
                (use-modules ((guix build utils)
                              #:select (mkdir-p strip-store-file-name))
                             ((ice-9 ftw)
                              #:select (scandir))
                             ((srfi srfi-1)
                              #:select (append-map every remove))
                             ((srfi srfi-26)
                              #:select (cut)))
                (define (symlink-to file directory transform)
                  "Creates a symlink to FILE named (TRANSFORM FILE) in DIRECTORY."
                  (symlink file (string-append directory "/" (transform file))))
                (define (directory-content directory)
                  "Creates a list of absolute path names inside DIRECTORY."
                  (map (lambda (name)
                         (string-append directory name))
                       (or (scandir directory (lambda (name)
                                                (not (member name '("." "..")))))
                           '())))
                (define name-ends-with-/? (cut string-suffix? "/" <>))
                (define (name-is-store-entry? name)
                  "Return #t if NAME is a direct store entry and nothing inside."
                  (not (string-index (strip-store-file-name name) #\/)))
                (let* ((collection (string-append #$output "/collection"))
                       (files '#$files)
                       (directories (filter name-ends-with-/? files))
                       (names-from-directories
                        (append-map (lambda (directory)
                                      (directory-content directory))
                                    directories))
                       (names (append names-from-directories
                                      (remove name-ends-with-/? files))))
                  (mkdir-p collection)
                  (if (every file-exists? names)
                      (begin
                        (for-each (lambda (name)
                                   (symlink-to name collection
                                                (if (name-is-store-entry? name)
                                                    strip-store-file-name
                                                    basename)))
                                  names)
                        #t)
                      #f)))))
    
        (gexp->derivation "bootloader-collection"
                          build
                          #:local-build? #t
                          #:substitutable? #f
                          #:properties
                          `((type . profile-hook)
                            (hook . bootloader-collection))))
    
      (profile (content (packages->manifest (list bootloader-package)))
               (name "bootloader-profile")
               (hooks (append (list bootloader-collection) hooks))
               (locales? #f)
               (allow-collisions? #f)
               (relative-symlinks? #f)))
    
    (define* (efi-bootloader-chain files
                                   final-bootloader
                                   #:key
                                   (hooks '())
                                   installer)
      "Define a bootloader chain with FINAL-BOOTLOADER as the final bootloader and
    certain directories and files from the store given in the list of FILES.
    
    FILES may contain file like objects produced by functions like plain-file,
    local-file, etc., or package contents produced with file-append.  They will be
    collected inside a directory collection/ inside a generated bootloader profile,
    which will be passed to the INSTALLER.
    
    If a directory name in FILES ends with '/', then the directory content instead
    of the directory itself will be symlinked into the collection/ directory.
    
    The procedures in the HOOKS list can be used to further modify the bootloader
    profile.  It is possible to pass a single function instead of a list.
    
    If the INSTALLER argument is used, then this function will be called to install
    the bootloader.  Otherwise the installer of the FINAL-BOOTLOADER will be called."
      (let* ((final-installer (or installer
                                  (bootloader-installer final-bootloader)))
             (profile (efi-bootloader-profile files
                                              (bootloader-package final-bootloader)
                                              (if (list? hooks)
                                                  hooks
                                                  (list hooks)))))
        (bootloader
         (inherit final-bootloader)
         (package profile)
         (installer
          #~(lambda (bootloader target mount-point)
              (#$final-installer bootloader target mount-point)
              (copy-recursively
               (string-append bootloader "/collection")
               (string-append mount-point target)
               #:follow-symlinks? #t
               #:log (%make-void-port "w")))))))