nixpkgs/nixos/modules/system/activation/top-level.nix
Artturin a34d7b67fd nixos/top-level.nix: add forbiddenDependenciesRegex option
useful for making sure that there's no dev outputs in the system
2022-12-02 21:15:24 +02:00

338 lines
12 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
systemBuilder =
let
kernelPath = "${config.boot.kernelPackages.kernel}/" +
"${config.system.boot.loader.kernelFile}";
initrdPath = "${config.system.build.initialRamdisk}/" +
"${config.system.boot.loader.initrdFile}";
in ''
mkdir $out
# Containers don't have their own kernel or initrd. They boot
# directly into stage 2.
${optionalString config.boot.kernel.enable ''
if [ ! -f ${kernelPath} ]; then
echo "The bootloader cannot find the proper kernel image."
echo "(Expecting ${kernelPath})"
false
fi
ln -s ${kernelPath} $out/kernel
ln -s ${config.system.modulesTree} $out/kernel-modules
${optionalString (config.hardware.deviceTree.package != null) ''
ln -s ${config.hardware.deviceTree.package} $out/dtbs
''}
echo -n "$kernelParams" > $out/kernel-params
ln -s ${initrdPath} $out/initrd
ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out
ln -s ${config.hardware.firmware}/lib/firmware $out/firmware
''}
echo "$activationScript" > $out/activate
echo "$dryActivationScript" > $out/dry-activate
substituteInPlace $out/activate --subst-var out
substituteInPlace $out/dry-activate --subst-var out
chmod u+x $out/activate $out/dry-activate
unset activationScript dryActivationScript
${if config.boot.initrd.systemd.enable then ''
cp ${config.system.build.bootStage2} $out/prepare-root
substituteInPlace $out/prepare-root --subst-var-by systemConfig $out
# This must not be a symlink or the abs_path of the grub builder for the tests
# will resolve the symlink and we end up with a path that doesn't point to a
# system closure.
cp "$systemd/lib/systemd/systemd" $out/init
'' else ''
cp ${config.system.build.bootStage2} $out/init
substituteInPlace $out/init --subst-var-by systemConfig $out
''}
ln -s ${config.system.build.etc}/etc $out/etc
ln -s ${config.system.path} $out/sw
ln -s "$systemd" $out/systemd
echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
echo -n "$nixosLabel" > $out/nixos-version
echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
mkdir $out/bin
export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
chmod +x $out/bin/switch-to-configuration
${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
if ! output=$($perl/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
echo "switch-to-configuration syntax is not valid:"
echo "$output"
exit 1
fi
''}
${config.system.systemBuilderCommands}
echo -n "$extraDependencies" > $out/extra-dependencies
${config.system.extraSystemBuilderCmds}
'';
# Putting it all together. This builds a store path containing
# symlinks to the various parts of the built configuration (the
# kernel, systemd units, init scripts, etc.) as well as a script
# `switch-to-configuration' that activates the configuration and
# makes it bootable.
baseSystem = pkgs.stdenvNoCC.mkDerivation ({
name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
preferLocalBuild = true;
allowSubstitutes = false;
buildCommand = systemBuilder;
inherit (pkgs) coreutils;
systemd = config.systemd.package;
shell = "${pkgs.bash}/bin/sh";
su = "${pkgs.shadow.su}/bin/su";
utillinux = pkgs.util-linux;
kernelParams = config.boot.kernelParams;
installBootLoader = config.system.build.installBootLoader;
activationScript = config.system.activationScripts.script;
dryActivationScript = config.system.dryActivationScript;
nixosLabel = config.system.nixos.label;
inherit (config.system) extraDependencies;
# Needed by switch-to-configuration.
perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
} // config.system.systemBuilderArgs);
# Handle assertions and warnings
failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
baseSystemAssertWarn = if failedAssertions != []
then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
else showWarnings config.warnings baseSystem;
# Replace runtime dependencies
system = foldr ({ oldDependency, newDependency }: drv:
pkgs.replaceDependency { inherit oldDependency newDependency drv; }
) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
in
{
imports = [
../build.nix
(mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
(mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.")
];
options = {
system.boot.loader.id = mkOption {
internal = true;
default = "";
description = lib.mdDoc ''
Id string of the used bootloader.
'';
};
system.boot.loader.kernelFile = mkOption {
internal = true;
default = pkgs.stdenv.hostPlatform.linux-kernel.target;
defaultText = literalExpression "pkgs.stdenv.hostPlatform.linux-kernel.target";
type = types.str;
description = lib.mdDoc ''
Name of the kernel file to be passed to the bootloader.
'';
};
system.boot.loader.initrdFile = mkOption {
internal = true;
default = "initrd";
type = types.str;
description = lib.mdDoc ''
Name of the initrd file to be passed to the bootloader.
'';
};
system.build = {
installBootLoader = mkOption {
internal = true;
# "; true" => make the `$out` argument from switch-to-configuration.pl
# go to `true` instead of `echo`, hiding the useless path
# from the log.
default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
description = lib.mdDoc ''
A program that writes a bootloader installation script to the path passed in the first command line argument.
See `nixos/modules/system/activation/switch-to-configuration.pl`.
'';
type = types.unique {
message = ''
Only one bootloader can be enabled at a time. This requirement has not
been checked until NixOS 22.05. Earlier versions defaulted to the last
definition. Change your configuration to enable only one bootloader.
'';
} (types.either types.str types.package);
};
toplevel = mkOption {
type = types.package;
readOnly = true;
description = lib.mdDoc ''
This option contains the store path that typically represents a NixOS system.
You can read this path in a custom deployment tool for example.
'';
};
};
system.copySystemConfiguration = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
If enabled, copies the NixOS configuration file
(usually {file}`/etc/nixos/configuration.nix`)
and links it from the resulting system
(getting to {file}`/run/current-system/configuration.nix`).
Note that only this single file is copied, even if it imports others.
'';
};
system.systemBuilderCommands = mkOption {
type = types.lines;
internal = true;
default = "";
description = ''
This code will be added to the builder creating the system store path.
'';
};
system.systemBuilderArgs = mkOption {
type = types.attrsOf types.unspecified;
internal = true;
default = {};
description = lib.mdDoc ''
`lib.mkDerivation` attributes that will be passed to the top level system builder.
'';
};
system.forbiddenDependenciesRegex = mkOption {
default = "";
example = "-dev$";
type = types.str;
description = lib.mdDoc ''
A POSIX Extended Regular Expression that matches store paths that
should not appear in the system closure, with the exception of {option}`system.extraDependencies`, which is not checked.
'';
};
system.extraSystemBuilderCmds = mkOption {
type = types.lines;
internal = true;
default = "";
description = lib.mdDoc ''
This code will be added to the builder creating the system store path.
'';
};
system.extraDependencies = mkOption {
type = types.listOf types.package;
default = [];
description = lib.mdDoc ''
A list of packages that should be included in the system
closure but not otherwise made available to users. This is
primarily used by the installation tests.
'';
};
system.replaceRuntimeDependencies = mkOption {
default = [];
example = lib.literalExpression "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]";
type = types.listOf (types.submodule (
{ ... }: {
options.original = mkOption {
type = types.package;
description = lib.mdDoc "The original package to override.";
};
options.replacement = mkOption {
type = types.package;
description = lib.mdDoc "The replacement package.";
};
})
);
apply = map ({ original, replacement, ... }: {
oldDependency = original;
newDependency = replacement;
});
description = lib.mdDoc ''
List of packages to override without doing a full rebuild.
The original derivation and replacement derivation must have the same
name length, and ideally should have close-to-identical directory layout.
'';
};
system.name = mkOption {
type = types.str;
default =
if config.networking.hostName == ""
then "unnamed"
else config.networking.hostName;
defaultText = literalExpression ''
if config.networking.hostName == ""
then "unnamed"
else config.networking.hostName;
'';
description = lib.mdDoc ''
The name of the system used in the {option}`system.build.toplevel` derivation.
That derivation has the following name:
`"nixos-system-''${config.system.name}-''${config.system.nixos.label}"`
'';
};
};
config = {
system.extraSystemBuilderCmds =
optionalString
config.system.copySystemConfiguration
''ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
"$out/configuration.nix"
'' +
optionalString
(config.system.forbiddenDependenciesRegex != "")
''
if [[ $forbiddenDependenciesRegex != "" && -n $closureInfo ]]; then
if forbiddenPaths="$(grep -E -- "$forbiddenDependenciesRegex" $closureInfo/store-paths)"; then
echo -e "System closure $out contains the following disallowed paths:\n$forbiddenPaths"
exit 1
fi
fi
'';
system.systemBuilderArgs = lib.optionalAttrs (config.system.forbiddenDependenciesRegex != "") {
inherit (config.system) forbiddenDependenciesRegex;
closureInfo = pkgs.closureInfo { rootPaths = [
# override to avoid infinite recursion (and to allow using extraDependencies to add forbidden dependencies)
(config.system.build.toplevel.overrideAttrs (_: { extraDependencies = []; closureInfo = null; }))
]; };
};
system.build.toplevel = system;
};
}