title: Generalize the bootloader subsystem and introduce Guix Bootspec id: 00X status: draft discussion: TBD authors: coopi <[email protected]> sponsors: TBD date: TBD draft-date: TBD discussion-date: TBD deliberation-date: TBD SPDX-License-Identifier: CC-BY-SA-4.0 OR GFDL-1.3-no-invariants-or-later ---
# Summary Guix's bootloader subsystem was designed around GRUB, and its generic interface still assumes an installer, a generated configuration file, and menu entries for Guix system generations. This GCD defines *Guix Bootspec*, a versioned, bootloader-independent launch contract for a Guix system generation with a closed core and typed namespaced extensions; backend-specific bootloader configuration; a boot set describing the generations to publish; and a backend contract applied to both live systems and system images. It builds on the [#72457 bootloader rewrite](https://issues.guix.gnu.org/72457) and the later [bootloader-subsystem GCD draft](https://issues.guix.gnu.org/79248). The Bootspec boundary and terminology are directly inspired by [NixOS RFC 0125, *Bootspec*](https://github.com/NixOS/rfcs/blob/master/rfcs/0125-bootspec.md). System-generation selection and bootloader policy become independent: selecting an older system generation keeps the active bootloader policy, while restoring historical bootloader policy is a separate operation. # Motivation The current bootloader subsystem exposes several abstractions that are generic in name but derive from GRUB's design. A `<bootloader>` describes an installer, a disk-image installer, a configuration file, and a configuration-file generator. `<bootloader-configuration>` contains settings whose meaning largely comes from GRUB, and `<menu-entry>` assumes that a bootable system is represented as an entry in a generated menu. The generation data stored by Guix already reflects this coupling. `<boot-parameters>` contains a `store-device` whose documented semantics are chosen specifically for GRUB: device file names such as `/dev/sda3` are filtered out because GRUB cannot use them directly. The same record also stores a bootloader name and generic menu entries so that generation switching can later reconstruct bootloader state. Generation switching relies on those assumptions. `guix system switch-generation` looks up the bootloader recorded by name, creates a default `<bootloader-configuration>`, regenerates its configuration file, and installs only that file. That works for GRUB, where generation state lives primarily in a generated configuration, but it cannot reproduce arbitrary backend-specific policy. Deployment is split in a different way. Live installation and disk-image construction use separate installer callbacks with different inputs. Generic image code also contains bootloader-specific behavior, including GRUB-specific EFI partition initialization. Existing U-Boot definitions exercise the other end of the problem: several install boot images at board-specific raw offsets, while their generation menu is provided through Extlinux. The task is narrower than designing a universal model of boot firmware. Guix needs a stable description of its own bootable system generations, a backend interface free of GRUB-specific representation, and one deployment model capable of expressing the behavior already supported by the in-tree bootloaders. These limitations motivated the [#72457 patch series](https://issues.guix.gnu.org/72457), which proposed a broad rewrite after observing that non-GRUB support had accumulated on top of an interface originally designed for GRUB. The subsequent [2025 bootloader-subsystem GCD draft](https://issues.guix.gnu.org/79248) refined that work by proposing backend-specific configuration records and target-specific installers. It also identified the generated configuration file as an unsuitable basis for generic generation switching. The diagnosis holds. The main difference here is the placement of the stable boundary: the proposal first separates the description of a bootable Guix system from the mechanism used to publish it to a boot environment, instead of centering the interface on a particular installer and target model. This separation is directly inspired by [NixOS RFC 0125](https://github.com/NixOS/rfcs/blob/master/rfcs/0125-bootspec.md). NixOS Bootspec is a machine-readable intermediate representation between a system generation and bootloader management tools. Guix Bootspec adopts the same architectural boundary, while the surrounding bootloader subsystem remains Guix-specific. The name should not be confused with the [Boot Loader Specification](https://uapi-group.org/specifications/specs/boot_loader_specification/) used by some EFI boot managers. As RFC 0125 notes, that specification addresses the representation and discovery of boot entries, rather than the description of a system generation. # Detailed Design ## Scope The stable interface separates four kinds of state: * a Guix Bootspec describes the launch contract of one realized system output; * a boot set describes the generations to publish and the selected generation; * backend configuration describes bootloader-specific policy; and * the deployment environment describes the resources on which a backend acts. Backend configuration languages, presentation options, custom boot entries, chainloading facilities, and installation targets stay outside Bootspec; they belong to backend configuration or deployment. The generic boot set contains Guix system generations. Discovering and managing foreign operating-system boot chains remains outside scope, although individual backends may provide custom or foreign entries as policy. Operation ordering and deployment-resource resolution are implementation details of the backend interface; their internal representation is left open. ## Guix Bootspec Guix Bootspec is the versioned interface between a system generation and bootloader tooling. It is an immutable launch contract for one realized system output: it records the payload semantics Guix has already selected, not the `operating-system` objects from which they were derived or a bootloader representation of that payload. Version 1 is stored as a data-only S-expression at `SYSTEM-PATH/boot-specification`, where `SYSTEM-PATH` is the system store item. Its canonical form is: ```scheme (boot-specification (version 1) (system TARGET-SYSTEM) (label LABEL) (system-path SYSTEM-PATH) (payload (type TYPE) (entry ENTRY) (command-line COMMAND-LINE) (components ((boot-component (file FILE)) …))) (extensions ((boot-extension (id EXTENSION-ID) (required? REQUIRED?) (artifacts (ARTIFACT …)) (data DATA)) …))) ``` A component with an associated command line contains an additional `(command-line COMMAND-LINE)` field after `file`. `extensions` is an empty list when the generation carries no extensions. In version 1, `TARGET-SYSTEM`, `LABEL`, `SYSTEM-PATH`, `ENTRY`, `FILE`, `COMMAND-LINE`, `EXTENSION-ID`, and `ARTIFACT` are strings; `TYPE` is a symbol; and `REQUIRED?` is a boolean. `version` is the exact integer `1`. `components`, `extensions`, and `artifacts` are proper lists. `TARGET-SYSTEM` is the canonical Guix system identifier of the system being booted, such as `x86_64-linux`, `aarch64-linux`, or `i586-gnu`. `LABEL` is the generation's intrinsic human-readable label. Generation number, creation time, and other profile metadata are not part of it. `SYSTEM-PATH` is the absolute store name of the system containing the Bootspec. Bootspec is written while constructing that system output, so the output path can appear in the serialized datum and in command lines without becoming an input dependency of the system. `ENTRY`, every component `FILE`, and every extension `ARTIFACT` are absolute store file names. Every store item named through one of these fields is reachable from the system closure. This permits generic boot tooling to retain and copy boot artifacts without interpreting payload- or extension-specific data. `COMMAND-LINE` is the exact command-line text presented to the corresponding entry or component, excluding any terminating NUL required by the payload protocol. It is not an `argv` representation. Generic bootloader code does not parse it. A backend may quote the string as required by its own configuration syntax, but does not change the text delivered to the payload. NUL is invalid in a Bootspec command line. Version 1 treats Linux command lines as UTF-8 text. This is a Guix Bootspec convention, not a claim that the Linux boot protocol itself assigns a character encoding. A `multiboot1` payload accepts only ASCII, as required by Multiboot 1. For a component, an omitted command line and an empty command line are distinct. The component sequence is ordered. Its order is part of the payload semantics. `TYPE` defines the semantics of the entry and components. Version 1 defines two payload types: * `linux` identifies a Linux kernel image. Its components are zero or more generation-owned initramfs fragments in the listed order. A Linux component has no component command line. The generation initramfs is their bytewise concatenation in that order. A backend which accepts a single initramfs buffer performs that concatenation; a backend which can name multiple initrds can pass them separately while preserving the same ordering. The [Linux initramfs buffer format](https://docs.kernel.org/driver-api/early-userspace/buffer-format.html) explicitly permits concatenated archives, and GRUB's [`initrd`](https://gnu.org/software/grub/manual/grub/html_node/initrd.html) command implements the same ordered composition. * `multiboot1` identifies a Multiboot 1 entry image. Its components are Multiboot modules in the listed order. A component command line is the string associated with that module by Multiboot 1. Entry and component command lines must be representable as the ASCII strings required by Multiboot 1. The payload type identifies payload semantics, not a bootloader implementation. Any mechanism is valid for a `linux` payload if it preserves the generation's entry image, command line, and ordered initramfs contribution. Active backend policy may supplement that contribution as described under bootloader configuration, while leaving the Bootspec payload unchanged. A `multiboot1` payload likewise describes the contract expected by the entry image, independent of GRUB's `multiboot` and `module` commands. The [Multiboot 1 specification]( https://www.gnu.org/software/grub/manual/multiboot/multiboot.html ) defines that contract. Bootspec contains no backend syntax. In particular, Multiboot module command lines contain the strings intended for the modules; GRUB-specific quoting belongs to the GRUB backend. ### Extensions Version 1 has a closed core schema and one extension point: the `extensions` field. Extensions add generation-associated information without adding fields to the core Bootspec grammar. `EXTENSION-ID` identifies one extension contract. It is a lower-case ASCII, reverse-domain-style name consisting of at least two dot-separated labels. Each label contains letters, digits, or hyphens and does not begin or end with a hyphen. For example, `org.gnu.guix.boot.device-tree` is valid. The reverse-domain prefix identifies the namespace owner, and `org.gnu.guix` is reserved for Guix. An identifier's semantics are stable. Incompatible semantics require a new identifier. `REQUIRED?` states whether correct realization of the generation requires the extension. Requiredness is a property of the extension definition, not of an individual instance. The public construction API associates each extension type with a fixed identifier, fixed requiredness, and validators for its artifacts and data. An extension instance supplies that type, its artifacts, and its data; ordinary construction takes neither an independent identifier nor a requiredness value. The serialized `required?` field repeats the type's requiredness so that an unaware consumer can determine whether realization may continue. A consumer which knows the extension verifies that the serialized value agrees with the extension definition. If ignoring any conforming instance of an extension can make the generation's boot semantics incorrect, that extension type is required. Version 1 has no per-instance or conditional requiredness. A contract whose instances genuinely have different compatibility semantics uses distinct extension identifiers instead of a user-selectable criticality bit. A required extension describes information that has to be processed to preserve the generation's boot semantics. A Bootspec with an unknown required extension remains parsable, but a deployment unable to satisfy it cannot realize that system. This check occurs before boot-state changes are performed. An unknown non-required extension can be ignored for realization. A recognized extension with malformed data is invalid regardless of requiredness. Extensions are additive. They do not replace, reinterpret, suppress, or reorder core Bootspec fields or payload components, alter the meaning of a payload type, or override another extension. Information which changes those semantics belongs in the core Bootspec or payload model instead. Boot-set metadata, bootloader policy, and deployment resources are likewise outside the extension mechanism. `ARTIFACT` lists the immutable store files required by the extension. Every store object whose contents are needed to interpret or realize an extension is listed there even when its path also appears in `DATA`. This list is part of the generic closure contract, so unknown extensions can keep their required store objects live without teaching generic code how to inspect their data. A known extension validates the relationship between its `artifacts` and `data`. `DATA` is one data-only S-expression composed recursively of booleans, exact integers, strings, symbols, bytevectors, and proper lists of those values. It is validated further by the extension type which defines it. Generic readers preserve unknown extension data and artifact lists without interpreting them. Extension order has no semantic meaning, duplicate identifiers are invalid, and the canonical writer orders extensions by identifier. The extension mechanism defines no generic dependency or precedence relation; a set of requirements which cannot be satisfied together cannot be realized. No process-global registry is required. Guix and external channels can provide first-class extension types and realization support for the identifiers they implement. ### Command-line construction Bootspec command lines are finalized during construction of the realized system output. They are not subsequently rewritten by deployment. This distinction matters for system images. Guix already specializes the `operating-system` used for an image before constructing its system output: `operating-system-for-image` assigns the image's deterministic root UUID and root file-system type, and the resulting system is built from that specialized value. The image receives its own Bootspec and root arguments. A launch requirement which genuinely changes with the system being built belongs in that system's Bootspec; topology which merely changes how the same store artifacts are reached belongs to deployment. Root-file-system data is an input to payload construction, not a Bootspec field. For a `linux` payload, Guix converts it to the corresponding Linux root arguments. For a `multiboot1` GNU system, Guix constructs the GNU Mach root-device argument. The resulting command line is stored in Bootspec. Payload construction also adds contextual Guix arguments, including `gnu.system` and `gnu.load`. Their values use `SYSTEM-PATH` directly. The current `system-service-type` assembles the top-level system with `file-union`, while the current `parameters` file is built separately. The Bootspec implementation changes only this final assembly step: the system builder creates the directory entries and writes `boot-specification` after its output path is bound. As with ordinary Guix builders, the output path is supplied by the store as the derivation output; embedding that string in the output introduces no self-dependency. System-generated arguments retain their current precedence over conflicting user-supplied arguments. Hurd bootstrap expressions stored in component command lines are the expressions delivered to the component. Any quoting required to embed them in GRUB syntax is applied by the GRUB backend. ### Store topology Bootspec names immutable store artifacts and leaves their reachability to the deployment layer. The current `<boot-parameters>` fields for store device, mount point, Btrfs directory prefix, and encrypted devices move to the deployment environment. Their values describe deployment topology, and some of their current semantics are GRUB-specific. For example, `store-device` filters device file names that GRUB cannot use directly. The active backend resolves the store paths named by each Bootspec against the current deployment environment. ### Versioning and validation Version 1 fixes the core field grammar and the meaning of those fields. A reader rejects an unsupported Bootspec version, unknown or duplicate core fields, malformed values, duplicate extension identifiers, and trailing data after the Bootspec datum. The canonical writer emits core fields in the order shown above and extensions in identifier order. An incompatible change to the core grammar or to the meaning of an existing core field uses a new Bootspec version. Independent extensions use stable identifiers of their own, without separate version fields. The set of payload types is also part of the versioned core contract: version 1 contains only `linux` and `multiboot1`, and any other payload type is invalid. Adding another launch protocol requires a new Bootspec version. Payload semantics stay out of an unnamespaced secondary extension mechanism; the lighter-weight extension facility remains available for orthogonal generation requirements. Newer Bootspec versions coexist with older ones. A writer emits the oldest version whose contract can represent the generation without loss. Thus a future payload protocol introduced in version 2 need not turn otherwise unchanged `linux` or `multiboot1` generations into version 2 documents. Consumers can support several Bootspec versions concurrently. The file is UTF-8 text containing exactly one datum. Its syntax is the [R7RS external representation](https://standards.scheme.org/r7rs-html5/index.html) restricted to the data types admitted by Bootspec: proper lists, strings, symbols, booleans, exact integers, and bytevectors. The canonical writer emits `#t` and `#f`, base-10 integers, `#u8(…)` bytevectors with decimal octets, and proper-list notation; it omits datum labels, dotted lists, vectors, characters, abbreviations, comments, alternate radix or exactness prefixes, and alternate boolean spellings. Core symbols use lower-case ASCII identifiers. Readers reject values outside this subset even when an underlying Scheme reader would accept them. Reading a Bootspec never evaluates the datum. The in-memory Bootspec representation mirrors the serialized model: a boot specification, a boot payload, boot components, and boot extensions. Public Bootspec fields have the semantics specified above. Constructor and procedure names that do not affect those semantics are ordinary implementation details. ## Bootable systems and boot sets A *bootable system* is the normalized in-memory value consumed internally by the bootloader subsystem. It is not a serialized compatibility format or a separate public API. It provides the system path, label, normalized payload type, entry, command line, ordered components, generation extensions when present, and profile metadata needed for selection and presentation. For a generation containing Bootspec v1, these launch fields come directly from that Bootspec. Historical generations are converted from their legacy `parameters` data into equivalent launch information without manufacturing a Bootspec v1 datum; their extension set is empty. Values absent from historical data, such as an explicit target-system identifier, remain unknown unless the existing compatibility path can derive them without changing legacy behavior. Backends consume these normalized launch fields and make no assumption that every bootable system originated as a complete Bootspec v1 value. Profile metadata includes the generation number and can include the generation creation time. A *boot set* is the collection of bootable systems to be published by a backend. It identifies the selected system and can identify the currently booted system when that distinction is useful. For generation switching, the default boot set contains every generation still present in the system profile, with the requested generation selected. During a reconfiguration, it contains the candidate system as the selected system together with every generation currently retained by the profile; once the profile switch succeeds, the candidate becomes the new profile generation. This matches the historical generations for which current Guix constructs boot entries while also letting bootloader deployment be validated before the candidate has to be treated as an existing profile generation. The selected system is always a member of the boot set. Deleting a system-profile generation removes it from subsequent boot sets. A future explicit policy for publishing a smaller subset of unselected retained generations can be added independently, but backends never impose such a policy silently. No system generation contains a snapshot of the other generations that existed when it was created. A backend maps the boot set to its own representation. Generic Guix code does not require the selected system to be the first menu entry, one menu entry per generation, or a menu at all. Accordingly, generic `<menu-entry>` objects are not the representation of Guix system generations. Backends can define entry types for backend-specific features such as custom or foreign boot entries. ## Bootloader configuration Bootloader configuration is backend-specific. A configured bootloader associates a backend with configuration understood by that backend. Configuration for one backend is not interpreted by another. Backend-specific options do not extend a universal configuration record. Generic Guix code identifies the backend and supplies its configuration when deployment is requested. The exact Scheme representation of that association is left open. Backend policy can supplement a generation payload where the backend explicitly provides such a facility. Policy-owned additions are not part of generation identity and follow the active policy when another generation is selected. They may neither remove nor reorder generation-owned payload components, nor rewrite the Bootspec command line. The existing GRUB `extra-initrd` maps cleanly to this rule: its additional initrd can precede the generation initramfs as backend policy without becoming a Bootspec component. ## Bootloader backend interface A bootloader backend is defined by one backend-specific configuration and the logic needed to realize boot state from: * that configuration; * a boot set; and * a deployment environment. The stable contract defines semantics, not a scheduler API. A backend may lower realization to one operation or several operations performed as their resources become available. The representation used to schedule those operations is internal to the implementation. This allows filesystem installation, configuration publication, and raw-device installation to share one backend definition without making a generic deployment graph part of the public interface. The interface requires neither a generated configuration file nor explicit menu entries nor a single installation target. The backend translates Bootspec payloads into the bootloader's representation; root arguments, `gnu.system`, `gnu.load`, and payload-specific component command lines have already been constructed. The deployment environment provides resources in generic system and image terms. The backend translates those resources into any paths, device identifiers, bootloader syntax, or on-disk representation it requires. Backend realization can also add auxiliary material owned by backend policy or by the deployment strategy when that material is needed to present the generation payload. Such material stays outside Bootspec and cannot be used to remove or reorder generation-owned components or rewrite the generation command line. The GRUB path which injects its LUKS helper into the initramfs when the store is encrypted is one existing instance of this distinction. Before deployment changes boot state, realization performs the capability and resource checks that can be established in advance for every system in the boot set, including payload-type and required-extension support. The boot set is accepted or rejected as a whole: backends cannot drop systems they fail to realize, and an unsupported system causes preflight failure. Runtime I/O or installer failures can still occur after preflight. Any decision to publish fewer generations is made when the boot set is constructed. Backends preserve the installation guarantees of the boot mechanisms they implement. Configuration files and similar resources are staged and replaced atomically where the current implementation already does so and the underlying filesystem permits it. Operations which are inherently non-transactional today, such as board-specific raw-device writes, are not made less reliable by the replacement interface. Redeploying the same boot set with another selected system uses the currently active policy realization. Historical backend configuration is not read from the selected system's Bootspec. Objects used only to resolve resources, order operations, or integrate with image construction are not part of the public backend API unless separately specified. ## Deployment and system images Live deployment and system-image construction use the same backend definition and payload semantics. They need not invoke one procedure at the same lifecycle point. The two environments can expose different concrete resources and different lifecycle points. During image construction, for example, individual filesystems can exist before the complete disk image. This lets a backend perform operations as the required resources become available, without a separate disk-image installer API. Generic system and image code supplies deployment resources and lifecycle information. Environment adapters can ask the backend to perform the portions of realization whose resources are available at a given lifecycle point. Bootloader-specific installation logic remains in the backend or shared bootloader support code, keeping bootloader syntax and board-specific write offsets out of generic image code. A deployment resource can have different concrete representations in different environments while retaining the same semantic role. This includes the location and accessibility of store paths named by Bootspec and policy-owned resources which are intentionally outside the store. The internal representation of deployment resources is not part of the stable interface. ## Generation switching and bootloader policy System-generation selection and bootloader policy are independent state. Selecting another system generation keeps the active bootloader policy and redeploys the current boot set with the requested system selected. A historical generation therefore has no effect on which backend or configuration is active. Restoring historical bootloader policy is a separate operation. Guix persists bootloader-policy history independently from system-profile history. Backend configuration needs no general-purpose serialization format. When a policy is created, Guix lowers the configured backend to an immutable *policy realization* in the store. That realization captures the backend code, lowered configuration, and immutable store references needed to deploy future boot sets under the policy without capturing a particular boot set. References to intentionally mutable, secret-bearing, or deployment-local resources remain references instead of being copied into the store; deployment resolves and checks them again. The store is not used as confidential storage. Generation switching can supply the then-current boot set without re-evaluating an old Scheme configuration or requiring the channel revision which originally constructed the policy. This adapts the useful retained-installer idea from the [2025 bootloader-subsystem GCD draft](https://issues.guix.gnu.org/79248) without freezing the generations that existed when the policy was created. Policy history identifies these lowered realizations. Explicit restoration selects one from this history; no system generation owns or identifies a bootloader policy. Restoration is subject to ordinary preflight: an old policy can be incompatible with a newer payload or required extension, and a referenced deployment-local resource may no longer exist. A retained policy realization must remain invocable for as long as its history entry is retained. Realizations therefore use a small internal invocation contract through which the current boot set and deployment environment are supplied. The implementation retains whatever runner or compatibility support is needed by existing history entries. This contract is not a public backend extension API, but its implementation cannot be changed in a way that strands retained policy history without migrating or retaining support for that history. A candidate policy realization and its closure are made GC-reachable before deployment begins. The previously active policy remains reachable throughout deployment. Activating policy has a separate commit point: the candidate is recorded as active only after backend deployment succeeds. A failed deployment therefore leaves the previous policy active and keeps its closure reachable. Policies retained for explicit historical restoration remain reachable for as long as they remain in policy history. The replacement provides an explicit policy-restoration operation before the legacy interface is removed. Its command-line syntax, the small persistent index used to name policy realizations, and the history-retention policy are left unspecified. ## User-facing configuration Bootloader selection remains part of the `operating-system` declaration. The selected backend carries backend-specific configuration instead of extending a shared `<bootloader-configuration>` record with fields for all backends. Low-level deployment machinery is not part of the user-facing interface unless a backend chooses to expose some part of it as configuration. Exact constructor and record names remain unspecified beyond the semantic interfaces above. ## Migration and implementation The bootloader subsystem has one coherent user-visible cutover. Preparatory internal changes can land incrementally with the current subsystem still active. The cutover occurs only when Bootspec generation, compatibility conversion, policy realization, generation switching, system-image integration, and all existing in-tree backends operate through the replacement without loss of supported behavior. The transition does not depend on adding new bootloaders. It is complete when the bootloader choices already provided by Guix operate through the new subsystem without loss of supported behavior. Before the cutover, automated coverage includes: * canonical Bootspec writing, parsing, validation, and rejection of malformed versions, payloads, and extensions; * conversion of representative historical `parameters` versions without manufacturing unavailable Bootspec fields; * equivalence of the Linux and GNU Mach/Hurd launch contracts before and after moving semantic construction out of backend renderers; * the supported live-deployment and system-image paths of every in-tree backend, including raw-device U-Boot installers where they can be exercised in images; * preservation of atomic replacement or staging on paths which provide it today; * rejection of an unsupported boot set during preflight without boot-state writes; and * failure injection around policy deployment and GC-root transitions, including preservation of the previously active realization after a failed deployment. Hardware-specific paths which cannot be exercised reliably in automated tests remain subject to targeted hardware validation before the legacy implementation is removed. The new API is the implementation used by in-tree backends. The existing public API remains as a deprecated compatibility interface where practical. Old bindings are defined in terms of the new API when this remains reasonably simple; narrowly scoped compatibility code can preserve old behavior otherwise. Backend-specific behavior currently exposed through the generic configuration, such as GRUB's `extra-initrd`, is translated to the corresponding backend policy instead of being promoted into Bootspec. System generations created before the refactor contain `parameters` rather than `boot-specification`. A shared compatibility path converts that data into a bootable system directly. It can use the enclosing system path to recover contextual arguments omitted by historical `parameters` files, without claiming that the result is a Bootspec v1 value when legacy data lacks fields required by that format. Individual backends do not implement this conversion. Compatibility bindings use `(guix deprecation)` where appropriate and follow the [Guix Deprecation Policy](https://guix.gnu.org/manual/devel/en/html_node/Deprecation-Policy.html). Documentation for deprecated interfaces identifies their replacements. The bindings can be removed after the applicable deprecation period. ## Alternatives considered ### Keep the current interface The existing subsystem can continue to add special cases for bootloaders which do not fit its configuration-file and menu-entry model. This avoids a large migration in the short term but preserves the structural problem that motivated the previous rewrites. It also leaves the representation of a Guix system coupled to concepts inherited from GRUB. ### Target-specific installers The [2025 bootloader-subsystem GCD draft](https://issues.guix.gnu.org/79248) proposed installer objects associated with typed targets. That design recognizes that different bootloaders operate on different resources and that image construction exposes those resources at different stages. It also makes a particular representation of deployment central to the public bootloader interface before several implementations have established that it is the right shared abstraction. Backends need to operate on different deployment resources, but target-specific installer callbacks are not the stable architectural boundary. ### A universal boot entry The existing `<menu-entry>` could instead be generalized until every bootloader can translate it into its own representation. Such an interface either describes only the features common to every backend or accumulates backend-specific alternatives over time. Bootspec instead describes the Guix system itself. Whether a backend creates a menu entry is a backend decision. ### Separate live and image interfaces Guix could continue to expose unrelated procedures for ordinary bootloader installation and disk-image installation. Each procedure stays simple, but the split duplicates the deployment model of one bootloader and encourages generic image code to contain bootloader-specific knowledge. The proposed interface instead describes one backend whose deployment can be realized in either environment. ### An opaque deployment program A configured bootloader could be represented solely by a program invoked whenever boot state needs to change. Such a program provides considerable implementation freedom, but by itself says nothing about the generation data it consumes, generation-switching semantics, or the relationship between ordinary deployment and image construction. For that reason, an opaque program is not the public backend interface. A lowered program is nevertheless useful internally as the persistent policy realization described above, where its input contract is already defined by the boot set and deployment environment. ### A generic deployment graph The public backend interface could expose arbitrary deployment actions and their dependencies as a general graph. A graph is expressive, but would also require generic semantics for ordering, partial failure, cleanup, resource ownership, and conflicts between privileged operations. That machinery is unnecessary as a public abstraction for this subsystem. Dependency information can be used internally where needed. ### No Bootspec extension mechanism Bootspec could require a new format version for every new generation-associated requirement not represented by the core schema. The format stays small, but unrelated evolution then shares one global compatibility boundary. A consumer would have to reject an otherwise familiar generation merely because it uses an independent feature irrelevant to that consumer. The extension mechanism limits that incompatibility to generations which carry a required extension. Changes to the common Bootspec contract use the Bootspec version. ### Unrestricted namespaced extensions [NixOS RFC 0125](https://github.com/NixOS/rfcs/blob/master/rfcs/0125-bootspec.md) allows namespaced extension data so consumers can attach information not covered by the core schema. Guix Bootspec retains namespacing but gives extensions a fixed requiredness and typed construction API. This makes unknown-extension behavior explicit and validates known extension data without turning the extension field into another place for arbitrary backend policy. The generic mechanism intentionally provides no override, precedence, dependency, or per-instance criticality rules. ## Cost of Reverting The expected compatibility impact of the refactor is level 2. Existing Scheme configurations continue to work through the deprecated compatibility interface, but generation switching intentionally changes in one uncommon case: selecting an older system generation after the active bootloader policy has changed keeps the current policy instead of reconstructing policy from generation data. This primarily affects advanced configurations which have changed bootloaders between system generations. The compatibility layer follows the Guix Deprecation Policy, so removal of the legacy public bindings is a later and separately communicated incompatibility. Until then, old bindings remain available while the in-tree implementation uses the new subsystem. Bootspec introduces a second compatibility boundary. Once a Bootspec version is consumed by external tools or third-party backends, its core semantics cannot be changed incompatibly; such changes require a new version. Published extension identifiers are compatibility commitments of their own: identifier meaning is stable, and required extensions must remain satisfiable for historical generations that use them. The same applies to the public backend contract once downstream channels begin implementing it. The normalized `bootable system`, deployment scheduler, and concrete policy-realization representation are not public extension APIs. Retained policy realizations nevertheless carry an internal compatibility obligation: their invocation support must remain available for as long as policy history promises that they can be restored. Reverting the implementation during the deprecation period remains possible by restoring the old subsystem behind the compatibility surface. After the legacy API is removed, a reversion would itself require another public API transition and continued support for any published Bootspec versions. # Drawbacks and Open Issues Bootspec v1 has a smaller scope than `<boot-parameters>`. Porting the existing backends requires moving payload construction out of backend rendering. The Hurd path is the sharpest case: GNU Mach root-device selection and semantic Multiboot module command lines currently live partly in the GRUB generator, while GRUB quoting is mixed into stored module arguments. The refactor must separate those responsibilities without changing the resulting Hurd bootstrap. A self-contained Bootspec changes construction of the top-level system output. The current `parameters` file is built independently and the final system is a `file-union`. The replacement final builder must instead create those links and write Bootspec after the output path is known. The construction change is localized; it is not an unresolved data dependency. Because it affects a central system derivation, it needs dedicated reproducibility and substitution tests. Backend realization must nevertheless cover several different operations already in Guix. Extlinux installs files and boot code, U-Boot backends write board-specific images at fixed disk offsets or install files for later flashing, and system images expose partitions before the final disk image exists. GRUB also has separate live and image paths today. The semantic boundary is now fixed—one backend definition may lower to several lifecycle-specific operations—while the scheduler and concrete resource records remain internal. Porting all existing in-tree backends is the test that this internal model is expressive enough before the legacy implementation is removed. The replacement must preserve atomic replacement and staging on paths which provide them today; raw-device installers retain the same power-loss and partial-write limitations they already have. Independent bootloader-policy history introduces persistent state and additional GC roots outside the system profile. Lowered policy realizations avoid requiring arbitrary backend configuration to remain serializable or re-evaluable, but the persistent index, internal invocation contract, and history-retention policy need implementation and compatibility tests. The implementation must keep each retained realization and its closure reachable for as long as the corresponding history entry can be restored. Mutable deployment-local resources cannot be made reproducible by GC rooting; restoration of a policy which refers to one can fail if that resource has disappeared or changed. The default boot set preserves every retained rollback generation. Consequently, changing to a backend which cannot realize one of those generations is rejected even if the selected generation itself is supported. Deleting the incompatible system generation resolves the conflict today; retaining a generation while suppressing its boot entry would require the separate generic boot-set publication policy left open above. The backend does not make that choice implicitly. The legacy compatibility surface includes `<menu-entry>`, which GRUB and Extlinux interpret differently and for which some fields are GRUB-specific. Deprecated configurations should translate to backend-specific equivalents when straightforward. Other legacy combinations can use narrowly scoped adapters; the old generic entry type does not become part of the replacement core. Historical `parameters` files do not contain every value available to Bootspec v1, including the explicit target `system`, and older parameter versions contain still less information. Compatibility conversion produces a bootable system directly and preserves the behavior of the existing reader. Missing values are not invented merely to manufacture a literal v1 Bootspec. The extension mechanism reduces format-version churn but creates a narrower compatibility commitment for each published identifier. Code can enforce the identifier syntax, fixed requiredness, artifact closure declarations, data validation, duplicate rejection, and unknown-extension behavior; it cannot prove that an extension was correctly classified as required or that the datum belongs in Bootspec instead of backend policy. That architectural judgment remains part of review when an extension type is introduced. Required extensions remain exceptional because each one narrows the set of implementations capable of realizing generations which use it. Finally, the refactor changes the recovery path of every supported Guix System bootloader. In-tree U-Boot backends include board-specific raw-disk writes that are difficult to validate fully without representative hardware, and a broken bootloader cannot necessarily be repaired through an ordinary system rollback. Preserving all existing bootloader choices is a merge criterion, but automated tests cannot replace targeted image and hardware validation.
haiii!! i've been poking at guix's bootloader subsystem for a while, and ended up writing a gcd about it :3 the main problem is that the generic bootloader interface is still very heavily shaped around grub: generated configuration files, menu entries, installer callbacks, etc. this gets especially awkward for generation switching, system images, and bootloaders which don't really fit that model. the draft introduces guix bootspec, which describes the launch contract of a system generation independently from whichever bootloader happens to publish it. it also separates the boot set, backend-specific policy, and deployment environment, so generation switching doesn't have to reconstruct bootloader state from old generation data anymore. the draft is attached. i'm looking for a sponsor (or sponsors), and would especially appreciate people willing to poke holes in the bootspec/backend boundary before the discussion period starts ^w^ thanks!! -- coopi lost: one train of thought. if found, please return.
