push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
{
options,
config,
lib,
pkgs,
...
}:
let
inherit (lib)
mkOption
types
;
systemBuilderArgs = {
activationScript = config.system.activationScripts.script;
dryActivationScript = config.system.dryActivationScript;
};
in
{
options = {
system.activatable = mkOption {
type = types.bool;
default = true;
description = ''
Whether to add the activation script to the system profile.
The default, to have the script available all the time, is what we normally
do, but for image based systems, this may not be needed or not be desirable.
'';
};
system.activatableSystemBuilderCommands = options.system.systemBuilderCommands // {
description = ''
Like `system.systemBuilderCommands`, but only for the commands that are
needed *both* when the system is activatable and when it isn't.
Disclaimer: This option might go away in the future. It might be
superseded by separating switch-to-configuration into a separate script
which will make this option superfluous. See
https://github.com/NixOS/nixpkgs/pull/263462#discussion_r1373104845 for
a discussion.
'';
};
system.build.separateActivationScript = mkOption {
type = types.package;
description = ''
A separate activation script package that's not part of the system profile.
This is useful for configurations where `system.activatable` is `false`.
Otherwise, you can just use `system.build.toplevel`.
'';
};
};
config = {
system.activatableSystemBuilderCommands = ''
echo "$activationScript" > $out/activate
echo "$dryActivationScript" > $out/dry-activate
substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar}
substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar}
chmod u+x $out/activate $out/dry-activate
unset activationScript dryActivationScript
'';
system.systemBuilderCommands = lib.mkIf config.system.activatable config.system.activatableSystemBuilderCommands;
system.systemBuilderArgs = lib.mkIf config.system.activatable (
systemBuilderArgs
// {
toplevelVar = "out";
}
);
system.build.separateActivationScript =
pkgs.runCommand "separate-activation-script"
(
systemBuilderArgs
// {
toplevelVar = "toplevel";
toplevel = config.system.build.toplevel;
}
)
''
mkdir $out
${config.system.activatableSystemBuilderCommands}
'';
};
}

View File

