Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support external bootloader backends (RFC-0125) #172237

Merged
merged 23 commits into from
Dec 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
942dcd2
nixos/activation/bootspec: init bootspec support (RFC-0125)
cole-h Jul 28, 2021
83d06ce
nixos/boot/external: init
cole-h Aug 5, 2021
6c0e4e8
nixos/activation/bootspec: embed the entire contents of specialisatio…
grahamc May 16, 2022
e9c85d6
nixos/activation/bootspec: embed the document into a bootspec subdir
grahamc May 18, 2022
e69c37e
nixos/activation: don't generate bootspec for containers
K900 Aug 12, 2022
ee27291
nixos/activation/bootspec: fix slurping specialisation bootspecs
RaitoBezarius Nov 30, 2022
9a431a5
nixos/activation/bootspec: add @raitobezarius as a code-owner
RaitoBezarius Nov 30, 2022
348ba1b
nixos/activation/bootspec: module-ify
RaitoBezarius Nov 30, 2022
092e6d3
nixos/tests/bootspec: init
RaitoBezarius Nov 30, 2022
980f501
nixos/boot/external: add @raitobezarius as maintainer and allow initr…
RaitoBezarius Nov 30, 2022
9832e3e
nixos/activation/bootspec: remove SB extension example in Cue schema
RaitoBezarius Nov 30, 2022
11dfbee
nixos/activation/bootspec: add bootspec chapter in NixOS manual
RaitoBezarius Nov 30, 2022
680369e
nixos/activation/bootspec: add some comments to explain the delicate …
RaitoBezarius Nov 30, 2022
ad6ea54
nixos/boot/external: DocBook -> Markdown
cole-h Dec 2, 2022
cc63293
nixos/boot/external: fixup typo in generated docs, regenerate docs
cole-h Dec 2, 2022
97f657c
nixos/activation/bootspec: DocBook -> Markdown, add description for e…
cole-h Dec 2, 2022
38e5089
nixos/activation/bootspec: drop problematic comment, only generate bo…
cole-h Dec 2, 2022
dce9add
nixos/activation/bootspec: refactor the generator script
cole-h Dec 2, 2022
fc88e4c
nixos/boot/external: drop duplicated external bootloader documentation
cole-h Dec 4, 2022
b37cee3
bootspec: init at unstable-2022-12-05
cole-h Dec 5, 2022
5af481f
nixos/activation/bootspec: fixup improper $out substitution
cole-h Dec 5, 2022
6eb04c5
nixos/activation/bootspec: bootspec owners also own the cue file
cole-h Dec 5, 2022
aac4134
nixos/tests/bootspec: add EFI support for GRUB test
RaitoBezarius Dec 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
/nixos/doc/manual/man-nixos-option.xml @nbp
/nixos/modules/installer/tools/nixos-option.sh @nbp
/nixos/modules/system @dasJ
/nixos/modules/system/activation/bootspec.nix @grahamc @cole-h @raitobezarius
/nixos/modules/system/activation/bootspec.cue @grahamc @cole-h @raitobezarius

