From 61d43dee5f1ca98be582ccc5dcde04015adb8f8f Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 10 Jun 2023 15:08:38 +0200 Subject: [PATCH 1/8] nixos: Extract module for activation script inclusion into toplevel Allows omission of this functionality through disabledModules, e.g. for image building. --- nixos/modules/module-list.nix | 1 + .../system/activation/activatable-system.nix | 47 +++++++++++++++++++ nixos/modules/system/activation/top-level.nix | 27 +---------- 3 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 nixos/modules/system/activation/activatable-system.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 83b2a45dbd3b1..fc8c554ad6737 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1326,6 +1326,7 @@ ./services/x11/xbanish.nix ./services/x11/xfs.nix ./services/x11/xserver.nix + ./system/activation/activatable-system.nix ./system/activation/activation-script.nix ./system/activation/specialisation.nix ./system/activation/bootspec.nix diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix new file mode 100644 index 0000000000000..69014331c2a0c --- /dev/null +++ b/nixos/modules/system/activation/activatable-system.nix @@ -0,0 +1,47 @@ +/* + This module adds the activation script to toplevel, so that any previously + built configuration can be activated again, as long as they're available in + the store, e.g. through the profile's older generations. + + Alternate applications of the NixOS modules may omit this module, e.g. to + build images that are pre-activated and omit the activation script and its + dependencies. + */ +{ config, lib, pkgs, ... }: + +let + inherit (lib) + optionalString + ; +in +{ + config = { + system.systemBuilderArgs = { + activationScript = config.system.activationScripts.script; + dryActivationScript = config.system.dryActivationScript; + localeArchive = "${config.i18n.glibcLocales}/lib/locale/locale-archive"; + distroId = config.system.nixos.distroId; + perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]); + }; + + system.systemBuilderCommands = '' + 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 + + mkdir $out/bin + 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 + ''} + ''; + }; +} diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index c4427149d9c91..4973d61da13ea 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -36,13 +36,6 @@ let 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 @@ -63,19 +56,6 @@ let 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" - export distroId=${config.system.nixos.distroId}; - 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} cp "$extraDependenciesPath" "$out/extra-dependencies" @@ -93,7 +73,7 @@ let # 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. + # makes it bootable. See `activatable-system.nix`. baseSystem = pkgs.stdenvNoCC.mkDerivation ({ name = "nixos-system-${config.system.name}-${config.system.nixos.label}"; preferLocalBuild = true; @@ -109,14 +89,9 @@ let 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 From a16986f1a32a44ddb5b6f84a179ea1ee0278b2b3 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 10 Jun 2023 15:31:49 +0200 Subject: [PATCH 2/8] nixos: Move installBootLoader to activation script modules --- .../system/activation/activatable-system.nix | 1 + .../system/activation/activation-script.nix | 21 +++++++++++++++++++ nixos/modules/system/activation/top-level.nix | 21 ------------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index 69014331c2a0c..656049053f157 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -19,6 +19,7 @@ in system.systemBuilderArgs = { activationScript = config.system.activationScripts.script; dryActivationScript = config.system.dryActivationScript; + 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 ]); diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix index f23d4809e356d..c8407dd6779a3 100644 --- a/nixos/modules/system/activation/activation-script.nix +++ b/nixos/modules/system/activation/activation-script.nix @@ -204,6 +204,27 @@ in `/usr/bin/env`. ''; }; + + 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); + }; + }; diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 4973d61da13ea..22bc6e6b92a37 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -88,7 +88,6 @@ let utillinux = pkgs.util-linux; kernelParams = config.boot.kernelParams; - installBootLoader = config.system.build.installBootLoader; nixosLabel = config.system.nixos.label; inherit (config.system) extraDependencies; @@ -153,26 +152,6 @@ in }; 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; From 193f4fea90a1b26d7bbf8d04b60f6522e830dd65 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 10 Jun 2023 16:37:58 +0200 Subject: [PATCH 3/8] nixos/activatable-system: Make substitutions explicit This helps with understanding the code. We might make this not depend on environment variables later. systemBuilderArgs is a form of global state, which isn't helpful. --- .../system/activation/activatable-system.nix | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index 656049053f157..1fbcd6dca6da1 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -34,7 +34,19 @@ in unset activationScript dryActivationScript mkdir $out/bin - substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration + substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ + --subst-var coreutils \ + --subst-var distroId \ + --subst-var installBootLoader \ + --subst-var localeArchive \ + --subst-var out \ + --subst-var perl \ + --subst-var shell \ + --subst-var su \ + --subst-var systemd \ + --subst-var utillinux \ + ; + 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 From 990b72f6af560fd43884338f2d442230d4c18137 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 21 Jun 2023 15:51:11 +0200 Subject: [PATCH 4/8] nixos/activatable-system: Make system builder commands env independent This way it will be easier to reuse in a different context, such as a separate build of the activation script by itself (TBD). --- .../system/activation/activatable-system.nix | 23 +++++++++++-------- nixos/modules/system/activation/top-level.nix | 2 ++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index 1fbcd6dca6da1..ee956434ccef1 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -13,6 +13,9 @@ let inherit (lib) optionalString ; + + perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]); + in { config = { @@ -35,21 +38,21 @@ in mkdir $out/bin substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ - --subst-var coreutils \ - --subst-var distroId \ - --subst-var installBootLoader \ - --subst-var localeArchive \ --subst-var out \ - --subst-var perl \ - --subst-var shell \ - --subst-var su \ - --subst-var systemd \ - --subst-var utillinux \ + --subst-var-by coreutils "${pkgs.coreutils}" \ + --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \ + --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \ + --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \ + --subst-var-by perl "${perlWrapped}" \ + --subst-var-by shell "${pkgs.bash}/bin/sh" \ + --subst-var-by su "${pkgs.shadow.su}/bin/su" \ + --subst-var-by systemd "${config.systemd.package}"\ + --subst-var-by utillinux "${pkgs.util-linux}" \ ; 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 + if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then echo "switch-to-configuration syntax is not valid:" echo "$output" exit 1 diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 22bc6e6b92a37..de7615a3ea3c1 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -81,6 +81,8 @@ let passAsFile = [ "extraDependencies" ]; buildCommand = systemBuilder; + # Some of these variables may be unused, but without certainly, removing + # them runs a risk of breaking out of tree systemBuilderCommands inherit (pkgs) coreutils; systemd = config.systemd.package; shell = "${pkgs.bash}/bin/sh"; From 7891c8cdafff3bb59476e48136964dcfe933d7df Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Jun 2023 12:48:15 +0200 Subject: [PATCH 5/8] nixos/activatable-system: Move legacy variables to top-level --- nixos/modules/system/activation/activatable-system.nix | 4 ---- nixos/modules/system/activation/top-level.nix | 10 ++++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index ee956434ccef1..b179fce417e70 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -22,10 +22,6 @@ in system.systemBuilderArgs = { activationScript = config.system.activationScripts.script; dryActivationScript = config.system.dryActivationScript; - 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 ]); }; system.systemBuilderCommands = '' diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index de7615a3ea3c1..0fb8bffebdd64 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -336,6 +336,16 @@ in ''; 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 + + # 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 From 9edad17d298a8f6cd7bdc6b540b74ff1f7f1338e Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 21 Jun 2023 15:54:14 +0200 Subject: [PATCH 6/8] nixos/top-level: Remove unused builder variables These variables were previously used by the activation script build commands, but are now embedded into those commands for to improve reusability for an upcoming addition. --- nixos/modules/system/activation/top-level.nix | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 0fb8bffebdd64..ff8226cee2b3a 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -81,13 +81,7 @@ let passAsFile = [ "extraDependencies" ]; buildCommand = systemBuilder; - # Some of these variables may be unused, but without certainly, removing - # them runs a risk of breaking out of tree systemBuilderCommands - 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; nixosLabel = config.system.nixos.label; From 89664199e18d45e1e629b011aaff0336987694b7 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 21 Jun 2023 17:36:40 +0200 Subject: [PATCH 7/8] nixos/tests/switch-test.nix: Fix warnings --- nixos/tests/switch-test.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index f891a2cb2f4c2..8cc4e68e78a1f 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -482,9 +482,9 @@ in { }; testScript = { nodes, ... }: let - originalSystem = nodes.machine.config.system.build.toplevel; - otherSystem = nodes.other.config.system.build.toplevel; - machine = nodes.machine.config.system.build.toplevel; + originalSystem = nodes.machine.system.build.toplevel; + otherSystem = nodes.other.system.build.toplevel; + machine = nodes.machine.system.build.toplevel; # Ensures failures pass through using pipefail, otherwise failing to # switch-to-configuration is hidden by the success of `tee`. From 772d6076e825dc999a04245f7dfa3cb19082ec28 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 28 Jun 2023 14:06:28 +0200 Subject: [PATCH 8/8] nixos: Add system.activatable flag for images that are pre-activated --- .../system/activation/activatable-system.nix | 121 +++++++++++------- .../activation/switch-to-configuration.pl | 34 ++--- nixos/tests/switch-test.nix | 41 +++++- 3 files changed, 132 insertions(+), 64 deletions(-) diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index b179fce417e70..7f6154794bd8d 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -1,59 +1,92 @@ -/* - This module adds the activation script to toplevel, so that any previously - built configuration can be activated again, as long as they're available in - the store, e.g. through the profile's older generations. - - Alternate applications of the NixOS modules may omit this module, e.g. to - build images that are pre-activated and omit the activation script and its - dependencies. - */ { config, lib, pkgs, ... }: let inherit (lib) + mkOption optionalString + types ; perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]); + systemBuilderArgs = { + activationScript = config.system.activationScripts.script; + dryActivationScript = config.system.dryActivationScript; + }; + + systemBuilderCommands = '' + 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 + + mkdir $out/bin + substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ + --subst-var out \ + --subst-var-by toplevel ''${!toplevelVar} \ + --subst-var-by coreutils "${pkgs.coreutils}" \ + --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \ + --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \ + --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \ + --subst-var-by perl "${perlWrapped}" \ + --subst-var-by shell "${pkgs.bash}/bin/sh" \ + --subst-var-by su "${pkgs.shadow.su}/bin/su" \ + --subst-var-by systemd "${config.systemd.package}" \ + --subst-var-by utillinux "${pkgs.util-linux}" \ + ; + + chmod +x $out/bin/switch-to-configuration + ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' + if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then + echo "switch-to-configuration syntax is not valid:" + echo "$output" + exit 1 + fi + ''} + ''; + in { - config = { - system.systemBuilderArgs = { - activationScript = config.system.activationScripts.script; - dryActivationScript = config.system.dryActivationScript; + 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.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.systemBuilderCommands = lib.mkIf config.system.activatable systemBuilderCommands; + system.systemBuilderArgs = lib.mkIf config.system.activatable + (systemBuilderArgs // { + toplevelVar = "out"; + }); - system.systemBuilderCommands = '' - 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 - - mkdir $out/bin - substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ - --subst-var out \ - --subst-var-by coreutils "${pkgs.coreutils}" \ - --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \ - --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \ - --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \ - --subst-var-by perl "${perlWrapped}" \ - --subst-var-by shell "${pkgs.bash}/bin/sh" \ - --subst-var-by su "${pkgs.shadow.su}/bin/su" \ - --subst-var-by systemd "${config.systemd.package}"\ - --subst-var-by utillinux "${pkgs.util-linux}" \ - ; - - chmod +x $out/bin/switch-to-configuration - ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' - if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then - echo "switch-to-configuration syntax is not valid:" - echo "$output" - exit 1 - fi - ''} - ''; + system.build.separateActivationScript = + pkgs.runCommand + "separate-activation-script" + (systemBuilderArgs // { + toplevelVar = "toplevel"; + toplevel = config.system.build.toplevel; + }) + '' + mkdir $out + ${systemBuilderCommands} + ''; }; } diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index de6e43dd30d83..a41e67a2294f0 100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -31,8 +31,10 @@ ## no critic(ValuesAndExpressions::ProhibitNoisyQuotes, ValuesAndExpressions::ProhibitMagicNumbers, ValuesAndExpressions::ProhibitEmptyQuotes, ValuesAndExpressions::ProhibitInterpolationOfLiterals) ## no critic(RegularExpressions::ProhibitEscapedMetacharacters) -# System closure path to switch to +# Location of activation scripts my $out = "@out@"; +# System closure path to switch to +my $toplevel = "@toplevel@"; # Path to the directory containing systemd tools of the old system my $cur_systemd = abs_path("/run/current-system/sw/bin"); # Path to the systemd store path of the new system @@ -96,7 +98,7 @@ chomp(my $install_boot_loader = <<'EOFBOOTLOADER'); @installBootLoader@ EOFBOOTLOADER - system("$install_boot_loader $out") == 0 or exit 1; + system("$install_boot_loader $toplevel") == 0 or exit 1; } # Just in case the new configuration hangs the system, do a sync now. @@ -110,7 +112,7 @@ # Check if we can activate the new configuration. my $cur_init_interface_version = read_file("/run/current-system/init-interface-version", err_mode => "quiet") // ""; -my $new_init_interface_version = read_file("$out/init-interface-version"); +my $new_init_interface_version = read_file("$toplevel/init-interface-version"); if ($new_init_interface_version ne $cur_init_interface_version) { print STDERR <<'EOF'; @@ -477,7 +479,7 @@ sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutin $units_to_stop->{$socket} = 1; # Only restart sockets that actually # exist in new configuration: - if (-e "$out/etc/systemd/system/$socket") { + if (-e "$toplevel/etc/systemd/system/$socket") { $units_to_start->{$socket} = 1; if ($units_to_start eq $units_to_restart) { record_unit($restart_list_file, $socket); @@ -539,13 +541,13 @@ sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutin my $base_unit = $unit; my $cur_unit_file = "/etc/systemd/system/$base_unit"; - my $new_unit_file = "$out/etc/systemd/system/$base_unit"; + my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; # Detect template instances. if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { $base_unit = "$1\@.$2"; $cur_unit_file = "/etc/systemd/system/$base_unit"; - $new_unit_file = "$out/etc/systemd/system/$base_unit"; + $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; } my $base_name = $base_unit; @@ -626,7 +628,7 @@ sub path_to_unit_name { # we generated units for all mounts; then we could unify this with the # unit checking code above. my ($cur_fss, $cur_swaps) = parse_fstab("/etc/fstab"); -my ($new_fss, $new_swaps) = parse_fstab("$out/etc/fstab"); +my ($new_fss, $new_swaps) = parse_fstab("$toplevel/etc/fstab"); foreach my $mount_point (keys(%{$cur_fss})) { my $cur = $cur_fss->{$mount_point}; my $new = $new_fss->{$mount_point}; @@ -670,7 +672,7 @@ sub path_to_unit_name { my $cur_pid1_path = abs_path("/proc/1/exe") // "/unknown"; my $cur_systemd_system_config = abs_path("/etc/systemd/system.conf") // "/unknown"; my $new_pid1_path = abs_path("$new_systemd/lib/systemd/systemd") or die; -my $new_systemd_system_config = abs_path("$out/etc/systemd/system.conf") // "/unknown"; +my $new_systemd_system_config = abs_path("$toplevel/etc/systemd/system.conf") // "/unknown"; my $restart_systemd = $cur_pid1_path ne $new_pid1_path; if ($cur_systemd_system_config ne $new_systemd_system_config) { @@ -709,12 +711,12 @@ sub filter_units { foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; my $base_unit = $unit; - my $new_unit_file = "$out/etc/systemd/system/$base_unit"; + my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; # Detect template instances. if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { $base_unit = "$1\@.$2"; - $new_unit_file = "$out/etc/systemd/system/$base_unit"; + $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; } my $base_name = $base_unit; @@ -757,7 +759,7 @@ sub filter_units { } -syslog(LOG_NOTICE, "switching to system configuration $out"); +syslog(LOG_NOTICE, "switching to system configuration $toplevel"); if (scalar(keys(%units_to_stop)) > 0) { if (scalar(@units_to_stop_filtered)) { @@ -781,12 +783,12 @@ sub filter_units { foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; my $base_unit = $unit; - my $new_unit_file = "$out/etc/systemd/system/$base_unit"; + my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; # Detect template instances. if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { $base_unit = "$1\@.$2"; - $new_unit_file = "$out/etc/systemd/system/$base_unit"; + $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; } my $base_name = $base_unit; @@ -857,7 +859,7 @@ sub filter_units { for my $unit (keys(%units_to_reload)) { if (!unit_is_active($unit)) { # Figure out if we need to start the unit - my %unit_info = parse_unit("$out/etc/systemd/system/$unit"); + my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit"); if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) { $units_to_start{$unit} = 1; record_unit($start_list_file, $unit); @@ -940,9 +942,9 @@ sub filter_units { } if ($res == 0) { - syslog(LOG_NOTICE, "finished switching to system configuration $out"); + syslog(LOG_NOTICE, "finished switching to system configuration $toplevel"); } else { - syslog(LOG_ERR, "switching to system configuration $out failed (status $res)"); + syslog(LOG_ERR, "switching to system configuration $toplevel failed (status $res)"); } exit($res); diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 8cc4e68e78a1f..f44dede7fef45 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -70,6 +70,19 @@ in { }; }; + simpleServiceSeparateActivationScript.configuration = { + system.activatable = false; + systemd.services.test = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.coreutils}/bin/true"; + ExecReload = "${pkgs.coreutils}/bin/true"; + }; + }; + }; + simpleServiceDifferentDescription.configuration = { imports = [ simpleService.configuration ]; systemd.services.test.description = "Test unit"; @@ -497,11 +510,15 @@ in { in /* python */ '' def switch_to_specialisation(system, name, action="test", fail=False): if name == "": - stc = f"{system}/bin/switch-to-configuration" + switcher = f"{system}/bin/switch-to-configuration" else: - stc = f"{system}/specialisation/{name}/bin/switch-to-configuration" - out = machine.fail(f"{stc} {action} 2>&1") if fail \ - else machine.succeed(f"{stc} {action} 2>&1") + switcher = f"{system}/specialisation/{name}/bin/switch-to-configuration" + return run_switch(switcher, action, fail) + + # like above but stc = switcher + def run_switch(switcher, action="test", fail=False): + out = machine.fail(f"{switcher} {action} 2>&1") if fail \ + else machine.succeed(f"{switcher} {action} 2>&1") assert_lacks(out, "switch-to-configuration line") # Perl warnings return out @@ -639,6 +656,22 @@ in { assert_lacks(out, "the following new units were started:") assert_contains(out, "would start the following units: test.service\n") + out = switch_to_specialisation("${machine}", "", action="test") + + # Ensure the service can be started when the activation script isn't in toplevel + # This is a lot like "Start a simple service", except activation-only deps could be gc-ed + out = run_switch("${nodes.machine.specialisation.simpleServiceSeparateActivationScript.configuration.system.build.separateActivationScript}/bin/switch-to-configuration"); + assert_lacks(out, "installing dummy bootloader") # test does not install a bootloader + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: dbus.service\n") # huh + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_contains(out, "the following new units were started: test.service\n") + machine.succeed("! test -e /run/current-system/activate") + machine.succeed("! test -e /run/current-system/dry-activate") + machine.succeed("! test -e /run/current-system/bin/switch-to-configuration") + # Ensure \ works in unit names out = switch_to_specialisation("${machine}", "unitWithBackslash") assert_contains(out, "stopping the following units: test.service\n")