Package: guix-patches;
Reported by: Lilah Tascheter <lilah <at> lunabee.space>
Date: Fri, 15 Aug 2025 20:06:01 UTC
Severity: normal
Tags: patch
Message #8 received at 79248 <at> debbugs.gnu.org (full text, mbox):
From: Lilah Tascheter <lilah <at> lunabee.space> To: guix-devel <at> gnu.org, 79248 <at> debbugs.gnu.org Subject: GCD006: Rewrite bootloader subsystem Date: Fri, 15 Aug 2025 15:13:49 -0500
hey everyone! about a year ago I proposed a patch series rewriting bootloader handling in Guix. seeing the new GCD process, I figured it was perfect for gathering discussion and consent for the rewrite. so, I rewrote the rewrite, and I'm proposing it here! this is a draft; if anyone has suggestions at this stage, please let me know! I'll need at least one sponsor, as well, for this to move to the discussion period. lilah title: Bootloader Subsystem Rewrite id: 006 status: draft discussion: https://issues.guix.gnu.org/79248 authors: Lilah Tascheter sponsors: <Sponsor Name> date: <date when the discussion period starts> SPDX-License-Identifier: CC-BY-SA-4.0 OR GFDL-1.3-no-invariants-or-later --- # Summary Guix's bootloader handling was originally designed solely for GRUB but, with greater adoption and widened system compatibility, this has proved insufficient. Unfortunately, core aspects of bootloader handling have remained unchanged, resulting in a system hard to fit new bootloaders into and confusing for end-users to configure. We propose an in-depth rewrite in order to create a more flexible and easier to understand interface for both end users and bootloader packagers. Prior work has been done as patchsets #69343, #73202, and #72457 on Mumi. This GCD is a new proposal improving upon these patches. # Motivation We identify three main ways the current bootloader handling proves insufficient: 1. Structural assumptions of GRUB exist in both user-facing bootloader configuration options as well as the packager-facing bootloader interface. 2. Multiple special cases exist in bootloader packaging and installation, making it incredibly easy to package bootloaders that either install onto the host during disk-image building or fail during system rollback. 3. Bootloader packaging is ill-documented and relies on a confusing interface, which is especially bad for such a sensitive part of system configuration. Take, first, the user-facing side of things. 11/14 fields of the `<bootloader-configuration>` record are only used in GRUB, with multiple having no imaginable use for non-GRUB bootloaders in the first place. Theoretically, new bootloaders could add fields to the record if they need their own configuration, but any external channels would not be able to do even that. Disk image handling is not well-specified, especially when attempting to create a disk image with a custom bootloader. Examples provide `/dev/vda1`, so a user would assume that Guix looks for that magic string, but instead bootloader handling uses a special install procedure. Notably, an unused target must be specified anyway. The `<menu-entry>` record is, also, made solely with GRUB in mind, even though dual-booting is theoretically something that should be supported by bootloaders in general. Fields are documented with semantics based solely on how GRUB interprets them. More notably, the current state of affairs hampers bootloader packaging significantly. The `<bootloader>` record contains the following fields: * `bootloader-name`: containing a symbol supposed to uniquely identify a bootloader package, for use in rollbacks. Notably, u-boot bootloaders ignore this requirement, to potentially disasterous consequences. It doesn't help that the only time one would notice problems with this (for example, a channel's bootloader defined outside gnu/bootloader, a bootloader defined as a procedure, or reused `bootloader-name`s) is when you already need to roll back to a different bootloader. * `bootloader-package`: This is only useful for switching which GRUB package is used for GRUB bootloaders. * `bootloader-configuration-file`: This is the sole mechanism through which rollbacks function. Few bootloaders utilize config file semantics, making bootloader packaging a pain of trying to coerce semantics to fit in this rigid box. This, also, prevents use of non-deterministic config files, such as those containing signed data for secure boot systems. * `bootloader-installer` and `bootloader-disk-image-installer`: `installer` is meant to be used on mounted filesystems. `disk-image-installer` is meant to be used on devices. `disk-image-installer` is used for disk image building or as a fallback. Some bootloaders (even GRUB!) need access to both a filesystem and disk, and the two separate code paths mean development can lead to poorly tested bootloading code. The arguments to both procedures are, also, poorly documented (and weird choices in general; root-index is purely used by GRUB). # Detailed Design ## Bootloader Definition First, we go over the general architecture of bootloader packaging. Each bootloader will have two things associated to it: a `<bootloader>` record denoting what functions it provides, and a configuration record unique to each type of bootloader. The `<bootloader>` record defines functions taking in the configuration record, as a way for the bootloader system to extract the information it needs from each bootloader's config. Going into specifics, the `<bootloader>` record will be redefined as such: * `bootloader-predicate`: A predicate denoting whether the configuration is of the right type, used to verify whether users provided the correct record. * `bootloader-installers`: A list of `<bootloader-installer>` records. `<bootloader-installer>` is a new record intended to split up individual target installation methods. This is necessary as disk images do not have all possible targets accessible at once (as the disk is generated strictly after partitions). The record is defined as such: * `<bootloader-installer-tag>`: An arbitrary symbol used by the bootloader to identify this installer and its targets. A `<bootloader>` may not have two `<bootloader-installers>` with the same tag. * `<bootloader-installer-type>`: A symbol indicating the sort of values allowed as targets. Allowed the following values: - `'file`: Only strings allowed, interpreted as paths. - `'block`: Strings (interpreted as block devices), `file-system-label`s, and `uuid`s allowed. `uuid`s are interpreted as filesystem UUIDs, partition UUIDs, or partition table UUIDs, attempted in that order. * `<bootloader-installer-targets>`: A function from the bootloader's configuration to a list of targets specified by the user. Typically this is just a configuration record field accessor. * `<bootloader-installer-installer>`: A function from the bootloader's configuration, an alist of generation numbers to `<boot-parameters>`, the current generation number, and the boot-time target map (see below), to a gexp containing an installer procedure as specified below. Installers are gexps containing a procedure taking keyword arguments. The procedure is called once for each target in the `<bootloader-installer>`, and provided keyword arguments depending on the `<bootloader-installer-type>`. Each keyword argument has a side, either boot-side or install-side. Install-side data is only valid during installation of the bootloader, and should not be used during bootup. This data is generated from system state at install-time. Boot-side data is usable during bootup, and generated from a given system's `<file-system>` records. However, if a `'block` type installer is provided a path to a block device, boot-time data must be generated at install-time. For this reason, usage of a path `'block` is not recommended. Keyword arguments are as follows: * `#:mount` (`'file`, install-time): Absolute path to the mount point of the filesystem the file is stored on. * `#:path` (`'file`, install-time): Absolute path to the specified file. * `#:device` (`'block`, install-time): Device file for the block device. * `#:offset` (`'file`, boot-time): Filesystem-relative path. Includes the btrfs subvolume prefix when applicable, so `#:path` may not always be `string-append`'ed `#:mount` and `#:offset`. * `#:label` (all, boot-time): Filesystem label. * `#:uuid` (all, boot-time): Filesystem uuid, unless `'block` and a partition UUID or partition table UUID is provided. In the future, more installer types and keyword arguments may be provided fully backwards-compatibly. The boot-time target map provieded to `bootloader-installers` is an alist of tags to lists of boot-time data, one for each target. Boot-time data is exactly the boot-time keyword arguments provided to installer gexp procedures, but are provided here for certain installers to know about others (for example, the GRUB early loader installed to a disk knowing about the full GRUB installation's partition and location). ## User Configuration Users will specify the `<bootloader>` they wish to use directly in the `operating-system-bootloader` field, which will be changed to allow `#f` (for no bootloaders, as in virtualized images with direct linux booting). `<operating-system>` will also receive a new `operating-system-bootloader-configuration` field. This field will accept a configuration used by the `operating-system-bootloader`, verified by `bootloader-predicate`. All further bootloader configuration, including targets and additional boot options, will be provided in the `operating-system-bootloader-configuration`. The current `<menu-entry>` and `<bootloader-configuration>` records will be re-adapted into grub-specific configuration. ## Rollbacks As the entire bootloader install process exists in gexps, each generation will simply save its combined bootloader install script as a GC root to be called on rollback, the exact way it's called on install. This eliminates the need for bootloader module search and bootloader configuration guessing (as is currently used), as well as making bootloader rollbacks more reliable. Due to this, the following `<boot-parameters>` fields may be removed: `boot-parameters-root-device`, `boot-parameter-bootloader-name`, and `boot-parameter-menu-entries`. The record shall be updated to version 2 to match. ## Disk Images While disk images will work automatically with the above system through the keyword argument discovery mechanisms, one instance of special-casing is unfortunately required. Any `<bootloader-installer>` with the tag `'disk` will, instead of using targets from `bootloader-installer-targets`, provide a single target, generated from the resulting disk image file. All other handling will be done invisibly to both end-users and bootloader packagers, due to the above defined semantics of target conversion into keyword arguments. ## Cascading Changes These changes will necessitate some edits to specific bootloaders and supporting code. The current GRUB bootloader installer calls `grub-install`, which guesses a lot of information about the system it's currently run on. This approach, of course, does not work for disk images, and so manual image creation is used there. We propose unifying the two approaches, manually creating disk images with the information given to us by the new targeting system. Furthermore, a new utility procedure will be created to facilitate EFI bootloaders, handling copying to the ESP, setting EFI boot options, configuring removability, and ensuring sufficient storage space in the ESP. ## Documentation A page will be added to the manual under the `Programming Interface` section describing the new bootloader interfaces. This page will note the default modules provided to installer gexps and the special-casing of the `'disk` tag. The `Bootloader Configuration` page will be updated to act as a unified documentation of the peculiarities of each bootloader and how to use it. Manual pages pertaining to bootloader installation will be updated to document the new user configuration format, and a blog post will be made to ease the transition. ## Bootloading Team Lastly, a bootloading team will be created as a point of contact and implementation group for the above changes. This team would develop the proposed changes in a public fork of Guix in order to facilitate user testing across the wide range of supported hardware. Merging with mainline will be done once patch review and sufficiently diverse testing as to minimize user impact has been completed. ## Examples ```scheme (define-record-type* <simple-configuration> simple-configuration make-simple-configuration simple-configuration? (copy-to simple-configuration-copy-to)) (define (simple-bootloader-installer conf params current targets) #~(lambda* (#:key path #:allow-other-keys) (copy-file #$(file-append simple-bootloader-pkg "/share/config") path))) (define simple-bootloader (bootloader (predicate simple-configuration?) (installers (bootloader-installer (tag 'copy-to) (type 'file) (targets (compose list simple-configuration-copy-to)) (installer simple-bootloader-installer))))) ``` ```scheme (operating-system (bootloader simple-bootloader) (bootloader-configuration (simple-configuration (copy-to "/boot/config")))) ``` # Migration Even though bootloader management may be considered part of the Guix API surface, we do not attempt to provide a clean migration path. Thus, upon merge immediate incompatibilities will occur for all channels packaging bootloaders. This, thankfully, proves to be a very small number. For operating system configuration, however, the existing `<bootloader-configuration>` and `<menu-entry>` structures shall be deprecated. `<operating-system>`s will go through a pass converting such structures into the new format, through a `bootloader-migration` field in `<bootloader>` allowed to be `#f`. This field may contain a function from `<bootloader-configuration>` to the bootloader's configuration. The fixup procedure will operate as follows, taking ```scheme (operating-system (bootloader (bootloader-configuration (bootloader test-bootloader)))) ``` to ```scheme (operating-system (bootloader test-bootloader) (bootloader-configuration ((bootloader-migrate test-bootloader) (bootloader-configuration (bootloader test-bootloader))))) ``` Target handling would require little to no special-casing in such migration functions. After a year, this deprecation will expire, and the fixup code, old records, and `bootloader-migration` field may be removed. This way, existing `<operating-system>`s may be used as-is for the period necessitated by the Deprecation Policy. ## Study of Affected Channels We have taken an informal study of Guix channels as of 2025-08-13 in order to estimate the impact of breaking API changes. 98 Guix channels were located from the following roots: * [whereiseverything channel toys](https://toys.whereis.social/channels) * [Github awesome-guix](https://github.com/franzos/awesome-guix) * [crafted-guix](https://codeberg.org/SystemCrafters/crafted-guix), an additional channel found through internet search. * [gooy-guix](https://git.squircle.space/gooy-guix.git), mentioned to me by Ada Avery and posted on guix-devel. Of those 98, we were able to locate exactly 4 which add bootloaders, none of which managed to fully support Guix's existing bootloader handling: * [rosenthal](https://codeberg.org/hako/Rosenthal), which just packages GRUB with an overridden package that supports LUKS2. Does not support rollbacks. * [gooy-guix](https://git.squircle.space/gooy-guix.git), packaging a systemd-boot EFI bootloader. It is impossible for this bootloader to support disk images in the current system. * [waggle](https://git.lunabee.space/waggle/files.html), my channel, which adds p-boot for the Pinephone and a UKI bootloader, whose roundabout implementation necessitated by the current system prompted me to begin this rewrite in the first place. Does not support rollbacks, and I didn't realize until after I needed one in the first place. It is impossible for these to support disk images in the current system. * [sakura](https://g.freya.cat/freya/sakura). The bootloader added is entirely copied from my channel and the channel's owner has switched to Nix anyway. We thus conclude change to be necessary, and the potential impact of breaking changes minimal. # Cost of Reverting While these changes will drastically affect internals, the vast majority of users should see a smooth transition. Similar methods to the above mentioned migration path may be employed if this changeset needs to be reverted, at the cost of another breaking change to bootloader-packaging channels. However, another roadblock in reverting could take place in the removal of boot parameter options. Thus, we propose postponing the boot parameter changes until after reverting is precluded as a possibility. Only then will the version be incremented and unnecessary fields dropped. # Drawbacks and Open Issues As these are bootloader changes, patches will need to be tested carefully in order to ensure no devices get bricked. And, even then, there could still be a risk due to how sensitiive bootloader installation and handling can be. This is the main obstacle for implementation. It may be useful, then, to have a more concrete procedure set in place to ensure proper testing after patch development. The deprecation process is not ideal, especially with the removal of the `bootloader-migrate` field resulting in two breaking changes to bootloader-packaging channels. The special-casing of the `'disk` tag is also not ideal. We could not think of any better method to provide such information.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.