# NixOS integration test driver
/nixos/lib/test-driver @tfc
Expand Down
36 changes: 36 additions & 0 deletions nixos/doc/manual/development/bootspec.chapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Experimental feature: Bootspec {#sec-experimental-bootspec}

Bootspec is a experimental feature, introduced in the [RFC-0125 proposal](https://github.com/NixOS/rfcs/pull/125), the reference implementation can be found [there](https://github.com/NixOS/nixpkgs/pull/172237) in order to standardize bootloader support
and advanced boot workflows such as SecureBoot and potentially more.

You can enable the creation of bootspec documents through [`boot.bootspec.enable = true`](options.html#opt-boot.bootspec.enable), which will prompt a warning until [RFC-0125](https://github.com/NixOS/rfcs/pull/125) is officially merged.

## Schema {#sec-experimental-bootspec-schema}

The bootspec schema is versioned and validated against [a CUE schema file](https://cuelang.org/) which should considered as the source of truth for your applications.

You will find the current version [here](../../../modules/system/activation/bootspec.cue).

## Extensions mechanism {#sec-experimental-bootspec-extensions}

Bootspec cannot account for all usecases.

For this purpose, Bootspec offers a generic extension facility [`boot.bootspec.extensions`](options.html#opt-boot.bootspec.extensions) which can be used to inject any data needed for your usecases.

An example for SecureBoot is to get the Nix store path to `/etc/os-release` in order to bake it into a unified kernel image:

```nix
{ config, lib, ... }: {
boot.bootspec.extensions = {
"org.secureboot.osRelease" = config.environment.etc."os-release".source;
};
}
```

To reduce incompatibility and prevent names from clashing between applications, it is **highly recommended** to use a unique namespace for your extensions.

## External bootloaders {#sec-experimental-bootspec-external-bootloaders}

It is possible to enable your own bootloader through [`boot.loader.external.installHook`](options.html#opt-boot.loader.external.installHook) which can wrap an existing bootloader.

Currently, there is no good story to compose existing bootloaders to enrich their features, e.g. SecureBoot, etc. It will be necessary to reimplement or reuse existing parts.
1 change: 1 addition & 0 deletions nixos/doc/manual/development/development.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<xi:include href="../from_md/development/sources.chapter.xml" />
<xi:include href="../from_md/development/writing-modules.chapter.xml" />
<xi:include href="../from_md/development/building-parts.chapter.xml" />
<xi:include href="../from_md/development/bootspec.chapter.xml" />
<xi:include href="../from_md/development/what-happens-during-a-system-switch.chapter.xml" />
<xi:include href="../from_md/development/writing-documentation.chapter.xml" />
<xi:include href="../from_md/development/nixos-tests.chapter.xml" />
Expand Down
73 changes: 73 additions & 0 deletions nixos/doc/manual/from_md/development/bootspec.chapter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-experimental-bootspec">
<title>Experimental feature: Bootspec</title>
<para>
Bootspec is a experimental feature, introduced in the
<link xlink:href="https://github.com/NixOS/rfcs/pull/125">RFC-0125
proposal</link>, the reference implementation can be found
<link xlink:href="https://github.com/NixOS/nixpkgs/pull/172237">there</link>
in order to standardize bootloader support and advanced boot
workflows such as SecureBoot and potentially more.
</para>
<para>
You can enable the creation of bootspec documents through
<link xlink:href="options.html#opt-boot.bootspec.enable"><literal>boot.bootspec.enable = true</literal></link>,
which will prompt a warning until
<link xlink:href="https://github.com/NixOS/rfcs/pull/125">RFC-0125</link>
is officially merged.
</para>
<section xml:id="sec-experimental-bootspec-schema">
<title>Schema</title>
<para>
The bootspec schema is versioned and validated against
<link xlink:href="https://cuelang.org/">a CUE schema file</link>
which should considered as the source of truth for your
applications.
</para>
<para>
You will find the current version
<link xlink:href="../../../modules/system/activation/bootspec.cue">here</link>.
</para>
</section>
<section xml:id="sec-experimental-bootspec-extensions">
<title>Extensions mechanism</title>
<para>
Bootspec cannot account for all usecases.
</para>
<para>
For this purpose, Bootspec offers a generic extension facility
<link xlink:href="options.html#opt-boot.bootspec.extensions"><literal>boot.bootspec.extensions</literal></link>
which can be used to inject any data needed for your usecases.
</para>
<para>
An example for SecureBoot is to get the Nix store path to
<literal>/etc/os-release</literal> in order to bake it into a
unified kernel image:
</para>
<programlisting language="bash">
{ config, lib, ... }: {
boot.bootspec.extensions = {
&quot;org.secureboot.osRelease&quot; = config.environment.etc.&quot;os-release&quot;.source;
};
}
</programlisting>
<para>
To reduce incompatibility and prevent names from clashing between
applications, it is <emphasis role="strong">highly
recommended</emphasis> to use a unique namespace for your
extensions.
</para>
</section>
<section xml:id="sec-experimental-bootspec-external-bootloaders">
<title>External bootloaders</title>
<para>
It is possible to enable your own bootloader through
<link xlink:href="options.html#opt-boot.loader.external.installHook"><literal>boot.loader.external.installHook</literal></link>
which can wrap an existing bootloader.
</para>
<para>
Currently, there is no good story to compose existing bootloaders
to enrich their features, e.g. SecureBoot, etc. It will be
necessary to reimplement or reuse existing parts.
</para>
</section>
</chapter>
2 changes: 2 additions & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,7 @@
./services/x11/xserver.nix
./system/activation/activation-script.nix
./system/activation/specialisation.nix
./system/activation/bootspec.nix
./system/activation/top-level.nix
./system/boot/binfmt.nix
./system/boot/emergency-mode.nix
Expand All @@ -1256,6 +1257,7 @@
./system/boot/loader/grub/grub.nix
./system/boot/loader/grub/ipxe.nix
./system/boot/loader/grub/memtest.nix
./system/boot/loader/external/external.nix
./system/boot/loader/init-script/init-script.nix
./system/boot/loader/loader.nix
./system/boot/loader/raspberrypi/raspberrypi.nix
Expand Down
17 changes: 17 additions & 0 deletions nixos/modules/system/activation/bootspec.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#V1: {
init: string
initrd?: string
initrdSecrets?: string
kernel: string
kernelParams: [...string]
label: string
toplevel: string
specialisation?: {
[=~"^"]: #V1
}
extensions?: {...}
}

Document: {
v1: #V1
}
124 changes: 124 additions & 0 deletions nixos/modules/system/activation/bootspec.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Note that these schemas are defined by RFC-0125.
# This document is considered a stable API, and is depended upon by external tooling.
# Changes to the structure of the document, or the semantics of the values should go through an RFC.
#
# See: https://github.com/NixOS/rfcs/pull/125
{ config
, pkgs
, lib
, ...
}:
let
cfg = config.boot.bootspec;
children = lib.mapAttrs (childName: childConfig: childConfig.configuration.system.build.toplevel) config.specialisation;
schemas = {
v1 = rec {
filename = "boot.json";
json =
pkgs.writeText filename
(builtins.toJSON
{
v1 = {
kernel = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
kernelParams = config.boot.kernelParams;
initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
label = "NixOS ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";

inherit (cfg) extensions;
};
});

generator =
let
# NOTE: Be careful to not introduce excess newlines at the end of the
# injectors, as that may affect the pipes and redirects.

# Inject toplevel and init into the bootspec.
# This can only be done here because we *cannot* depend on $out
# referring to the toplevel, except by living in the toplevel itself.
toplevelInjector = lib.escapeShellArgs [
"${pkgs.jq}/bin/jq"
''
.v1.toplevel = $toplevel |
.v1.init = $init
''
"--sort-keys"
"--arg" "toplevel" "${placeholder "out"}"
"--arg" "init" "${placeholder "out"}/init"
] + " < ${json}";

# We slurp all specialisations and inject them as values, such that
# `.specialisations.${name}` embeds the specialisation's bootspec
# document.
specialisationInjector =
let
specialisationLoader = (lib.mapAttrsToList
(childName: childToplevel: lib.escapeShellArgs [ "--slurpfile" childName "${childToplevel}/bootspec/${filename}" ])
children);
in
lib.escapeShellArgs [
"${pkgs.jq}/bin/jq"
"--sort-keys"
".v1.specialisation = ($ARGS.named | map_values(. | first | .v1))"
] + " ${lib.concatStringsSep " " specialisationLoader}";
in
''
mkdir -p $out/bootspec

${toplevelInjector} | ${specialisationInjector} > $out/bootspec/${filename}
'';

validator = pkgs.writeCueValidator ./bootspec.cue {
document = "Document"; # Universal validator for any version as long the schema is correctly set.
};
};
};
in
{
options.boot.bootspec = {
enable = lib.mkEnableOption (lib.mdDoc "Enable generation of RFC-0125 bootspec in $system/bootspec, e.g. /run/current-system/bootspec");

extensions = lib.mkOption {
type = lib.types.attrs;
default = { };
description = lib.mdDoc ''
User-defined data that extends the bootspec document.

To reduce incompatibility and prevent names from clashing
between applications, it is **highly recommended** to use a
unique namespace for your extensions.
'';
};

# This will be run as a part of the `systemBuilder` in ./top-level.nix. This
# means `$out` points to the output of `config.system.build.toplevel` and can
# be used for a variety of things (though, for now, it's only used to report
# the path of the `toplevel` itself and the `init` executable).
writer = lib.mkOption {
internal = true;
default = schemas.v1.generator;
};

validator = lib.mkOption {
internal = true;
default = schemas.v1.validator;
};

filename = lib.mkOption {
internal = true;
default = schemas.v1.filename;
};
};

config = lib.mkIf (cfg.enable) {
warnings = [
''RFC-0125 is not merged yet, this is a feature preview of bootspec.
The schema is not definitive and features are not guaranteed to be stable until RFC-0125 is merged.
See:
- https://github.com/NixOS/nixpkgs/pull/172237 to track merge status in nixpkgs.
- https://github.com/NixOS/rfcs/pull/125 to track RFC status.
''
];
Comment on lines +115 to +122
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no native way to turn this rather annoying warning off other than introducing another option?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An other way to turn it off is to merge RFC-0125 :)

};
}
5 changes: 5 additions & 0 deletions nixos/modules/system/activation/top-level.nix
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ let

echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies

${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
${config.boot.bootspec.writer}
${config.boot.bootspec.validator} "$out/bootspec/${config.boot.bootspec.filename}"
''}

${config.system.extraSystemBuilderCmds}
'';

Expand Down
26 changes: 26 additions & 0 deletions nixos/modules/system/boot/loader/external/external.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# External Bootloader Backends {#sec-bootloader-external}

NixOS has support for several bootloader backends by default: systemd-boot, grub, uboot, etc.
The built-in bootloader backend support is generic and supports most use cases.
Some users may prefer to create advanced workflows around managing the bootloader and bootable entries.

You can replace the built-in bootloader support with your own tooling using the "external" bootloader option.

Imagine you have created a new package called FooBoot.
FooBoot provides a program at `${pkgs.fooboot}/bin/fooboot-install` which takes the system closure's path as its only argument and configures the system's bootloader.

You can enable FooBoot like this:

```nix
{ pkgs, ... }: {
boot.loader.external = {
enable = true;
installHook = "${pkgs.fooboot}/bin/fooboot-install";
};
}
```

## Developing Custom Bootloader Backends

Bootloaders should use [RFC-0125](https://github.com/NixOS/rfcs/pull/125)'s Bootspec format and synthesis tools to identify the key properties for bootable system generations.

38 changes: 38 additions & 0 deletions nixos/modules/system/boot/loader/external/external.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{ config, lib, pkgs, ... }:

with lib;

let
cfg = config.boot.loader.external;
in
{
meta = {
maintainers = with maintainers; [ cole-h grahamc raitobezarius ];
# Don't edit the docbook xml directly, edit the md and generate it:
# `pandoc external.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > external.xml`
doc = ./external.xml;
};

options.boot.loader.external = {
RaitoBezarius marked this conversation as resolved.
Show resolved Hide resolved
enable = mkEnableOption (lib.mdDoc "use an external tool to install your bootloader");

installHook = mkOption {
type = with types; path;
description = lib.mdDoc ''
The full path to a program of your choosing which performs the bootloader installation process.

The program will be called with an argument pointing to the output of the system's toplevel.
'';
};
};

config = mkIf cfg.enable {
boot.loader = {
grub.enable = mkDefault false;
systemd-boot.enable = mkDefault false;
supportsInitrdSecrets = mkDefault false;
};

system.build.installBootLoader = cfg.installHook;
};
}
Loading