grub.scm 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
  4. ;;; Copyright © 2017 Leo Famulari <leo@famulari.name>
  5. ;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
  6. ;;; Copyright © 2019, 2020, 2023 Janneke Nieuwenhuizen <janneke@gnu.org>
  7. ;;; Copyright © 2019, 2020 Miguel Ángel Arruga Vivas <rosen644835@gmail.com>
  8. ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  9. ;;; Copyright © 2020 Stefan <stefan-guix@vodafonemail.de>
  10. ;;; Copyright © 2022 Karl Hallsby <karl@hallsby.com>
  11. ;;; Copyright © 2022 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
  12. ;;;
  13. ;;; This file is part of GNU Guix.
  14. ;;;
  15. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  16. ;;; under the terms of the GNU General Public License as published by
  17. ;;; the Free Software Foundation; either version 3 of the License, or (at
  18. ;;; your option) any later version.
  19. ;;;
  20. ;;; GNU Guix is distributed in the hope that it will be useful, but
  21. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  22. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. ;;; GNU General Public License for more details.
  24. ;;;
  25. ;;; You should have received a copy of the GNU General Public License
  26. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  27. (define-module (gnu bootloader grub)
  28. #:use-module (guix build union)
  29. #:use-module (guix records)
  30. #:use-module (guix store)
  31. #:use-module (guix utils)
  32. #:use-module (guix gexp)
  33. #:use-module (gnu artwork)
  34. #:use-module (gnu bootloader)
  35. #:use-module (gnu system uuid)
  36. #:use-module (gnu system file-systems)
  37. #:use-module (gnu system keyboard)
  38. #:use-module (gnu system locale)
  39. #:use-module (gnu packages bootloaders)
  40. #:autoload (gnu packages gtk) (guile-cairo guile-rsvg)
  41. #:autoload (gnu packages xorg) (xkeyboard-config)
  42. #:use-module (ice-9 match)
  43. #:use-module (ice-9 regex)
  44. #:use-module (srfi srfi-1)
  45. #:use-module (srfi srfi-2)
  46. #:export (grub-theme
  47. grub-theme?
  48. grub-theme-image
  49. grub-theme-resolution
  50. grub-theme-color-normal
  51. grub-theme-color-highlight
  52. grub-theme-gfxmode
  53. install-grub-efi-removable
  54. make-grub-efi-netboot-installer
  55. grub-bootloader
  56. grub-efi-bootloader
  57. grub-efi-removable-bootloader
  58. grub-efi32-bootloader
  59. grub-efi-netboot-bootloader
  60. grub-efi-netboot-removable-bootloader
  61. grub-mkrescue-bootloader
  62. grub-minimal-bootloader
  63. grub-configuration))
  64. ;;; Commentary:
  65. ;;;
  66. ;;; Configuration of GNU GRUB.
  67. ;;;
  68. ;;; Code:
  69. (define* (normalize-file file mount-point store-directory-prefix)
  70. "Strip MOUNT-POINT and prepend STORE-DIRECTORY-PREFIX, if any, to FILE, a
  71. G-expression or other lowerable object denoting a file name."
  72. (define (strip-mount-point mount-point file)
  73. (if mount-point
  74. (if (string=? mount-point "/")
  75. file
  76. #~(let ((file #$file))
  77. (if (string-prefix? #$mount-point file)
  78. (substring #$file #$(string-length mount-point))
  79. file)))
  80. file))
  81. (define (prepend-store-directory-prefix store-directory-prefix file)
  82. (if store-directory-prefix
  83. #~(string-append #$store-directory-prefix #$file)
  84. file))
  85. (prepend-store-directory-prefix store-directory-prefix
  86. (strip-mount-point mount-point file)))
  87. (define-record-type* <grub-theme>
  88. ;; Default theme contributed by Felipe López.
  89. grub-theme make-grub-theme
  90. grub-theme?
  91. (image grub-theme-image
  92. (default (file-append %artwork-repository
  93. "/grub/GuixSD-fully-black-4-3.svg")))
  94. (resolution grub-theme-resolution
  95. (default '(1024 . 768)))
  96. (color-normal grub-theme-color-normal
  97. (default '((fg . light-gray) (bg . black))))
  98. (color-highlight grub-theme-color-highlight
  99. (default '((fg . yellow) (bg . black))))
  100. (gfxmode grub-theme-gfxmode
  101. (default '("auto")))) ;list of string
  102. ;;;
  103. ;;; Background image & themes.
  104. ;;;
  105. (define (bootloader-theme config)
  106. "Return user defined theme in CONFIG if defined or a default theme
  107. otherwise."
  108. (or (bootloader-configuration-theme config) (grub-theme)))
  109. (define* (image->png image #:key width height)
  110. "Build a PNG of HEIGHT x WIDTH from IMAGE if its file suffix is \".svg\".
  111. Otherwise the picture in IMAGE is just copied."
  112. (computed-file "grub-image.png"
  113. (with-imported-modules '((gnu build svg))
  114. (with-extensions (list guile-rsvg guile-cairo)
  115. #~(if (string-suffix? ".svg" #+image)
  116. (begin
  117. (use-modules (gnu build svg))
  118. (svg->png #+image #$output
  119. #:width #$width
  120. #:height #$height))
  121. (copy-file #+image #$output))))))
  122. (define* (grub-background-image config)
  123. "Return the GRUB background image defined in CONFIG or #f if none was found.
  124. If the suffix of the image file is \".svg\", then it is converted into a PNG
  125. file with the resolution provided in CONFIG."
  126. (let* ((theme (bootloader-theme config))
  127. (image (grub-theme-image theme)))
  128. (and image
  129. (match (grub-theme-resolution theme)
  130. (((? number? width) . (? number? height))
  131. (image->png image #:width width #:height height))
  132. (_ #f)))))
  133. (define (grub-locale-directory grub)
  134. "Generate a directory with the locales from GRUB."
  135. (define builder
  136. #~(begin
  137. (use-modules (ice-9 ftw))
  138. (let ((locale (string-append #$grub "/share/locale"))
  139. (out #$output))
  140. (mkdir out)
  141. (chdir out)
  142. (for-each (lambda (lang)
  143. (let ((file (string-append locale "/" lang
  144. "/LC_MESSAGES/grub.mo"))
  145. (dest (string-append lang ".mo")))
  146. (when (file-exists? file)
  147. (copy-file file dest))))
  148. (scandir locale)))))
  149. (computed-file "grub-locales" builder))
  150. (define* (eye-candy config store-device store-mount-point
  151. #:key store-directory-prefix port)
  152. "Return a gexp that writes to PORT (a port-valued gexp) the 'grub.cfg' part
  153. concerned with graphics mode, background images, colors, and all that.
  154. STORE-DEVICE designates the device holding the store, and STORE-MOUNT-POINT is
  155. its mount point; these are used to determine where the background image and
  156. fonts must be searched for. STORE-DIRECTORY-PREFIX is a directory prefix to
  157. prepend to any store file name."
  158. (define (setup-gfxterm config)
  159. (if (memq 'gfxterm (bootloader-configuration-terminal-outputs config))
  160. #~(format #f "
  161. if loadfont unicode; then
  162. set gfxmode=~a
  163. insmod all_video
  164. insmod gfxterm
  165. fi~%"
  166. #$(string-join
  167. (grub-theme-gfxmode (bootloader-theme config))
  168. ";"))
  169. ""))
  170. (define (theme-colors type)
  171. (let* ((theme (bootloader-theme config))
  172. (colors (type theme)))
  173. (string-append (symbol->string (assoc-ref colors 'fg)) "/"
  174. (symbol->string (assoc-ref colors 'bg)))))
  175. (define image
  176. (normalize-file (grub-background-image config)
  177. store-mount-point
  178. store-directory-prefix))
  179. (and image
  180. #~(format #$port "
  181. # Set 'root' to the partition that contains /gnu/store.
  182. ~a
  183. ~a
  184. ~a
  185. insmod png
  186. if background_image ~a; then
  187. set color_normal=~a
  188. set color_highlight=~a
  189. else
  190. set menu_color_normal=cyan/blue
  191. set menu_color_highlight=white/blue
  192. fi~%"
  193. #$(grub-root-search store-device image)
  194. #$(setup-gfxterm config)
  195. #$(grub-setup-io config)
  196. #$image
  197. #$(theme-colors grub-theme-color-normal)
  198. #$(theme-colors grub-theme-color-highlight))))
  199. ;;;
  200. ;;; Configuration file.
  201. ;;;
  202. (define* (keyboard-layout-file layout
  203. #:key
  204. (grub grub))
  205. "Process the X keyboard layout description LAYOUT, a <keyboard-layout> record,
  206. and return a file in the format for GRUB keymaps. LAYOUT must be present in
  207. the 'share/X11/xkb/symbols/' directory of 'xkeyboard-config'."
  208. (define builder
  209. (with-imported-modules '((guix build utils))
  210. #~(begin
  211. (use-modules (guix build utils))
  212. ;; 'grub-kbdcomp' passes all its arguments but '-o' to 'ckbcomp'
  213. ;; (from the 'console-setup' package).
  214. (invoke #+(file-append grub "/bin/grub-mklayout")
  215. "-i" #+(keyboard-layout->console-keymap layout)
  216. "-o" #$output))))
  217. (computed-file (string-append "grub-keymap."
  218. (string-map (match-lambda
  219. (#\, #\-)
  220. (chr chr))
  221. (keyboard-layout-name layout)))
  222. builder))
  223. (define (grub-setup-io config)
  224. "Return GRUB commands to configure the input / output interfaces. The result
  225. is a string that can be inserted in grub.cfg."
  226. (let* ((symbols->string (lambda (list)
  227. (string-join (map symbol->string list) " ")))
  228. (outputs (bootloader-configuration-terminal-outputs config))
  229. (inputs (bootloader-configuration-terminal-inputs config))
  230. (unit (bootloader-configuration-serial-unit config))
  231. (speed (bootloader-configuration-serial-speed config))
  232. ;; Respectively, GRUB_TERMINAL_OUTPUT and GRUB_TERMINAL_INPUT,
  233. ;; as documented in GRUB manual section "Simple Configuration
  234. ;; Handling".
  235. (valid-outputs '(console serial serial_0 serial_1 serial_2 serial_3
  236. gfxterm vga_text mda_text morse spkmodem))
  237. (valid-inputs '(console serial serial_0 serial_1 serial_2 serial_3
  238. at_keyboard usb_keyboard))
  239. (io (string-append
  240. ;; UNIT and SPEED are arguments to the same GRUB command
  241. ;; ("serial"), so we process them together.
  242. (if (or unit speed)
  243. (string-append
  244. "serial"
  245. (if unit
  246. ;; COM ports 1 through 4
  247. (if (and (exact-integer? unit) (<= unit 3) (>= unit 0))
  248. (string-append " --unit=" (number->string unit))
  249. #f)
  250. "")
  251. (if speed
  252. (if (exact-integer? speed)
  253. (string-append " --speed=" (number->string speed))
  254. #f)
  255. "")
  256. "\n")
  257. "")
  258. (if (null? inputs)
  259. ""
  260. (string-append
  261. "terminal_input "
  262. (symbols->string
  263. (map
  264. (lambda (input)
  265. (if (memq input valid-inputs) input #f)) inputs))
  266. "\n"))
  267. "terminal_output "
  268. (symbols->string
  269. (map
  270. (lambda (output)
  271. (if (memq output valid-outputs) output #f)) outputs)))))
  272. (format #f "~a" io)))
  273. (define (grub-root-search device file)
  274. "Return the GRUB 'search' command to look for DEVICE, which contains FILE,
  275. a gexp. The result is a gexp that can be inserted in the grub.cfg-generation
  276. code."
  277. ;; Usually FILE is a file name gexp like "/gnu/store/…-linux/vmlinuz", but
  278. ;; it can also be something like "(hd0,msdos1)/vmlinuz" in the case of
  279. ;; custom menu entries. In the latter case, don't emit a 'search' command.
  280. (if (and (string? file) (not (string-prefix? "/" file)))
  281. ""
  282. (match device
  283. ;; Preferably refer to DEVICE by its UUID or label. This is more
  284. ;; efficient and less ambiguous, see <http://bugs.gnu.org/22281>.
  285. ((? uuid? uuid)
  286. (format #f "search --fs-uuid --set ~a"
  287. (uuid->string device)))
  288. ((? file-system-label? label)
  289. (format #f "search --label --set ~a"
  290. (file-system-label->string label)))
  291. ((? (lambda (device)
  292. (and (string? device) (string-contains device ":/"))) nfs-uri)
  293. ;; If the device is an NFS share, then we assume that the expected
  294. ;; file on that device (e.g. the GRUB background image or the kernel)
  295. ;; has to be loaded over the network. Otherwise we would need an
  296. ;; additional device information for some local disk to look for that
  297. ;; file, which we do not have.
  298. ;;
  299. ;; We explicitly set "root=(tftp)" here even though if grub.cfg
  300. ;; had been loaded via TFTP, Grub would have set "root=(tftp)"
  301. ;; automatically anyway. The reason is if you have a system that
  302. ;; used to be on NFS but now is local, root would be set to local
  303. ;; disk. If you then selected an older system generation that is
  304. ;; supposed to boot from network in the Grub boot menu, Grub still
  305. ;; wouldn't load those files from network otherwise.
  306. ;;
  307. ;; TFTP is preferred to HTTP because it is used more widely and
  308. ;; specified in standards more widely--especially BOOTP/DHCPv4
  309. ;; defines a TFTP server for DHCP option 66, but not HTTP.
  310. ;;
  311. ;; Note: DHCPv6 specifies option 59 to contain a boot-file-url,
  312. ;; which can contain a HTTP or TFTP URL.
  313. ;;
  314. ;; Note: It is assumed that the file paths are of a similar
  315. ;; setup on both the TFTP server and the NFS server (it is
  316. ;; not possible to search for files on TFTP).
  317. ;;
  318. ;; TODO: Allow HTTP.
  319. "set root=(tftp)")
  320. ((or #f (? string?))
  321. #~(format #f "search --file --set ~a" #$file)))))
  322. (define* (make-grub-configuration grub config entries
  323. #:key
  324. (locale #f)
  325. (system (%current-system))
  326. (old-entries '())
  327. (store-crypto-devices '())
  328. store-directory-prefix)
  329. "Return the GRUB configuration file corresponding to CONFIG, a
  330. <bootloader-configuration> object, and where the store is available at
  331. STORE-FS, a <file-system> object. OLD-ENTRIES is taken to be a list of menu
  332. entries corresponding to old generations of the system.
  333. STORE-CRYPTO-DEVICES contain the UUIDs of the encrypted units that must
  334. be unlocked to access the store contents.
  335. STORE-DIRECTORY-PREFIX may be used to specify a store prefix, as is required
  336. when booting a root file system on a Btrfs subvolume."
  337. (define all-entries
  338. (append entries (bootloader-configuration-menu-entries config)))
  339. (define (menu-entry->gexp entry)
  340. (let ((label (menu-entry-label entry))
  341. (linux (menu-entry-linux entry))
  342. (device (menu-entry-device entry))
  343. (device-mount-point (menu-entry-device-mount-point entry))
  344. (multiboot-kernel (menu-entry-multiboot-kernel entry))
  345. (chain-loader (menu-entry-chain-loader entry)))
  346. (cond
  347. (linux
  348. (let ((arguments (menu-entry-linux-arguments entry))
  349. (linux (normalize-file linux
  350. device-mount-point
  351. store-directory-prefix))
  352. (initrd (normalize-file (menu-entry-initrd entry)
  353. device-mount-point
  354. store-directory-prefix)))
  355. ;; Here DEVICE is the store and DEVICE-MOUNT-POINT is its mount point.
  356. ;; Use the right file names for LINUX and INITRD in case
  357. ;; DEVICE-MOUNT-POINT is not "/", meaning that the store is on a
  358. ;; separate partition.
  359. ;; When STORE-DIRECTORY-PREFIX is defined, prepend it the linux and
  360. ;; initrd paths, to allow booting from a Btrfs subvolume.
  361. #~(format port "menuentry ~s {
  362. ~a
  363. linux ~a ~a
  364. initrd ~a
  365. }~%"
  366. #$label
  367. #$(grub-root-search device linux)
  368. #$linux (string-join (list #$@arguments))
  369. #$initrd)))
  370. (multiboot-kernel
  371. (let* ((kernel (menu-entry-multiboot-kernel entry))
  372. (arguments (menu-entry-multiboot-arguments entry))
  373. ;; Choose between device names as understood by Mach's built-in
  374. ;; IDE driver ("hdX") and those understood by rumpdisk ("wdX"
  375. ;; in the "noide" case).
  376. (disk (if (member "noide" arguments) "w" "h"))
  377. (modules (menu-entry-multiboot-modules entry))
  378. (root-index 1)) ; XXX EFI will need root-index 2
  379. #~(format port "
  380. menuentry ~s {
  381. multiboot ~a root=part:~a:device:~ad0~a~a
  382. }~%"
  383. #$label
  384. #$kernel
  385. #$root-index
  386. #$disk
  387. (string-join (list #$@arguments) " " 'prefix)
  388. (string-join (map string-join '#$modules)
  389. "\n module " 'prefix))))
  390. (chain-loader
  391. #~(format port "
  392. menuentry ~s {
  393. ~a
  394. chainloader ~a
  395. }~%"
  396. #$label
  397. #$(grub-root-search device chain-loader)
  398. #$chain-loader)))))
  399. (define (crypto-devices)
  400. (define (crypto-device->cryptomount dev)
  401. (if (uuid? dev)
  402. #~(format port "cryptomount -u ~a~%"
  403. ;; cryptomount only accepts UUID without the hypen.
  404. #$(string-delete #\- (uuid->string dev)))
  405. ;; Other type of devices aren't implemented.
  406. #~()))
  407. (let ((devices (map crypto-device->cryptomount store-crypto-devices))
  408. (modules #~(format port "insmod luks~%insmod luks2~%")))
  409. (if (null? devices)
  410. devices
  411. (cons modules devices))))
  412. (define (sugar)
  413. (let* ((entry (first all-entries))
  414. (device (menu-entry-device entry))
  415. (mount-point (menu-entry-device-mount-point entry)))
  416. (eye-candy config
  417. device
  418. mount-point
  419. #:store-directory-prefix store-directory-prefix
  420. #:port #~port)))
  421. (define locale-config
  422. (let* ((entry (first all-entries))
  423. (device (menu-entry-device entry))
  424. (mount-point (menu-entry-device-mount-point entry)))
  425. #~(let ((locale #$(and locale
  426. (locale-definition-source
  427. (locale-name->definition locale))))
  428. (locales #$(and locale
  429. (normalize-file (grub-locale-directory grub)
  430. mount-point
  431. store-directory-prefix))))
  432. (when locale
  433. (format port "\
  434. # Localization configuration.
  435. ~asearch --file --set ~a/en@quot.mo
  436. set locale_dir=~a
  437. set lang=~a~%"
  438. ;; Skip the search if there is an image, as it has already
  439. ;; been performed by eye-candy and traversing the store is
  440. ;; an expensive operation.
  441. #$(if (grub-theme-image (bootloader-theme config))
  442. "# "
  443. "")
  444. locales
  445. locales
  446. locale)))))
  447. (define keyboard-layout-config
  448. (let* ((layout (bootloader-configuration-keyboard-layout config))
  449. (keymap* (and layout
  450. (keyboard-layout-file layout #:grub grub)))
  451. (entry (first all-entries))
  452. (device (menu-entry-device entry))
  453. (mount-point (menu-entry-device-mount-point entry))
  454. (keymap (and keymap*
  455. (normalize-file keymap* mount-point
  456. store-directory-prefix))))
  457. #~(when #$keymap
  458. (format port "\
  459. insmod keylayouts
  460. keymap ~a~%" #$keymap))))
  461. (define builder
  462. #~(call-with-output-file #$output
  463. (lambda (port)
  464. (format port
  465. "# This file was generated from your Guix configuration. Any changes
  466. # will be lost upon reconfiguration.
  467. ")
  468. #$@(crypto-devices)
  469. #$(sugar)
  470. #$locale-config
  471. #$keyboard-layout-config
  472. (format port "
  473. set default=~a
  474. set timeout=~a~%"
  475. #$(bootloader-configuration-default-entry config)
  476. #$(bootloader-configuration-timeout config))
  477. #$@(map menu-entry->gexp all-entries)
  478. #$@(if (pair? old-entries)
  479. #~((format port "
  480. submenu \"GNU system, old configurations...\" {~%")
  481. #$@(map menu-entry->gexp old-entries)
  482. (format port "}~%"))
  483. #~())
  484. (format port "
  485. if [ \"${grub_platform}\" == efi ]; then
  486. menuentry \"Firmware setup\" {
  487. fwsetup
  488. }
  489. fi~%"))))
  490. ;; Since this file is rather unique, there's no point in trying to
  491. ;; substitute it.
  492. (computed-file "grub.cfg" builder
  493. #:options '(#:local-build? #t
  494. #:substitutable? #f)))
  495. (define (grub-configuration-file config . args)
  496. (let* ((bootloader (bootloader-configuration-bootloader config))
  497. (grub (bootloader-package bootloader)))
  498. (apply make-grub-configuration grub config args)))
  499. (define (grub-efi-configuration-file . args)
  500. (apply make-grub-configuration grub-efi args))
  501. (define grub-cfg "/boot/grub/grub.cfg")
  502. ;;;
  503. ;;; Install procedures.
  504. ;;;
  505. (define install-grub
  506. #~(lambda (bootloader device mount-point)
  507. (let ((grub (string-append bootloader "/sbin/grub-install"))
  508. (install-dir (string-append mount-point "/boot")))
  509. ;; Install GRUB on DEVICE which is mounted at MOUNT-POINT. If DEVICE
  510. ;; is #f, then we populate the disk-image rooted at MOUNT-POINT.
  511. (if device
  512. (begin
  513. ;; Tell 'grub-install' that there might be a LUKS-encrypted
  514. ;; /boot or root partition.
  515. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  516. ;; Hide potentially confusing messages from the user, such as
  517. ;; "Installing for i386-pc platform."
  518. (invoke/quiet grub "--no-floppy" "--target=i386-pc"
  519. "--boot-directory" install-dir
  520. device))
  521. ;; When creating a disk-image, only install a font and GRUB modules.
  522. (let* ((fonts (string-append install-dir "/grub/fonts")))
  523. (mkdir-p fonts)
  524. (copy-file (string-append bootloader "/share/grub/unicode.pf2")
  525. (string-append fonts "/unicode.pf2"))
  526. (copy-recursively (string-append bootloader "/lib/")
  527. install-dir))))))
  528. (define install-grub-disk-image
  529. #~(lambda (bootloader root-index image)
  530. ;; Install GRUB on the given IMAGE. The root partition index is
  531. ;; ROOT-INDEX.
  532. (let ((grub-mkimage
  533. (string-append bootloader "/bin/grub-mkimage"))
  534. (modules '("biosdisk" "part_msdos" "fat" "ext2"))
  535. (grub-bios-setup
  536. (string-append bootloader "/sbin/grub-bios-setup"))
  537. (root-device (format #f "hd0,msdos~a" root-index))
  538. (boot-img (string-append bootloader "/lib/grub/i386-pc/boot.img"))
  539. (device-map "device.map"))
  540. ;; Create a minimal, standalone GRUB image that will be written
  541. ;; directly in the MBR-GAP (space between the end of the MBR and the
  542. ;; first partition).
  543. (apply invoke grub-mkimage
  544. "-O" "i386-pc"
  545. "-o" "core.img"
  546. "-p" (format #f "(~a)/boot/grub" root-device)
  547. modules)
  548. ;; Create a device mapping file.
  549. (call-with-output-file device-map
  550. (lambda (port)
  551. (format port "(hd0) ~a~%" image)))
  552. ;; Copy the default boot.img, that will be written on the MBR sector
  553. ;; by GRUB-BIOS-SETUP.
  554. (copy-file boot-img "boot.img")
  555. ;; Install both the "boot.img" and the "core.img" files on the given
  556. ;; IMAGE. On boot, the MBR sector will execute the minimal GRUB
  557. ;; written in the MBR-GAP. GRUB configuration and missing modules will
  558. ;; be read from ROOT-DEVICE.
  559. (invoke grub-bios-setup
  560. "-m" device-map
  561. "-r" root-device
  562. "-d" "."
  563. image))))
  564. (define install-grub-efi
  565. #~(lambda (bootloader efi-dir mount-point)
  566. ;; There is nothing useful to do when called in the context of a disk
  567. ;; image generation.
  568. (when efi-dir
  569. ;; Install GRUB onto the EFI partition mounted at EFI-DIR, for the
  570. ;; system whose root is mounted at MOUNT-POINT.
  571. (let ((grub-install (string-append bootloader "/sbin/grub-install"))
  572. (install-dir (string-append mount-point "/boot"))
  573. ;; When installing Guix, it's common to mount EFI-DIR below
  574. ;; MOUNT-POINT rather than /boot/efi on the live image.
  575. (target-esp (if (file-exists? (string-append mount-point efi-dir))
  576. (string-append mount-point efi-dir)
  577. efi-dir)))
  578. ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
  579. ;; root partition.
  580. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  581. (invoke/quiet grub-install "--boot-directory" install-dir
  582. "--bootloader-id=Guix"
  583. "--efi-directory" target-esp)))))
  584. (define install-grub-efi-removable
  585. #~(lambda (bootloader efi-dir mount-point)
  586. ;; NOTE: mount-point is /mnt in guix system init /etc/config.scm /mnt/point
  587. ;; NOTE: efi-dir comes from target list of booloader configuration
  588. ;; There is nothing useful to do when called in the context of a disk
  589. ;; image generation.
  590. (when efi-dir
  591. ;; Install GRUB onto the EFI partition mounted at EFI-DIR, for the
  592. ;; system whose root is mounted at MOUNT-POINT.
  593. (let ((grub-install (string-append bootloader "/sbin/grub-install"))
  594. (install-dir (string-append mount-point "/boot"))
  595. ;; When installing Guix, it's common to mount EFI-DIR below
  596. ;; MOUNT-POINT rather than /boot/efi on the live image.
  597. (target-esp (if (file-exists? (string-append mount-point efi-dir))
  598. (string-append mount-point efi-dir)
  599. efi-dir)))
  600. ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
  601. ;; root partition.
  602. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  603. (invoke/quiet grub-install "--boot-directory" install-dir
  604. "--removable"
  605. ;; "--no-nvram"
  606. "--bootloader-id=Guix"
  607. "--efi-directory" target-esp)))))
  608. (define install-grub-efi32
  609. #~(lambda (bootloader efi-dir mount-point)
  610. ;; There is nothing useful to do when called in the context of a disk
  611. ;; image generation.
  612. (when efi-dir
  613. ;; Install GRUB onto the EFI partition mounted at EFI-DIR, for the
  614. ;; system whose root is mounted at MOUNT-POINT.
  615. (let ((grub-install (string-append bootloader "/sbin/grub-install"))
  616. (install-dir (string-append mount-point "/boot"))
  617. ;; When installing Guix, it's common to mount EFI-DIR below
  618. ;; MOUNT-POINT rather than /boot/efi on the live image.
  619. (target-esp (if (file-exists? (string-append mount-point efi-dir))
  620. (string-append mount-point efi-dir)
  621. efi-dir)))
  622. ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
  623. ;; root partition.
  624. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  625. (invoke/quiet grub-install "--boot-directory" install-dir
  626. "--bootloader-id=Guix"
  627. (cond ((target-x86?) "--target=i386-efi")
  628. ((target-arm?) "--target=arm-efi"))
  629. "--efi-directory" target-esp)))))
  630. (define* (make-grub-efi-netboot-installer grub-efi grub-cfg subdir)
  631. "Make a bootloader-installer for a grub-efi-netboot bootloader, which expects
  632. its files in SUBDIR and its configuration file in GRUB-CFG.
  633. As a grub-efi-netboot package is already pre-installed by 'grub-mknetdir', the
  634. installer basically copies all files from the bootloader-package (or profile)
  635. into the bootloader-target directory.
  636. Additionally for network booting over TFTP, two relative symlinks to the store
  637. and to the GRUB-CFG file are necessary. Due to this a TFTP root directory must
  638. not be located on a FAT file-system.
  639. If the bootloader-target does not support symlinks, then it is assumed to be a
  640. kind of EFI System Partition (ESP). In this case an intermediate configuration
  641. file is created with the help of GRUB-EFI to load the GRUB-CFG.
  642. The installer is usable for any efi-bootloader-chain, which prepares the
  643. bootloader-profile in a way ready for copying.
  644. The installer does not manipulate the system's 'UEFI Boot Manager'.
  645. The returned installer accepts the BOOTLOADER, TARGET and MOUNT-POINT
  646. arguments. Its job is to copy the BOOTLOADER, which must be a pre-installed
  647. grub-efi-netboot package with a SUBDIR like efi/boot or efi/Guix, below the
  648. directory TARGET for the system whose root is mounted at MOUNT-POINT.
  649. MOUNT-POINT is the last argument in 'guix system init /etc/config.scm mnt/point'
  650. or '/' for other 'guix system' commands.
  651. Where TARGET comes from the targets argument given to the
  652. bootloader-configuration in:
  653. (operating-system
  654. (bootloader (bootloader-configuration
  655. (targets '(\"/boot/efi\"))
  656. …))
  657. …)
  658. TARGET is required to be an absolute directory name, usually mounted via NFS,
  659. and finally needs to be provided by a TFTP server as
  660. the TFTP root directory.
  661. Usually the installer will be used to prepare network booting over TFTP. Then
  662. GRUB will load tftp://server/SUBDIR/grub.cfg and this file will instruct it to
  663. load more files from the store like tftp://server/gnu/store/…-linux…/Image.
  664. To make this possible two symlinks are created. The first symlink points
  665. relatively form MOUNT-POINT/TARGET/SUBDIR/grub.cfg to
  666. MOUNT-POINT/boot/grub/grub.cfg, and the second symlink points relatively from
  667. MOUNT-POINT/TARGET/%store-prefix to MOUNT-POINT/%store-prefix.
  668. It is important to note that these symlinks need to be relative, as the absolute
  669. paths on the TFTP server side are unknown.
  670. It is also important to note that both symlinks will point outside the TFTP root
  671. directory and that the TARGET/%store-prefix symlink makes the whole store
  672. accessible via TFTP. Possibly the TFTP server must be configured to allow
  673. accesses outside its TFTP root directory. This all may need to be considered
  674. for security aspects. It is advised to disable any TFTP write access!
  675. The installer can also be used to prepare booting from local storage, if the
  676. underlying file-system, like FAT on an EFI System Partition (ESP), does not
  677. support symlinks. In this case the MOUNT-POINT/TARGET/SUBDIR/grub.cfg will be
  678. created with the help of GRUB-EFI to load the /boot/grub/grub.cfg file. A
  679. symlink to the store is not needed in this case."
  680. (with-imported-modules '((guix build union))
  681. #~(lambda (bootloader target mount-point)
  682. ;; In context of a disk image creation TARGET will be #f and an
  683. ;; installer is expected to do necessary installations on MOUNT-POINT,
  684. ;; which will become the root file system. If TARGET is #f, this
  685. ;; installer has nothing to do, as it only cares about the EFI System
  686. ;; Partition (ESP).
  687. (when target
  688. (use-modules ((guix build union) #:select (symlink-relative))
  689. (ice-9 popen)
  690. (ice-9 rdelim))
  691. (let* ((mount-point/target (string-append mount-point target "/"))
  692. ;; When installing Guix, it is common to mount TARGET below
  693. ;; MOUNT-POINT rather than the root directory.
  694. (bootloader-target (if (file-exists? mount-point/target)
  695. mount-point/target
  696. target))
  697. (store (string-append mount-point (%store-prefix)))
  698. (store-link (string-append bootloader-target (%store-prefix)))
  699. (grub-cfg (string-append mount-point #$grub-cfg))
  700. (grub-cfg-link (string-append bootloader-target
  701. #$subdir "/"
  702. (basename grub-cfg))))
  703. ;; Copy the bootloader into the bootloader-target directory.
  704. ;; Should we beforehand recursively delete any existing file?
  705. (copy-recursively bootloader bootloader-target
  706. #:follow-symlinks? #t
  707. #:log (%make-void-port "w"))
  708. ;; For TFTP we need to install additional relative symlinks.
  709. ;; If we install on an EFI System Partition (ESP) or some other FAT
  710. ;; file-system, then symlinks cannot be created and are not needed.
  711. ;; Therefore we ignore exceptions when trying.
  712. ;; Prepare the symlink to the grub.cfg.
  713. (mkdir-p (dirname grub-cfg-link))
  714. (false-if-exception (delete-file grub-cfg-link))
  715. (if (unspecified?
  716. (false-if-exception (symlink-relative grub-cfg grub-cfg-link)))
  717. ;; Symlinks are supported.
  718. (begin
  719. ;; Prepare the symlink to the store.
  720. (mkdir-p (dirname store-link))
  721. (false-if-exception (delete-file store-link))
  722. (symlink-relative store store-link))
  723. ;; Creating symlinks does not seem to be supported. Probably
  724. ;; an ESP is used. Add a script to search and load the actual
  725. ;; grub.cfg.
  726. (let* ((probe #$(file-append grub-efi "/sbin/grub-probe"))
  727. (port (open-pipe* OPEN_READ probe "--target=fs_uuid"
  728. grub-cfg))
  729. (search-root
  730. (match (read-line port)
  731. ((? eof-object?)
  732. ;; There is no UUID available. As a fallback search
  733. ;; everywhere for the grub.cfg.
  734. (string-append "search --file --set " #$grub-cfg))
  735. (fs-uuid
  736. ;; The UUID to load the grub.cfg from is known.
  737. (string-append "search --fs-uuid --set " fs-uuid))))
  738. (load-grub-cfg (string-append "configfile " #$grub-cfg)))
  739. (close-pipe port)
  740. (with-output-to-file grub-cfg-link
  741. (lambda ()
  742. (display (string-join (list search-root
  743. load-grub-cfg)
  744. "\n")))))))))))
  745. ;;;
  746. ;;; Bootloader definitions.
  747. ;;;
  748. ;;; For all these grub-bootloader variables the path to /boot/grub/grub.cfg
  749. ;;; is fixed. Inheriting and overwriting the field 'configuration-file' will
  750. ;;; break 'guix system delete-generations', 'guix system switch-generation',
  751. ;;; and 'guix system roll-back'.
  752. (define grub-bootloader
  753. (bootloader
  754. (name 'grub)
  755. (package grub)
  756. (installer install-grub)
  757. (disk-image-installer install-grub-disk-image)
  758. (configuration-file grub-cfg)
  759. (configuration-file-generator grub-configuration-file)))
  760. (define grub-minimal-bootloader
  761. (bootloader
  762. (inherit grub-bootloader)
  763. (package grub-minimal)))
  764. (define grub-efi-bootloader
  765. (bootloader
  766. (name 'grub-efi)
  767. (package grub-efi)
  768. (installer install-grub-efi)
  769. (disk-image-installer #f)
  770. (configuration-file grub-cfg)
  771. (configuration-file-generator grub-configuration-file)))
  772. (define grub-efi-removable-bootloader
  773. (bootloader
  774. (inherit grub-efi-bootloader)
  775. (name 'grub-efi-removable-bootloader)
  776. (installer install-grub-efi-removable)))
  777. (define grub-efi32-bootloader
  778. (bootloader
  779. (inherit grub-efi-bootloader)
  780. (installer install-grub-efi32)
  781. (name 'grub-efi32)
  782. (package grub-efi32)))
  783. (define (make-grub-efi-netboot-bootloader name subdir)
  784. (bootloader
  785. (name name)
  786. (package (make-grub-efi-netboot (symbol->string name) subdir))
  787. (installer (make-grub-efi-netboot-installer grub-efi grub-cfg subdir))
  788. (disk-image-installer #f)
  789. (configuration-file grub-cfg)
  790. (configuration-file-generator grub-efi-configuration-file)))
  791. (define grub-efi-netboot-bootloader
  792. (make-grub-efi-netboot-bootloader 'grub-efi-netboot-bootloader
  793. "efi/Guix"))
  794. (define grub-efi-netboot-removable-bootloader
  795. (make-grub-efi-netboot-bootloader 'grub-efi-netboot-removable-bootloader
  796. "efi/boot"))
  797. (define grub-mkrescue-bootloader
  798. (bootloader
  799. (inherit grub-efi-bootloader)
  800. (package grub-hybrid)))
  801. ;;;
  802. ;;; Compatibility macros.
  803. ;;;
  804. (define-syntax grub-configuration
  805. (syntax-rules (grub)
  806. ((_ (grub package) fields ...)
  807. (if (eq? package grub)
  808. (bootloader-configuration
  809. (bootloader grub-bootloader)
  810. fields ...)
  811. (bootloader-configuration
  812. (bootloader grub-efi-bootloader)
  813. fields ...)))
  814. ((_ fields ...)
  815. (bootloader-configuration
  816. (bootloader grub-bootloader)
  817. fields ...))))
  818. ;;; grub.scm ends here