@@ -0,0 +1,331 @@
# generate the script used to activate the configuration.
{
config,
lib,
pkgs,
...
}:
with lib;
let
addAttributeName = mapAttrs (
a: v:
v
// {
text = ''
#### Activation script snippet ${a}:
_localstatus=0
${v.text}
if (( _localstatus > 0 )); then
printf "Activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus"
fi
'';
}
);
systemActivationScript =
set: onlyDry:
let
set' = mapAttrs (
_: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v
) set;
withHeadlines = addAttributeName set';
# When building a dry activation script, this replaces all activation scripts
# that do not support dry mode with a comment that does nothing. Filtering these
# activation scripts out so they don't get generated into the dry activation script
# does not work because when an activation script that supports dry mode depends on
# an activation script that does not, the dependency cannot be resolved and the eval
# fails.
withDrySnippets = mapAttrs (
a: v:
if onlyDry && !v.supportsDryActivation then
v
// {
text = "#### Activation script snippet ${a} does not support dry activation.";
}
else
v
) withHeadlines;
in
''
#!${pkgs.runtimeShell}
source ${./lib/lib.sh}
systemConfig='@out@'
export PATH=/empty
for i in ${toString path}; do
PATH=$PATH:$i/bin:$i/sbin
done
_status=0
trap "_status=1 _localstatus=\$?" ERR
# Ensure a consistent umask.
umask 0022
${textClosureMap id withDrySnippets (attrNames withDrySnippets)}
''
+ optionalString (!onlyDry) ''
# Make this configuration the current configuration.
# The readlink is there to ensure that when $systemConfig = /system
# (which is a symlink to the store), /run/current-system is still
# used as a garbage collection root.
ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
exit $_status
'';
path =
with pkgs;
map getBin [
coreutils
gnugrep
findutils
getent
stdenv.cc.libc # nscd in update-users-groups.pl
shadow
util-linux # needed for mount and mountpoint
];
scriptType =
withDry:
with types;
let
scriptOptions = {
deps = mkOption {
type = types.listOf types.str;
default = [ ];
description = "List of dependencies. The script will run after these.";
};
text = mkOption {
type = types.lines;
description = "The content of the script.";
};
}
// optionalAttrs withDry {
supportsDryActivation = mkOption {
type = types.bool;
default = false;
description = ''
Whether this activation script supports being dry-activated.
These activation scripts will also be executed on dry-activate
activations with the environment variable
`NIXOS_ACTION` being set to `dry-activate`.
it's important that these activation scripts don't
modify anything about the system when the variable is set.
'';
};
};
in
either str (submodule {
options = scriptOptions;
});
in
{
###### interface
options = {
system.activationScripts = mkOption {
default = { };
example = literalExpression ''
{
stdio = {
# Run after /dev has been mounted
deps = [ "specialfs" ];
text =
'''
# Needed by some programs.
ln -sfn /proc/self/fd /dev/fd
ln -sfn /proc/self/fd/0 /dev/stdin
ln -sfn /proc/self/fd/1 /dev/stdout
ln -sfn /proc/self/fd/2 /dev/stderr
''';
};
}
'';
description = ''
A set of shell script fragments that are executed when a NixOS
system configuration is activated. Examples are updating
/etc, creating accounts, and so on. Since these are executed
every time you boot the system or run
{command}`nixos-rebuild`, it's important that they are
idempotent and fast.
'';
type = types.attrsOf (scriptType true);
apply =
set:
set
// {
script = systemActivationScript set false;
};
};
system.dryActivationScript = mkOption {
description = "The shell script that is to be run when dry-activating a system.";
readOnly = true;
internal = true;
default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
defaultText = literalMD "generated activation script";
};
system.userActivationScripts = mkOption {
default = { };
example = literalExpression ''
{ plasmaSetup = {
text = '''
''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
''';
deps = [];
};
}
'';
description = ''
A set of shell script fragments that are executed by a systemd user
service when a NixOS system configuration is activated. Examples are
rebuilding the .desktop file cache for showing applications in the menu.
Since these are executed every time you run
{command}`nixos-rebuild`, it's important that they are
idempotent and fast.
'';
type = with types; attrsOf (scriptType false);
apply = set: {
script = ''
export PATH=
for i in ${toString path}; do
PATH=$PATH:$i/bin:$i/sbin
done
_status=0
trap "_status=1 _localstatus=\$?" ERR
${
let
set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
withHeadlines = addAttributeName set';
in
textClosureMap id withHeadlines (attrNames withHeadlines)
}
exit $_status
'';
};
};
environment.usrbinenv = mkOption {
default = "${pkgs.coreutils}/bin/env";
defaultText = literalExpression ''"''${pkgs.coreutils}/bin/env"'';
example = literalExpression ''"''${pkgs.busybox}/bin/env"'';
type = types.nullOr types.path;
visible = false;
description = ''
The {manpage}`env(1)` executable that is linked system-wide to
`/usr/bin/env`.
'';
};
system.build.installBootLoader = mkOption {
internal = true;
default = pkgs.writeShellScript "no-bootloader" ''
echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2
'';
defaultText = lib.literalExpression ''
pkgs.writeShellScript "no-bootloader" '''
echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2
'''
'';
description = ''
A program that writes a bootloader installation script to the path passed in the first command line argument.
See `pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs`.
'';
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);
};
};
###### implementation
config = {
system.activationScripts.stdio = ""; # obsolete
system.activationScripts.var = ""; # obsolete
systemd.tmpfiles.rules = [
"D /var/empty 0555 root root -"
"h /var/empty - - - - +i"
]
++ lib.optionals config.nix.enable [
# Prevent the current configuration from being garbage-collected.
"d /nix/var/nix/gcroots -"
"L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system"
];
system.activationScripts.usrbinenv =
if config.environment.usrbinenv != null then
''
mkdir -p /usr/bin
chmod 0755 /usr/bin
ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
''
else
''
rm -f /usr/bin/env
if test -d /usr/bin; then rmdir --ignore-fail-on-non-empty /usr/bin; fi
if test -d /usr; then rmdir --ignore-fail-on-non-empty /usr; fi
'';
system.activationScripts.specialfs = ''
specialMount() {
local device="$1"
local mountPoint="$2"
local options="$3"
local fsType="$4"
if mountpoint -q "$mountPoint"; then
local options="remount,$options"
else
mkdir -p "$mountPoint"
chmod 0755 "$mountPoint"
fi
mount -t "$fsType" -o "$options" "$device" "$mountPoint"
}
source ${config.system.build.earlyMountScript}
'';
systemd.user = lib.mkIf config.system.activatable {
services.nixos-activation = {
description = "Run user-specific NixOS activation";
script = config.system.userActivationScripts.script;
unitConfig.ConditionUser = "!@system";
serviceConfig.Type = "oneshot";
wantedBy = [ "default.target" ];
};
};
};
}

