Merge pull request #81848 from grahamc/nested-specialisation

specialisation: replace nesting with named configurations

Changed files
+230 -189
nixos
+1 -1
nixos/doc/manual/installation/installing-behind-a-proxy.xml
···
<note>
<para>
If you are switching networks with different proxy configurations, use the
-
<literal>nesting.clone</literal> option in
+
<literal>specialisation</literal> option in
<literal>configuration.nix</literal> to switch proxies at runtime. Refer to
<xref linkend="ch-options" /> for more information.
</para>
+44
nixos/doc/manual/release-notes/rl-2009.xml
···
<link xlink:href="https://github.com/gollum/gollum/wiki/5.0-release-notes#migrating-your-wiki">here</link>.
</para>
</listitem>
+
+
<listitem>
+
<para>
+
The NixOS options <literal>nesting.clone</literal> and
+
<literal>nesting.children</literal> have been deleted, and
+
replaced with named <xref linkend="opt-specialisation"/>
+
configurations.
+
</para>
+
+
<para>
+
Replace a <literal>nesting.clone</literal> entry with:
+
+
<programlisting>{
+
<link xlink:href="#opt-specialisation">specialisation.example-sub-configuration</link> = {
+
<link xlink:href="#opt-specialisation._name_.configuration">configuration</link> = {
+
...
+
};
+
};</programlisting>
+
+
</para>
+
<para>
+
Replace a <literal>nesting.children</literal> entry with:
+
+
<programlisting>{
+
<link xlink:href="#opt-specialisation">specialisation.example-sub-configuration</link> = {
+
<link xlink:href="#opt-specialisation._name_.inheritParentConfig">inheritParentConfig</link> = false;
+
<link xlink:href="#opt-specialisation._name_.configuration">configuration</link> = {
+
...
+
};
+
};</programlisting>
+
</para>
+
+
<para>
+
To switch to a specialised configuration at runtime you need to
+
run:
+
<programlisting>
+
# sudo /run/current-system/specialisation/example-sub-configuration/bin/switch-to-configuration test
+
</programlisting>
+
Before you would have used:
+
<programlisting>
+
# sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test
+
</programlisting>
+
</para>
+
</listitem>
</itemizedlist>
</section>
+1 -2
nixos/modules/system/activation/no-clone.nix
···
{
boot.loader.grub.device = mkOverride 0 "nodev";
-
nesting.children = mkOverride 0 [];
-
nesting.clone = mkOverride 0 [];
+
specialisation = mkOverride 0 {};
}
+35 -30
nixos/modules/system/activation/top-level.nix
···
# you can provide an easy way to boot the same configuration
# as you use, but with another kernel
# !!! fix this
-
cloner = inheritParent: list:
-
map (childConfig:
+
children = mapAttrs (childName: childConfig:
(import ../../../lib/eval-config.nix {
inherit baseModules;
system = config.nixpkgs.initialSystem;
modules =
-
(optionals inheritParent modules)
+
(optionals childConfig.inheritParentConfig modules)
++ [ ./no-clone.nix ]
-
++ [ childConfig ];
+
++ [ childConfig.configuration ];
}).config.system.build.toplevel
-
) list;
-
-
children =
-
cloner false config.nesting.children
-
++ cloner true config.nesting.clone;
+
) config.specialisation;
systemBuilder =
let
···
echo -n "$nixosLabel" > $out/nixos-version
echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
-
mkdir $out/fine-tune
-
childCount=0
-
for i in $children; do
-
childCount=$(( childCount + 1 ))
-
ln -s $i $out/fine-tune/child-$childCount
-
done
+
mkdir $out/specialisation
+
${concatStringsSep "\n"
+
(mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)}
mkdir $out/bin
export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
···
shell = "${pkgs.bash}/bin/sh";
su = "${pkgs.shadow.su}/bin/su";
-
inherit children;
kernelParams = config.boot.kernelParams;
installBootLoader =
config.system.build.installBootLoader
···
in
{
+
imports = [
+
(mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.")
+
(mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.")
+
];
+
options = {
system.build = mkOption {
···
'';
};
-
nesting.children = mkOption {
-
default = [];
+
specialisation = mkOption {
+
default = {};
+
example = lib.literalExample "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }";
description = ''
-
Additional configurations to build.
-
'';
-
};
-
-
nesting.clone = mkOption {
-
default = [];
-
description = ''
-
Additional configurations to build based on the current
-
configuration which then has a lower priority.
+
Additional configurations to build. If
+
<literal>inheritParentConfig</literal> is true, the system
+
will be based on the overall system configuration.
-
To switch to a cloned configuration (e.g. <literal>child-1</literal>)
-
at runtime, run
+
To switch to a specialised configuration
+
(e.g. <literal>fewJobsManyCores</literal>) at runtime, run:
<programlisting>
-
# sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test
+
# sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test
</programlisting>
'';
+
type = types.attrsOf (types.submodule (
+
{ ... }: {
+
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 options.";
+
};
+
})
+
);
};
system.boot.loader.id = mkOption {
+3 -2
nixos/modules/system/boot/loader/grub/install-grub.pl
···
# Find all the children of the current default configuration
# Do not search for grand children
-
my @links = sort (glob "$defaultConfig/fine-tune/*");
+
my @links = sort (glob "$defaultConfig/specialisation/*");
foreach my $link (@links) {
my $entryName = "";
···
if ($cfgName) {
$entryName = $cfgName;
} else {
-
$entryName = "($date - $version)";
+
my $linkname = basename($link);
+
$entryName = "($linkname - $date - $version)";
}
addEntry("NixOS - $entryName", $link);
}
+1 -1
nixos/modules/system/boot/loader/init-script/init-script-builder.sh
···
# Add all generations of the system profile to the menu, in reverse
# (most recent to least recent) order.
-
for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do
+
for link in $((ls -d $defaultConfig/specialisation/* ) | sort -n); do
date=$(stat --printf="%y\n" $link | sed 's/\..*//')
addEntry "NixOS - variation" $link ""
done
+46 -48
nixos/tests/acme.nix
···
security.acme.server = "https://acme-v02.api.letsencrypt.org/dir";
-
nesting.clone = [
-
({pkgs, ...}: {
-
systemd.targets."acme-finished-b.example.com" = {};
-
systemd.services."acme-b.example.com" = {
-
wants = [ "acme-finished-b.example.com.target" ];
-
before = [ "acme-finished-b.example.com.target" ];
-
after = [ "nginx.service" ];
-
};
-
services.nginx.virtualHosts."b.example.com" = {
-
enableACME = true;
-
forceSSL = true;
-
locations."/".root = pkgs.runCommand "docroot" {} ''
-
mkdir -p "$out"
-
echo hello world > "$out/index.html"
-
'';
-
};
-
})
-
({pkgs, config, nodes, lib, ...}: {
-
security.acme.certs."example.com" = {
-
domain = "*.example.com";
-
dnsProvider = "exec";
-
dnsPropagationCheck = false;
-
credentialsFile = with pkgs; writeText "wildcard.env" ''
-
EXEC_PATH=${dnsScript { inherit writeScript bash curl; dnsAddress = nodes.dnsserver.config.networking.primaryIPAddress; }}
-
'';
-
user = config.services.nginx.user;
-
group = config.services.nginx.group;
-
};
-
systemd.targets."acme-finished-example.com" = {};
-
systemd.services."acme-example.com" = {
-
wants = [ "acme-finished-example.com.target" ];
-
before = [ "acme-finished-example.com.target" "nginx.service" ];
-
wantedBy = [ "nginx.service" ];
-
};
-
services.nginx.virtualHosts."c.example.com" = {
-
forceSSL = true;
-
sslCertificate = config.security.acme.certs."example.com".directory + "/cert.pem";
-
sslTrustedCertificate = config.security.acme.certs."example.com".directory + "/full.pem";
-
sslCertificateKey = config.security.acme.certs."example.com".directory + "/key.pem";
-
locations."/".root = pkgs.runCommand "docroot" {} ''
-
mkdir -p "$out"
-
echo hello world > "$out/index.html"
-
'';
-
};
-
})
-
];
+
specialisation.second-cert.configuration = {pkgs, ...}: {
+
systemd.targets."acme-finished-b.example.com" = {};
+
systemd.services."acme-b.example.com" = {
+
wants = [ "acme-finished-b.example.com.target" ];
+
before = [ "acme-finished-b.example.com.target" ];
+
after = [ "nginx.service" ];
+
};
+
services.nginx.virtualHosts."b.example.com" = {
+
enableACME = true;
+
forceSSL = true;
+
locations."/".root = pkgs.runCommand "docroot" {} ''
+
mkdir -p "$out"
+
echo hello world > "$out/index.html"
+
'';
+
};
+
};
+
specialisation.dns-01.configuration = {pkgs, config, nodes, lib, ...}: {
+
security.acme.certs."example.com" = {
+
domain = "*.example.com";
+
dnsProvider = "exec";
+
dnsPropagationCheck = false;
+
credentialsFile = with pkgs; writeText "wildcard.env" ''
+
EXEC_PATH=${dnsScript { inherit writeScript bash curl; dnsAddress = nodes.dnsserver.config.networking.primaryIPAddress; }}
+
'';
+
user = config.services.nginx.user;
+
group = config.services.nginx.group;
+
};
+
systemd.targets."acme-finished-example.com" = {};
+
systemd.services."acme-example.com" = {
+
wants = [ "acme-finished-example.com.target" ];
+
before = [ "acme-finished-example.com.target" "nginx.service" ];
+
wantedBy = [ "nginx.service" ];
+
};
+
services.nginx.virtualHosts."c.example.com" = {
+
forceSSL = true;
+
sslCertificate = config.security.acme.certs."example.com".directory + "/cert.pem";
+
sslTrustedCertificate = config.security.acme.certs."example.com".directory + "/full.pem";
+
sslCertificateKey = config.security.acme.certs."example.com".directory + "/key.pem";
+
locations."/".root = pkgs.runCommand "docroot" {} ''
+
mkdir -p "$out"
+
echo hello world > "$out/index.html"
+
'';
+
};
+
};
};
client = {nodes, lib, ...}: {
···
with subtest("Can add another certificate for nginx service"):
webserver.succeed(
-
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
+
"/run/current-system/specialisation/second-cert/bin/switch-to-configuration test"
)
webserver.wait_for_unit("acme-finished-b.example.com.target")
client.succeed(
···
"${switchToNewServer}"
)
webserver.succeed(
-
"/run/current-system/fine-tune/child-2/bin/switch-to-configuration test"
+
"/run/current-system/specialisation/dns-01/bin/switch-to-configuration test"
)
webserver.wait_for_unit("acme-finished-example.com.target")
client.succeed(
+1 -1
nixos/tests/all-tests.nix
···
nat.standalone = handleTest ./nat.nix { withFirewall = false; };
ndppd = handleTest ./ndppd.nix {};
neo4j = handleTest ./neo4j.nix {};
-
nesting = handleTest ./nesting.nix {};
+
specialisation = handleTest ./specialisation.nix {};
netdata = handleTest ./netdata.nix {};
networking.networkd = handleTest ./networking.nix { networkd = true; };
networking.scripted = handleTest ./networking.nix { networkd = false; };
+21 -23
nixos/tests/caddy.nix
···
}
'';
-
nesting.clone = [
-
{
-
services.caddy.config = lib.mkForce ''
-
http://localhost {
-
gzip
+
specialisation.etag.configuration = {
+
services.caddy.config = lib.mkForce ''
+
http://localhost {
+
gzip
-
root ${
-
pkgs.runCommand "testdir2" {} ''
-
mkdir "$out"
-
echo changed > "$out/example.html"
-
''
-
}
+
root ${
+
pkgs.runCommand "testdir2" {} ''
+
mkdir "$out"
+
echo changed > "$out/example.html"
+
''
}
-
'';
-
}
+
}
+
'';
+
};
-
{
-
services.caddy.config = ''
-
http://localhost:8080 {
-
}
-
'';
-
}
-
];
+
specialisation.config-reload.configuration = {
+
services.caddy.config = ''
+
http://localhost:8080 {
+
}
+
'';
+
};
};
};
testScript = { nodes, ... }: let
-
etagSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-1";
-
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-2";
+
etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag";
+
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload";
in ''
url = "http://localhost/example.html"
webserver.wait_for_unit("caddy")
···
assert old_etag != new_etag, "Old ETag {} is the same as {}".format(
old_etag, new_etag
)
-
+
with subtest("config is reloaded on nixos-rebuild switch"):
webserver.succeed(
"${justReloadSystem}/bin/switch-to-configuration test >&2"
+10 -10
nixos/tests/installer.nix
···
# partitions and filesystems.
testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
, grubIdentifier, preBootCommands, extraConfig
-
, testCloneConfig
+
, testSpecialisationConfig
}:
let iface = if grubVersion == 1 then "ide" else "virtio";
isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
···
# Tests for validating clone configuration entries in grub menu
''
-
+ optionalString testCloneConfig ''
+
+ optionalString testSpecialisationConfig ''
# Reboot Machine
machine = create_machine_named("clone-default-config")
${preBootCommands}
···
, bootLoader ? "grub" # either "grub" or "systemd-boot"
, grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
, enableOCR ? false, meta ? {}
-
, testCloneConfig ? false
+
, testSpecialisationConfig ? false
}:
makeTest {
inherit enableOCR;
···
testScript = testScriptFun {
inherit bootLoader createPartitions preBootCommands
grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
-
testCloneConfig;
+
testSpecialisationConfig;
};
};
···
grubUseEfi = true;
};
-
clone-test-extraconfig = {
+
specialisation-test-extraconfig = {
extraConfig = ''
environment.systemPackages = [ pkgs.grub2 ];
boot.loader.grub.configurationName = "Home";
-
nesting.clone = [ {
+
specialisation.work.configuration = {
boot.loader.grub.configurationName = lib.mkForce "Work";
environment.etc = {
···
gitproxy = none for work.com
";
};
-
} ];
+
};
'';
-
testCloneConfig = true;
+
testSpecialisationConfig = true;
};
···
simple = makeInstallerTest "simple" simple-test-config;
# Test cloned configurations with the simple grub configuration
-
simpleClone = makeInstallerTest "simpleClone" (simple-test-config // clone-test-extraconfig);
+
simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig);
# Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
···
simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
# Test cloned configurations with the uefi grub configuration
-
simpleUefiGrubClone = makeInstallerTest "simpleUefiGrubClone" (simple-uefi-grub-config // clone-test-extraconfig);
+
simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (simple-uefi-grub-config // specialisation-test-extraconfig);
# Same as the previous, but now with a separate /boot partition.
separateBoot = makeInstallerTest "separateBoot" {
-44
nixos/tests/nesting.nix
···
-
import ./make-test-python.nix {
-
name = "nesting";
-
nodes = {
-
clone = { pkgs, ... }: {
-
environment.systemPackages = [ pkgs.cowsay ];
-
nesting.clone = [
-
({ pkgs, ... }: {
-
environment.systemPackages = [ pkgs.hello ];
-
})
-
];
-
};
-
children = { pkgs, ... }: {
-
environment.systemPackages = [ pkgs.cowsay ];
-
nesting.children = [
-
({ pkgs, ... }: {
-
environment.systemPackages = [ pkgs.hello ];
-
})
-
];
-
};
-
};
-
testScript = ''
-
clone.wait_for_unit("default.target")
-
clone.succeed("cowsay hey")
-
clone.fail("hello")
-
-
with subtest("Nested clones do inherit from parent"):
-
clone.succeed(
-
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
-
)
-
clone.succeed("cowsay hey")
-
clone.succeed("hello")
-
-
children.wait_for_unit("default.target")
-
children.succeed("cowsay hey")
-
children.fail("hello")
-
-
with subtest("Nested children do not inherit from parent"):
-
children.succeed(
-
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
-
)
-
children.fail("cowsay hey")
-
children.succeed("hello")
-
'';
-
}
+2 -2
nixos/tests/nginx-etag.nix
···
'';
};
-
nesting.clone = lib.singleton {
+
specialisation.pass-checks.configuration = {
services.nginx.virtualHosts.server = {
root = lib.mkForce (pkgs.runCommandLocal "testdir2" {} ''
mkdir "$out"
···
testScript = { nodes, ... }: let
inherit (nodes.server.config.system.build) toplevel;
-
newSystem = "${toplevel}/fine-tune/child-1";
+
newSystem = "${toplevel}/specialisation/pass-checks";
in ''
start_all()
+22 -25
nixos/tests/nginx.nix
···
services.nginx.enableReload = true;
-
nesting.clone = [
-
{
-
services.nginx.virtualHosts.localhost = {
-
root = lib.mkForce (pkgs.runCommand "testdir2" {} ''
-
mkdir "$out"
-
echo content changed > "$out/index.html"
-
'');
-
};
-
}
+
specialisation.etagSystem.configuration = {
+
services.nginx.virtualHosts.localhost = {
+
root = lib.mkForce (pkgs.runCommand "testdir2" {} ''
+
mkdir "$out"
+
echo content changed > "$out/index.html"
+
'');
+
};
+
};
-
{
-
services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }];
-
}
+
specialisation.justReloadSystem.configuration = {
+
services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }];
+
};
-
{
-
services.nginx.package = pkgs.nginxUnstable;
-
}
+
specialisation.reloadRestartSystem.configuration = {
+
services.nginx.package = pkgs.nginxUnstable;
+
};
-
{
-
services.nginx.package = pkgs.nginxUnstable;
-
services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
-
}
-
];
+
specialisation.reloadWithErrorsSystem.configuration = {
+
services.nginx.package = pkgs.nginxUnstable;
+
services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
+
};
};
-
};
testScript = { nodes, ... }: let
-
etagSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-1";
-
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-2";
-
reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-3";
-
reloadWithErrorsSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-4";
+
etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etagSystem";
+
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/justReloadSystem";
+
reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadRestartSystem";
+
reloadWithErrorsSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadWithErrorsSystem";
in ''
url = "http://localhost/index.html"
+43
nixos/tests/specialisation.nix
···
+
import ./make-test-python.nix {
+
name = "specialisation";
+
nodes = {
+
inheritconf = { pkgs, ... }: {
+
environment.systemPackages = [ pkgs.cowsay ];
+
specialisation.inheritconf.configuration = { pkgs, ... }: {
+
environment.systemPackages = [ pkgs.hello ];
+
};
+
};
+
noinheritconf = { pkgs, ... }: {
+
environment.systemPackages = [ pkgs.cowsay ];
+
specialisation.noinheritconf = {
+
inheritParentConfig = false;
+
configuration = { pkgs, ... }: {
+
environment.systemPackages = [ pkgs.hello ];
+
};
+
};
+
};
+
};
+
testScript = ''
+
inheritconf.wait_for_unit("default.target")
+
inheritconf.succeed("cowsay hey")
+
inheritconf.fail("hello")
+
+
with subtest("Nested clones do inherit from parent"):
+
inheritconf.succeed(
+
"/run/current-system/specialisation/inheritconf/bin/switch-to-configuration test"
+
)
+
inheritconf.succeed("cowsay hey")
+
inheritconf.succeed("hello")
+
+
noinheritconf.wait_for_unit("default.target")
+
noinheritconf.succeed("cowsay hey")
+
noinheritconf.fail("hello")
+
+
with subtest("Nested children do not inherit from parent"):
+
noinheritconf.succeed(
+
"/run/current-system/specialisation/noinheritconf/bin/switch-to-configuration test"
+
)
+
noinheritconf.fail("cowsay hey")
+
noinheritconf.succeed("hello")
+
'';
+
}