nixpkgs/nixos/modules/system/activation/bootspec.nix
Raito Bezarius 139bfb5b5e nixos/system/activation/bootspec: generalize the bootspec tooling package
After RFC-0125 implementation, Determinate Systems was pinged multiple
times to transfer the repository ownership of the tooling to a
vendor-neutral repository.

Unfortunately, this never manifested. Additionally, the leadership of
the NixOS project was too dysfunctional to deal with this sort of
problem. It might even still be the case up to this day.

Nonetheless, nixpkgs is about enabling end users to enact their own
policies. It would be better to live in a world where there is one
obvious choice of bootspec tooling, in the meantime, we can live in a
world where people can choose their bootspec tooling.

The Lix forge possess one fork of the Bootspec tooling:
https://git.lix.systems/lix-community/bootspec which will live its own
life from now on.

Change-Id: I00c4dd64e00b4c24f6641472902e7df60ed13b55
Signed-off-by: Raito Bezarius <masterancpp@gmail.com>
2025-04-24 18:22:02 +02:00

148 lines
5.5 KiB
Nix

# 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;
hasAtLeastOneInitrdSecret = lib.length (lib.attrNames config.boot.initrd.secrets) > 0;
schemas = {
v1 = rec {
filename = "boot.json";
json = pkgs.writeText filename (
builtins.toJSON
# Merge extensions first to not let them shadow NixOS bootspec data.
(
cfg.extensions
// {
"org.nixos.bootspec.v1" =
{
system = config.boot.kernelPackages.stdenv.hostPlatform.system;
kernel = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
kernelParams = config.boot.kernelParams;
label = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";
}
// lib.optionalAttrs config.boot.initrd.enable {
initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
}
// lib.optionalAttrs hasAtLeastOneInitrdSecret {
initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
};
}
)
);
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.buildPackages.jq}/bin/jq"
''
."org.nixos.bootspec.v1".toplevel = $toplevel |
."org.nixos.bootspec.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}/${filename}"
]
) children
);
in
lib.escapeShellArgs [
"${pkgs.buildPackages.jq}/bin/jq"
"--sort-keys"
''."org.nixos.specialisation.v1" = ($ARGS.named | map_values(. | first))''
]
+ " ${lib.concatStringsSep " " specialisationLoader}";
in
"${toplevelInjector} | ${specialisationInjector} > $out/${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 "the generation of RFC-0125 bootspec in $system/boot.json, e.g. /run/current-system/boot.json"
// {
default = true;
internal = true;
};
enableValidation = lib.mkEnableOption ''
the validation of bootspec documents for each build.
This will introduce Go in the build-time closure as we are relying on [Cuelang](https://cuelang.org/) for schema validation.
Enable this option if you want to ascertain that your documents are correct
'';
package = lib.mkPackageOption pkgs "bootspec" { };
extensions = lib.mkOption {
# NOTE(RaitoBezarius): this is not enough to validate: extensions."osRelease" = drv; those are picked up by cue validation.
type = lib.types.attrsOf lib.types.anything; # <namespace>: { ...namespace-specific fields }
default = { };
description = ''
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;
};
};
}