View File

@@ -0,0 +1,31 @@
import "struct"
#BootspecV1: {
system: string
init: string
initrd?: string
initrdSecrets?: string
kernel: string
kernelParams: [...string]
label: string
toplevel: string
}
// A restricted document does not allow any official specialisation
// information in it to avoid "recursive specialisations".
#RestrictedDocument: struct.MinFields(1) & {
"org.nixos.bootspec.v1": #BootspecV1
[=~"^"]: #BootspecExtension
}
// Specialisations are a hashmap of strings
#BootspecSpecialisationV1: [string]: #RestrictedDocument
// Bootspec extensions are defined by the extension author.
#BootspecExtension: {...}
// A "full" document allows official specialisation information
// in the top-level with a reserved namespaced key.
Document: #RestrictedDocument & {
"org.nixos.specialisation.v1"?: #BootspecSpecialisationV1
}

View File

@@ -0,0 +1,147 @@
# 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;
};
};
}

View File

@@ -0,0 +1,5 @@
# shellcheck shell=bash
warn() {
printf "\033[1;35mwarning:\033[0m %s\n" "$*" >&2
}

View File

@@ -0,0 +1,41 @@
# Run:
# nix-build -A nixosTests.activation-lib
{
lib,
stdenv,
testers,
}:
let
inherit (lib) fileset;
runTests = stdenv.mkDerivation {
name = "tests-activation-lib";
src = fileset.toSource {
root = ./.;
fileset = fileset.unions [
./lib.sh
./test.sh
];
};
buildPhase = ":";
doCheck = true;
postUnpack = ''
patchShebangs --build .
'';
checkPhase = ''
./test.sh
'';
installPhase = ''
touch $out
'';
};
runShellcheck = testers.shellcheck {
name = "activation-lib";
src = runTests.src;
};
in
lib.recurseIntoAttrs {
inherit runTests runShellcheck;
}

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Run:
# ./test.sh
# or:
# nix-build -A nixosTests.activation-lib
cd "$(dirname "${BASH_SOURCE[0]}")"
set -euo pipefail
# report failure
onerr() {
set +e
# find failed statement
echo "call trace:"
local i=0
while t="$(caller $i)"; do
line="${t%% *}"
file="${t##* }"
echo " $file:$line" >&2
((i++))
done
# red
printf "\033[1;31mtest failed\033[0m\n" >&2
exit 1
}
trap onerr ERR
# shellcheck source-path=SCRIPTDIR
source ./lib.sh
(warn hi, this works >/dev/null) 2>&1 | grep -E $'.*warning:.* hi, this works' >/dev/null
# green
printf "\033[1;32mok\033[0m\n"

View File

@@ -0,0 +1,31 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.system.nixos-init;
in
{
options.system.nixos-init = {
enable = lib.mkEnableOption ''
nixos-init, a system for bashless initialization.
This doesn't use any `activationScripts`. Anything set in these options is
a no-op here.
'';
package = lib.mkPackageOption pkgs "nixos-init" { };
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.boot.initrd.systemd.enable;
message = "nixos-init can only be used with systemd initrd";
}
];
};
}

View File

@@ -0,0 +1,9 @@
{ lib, ... }:
with lib;
{
boot.loader.grub.device = mkOverride 0 "nodev";
specialisation = mkOverride 0 { };
isSpecialisation = mkOverride 0 true;
}

View File

@@ -0,0 +1,54 @@
{
lib,
config,
pkgs,
...
}:
let
preSwitchCheckScript = lib.concatLines (
lib.mapAttrsToList (name: text: ''
# pre-switch check ${name}
if ! (
${text}
) >&2 ; then
echo "Pre-switch check '${name}' failed" >&2
exit 1
fi
'') config.system.preSwitchChecks
);
in
{
options.system.preSwitchChecksScript = lib.mkOption {
type = lib.types.pathInStore;
internal = true;
readOnly = true;
default = lib.getExe (
pkgs.writeShellApplication {
name = "pre-switch-checks";
text = preSwitchCheckScript;
}
);
};
options.system.preSwitchChecks = lib.mkOption {
default = { };
example = lib.literalExpression ''
{ failsEveryTime =
'''
false
''';
}
'';
description = ''
A set of shell script fragments that are executed before the switch to a
new NixOS system configuration. A failure in any of these fragments will
cause the switch to fail and exit early.
The scripts receive the new configuration path and the action verb passed
to switch-to-configuration, as the first and second positional arguments
(meaning that you can access them using `$1` and `$2`, respectively).
'';
type = lib.types.attrsOf lib.types.str;
};
}

