depot/third_party/nixpkgs/nixos/modules/system/activation/bootspec.nix
Default email e7ec2969af Project import generated by Copybara.
GitOrigin-RevId: 9b19f5e77dd906cb52dade0b7bd280339d2a1f3d
2024-01-13 09:15:51 +01:00

120 lines
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 (lib.mdDoc "the generation of RFC-0125 bootspec in $system/boot.json, e.g. /run/current-system/boot.json")
// { default = true; internal = true; };
enableValidation = lib.mkEnableOption (lib.mdDoc ''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
''
);
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 = 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;
};
};
}