grub.scm 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  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 Jan (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. (modules (menu-entry-multiboot-modules entry))
  374. (root-index 1)) ; XXX EFI will need root-index 2
  375. #~(format port "
  376. menuentry ~s {
  377. multiboot ~a root=device:hd0s~a~a~a
  378. }~%"
  379. #$label
  380. #$kernel
  381. #$root-index (string-join (list #$@arguments) " " 'prefix)
  382. (string-join (map string-join '#$modules)
  383. "\n module " 'prefix))))
  384. (chain-loader
  385. #~(format port "
  386. menuentry ~s {
  387. ~a
  388. chainloader ~a
  389. }~%"
  390. #$label
  391. #$(grub-root-search device chain-loader)
  392. #$chain-loader)))))
  393. (define (crypto-devices)
  394. (define (crypto-device->cryptomount dev)
  395. (if (uuid? dev)
  396. #~(format port "cryptomount -u ~a~%"
  397. ;; cryptomount only accepts UUID without the hypen.
  398. #$(string-delete #\- (uuid->string dev)))
  399. ;; Other type of devices aren't implemented.
  400. #~()))
  401. (let ((devices (map crypto-device->cryptomount store-crypto-devices))
  402. (modules #~(format port "insmod luks~%insmod luks2~%")))
  403. (if (null? devices)
  404. devices
  405. (cons modules devices))))
  406. (define (sugar)
  407. (let* ((entry (first all-entries))
  408. (device (menu-entry-device entry))
  409. (mount-point (menu-entry-device-mount-point entry)))
  410. (eye-candy config
  411. device
  412. mount-point
  413. #:store-directory-prefix store-directory-prefix
  414. #:port #~port)))
  415. (define locale-config
  416. (let* ((entry (first all-entries))
  417. (device (menu-entry-device entry))
  418. (mount-point (menu-entry-device-mount-point entry)))
  419. #~(let ((locale #$(and locale
  420. (locale-definition-source
  421. (locale-name->definition locale))))
  422. (locales #$(and locale
  423. (normalize-file (grub-locale-directory grub)
  424. mount-point
  425. store-directory-prefix))))
  426. (when locale
  427. (format port "\
  428. # Localization configuration.
  429. ~asearch --file --set ~a/en@quot.mo
  430. set locale_dir=~a
  431. set lang=~a~%"
  432. ;; Skip the search if there is an image, as it has already
  433. ;; been performed by eye-candy and traversing the store is
  434. ;; an expensive operation.
  435. #$(if (grub-theme-image (bootloader-theme config))
  436. "# "
  437. "")
  438. locales
  439. locales
  440. locale)))))
  441. (define keyboard-layout-config
  442. (let* ((layout (bootloader-configuration-keyboard-layout config))
  443. (keymap* (and layout
  444. (keyboard-layout-file layout #:grub grub)))
  445. (entry (first all-entries))
  446. (device (menu-entry-device entry))
  447. (mount-point (menu-entry-device-mount-point entry))
  448. (keymap (and keymap*
  449. (normalize-file keymap* mount-point
  450. store-directory-prefix))))
  451. #~(when #$keymap
  452. (format port "\
  453. insmod keylayouts
  454. keymap ~a~%" #$keymap))))
  455. (define builder
  456. #~(call-with-output-file #$output
  457. (lambda (port)
  458. (format port
  459. "# This file was generated from your Guix configuration. Any changes
  460. # will be lost upon reconfiguration.
  461. ")
  462. #$@(crypto-devices)
  463. #$(sugar)
  464. #$locale-config
  465. #$keyboard-layout-config
  466. (format port "
  467. set default=~a
  468. set timeout=~a~%"
  469. #$(bootloader-configuration-default-entry config)
  470. #$(bootloader-configuration-timeout config))
  471. #$@(map menu-entry->gexp all-entries)
  472. #$@(if (pair? old-entries)
  473. #~((format port "
  474. submenu \"GNU system, old configurations...\" {~%")
  475. #$@(map menu-entry->gexp old-entries)
  476. (format port "}~%"))
  477. #~())
  478. (format port "
  479. if [ \"${grub_platform}\" == efi ]; then
  480. menuentry \"Firmware setup\" {
  481. fwsetup
  482. }
  483. fi~%"))))
  484. ;; Since this file is rather unique, there's no point in trying to
  485. ;; substitute it.
  486. (computed-file "grub.cfg" builder
  487. #:options '(#:local-build? #t
  488. #:substitutable? #f)))
  489. (define (grub-configuration-file config . args)
  490. (let* ((bootloader (bootloader-configuration-bootloader config))
  491. (grub (bootloader-package bootloader)))
  492. (apply make-grub-configuration grub config args)))
  493. (define (grub-efi-configuration-file . args)
  494. (apply make-grub-configuration grub-efi args))
  495. (define grub-cfg "/boot/grub/grub.cfg")
  496. ;;;
  497. ;;; Install procedures.
  498. ;;;
  499. (define install-grub
  500. #~(lambda (bootloader device mount-point)
  501. (let ((grub (string-append bootloader "/sbin/grub-install"))
  502. (install-dir (string-append mount-point "/boot")))
  503. ;; Install GRUB on DEVICE which is mounted at MOUNT-POINT. If DEVICE
  504. ;; is #f, then we populate the disk-image rooted at MOUNT-POINT.
  505. (if device
  506. (begin
  507. ;; Tell 'grub-install' that there might be a LUKS-encrypted
  508. ;; /boot or root partition.
  509. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  510. ;; Hide potentially confusing messages from the user, such as
  511. ;; "Installing for i386-pc platform."
  512. (invoke/quiet grub "--no-floppy" "--target=i386-pc"
  513. "--boot-directory" install-dir
  514. device))
  515. ;; When creating a disk-image, only install a font and GRUB modules.
  516. (let* ((fonts (string-append install-dir "/grub/fonts")))
  517. (mkdir-p fonts)
  518. (copy-file (string-append bootloader "/share/grub/unicode.pf2")
  519. (string-append fonts "/unicode.pf2"))
  520. (copy-recursively (string-append bootloader "/lib/")
  521. install-dir))))))
  522. (define install-grub-disk-image
  523. #~(lambda (bootloader root-index image)
  524. ;; Install GRUB on the given IMAGE. The root partition index is
  525. ;; ROOT-INDEX.
  526. (let ((grub-mkimage
  527. (string-append bootloader "/bin/grub-mkimage"))
  528. (modules '("biosdisk" "part_msdos" "fat" "ext2"))
  529. (grub-bios-setup
  530. (string-append bootloader "/sbin/grub-bios-setup"))
  531. (root-device (format #f "hd0,msdos~a" root-index))
  532. (boot-img (string-append bootloader "/lib/grub/i386-pc/boot.img"))
  533. (device-map "device.map"))
  534. ;; Create a minimal, standalone GRUB image that will be written
  535. ;; directly in the MBR-GAP (space between the end of the MBR and the
  536. ;; first partition).
  537. (apply invoke grub-mkimage
  538. "-O" "i386-pc"
  539. "-o" "core.img"
  540. "-p" (format #f "(~a)/boot/grub" root-device)
  541. modules)
  542. ;; Create a device mapping file.
  543. (call-with-output-file device-map
  544. (lambda (port)
  545. (format port "(hd0) ~a~%" image)))
  546. ;; Copy the default boot.img, that will be written on the MBR sector
  547. ;; by GRUB-BIOS-SETUP.
  548. (copy-file boot-img "boot.img")
  549. ;; Install both the "boot.img" and the "core.img" files on the given
  550. ;; IMAGE. On boot, the MBR sector will execute the minimal GRUB
  551. ;; written in the MBR-GAP. GRUB configuration and missing modules will
  552. ;; be read from ROOT-DEVICE.
  553. (invoke grub-bios-setup
  554. "-m" device-map
  555. "-r" root-device
  556. "-d" "."
  557. image))))
  558. (define install-grub-efi
  559. #~(lambda (bootloader efi-dir mount-point)
  560. ;; There is nothing useful to do when called in the context of a disk
  561. ;; image generation.
  562. (when efi-dir
  563. ;; Install GRUB onto the EFI partition mounted at EFI-DIR, for the
  564. ;; system whose root is mounted at MOUNT-POINT.
  565. (let ((grub-install (string-append bootloader "/sbin/grub-install"))
  566. (install-dir (string-append mount-point "/boot"))
  567. ;; When installing Guix, it's common to mount EFI-DIR below
  568. ;; MOUNT-POINT rather than /boot/efi on the live image.
  569. (target-esp (if (file-exists? (string-append mount-point efi-dir))
  570. (string-append mount-point efi-dir)
  571. efi-dir)))
  572. ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
  573. ;; root partition.
  574. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  575. (invoke/quiet grub-install "--boot-directory" install-dir
  576. "--bootloader-id=Guix"
  577. "--efi-directory" target-esp)))))
  578. (define install-grub-efi-removable
  579. #~(lambda (bootloader efi-dir mount-point)
  580. ;; NOTE: mount-point is /mnt in guix system init /etc/config.scm /mnt/point
  581. ;; NOTE: efi-dir comes from target list of booloader configuration
  582. ;; There is nothing useful to do when called in the context of a disk
  583. ;; image generation.
  584. (when efi-dir
  585. ;; Install GRUB onto the EFI partition mounted at EFI-DIR, for the
  586. ;; system whose root is mounted at MOUNT-POINT.
  587. (let ((grub-install (string-append bootloader "/sbin/grub-install"))
  588. (install-dir (string-append mount-point "/boot"))
  589. ;; When installing Guix, it's common to mount EFI-DIR below
  590. ;; MOUNT-POINT rather than /boot/efi on the live image.
  591. (target-esp (if (file-exists? (string-append mount-point efi-dir))
  592. (string-append mount-point efi-dir)
  593. efi-dir)))
  594. ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
  595. ;; root partition.
  596. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  597. (invoke/quiet grub-install "--boot-directory" install-dir
  598. "--removable"
  599. ;; "--no-nvram"
  600. "--bootloader-id=Guix"
  601. "--efi-directory" target-esp)))))
  602. (define install-grub-efi32
  603. #~(lambda (bootloader efi-dir mount-point)
  604. ;; There is nothing useful to do when called in the context of a disk
  605. ;; image generation.
  606. (when efi-dir
  607. ;; Install GRUB onto the EFI partition mounted at EFI-DIR, for the
  608. ;; system whose root is mounted at MOUNT-POINT.
  609. (let ((grub-install (string-append bootloader "/sbin/grub-install"))
  610. (install-dir (string-append mount-point "/boot"))
  611. ;; When installing Guix, it's common to mount EFI-DIR below
  612. ;; MOUNT-POINT rather than /boot/efi on the live image.
  613. (target-esp (if (file-exists? (string-append mount-point efi-dir))
  614. (string-append mount-point efi-dir)
  615. efi-dir)))
  616. ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or
  617. ;; root partition.
  618. (setenv "GRUB_ENABLE_CRYPTODISK" "y")
  619. (invoke/quiet grub-install "--boot-directory" install-dir
  620. "--bootloader-id=Guix"
  621. (cond ((target-x86?) "--target=i386-efi")
  622. ((target-arm?) "--target=arm-efi"))
  623. "--efi-directory" target-esp)))))
  624. (define* (make-grub-efi-netboot-installer grub-efi grub-cfg subdir)
  625. "Make a bootloader-installer for a grub-efi-netboot bootloader, which expects
  626. its files in SUBDIR and its configuration file in GRUB-CFG.
  627. As a grub-efi-netboot package is already pre-installed by 'grub-mknetdir', the
  628. installer basically copies all files from the bootloader-package (or profile)
  629. into the bootloader-target directory.
  630. Additionally for network booting over TFTP, two relative symlinks to the store
  631. and to the GRUB-CFG file are necessary. Due to this a TFTP root directory must
  632. not be located on a FAT file-system.
  633. If the bootloader-target does not support symlinks, then it is assumed to be a
  634. kind of EFI System Partition (ESP). In this case an intermediate configuration
  635. file is created with the help of GRUB-EFI to load the GRUB-CFG.
  636. The installer is usable for any efi-bootloader-chain, which prepares the
  637. bootloader-profile in a way ready for copying.
  638. The installer does not manipulate the system's 'UEFI Boot Manager'.
  639. The returned installer accepts the BOOTLOADER, TARGET and MOUNT-POINT
  640. arguments. Its job is to copy the BOOTLOADER, which must be a pre-installed
  641. grub-efi-netboot package with a SUBDIR like efi/boot or efi/Guix, below the
  642. directory TARGET for the system whose root is mounted at MOUNT-POINT.
  643. MOUNT-POINT is the last argument in 'guix system init /etc/config.scm mnt/point'
  644. or '/' for other 'guix system' commands.
  645. Where TARGET comes from the targets argument given to the
  646. bootloader-configuration in:
  647. (operating-system
  648. (bootloader (bootloader-configuration
  649. (targets '(\"/boot/efi\"))
  650. …))
  651. …)
  652. TARGET is required to be an absolute directory name, usually mounted via NFS,
  653. and finally needs to be provided by a TFTP server as
  654. the TFTP root directory.
  655. Usually the installer will be used to prepare network booting over TFTP. Then
  656. GRUB will load tftp://server/SUBDIR/grub.cfg and this file will instruct it to
  657. load more files from the store like tftp://server/gnu/store/…-linux…/Image.
  658. To make this possible two symlinks are created. The first symlink points
  659. relatively form MOUNT-POINT/TARGET/SUBDIR/grub.cfg to
  660. MOUNT-POINT/boot/grub/grub.cfg, and the second symlink points relatively from
  661. MOUNT-POINT/TARGET/%store-prefix to MOUNT-POINT/%store-prefix.
  662. It is important to note that these symlinks need to be relative, as the absolute
  663. paths on the TFTP server side are unknown.
  664. It is also important to note that both symlinks will point outside the TFTP root
  665. directory and that the TARGET/%store-prefix symlink makes the whole store
  666. accessible via TFTP. Possibly the TFTP server must be configured to allow
  667. accesses outside its TFTP root directory. This all may need to be considered
  668. for security aspects. It is advised to disable any TFTP write access!
  669. The installer can also be used to prepare booting from local storage, if the
  670. underlying file-system, like FAT on an EFI System Partition (ESP), does not
  671. support symlinks. In this case the MOUNT-POINT/TARGET/SUBDIR/grub.cfg will be
  672. created with the help of GRUB-EFI to load the /boot/grub/grub.cfg file. A
  673. symlink to the store is not needed in this case."
  674. (with-imported-modules '((guix build union))
  675. #~(lambda (bootloader target mount-point)
  676. ;; In context of a disk image creation TARGET will be #f and an
  677. ;; installer is expected to do necessary installations on MOUNT-POINT,
  678. ;; which will become the root file system. If TARGET is #f, this
  679. ;; installer has nothing to do, as it only cares about the EFI System
  680. ;; Partition (ESP).
  681. (when target
  682. (use-modules ((guix build union) #:select (symlink-relative))
  683. (ice-9 popen)
  684. (ice-9 rdelim))
  685. (let* ((mount-point/target (string-append mount-point target "/"))
  686. ;; When installing Guix, it is common to mount TARGET below
  687. ;; MOUNT-POINT rather than the root directory.
  688. (bootloader-target (if (file-exists? mount-point/target)
  689. mount-point/target
  690. target))
  691. (store (string-append mount-point (%store-prefix)))
  692. (store-link (string-append bootloader-target (%store-prefix)))
  693. (grub-cfg (string-append mount-point #$grub-cfg))
  694. (grub-cfg-link (string-append bootloader-target
  695. #$subdir "/"
  696. (basename grub-cfg))))
  697. ;; Copy the bootloader into the bootloader-target directory.
  698. ;; Should we beforehand recursively delete any existing file?
  699. (copy-recursively bootloader bootloader-target
  700. #:follow-symlinks? #t
  701. #:log (%make-void-port "w"))
  702. ;; For TFTP we need to install additional relative symlinks.
  703. ;; If we install on an EFI System Partition (ESP) or some other FAT
  704. ;; file-system, then symlinks cannot be created and are not needed.
  705. ;; Therefore we ignore exceptions when trying.
  706. ;; Prepare the symlink to the grub.cfg.
  707. (mkdir-p (dirname grub-cfg-link))
  708. (false-if-exception (delete-file grub-cfg-link))
  709. (if (unspecified?
  710. (false-if-exception (symlink-relative grub-cfg grub-cfg-link)))
  711. ;; Symlinks are supported.
  712. (begin
  713. ;; Prepare the symlink to the store.
  714. (mkdir-p (dirname store-link))
  715. (false-if-exception (delete-file store-link))
  716. (symlink-relative store store-link))
  717. ;; Creating symlinks does not seem to be supported. Probably
  718. ;; an ESP is used. Add a script to search and load the actual
  719. ;; grub.cfg.
  720. (let* ((probe #$(file-append grub-efi "/sbin/grub-probe"))
  721. (port (open-pipe* OPEN_READ probe "--target=fs_uuid"
  722. grub-cfg))
  723. (search-root
  724. (match (read-line port)
  725. ((? eof-object?)
  726. ;; There is no UUID available. As a fallback search
  727. ;; everywhere for the grub.cfg.
  728. (string-append "search --file --set " #$grub-cfg))
  729. (fs-uuid
  730. ;; The UUID to load the grub.cfg from is known.
  731. (string-append "search --fs-uuid --set " fs-uuid))))
  732. (load-grub-cfg (string-append "configfile " #$grub-cfg)))
  733. (close-pipe port)
  734. (with-output-to-file grub-cfg-link
  735. (lambda ()
  736. (display (string-join (list search-root
  737. load-grub-cfg)
  738. "\n")))))))))))
  739. ;;;
  740. ;;; Bootloader definitions.
  741. ;;;
  742. ;;; For all these grub-bootloader variables the path to /boot/grub/grub.cfg
  743. ;;; is fixed. Inheriting and overwriting the field 'configuration-file' will
  744. ;;; break 'guix system delete-generations', 'guix system switch-generation',
  745. ;;; and 'guix system roll-back'.
  746. (define grub-bootloader
  747. (bootloader
  748. (name 'grub)
  749. (package grub)
  750. (installer install-grub)
  751. (disk-image-installer install-grub-disk-image)
  752. (configuration-file grub-cfg)
  753. (configuration-file-generator grub-configuration-file)))
  754. (define grub-minimal-bootloader
  755. (bootloader
  756. (inherit grub-bootloader)
  757. (package grub-minimal)))
  758. (define grub-efi-bootloader
  759. (bootloader
  760. (name 'grub-efi)
  761. (package grub-efi)
  762. (installer install-grub-efi)
  763. (disk-image-installer #f)
  764. (configuration-file grub-cfg)
  765. (configuration-file-generator grub-configuration-file)))
  766. (define grub-efi-removable-bootloader
  767. (bootloader
  768. (inherit grub-efi-bootloader)
  769. (name 'grub-efi-removable-bootloader)
  770. (installer install-grub-efi-removable)))
  771. (define grub-efi32-bootloader
  772. (bootloader
  773. (inherit grub-efi-bootloader)
  774. (installer install-grub-efi32)
  775. (name 'grub-efi32)
  776. (package grub-efi32)))
  777. (define (make-grub-efi-netboot-bootloader name subdir)
  778. (bootloader
  779. (name name)
  780. (package (make-grub-efi-netboot (symbol->string name) subdir))
  781. (installer (make-grub-efi-netboot-installer grub-efi grub-cfg subdir))
  782. (disk-image-installer #f)
  783. (configuration-file grub-cfg)
  784. (configuration-file-generator grub-efi-configuration-file)))
  785. (define grub-efi-netboot-bootloader
  786. (make-grub-efi-netboot-bootloader 'grub-efi-netboot-bootloader
  787. "efi/Guix"))
  788. (define grub-efi-netboot-removable-bootloader
  789. (make-grub-efi-netboot-bootloader 'grub-efi-netboot-removable-bootloader
  790. "efi/boot"))
  791. (define grub-mkrescue-bootloader
  792. (bootloader
  793. (inherit grub-efi-bootloader)
  794. (package grub-hybrid)))
  795. ;;;
  796. ;;; Compatibility macros.
  797. ;;;
  798. (define-syntax grub-configuration
  799. (syntax-rules (grub)
  800. ((_ (grub package) fields ...)
  801. (if (eq? package grub)
  802. (bootloader-configuration
  803. (bootloader grub-bootloader)
  804. fields ...)
  805. (bootloader-configuration
  806. (bootloader grub-efi-bootloader)
  807. fields ...)))
  808. ((_ fields ...)
  809. (bootloader-configuration
  810. (bootloader grub-bootloader)
  811. fields ...))))
  812. ;;; grub.scm ends here