View File

@@ -0,0 +1,107 @@
{
config,
lib,
pkgs,
extendModules,
noUserModules,
...
}:
let
inherit (lib)
concatStringsSep
escapeShellArg
hasInfix
mapAttrs
mapAttrsToList
mkOption
types
;
# This attribute is responsible for creating boot entries for
# child configuration. They are only (directly) accessible
# when the parent configuration is boot default. For example,
# you can provide an easy way to boot the same configuration
# as you use, but with another kernel
# !!! fix this
children = mapAttrs (
childName: childConfig: childConfig.configuration.system.build.toplevel
) config.specialisation;
in
{
options = {
isSpecialisation = mkOption {
type = lib.types.bool;
internal = true;
default = false;
description = "Whether this system is a specialisation of another.";
};
specialisation = mkOption {
default = { };
example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.settings = { core = 0; max-jobs = 1; }; }; }";
description = ''
Additional configurations to build. If
`inheritParentConfig` is true, the system
will be based on the overall system configuration.
To switch to a specialised configuration
(e.g. `fewJobsManyCores`) at runtime, run:
```
sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test
```
'';
type = types.attrsOf (
types.submodule (
local@{ ... }:
let
extend = if local.config.inheritParentConfig then extendModules else noUserModules.extendModules;
in
{
options.inheritParentConfig = mkOption {
type = types.bool;
default = true;
description = "Include the entire system's configuration. Set to false to make a completely differently configured system.";
};
options.configuration = mkOption {
default = { };
description = ''
Arbitrary NixOS configuration.
Anything you can add to a normal NixOS configuration, you can add
here, including imports and config values, although nested
specialisations will be ignored.
'';
visible = "shallow";
inherit (extend { modules = [ ./no-clone.nix ]; }) type;
};
}
)
);
};
};
config = {
assertions = mapAttrsToList (name: _: {
assertion = !hasInfix "/" name;
message = ''
Specialisation names must not contain forward slashes.
Invalid specialisation name: ${name}
'';
}) config.specialisation;
system.systemBuilderCommands = ''
mkdir $out/specialisation
${concatStringsSep "\n" (
mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${escapeShellArg name}") children
)}
'';
};
# uses extendModules to generate a type
meta.buildDocsInSandbox = false;
}

View File

@@ -0,0 +1,53 @@
{
config,
lib,
pkgs,
...
}:
{
imports = [
(lib.mkRemovedOptionModule [ "system" "switch" "enableNg" ] ''
This option controlled the usage of the new switch-to-configuration-ng,
which is now the only switch-to-configuration implementation. This option
can be removed from configuration. If there are outstanding issues
preventing you from using the new implementation, please open an issue on
GitHub.
'')
];
options.system.switch.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to include the capability to switch configurations.
Disabling this makes the system unable to be reconfigured via `nixos-rebuild`.
This is good for image based appliances where updates are handled
outside the image. Reducing features makes the image lighter and
slightly more secure.
'';
};
config = lib.mkIf config.system.switch.enable {
# Use a subshell so we can source makeWrapper's setup hook without
# affecting the rest of activatableSystemBuilderCommands.
system.activatableSystemBuilderCommands = ''
(
source ${pkgs.buildPackages.makeWrapper}/nix-support/setup-hook
mkdir $out/bin
ln -sf ${lib.getExe pkgs.switch-to-configuration-ng} $out/bin/switch-to-configuration
wrapProgram $out/bin/switch-to-configuration \
--set OUT $out \
--set TOPLEVEL ''${!toplevelVar} \
--set DISTRO_ID ${lib.escapeShellArg config.system.nixos.distroId} \
--set INSTALL_BOOTLOADER ${lib.escapeShellArg config.system.build.installBootLoader} \
--set PRE_SWITCH_CHECK ${lib.escapeShellArg config.system.preSwitchChecksScript} \
--set LOCALE_ARCHIVE ${config.i18n.glibcLocales}/lib/locale/locale-archive \
--set SYSTEMD ${config.systemd.package}
)
'';
};
}

View File

@@ -0,0 +1,35 @@
{
lib,
nixos,
expect,
testers,
}:
let
node-forbiddenDependencies-fail = nixos (
{ ... }:
{
system.forbiddenDependenciesRegexes = [ "-dev$" ];
environment.etc."dev-dependency" = {
text = "${expect.dev}";
};
documentation.enable = false;
fileSystems."/".device = "ignore-root-device";
boot.loader.grub.enable = false;
}
);
node-forbiddenDependencies-succeed = nixos (
{ ... }:
{
system.forbiddenDependenciesRegexes = [ "-dev$" ];
system.extraDependencies = [ expect.dev ];
documentation.enable = false;
fileSystems."/".device = "ignore-root-device";
boot.loader.grub.enable = false;
}
);
in
lib.recurseIntoAttrs {
test-forbiddenDependencies-fail = testers.testBuildFailure node-forbiddenDependencies-fail.config.system.build.toplevel;
test-forbiddenDependencies-succeed =
node-forbiddenDependencies-succeed.config.system.build.toplevel;
}

View File

@@ -0,0 +1,407 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
systemBuilder = ''
mkdir $out
${
if config.boot.initrd.enable && config.boot.initrd.systemd.enable then
''
# 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
${lib.optionalString (!config.system.nixos-init.enable) ''
cp ${config.system.build.bootStage2} $out/prepare-root
substituteInPlace $out/prepare-root --subst-var-by systemConfig $out
''}
''
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
${lib.optionalString config.system.etc.overlay.enable ''
ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image
ln -s ${config.system.build.etcBasedir} $out/etc-basedir
''}
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
${config.system.systemBuilderCommands}
cp "$extraDependenciesPath" "$out/extra-dependencies"
${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
${config.boot.bootspec.writer}
${optionalString config.boot.bootspec.enableValidation ''${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"''}
''}
${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. See `activatable-system.nix`.
baseSystem = pkgs.stdenvNoCC.mkDerivation (
{
name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
preferLocalBuild = true;
allowSubstitutes = false;
passAsFile = [ "extraDependencies" ];
buildCommand = systemBuilder;
systemd = config.systemd.package;
nixosLabel = config.system.nixos.label;
inherit (config.system) extraDependencies;
}
// config.system.systemBuilderArgs
);
# Handle assertions and warnings
baseSystemAssertWarn = lib.asserts.checkAssertWarn config.assertions config.warnings baseSystem;
# Replace runtime dependencies
system =
let
inherit (config.system.replaceDependencies) replacements cutoffPackages;
in
if replacements == [ ] then
# Avoid IFD if possible, by sidestepping replaceDependencies if no replacements are specified.
baseSystemAssertWarn
else
(pkgs.replaceDependencies.override {
replaceDirectDependencies = pkgs.replaceDirectDependencies.override {
nix = config.nix.package;
};
})
{
drv = baseSystemAssertWarn;
inherit replacements cutoffPackages;
};
systemWithBuildDeps = system.overrideAttrs (o: {
systemBuildClosure = pkgs.closureInfo { rootPaths = [ system.drvPath ]; };
buildCommand = o.buildCommand + ''
ln -sn $systemBuildClosure $out/build-closure
'';
});
in
{
imports = [
../build.nix
(mkRemovedOptionModule [
"nesting"
"clone"
] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
(mkRemovedOptionModule [
"nesting"
"children"
] "Use `specialisation.«name».configuration = { ... }` instead.")
(mkRenamedOptionModule
[ "system" "forbiddenDependenciesRegex" ]
[ "system" "forbiddenDependenciesRegexes" ]
)
(mkRenamedOptionModule
[ "system" "replaceRuntimeDependencies" ]
[ "system" "replaceDependencies" "replacements" ]
)
];
options = {
system.boot.loader.id = mkOption {
internal = true;
default = "";
description = ''
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 = ''
Name of the kernel file to be passed to the bootloader.
'';
};
system.boot.loader.initrdFile = mkOption {
internal = true;
default = "initrd";
type = types.str;
description = ''
Name of the initrd file to be passed to the bootloader.
'';
};
system.build = {
toplevel = mkOption {
type = types.package;
readOnly = true;
description = ''
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 = ''
If enabled, copies the NixOS configuration file
(usually {file}`/etc/nixos/configuration.nix`)
and symlinks 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.
Warning: This feature cannot be used when the system is configured by a flake
'';
};
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.mkDerivation` attributes that will be passed to the top level system builder.
'';
};
system.forbiddenDependenciesRegexes = mkOption {
default = [ ];
example = [ "-dev$" ];
type = types.listOf types.str;
description = ''
POSIX Extended Regular Expressions that match 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 = ''
This code will be added to the builder creating the system store path.
'';
};
system.extraDependencies = mkOption {
type = types.listOf types.pathInStore;
default = [ ];
description = ''
A list of paths that should be included in the system
closure but generally not visible to users.
This option has also been used for build-time checks, but the
`system.checks` option is more appropriate for that purpose as checks
should not leave a trace in the built system configuration.
'';
};
system.checks = mkOption {
type = types.listOf types.package;
default = [ ];
description = ''
Packages that are added as dependencies of the system's build, usually
for the purpose of validating some part of the configuration.
Unlike `system.extraDependencies`, these store paths do not
become part of the built system configuration.
'';
};
system.replaceDependencies = {
replacements = mkOption {
default = [ ];
example = lib.literalExpression "[ ({ oldDependency = pkgs.openssl; newDependency = pkgs.callPackage /path/to/openssl { }; }) ]";
type = types.listOf (
types.submodule (
{ ... }:
{
imports = [
(mkRenamedOptionModule [ "original" ] [ "oldDependency" ])
(mkRenamedOptionModule [ "replacement" ] [ "newDependency" ])
];
options.oldDependency = mkOption {
type = types.package;
description = "The original package to override.";
};
options.newDependency = mkOption {
type = types.package;
description = "The replacement package.";
};
}
)
);
apply = map (
{ oldDependency, newDependency, ... }:
{
inherit oldDependency newDependency;
}
);
description = ''
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.
'';
};
cutoffPackages = mkOption {
default = lib.optionals config.boot.initrd.enable [ config.system.build.initialRamdisk ];
defaultText = literalExpression "lib.optionals config.boot.initrd.enable [ config.system.build.initialRamdisk ]";
type = types.listOf types.package;
description = ''
Packages to which no replacements should be applied.
The initrd is matched by default, because its structure renders the replacement process ineffective and prone to breakage.
'';
};
};
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 = ''
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}"`
'';
};
system.includeBuildDependencies = mkOption {
type = types.bool;
default = false;
description = ''
Whether to include the build closure of the whole system in
its runtime closure. This can be useful for making changes
fully offline, as it includes all sources, patches, and
intermediate outputs required to build all the derivations
that the system depends on.
Note that this includes _all_ the derivations, down from the
included applications to their sources, the compilers used to
build them, and even the bootstrap compiler used to compile
the compilers. This increases the size of the system and the
time needed to download its dependencies drastically: a
minimal configuration with no extra services enabled grows
from ~670MiB in size to 13.5GiB, and takes proportionally
longer to download.
'';
};
};
config = {
assertions = [
{
assertion = config.system.copySystemConfiguration -> !lib.inPureEvalMode;
message = "system.copySystemConfiguration is not supported with flakes";
}
];
system.extraSystemBuilderCmds =
optionalString config.system.copySystemConfiguration ''
ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
"$out/configuration.nix"
''
+ optionalString (config.system.forbiddenDependenciesRegexes != [ ]) (
lib.concatStringsSep "\n" (
map (regex: ''
if [[ ${regex} != "" && -n $closureInfo ]]; then
if forbiddenPaths="$(grep -E -- "${regex}" $closureInfo/store-paths)"; then
echo -e "System closure $out contains the following disallowed paths:\n$forbiddenPaths"
exit 1
fi
fi
'') config.system.forbiddenDependenciesRegexes
)
);
system.systemBuilderArgs = {
# Legacy environment variables. These were used by the activation script,
# but some other script might still depend on them, although unlikely.
installBootLoader = config.system.build.installBootLoader;
localeArchive = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
distroId = config.system.nixos.distroId;
perl = pkgs.perl.withPackages (
p: with p; [
ConfigIniFiles
FileSlurp
]
);
# End if legacy environment variables
preSwitchCheck = config.system.preSwitchChecksScript;
# Not actually used in the builder. `passedChecks` is just here to create
# the build dependencies. Checks are similar to build dependencies in the
# sense that if they fail, the system build fails. However, checks do not
# produce any output of value, so they are not used by the system builder.
# In fact, using them runs the risk of accidentally adding unneeded paths
# to the system closure, which defeats the purpose of the `system.checks`
# option, as opposed to `system.extraDependencies`.
passedChecks = concatStringsSep " " config.system.checks;
}
// lib.optionalAttrs (config.system.forbiddenDependenciesRegexes != [ ]) {
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 =
if config.system.includeBuildDependencies then systemWithBuildDeps else system;
};
}