From d729cc8a53915847a586c367bc48e7113f82412d Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sat, 29 Jan 2022 14:06:25 +0100 Subject: nixos/switch-to-configuration: Skip [Install] section --- nixos/modules/system/activation/switch-to-configuration.pl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nixos') diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 1fe346114e43..d6f780f7b61a 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -131,6 +131,10 @@ sub parseSystemdIni { # Copy over all sections foreach my $sectionName (keys %fileContents) { + if ($sectionName eq "Install") { + # Skip the [Install] section because it has no relevant keys for us + next; + } # Copy over all keys foreach my $iniKey (keys %{$fileContents{$sectionName}}) { # Ensure the value is an array so it's easier to work with -- cgit 1.4.1 From 78db7b6529a12f2afbac4d3958b68c23b04e8996 Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sat, 29 Jan 2022 14:23:59 +0100 Subject: nixos/switch-to-configuration: Allow passing parsed unit contents --- nixos/modules/system/activation/switch-to-configuration.pl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nixos') diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index d6f780f7b61a..f2c1cf795d1b 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -205,7 +205,7 @@ sub fingerprintUnit { } sub handleModifiedUnit { - my ($unit, $baseName, $newUnitFile, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_; + my ($unit, $baseName, $newUnitFile, $newUnitInfo, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_; if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/ || $unit =~ /\.slice$/) { # Do nothing. These cannot be restarted directly. @@ -223,7 +223,7 @@ sub handleModifiedUnit { # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609 # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430 } else { - my %unitInfo = parseUnit($newUnitFile); + my %unitInfo = $newUnitInfo ? %{$newUnitInfo} : parseUnit($newUnitFile); if (parseSystemdBool(\%unitInfo, "Service", "X-ReloadIfChanged", 0)) { $unitsToReload->{$unit} = 1; recordUnit($reloadListFile, $unit); @@ -349,7 +349,7 @@ while (my ($unit, $state) = each %{$activePrev}) { } elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) { - handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); } } } @@ -473,7 +473,7 @@ if ($action eq "dry-activate") { next; } - handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); } unlink($dryRestartByActivationFile); @@ -529,7 +529,7 @@ foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // next; } - handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); } # We can remove the file now because it has been propagated to the other restart/reload files unlink($restartByActivationFile); -- cgit 1.4.1 From b9bb1de34121312c4c37829b8ee10c81ffd2187a Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sat, 29 Jan 2022 22:56:52 +0100 Subject: nixos/switch-to-configuration: Implement reload support This is accomplished by comparing the hashes that the unit files contain. By filtering for a special key `X-Reload-Triggers` in the `[Unit]` section, we can differentiate between reloads and restarts. Since activation scripts can request reloads of units as well, more checking of this behaviour is implemented. If a unit is to be restarted, it's never reloaded as well which would make no sense. Also removes a useless subroutine and perl dependencies that are nowadays handled by the propagated build inputs feature of `perl.withPackages`. --- .../system/activation/switch-to-configuration.pl | 161 ++++++++++++++++++--- nixos/modules/system/activation/top-level.nix | 2 +- 2 files changed, 140 insertions(+), 23 deletions(-) (limited to 'nixos') diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index f2c1cf795d1b..2fe78634d627 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -2,10 +2,11 @@ use strict; use warnings; +use Array::Compare; use Config::IniFiles; use File::Path qw(make_path); use File::Basename; -use File::Slurp; +use File::Slurp qw(read_file write_file edit_file); use Net::DBus; use Sys::Syslog qw(:standard :macros); use Cwd 'abs_path'; @@ -20,12 +21,19 @@ my $restartListFile = "/run/nixos/restart-list"; my $reloadListFile = "/run/nixos/reload-list"; # Parse restart/reload requests by the activation script. -# Activation scripts may write newline-separated units to this +# Activation scripts may write newline-separated units to the restart # file and switch-to-configuration will handle them. While # `stopIfChanged = true` is ignored, switch-to-configuration will # handle `restartIfChanged = false` and `reloadIfChanged = true`. +# This is the same as specifying a restart trigger in the NixOS module. +# +# The reload file asks the script to reload a unit. This is the same as +# specifying a reload trigger in the NixOS module and can be ignored if +# the unit is restarted in this activation. my $restartByActivationFile = "/run/nixos/activation-restart-list"; +my $reloadByActivationFile = "/run/nixos/activation-reload-list"; my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list"; +my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list"; make_path("/run/nixos", { mode => oct(755) }); @@ -196,12 +204,88 @@ sub recordUnit { write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate"; } -# As a fingerprint for determining whether a unit has changed, we use -# its absolute path. If it has an override file, we append *its* -# absolute path as well. -sub fingerprintUnit { - my ($s) = @_; - return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : ""); +# The opposite of recordUnit, removes a unit name from a file +sub unrecord_unit { + my ($fn, $unit) = @_; + edit_file { s/^$unit\n//msx } $fn if $action ne "dry-activate"; +} + +# Compare the contents of two unit files and return whether the unit +# needs to be restarted or reloaded. If the units differ, the service +# is restarted unless the only difference is `X-Reload-Triggers` in the +# `Unit` section. If this is the only modification, the unit is reloaded +# instead of restarted. +# Returns: +# - 0 if the units are equal +# - 1 if the units are different and a restart action is required +# - 2 if the units are different and a reload action is required +sub compare_units { + my ($old_unit, $new_unit) = @_; + my $comp = Array::Compare->new; + my $ret = 0; + + # Comparison hash for the sections + my %section_cmp = map { $_ => 1 } keys %{$new_unit}; + # Iterate over the sections + foreach my $section_name (keys %{$old_unit}) { + # Missing section in the new unit? + if (not exists $section_cmp{$section_name}) { + if ($section_name eq 'Unit' and %{$old_unit->{'Unit'}} == 1 and defined(%{$old_unit->{'Unit'}}{'X-Reload-Triggers'})) { + # If a new [Unit] section was removed that only contained X-Reload-Triggers, + # do nothing. + next; + } else { + return 1; + } + } + delete $section_cmp{$section_name}; + # Comparison hash for the section contents + my %ini_cmp = map { $_ => 1 } keys %{$new_unit->{$section_name}}; + # Iterate over the keys of the section + foreach my $ini_key (keys %{$old_unit->{$section_name}}) { + delete $ini_cmp{$ini_key}; + my @old_value = @{$old_unit->{$section_name}{$ini_key}}; + # If the key is missing in the new unit, they are different... + if (not $new_unit->{$section_name}{$ini_key}) { + # ... unless the key that is now missing was the reload trigger + if ($section_name eq 'Unit' and $ini_key eq 'X-Reload-Triggers') { + next; + } + return 1; + } + my @new_value = @{$new_unit->{$section_name}{$ini_key}}; + # If the contents are different, the units are different + if (not $comp->compare(\@old_value, \@new_value)) { + # Check if only the reload triggers changed + if ($section_name eq 'Unit' and $ini_key eq 'X-Reload-Triggers') { + $ret = 2; + } else { + return 1; + } + } + } + # A key was introduced that was missing in the old unit + if (%ini_cmp) { + if ($section_name eq 'Unit' and %ini_cmp == 1 and defined($ini_cmp{'X-Reload-Triggers'})) { + # If the newly introduced key was the reload triggers, reload the unit + $ret = 2; + } else { + return 1; + } + }; + } + # A section was introduced that was missing in the old unit + if (%section_cmp) { + if (%section_cmp == 1 and defined($section_cmp{'Unit'}) and %{$new_unit->{'Unit'}} == 1 and defined(%{$new_unit->{'Unit'}}{'X-Reload-Triggers'})) { + # If a new [Unit] section was introduced that only contains X-Reload-Triggers, + # reload instead of restarting + $ret = 2; + } else { + return 1; + } + } + + return $ret; } sub handleModifiedUnit { @@ -224,7 +308,7 @@ sub handleModifiedUnit { # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430 } else { my %unitInfo = $newUnitInfo ? %{$newUnitInfo} : parseUnit($newUnitFile); - if (parseSystemdBool(\%unitInfo, "Service", "X-ReloadIfChanged", 0)) { + if (parseSystemdBool(\%unitInfo, "Service", "X-ReloadIfChanged", 0) and not $unitsToRestart->{$unit} and not $unitsToStop->{$unit}) { $unitsToReload->{$unit} = 1; recordUnit($reloadListFile, $unit); } @@ -238,6 +322,11 @@ sub handleModifiedUnit { # stopped and started. $unitsToRestart->{$unit} = 1; recordUnit($restartListFile, $unit); + # Remove from units to reload so we don't restart and reload + if ($unitsToReload->{$unit}) { + delete $unitsToReload->{$unit}; + unrecord_unit($reloadListFile, $unit); + } } else { # If this unit is socket-activated, then stop the # socket unit(s) as well, and restart the @@ -258,6 +347,11 @@ sub handleModifiedUnit { recordUnit($startListFile, $socket); $socketActivated = 1; } + # Remove from units to reload so we don't restart and reload + if ($unitsToReload->{$unit}) { + delete $unitsToReload->{$unit}; + unrecord_unit($reloadListFile, $unit); + } } } } @@ -272,6 +366,11 @@ sub handleModifiedUnit { } $unitsToStop->{$unit} = 1; + # Remove from units to reload so we don't restart and reload + if ($unitsToReload->{$unit}) { + delete $unitsToReload->{$unit}; + unrecord_unit($reloadListFile, $unit); + } } } } @@ -348,8 +447,16 @@ while (my ($unit, $state) = each %{$activePrev}) { } } - elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) { - handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + else { + my %old_unit_info = parseUnit($prevUnitFile); + my %new_unit_info = parseUnit($newUnitFile); + my $diff = compare_units(\%old_unit_info, \%new_unit_info); + if ($diff eq 1) { + handleModifiedUnit($unit, $baseName, $newUnitFile, \%new_unit_info, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + } elsif ($diff eq 2 and not $unitsToRestart{$unit}) { + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } } } } @@ -365,17 +472,6 @@ sub pathToUnitName { return $escaped; } -sub unique { - my %seen; - my @res; - foreach my $name (@_) { - next if $seen{$name}; - $seen{$name} = 1; - push @res, $name; - } - return @res; -} - # Compare the previous and new fstab to figure out which filesystems # need a remount or need to be unmounted. New filesystems are mounted # automatically by starting local-fs.target. FIXME: might be nicer if @@ -477,6 +573,16 @@ if ($action eq "dry-activate") { } unlink($dryRestartByActivationFile); + foreach (split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // "")) { + my $unit = $_; + + if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) { + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } + } + unlink($dryReloadByActivationFile); + print STDERR "would restart systemd\n" if $restartSystemd; print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" if scalar(keys %unitsToReload) > 0; @@ -534,6 +640,17 @@ foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // # We can remove the file now because it has been propagated to the other restart/reload files unlink($restartByActivationFile); +foreach (split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // "")) { + my $unit = $_; + + if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) { + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } +} +# We can remove the file now because it has been propagated to the other reload file +unlink($reloadByActivationFile); + # Restart systemd if necessary. Note that this is done using the # current version of systemd, just in case the new one has trouble # communicating with the running pid 1. diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 9e6ca75b9da4..4745239050b2 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -117,7 +117,7 @@ let configurationName = config.boot.loader.grub.configurationName; # Needed by switch-to-configuration. - perl = pkgs.perl.withPackages (p: with p; [ FileSlurp NetDBus XMLParser XMLTwig ConfigIniFiles ]); + perl = pkgs.perl.withPackages (p: with p; [ ArrayCompare ConfigIniFiles FileSlurp NetDBus ]); }; # Handle assertions and warnings -- cgit 1.4.1 From b5b3ee4f7884bf70c04f325e043fe76843b5eacd Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sat, 29 Jan 2022 23:01:24 +0100 Subject: nixos/systemd: Add reloadTriggers to services --- .../manual/from_md/release-notes/rl-2205.section.xml | 17 +++++++++++++++++ nixos/doc/manual/release-notes/rl-2205.section.md | 3 +++ nixos/lib/systemd-unit-options.nix | 16 ++++++++++++++++ nixos/modules/system/boot/systemd.nix | 5 +++++ 4 files changed, 41 insertions(+) (limited to 'nixos') diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 04bb7ec12d3e..811702f5e52f 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -42,6 +42,14 @@ upgrade notes. + + + systemd services can now set + systemd.services.<name>.reloadTriggers + instead of reloadIfChanged for a more + granular distinction between reloads and restarts. + +
@@ -550,6 +558,15 @@ honors restartIfChanged and reloadIfChanged of the units. + + + + Preferring to reload instead of restarting can still + be achieved using + /run/nixos/activation-reload-list. + + + diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index a2e8038ae1f8..fca37420977e 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -17,6 +17,8 @@ In addition to numerous new and upgraded packages, this release has the followin Migrations may take a while, see the [changelog](https://docs.mattermost.com/install/self-managed-changelog.html#release-v6-3-extended-support-release) and [important upgrade notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html). +- systemd services can now set [systemd.services.\.reloadTriggers](#opt-systemd.services) instead of `reloadIfChanged` for a more granular distinction between reloads and restarts. + ## New Services {#sec-release-22.05-new-services} - [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable). @@ -179,6 +181,7 @@ In addition to numerous new and upgraded packages, this release has the followin - `switch-to-configuration` (the script that is run when running `nixos-rebuild switch` for example) has been reworked * The interface that allows activation scripts to restart units has been streamlined. Restarting and reloading is now done by a single file `/run/nixos/activation-restart-list` that honors `restartIfChanged` and `reloadIfChanged` of the units. + * Preferring to reload instead of restarting can still be achieved using `/run/nixos/activation-reload-list`. * The script now uses a proper ini-file parser to parse systemd units. Some values are now only searched in one section instead of in the entire unit. This is only relevant for units that don't use the NixOS systemd moule. * `RefuseManualStop`, `X-OnlyManualStart`, `X-StopOnRemoval`, `X-StopOnReconfiguration` are only searched in the `[Unit]` section * `X-ReloadIfChanged`, `X-RestartIfChanged`, `X-StopIfChanged` are only searched in the `[Service]` section diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix index 520f2e982a26..8029ba0e3f6c 100644 --- a/nixos/lib/systemd-unit-options.nix +++ b/nixos/lib/systemd-unit-options.nix @@ -201,6 +201,17 @@ in rec { ''; }; + reloadTriggers = mkOption { + default = []; + type = types.listOf unitOption; + description = '' + An arbitrary list of items such as derivations. If any item + in the list changes between reconfigurations, the service will + be reloaded. If anything but a reload trigger changes in the + unit file, the unit will be restarted instead. + ''; + }; + onFailure = mkOption { default = []; type = types.listOf unitNameType; @@ -338,6 +349,11 @@ in rec { configuration switch if its definition has changed. If enabled, the value of is ignored. + + This option should not be used anymore in favor of + which allows more granular + control of when a service is reloaded and when a service + is restarted. ''; }; diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 9dcf9eb769f8..1f2dd618698c 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -243,6 +243,8 @@ let { Requisite = toString config.requisite; } // optionalAttrs (config.restartTriggers != []) { X-Restart-Triggers = toString config.restartTriggers; } + // optionalAttrs (config.reloadTriggers != []) + { X-Reload-Triggers = toString config.reloadTriggers; } // optionalAttrs (config.description != "") { Description = config.description; } // optionalAttrs (config.documentation != []) { @@ -917,6 +919,9 @@ in (optional hasDeprecated "Service '${name}.service' uses the attribute 'StartLimitInterval' in the Service section, which is deprecated. See https://github.com/NixOS/nixpkgs/issues/45786." ) + (optional (service.reloadIfChanged && service.reloadTriggers != []) + "Service '${name}.service' has both 'reloadIfChanged' and 'reloadTriggers' set. This is probably not what you want, because 'reloadTriggers' behave the same whay as 'restartTriggers' if 'reloadIfChanged' is set." + ) ] ) cfg.services -- cgit 1.4.1 From 1c1f8c59e13590b13e51f829cdb6c98d47d13e7c Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sat, 29 Jan 2022 23:01:48 +0100 Subject: nixos/switch-test: Test the unit file parser and reloads --- nixos/tests/switch-test.nix | 247 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 241 insertions(+), 6 deletions(-) (limited to 'nixos') diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 8e425f0f8779..3357d83342de 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -18,6 +18,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${pkgs.coreutils}/bin/true"; + ExecReload = "${pkgs.coreutils}/bin/true"; }; }; }; @@ -70,6 +71,80 @@ import ./make-test-python.nix ({ pkgs, ...} : { }; }; + simpleServiceWithExtraSection.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.packages = [ (pkgs.writeTextFile { + name = "systemd-extra-section"; + destination = "/etc/systemd/system/test.service"; + text = '' + [X-Test] + X-Test-Value=a + ''; + }) ]; + }; + + simpleServiceWithExtraSectionOtherName.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.packages = [ (pkgs.writeTextFile { + name = "systemd-extra-section"; + destination = "/etc/systemd/system/test.service"; + text = '' + [X-Test2] + X-Test-Value=a + ''; + }) ]; + }; + + simpleServiceWithInstallSection.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.packages = [ (pkgs.writeTextFile { + name = "systemd-extra-section"; + destination = "/etc/systemd/system/test.service"; + text = '' + [Install] + WantedBy=multi-user.target + ''; + }) ]; + }; + + simpleServiceWithExtraKey.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test.serviceConfig."X-Test" = "test"; + }; + + simpleServiceWithExtraKeyOtherValue.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test.serviceConfig."X-Test" = "test2"; + }; + + simpleServiceWithExtraKeyOtherName.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test.serviceConfig."X-Test2" = "test"; + }; + + simpleServiceReloadTrigger.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test.reloadTriggers = [ "/dev/null" ]; + }; + + simpleServiceReloadTriggerModified.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test.reloadTriggers = [ "/dev/zero" ]; + }; + + simpleServiceReloadTriggerModifiedAndSomethingElse.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test = { + reloadTriggers = [ "/dev/zero" ]; + serviceConfig."X-Test" = "test"; + }; + }; + + simpleServiceReloadTriggerModifiedSomethingElse.configuration = { + imports = [ simpleServiceNostop.configuration ]; + systemd.services.test.serviceConfig."X-Test" = "test"; + }; + restart-and-reload-by-activation-script.configuration = { systemd.services = rec { simple-service = { @@ -93,6 +168,17 @@ import ./make-test-python.nix ({ pkgs, ...} : { no-restart-service = simple-service // { restartIfChanged = false; }; + + reload-triggers = simple-service // { + wantedBy = [ "multi-user.target" ]; + }; + + reload-triggers-and-restart-by-as = simple-service; + + reload-triggers-and-restart = simple-service // { + stopIfChanged = false; # easier to check for this + wantedBy = [ "multi-user.target" ]; + }; }; system.activationScripts.restart-and-reload-test = { @@ -101,19 +187,33 @@ import ./make-test-python.nix ({ pkgs, ...} : { text = '' if [ "$NIXOS_ACTION" = dry-activate ]; then f=/run/nixos/dry-activation-restart-list + g=/run/nixos/dry-activation-reload-list else f=/run/nixos/activation-restart-list + g=/run/nixos/activation-reload-list fi cat <> "$f" simple-service.service simple-restart-service.service simple-reload-service.service no-restart-service.service + reload-triggers-and-restart-by-as.service + EOF + + cat <> "$g" + reload-triggers.service + reload-triggers-and-restart-by-as.service + reload-triggers-and-restart.service EOF ''; }; }; + restart-and-reload-by-activation-script-modified.configuration = { + imports = [ restart-and-reload-by-activation-script.configuration ]; + systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test"; + }; + mount.configuration = { systemd.mounts = [ { @@ -241,6 +341,8 @@ import ./make-test-python.nix ({ pkgs, ...} : { raise Exception(f"Unexpected string '{needle}' was found") + machine.wait_for_unit("multi-user.target") + machine.succeed( "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test" ) @@ -379,6 +481,130 @@ import ./make-test-python.nix ({ pkgs, ...} : { assert_contains(out, "Main PID:") # output of systemctl assert_lacks(out, "as well:") + with subtest("unit file parser"): + # Switch to a well-known state + switch_to_specialisation("${machine}", "simpleServiceNostop") + + # Add a section + out = switch_to_specialisation("${machine}", "simpleServiceWithExtraSection") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Rename it + out = switch_to_specialisation("${machine}", "simpleServiceWithExtraSectionOtherName") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Remove it + out = switch_to_specialisation("${machine}", "simpleServiceNostop") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # [Install] section is ignored + out = switch_to_specialisation("${machine}", "simpleServiceWithInstallSection") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Add a key + out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKey") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Change its value + out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKeyOtherValue") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Rename it + out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKeyOtherName") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Remove it + out = switch_to_specialisation("${machine}", "simpleServiceNostop") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Add a reload trigger + out = switch_to_specialisation("${machine}", "simpleServiceReloadTrigger") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: test.service\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Modify the reload trigger + out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModified") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: test.service\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Modify the reload trigger and something else + out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModifiedAndSomethingElse") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + + # Remove the reload trigger + out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModifiedSomethingElse") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + assert_lacks(out, "as well:") + with subtest("restart and reload by activation script"): switch_to_specialisation("${machine}", "simpleServiceNorestart") out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script") @@ -386,23 +612,32 @@ import ./make-test-python.nix ({ pkgs, ...} : { assert_lacks(out, "NOT restarting the following changed units:") assert_lacks(out, "reloading the following units:") assert_lacks(out, "restarting the following units:") - assert_contains(out, "\nstarting the following units: no-restart-service.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n") + assert_contains(out, "\nstarting the following units: no-restart-service.service, reload-triggers-and-restart-by-as.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n") assert_lacks(out, "as well:") # Switch to the same system where the example services get restarted - # by the activation script + # and reloaded by the activation script out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script") assert_lacks(out, "stopping the following units:") assert_lacks(out, "NOT restarting the following changed units:") - assert_contains(out, "reloading the following units: simple-reload-service.service\n") - assert_contains(out, "restarting the following units: simple-restart-service.service, simple-service.service\n") + assert_contains(out, "reloading the following units: reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service\n") + assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, simple-restart-service.service, simple-service.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "as well:") + # Switch to the same system and see if the service gets restarted when it's modified + # while the fact that it's supposed to be reloaded by the activation script is ignored. + out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: reload-triggers.service, simple-reload-service.service\n") + assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n") assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "as well:") # The same, but in dry mode out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate") assert_lacks(out, "would stop the following units:") assert_lacks(out, "would NOT stop the following changed units:") - assert_contains(out, "would reload the following units: simple-reload-service.service\n") - assert_contains(out, "would restart the following units: simple-restart-service.service, simple-service.service\n") + assert_contains(out, "would reload the following units: reload-triggers.service, simple-reload-service.service\n") + assert_contains(out, "would restart the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n") assert_lacks(out, "\nwould start the following units:") assert_lacks(out, "as well:") -- cgit 1.4.1 From 08cd8ab8b6782d9f48cdccd127a12d82257e1b3d Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sat, 29 Jan 2022 23:21:42 +0100 Subject: nixos/switch-to-configuration: Don't stop swaps in dry-activate --- nixos/modules/system/activation/switch-to-configuration.pl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'nixos') diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 2fe78634d627..2ea871626e20 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -507,8 +507,12 @@ foreach my $device (keys %$prevSwaps) { # "systemctl stop" here because systemd has lots of alias # units that prevent a stop from actually calling # "swapoff". - print STDERR "stopping swap device: $device\n"; - system("@utillinux@/sbin/swapoff", $device); + if ($action ne "dry-activate") { + print STDERR "would stop swap device: $device\n"; + } else { + print STDERR "stopping swap device: $device\n"; + system("@utillinux@/sbin/swapoff", $device); + } } # FIXME: update swap options (i.e. its priority). } -- cgit 1.4.1 From 8d925cc8db5fcc0fe0e091d819d93f8580e62c53 Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Sun, 30 Jan 2022 00:37:55 +0100 Subject: nixos/doc: Document the activation script This may be helpful to new module developers, curious users, and people who just need a reference without having to look at the implementation --- .../development/activation-script.section.md | 72 ++++++++++ nixos/doc/manual/development/development.xml | 1 + .../manual/development/unit-handling.section.md | 57 ++++++++ .../what-happens-during-a-system-switch.chapter.md | 53 ++++++++ .../development/activation-script.section.xml | 150 +++++++++++++++++++++ .../from_md/development/unit-handling.section.xml | 119 ++++++++++++++++ ...what-happens-during-a-system-switch.chapter.xml | 122 +++++++++++++++++ 7 files changed, 574 insertions(+) create mode 100644 nixos/doc/manual/development/activation-script.section.md create mode 100644 nixos/doc/manual/development/unit-handling.section.md create mode 100644 nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md create mode 100644 nixos/doc/manual/from_md/development/activation-script.section.xml create mode 100644 nixos/doc/manual/from_md/development/unit-handling.section.xml create mode 100644 nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml (limited to 'nixos') diff --git a/nixos/doc/manual/development/activation-script.section.md b/nixos/doc/manual/development/activation-script.section.md new file mode 100644 index 000000000000..df6836624040 --- /dev/null +++ b/nixos/doc/manual/development/activation-script.section.md @@ -0,0 +1,72 @@ +# Activation script {#sec-activation-script} + +The activation script is a bash script called to activate the new +configuration which resides in a NixOS system in `$out/activate`. Since its +contents depend on your system configuration, the contents may differ. +This chapter explains how the script works in general and some common NixOS +snippets. Please be aware that the script is executed on every boot and system +switch, so tasks that can be performed in other places should be performed +there (for example letting a directory of a service be created by systemd using +mechanisms like `StateDirectory`, `CacheDirectory`, ... or if that's not +possible using `preStart` of the service). + +Activation scripts are defined as snippets using +[](#opt-system.activationScripts). They can either be a simple multiline string +or an attribute set that can depend on other snippets. The builder for the +activation script will take these dependencies into account and order the +snippets accordingly. As a simple example: + +```nix +system.activationScripts.my-activation-script = { + deps = [ "etc" ]; + # supportsDryActivation = true; + text = '' + echo "Hallo i bims" + ''; +}; +``` + +This example creates an activation script snippet that is run after the `etc` +snippet. The special variable `supportsDryActivation` can be set so the snippet +is also run when `nixos-rebuild dry-activate` is run. To differentiate between +real and dry activation, the `$NIXOS_ACTION` environment variable can be +read which is set to `dry-activate` when a dry activation is done. + +An activation script can write to special files instructing +`switch-to-configuration` to restart/reload units. The script will take these +requests into account and will incorperate the unit configuration as described +above. This means that the activation script will "fake" a modified unit file +and `switch-to-configuration` will act accordingly. By doing so, configuration +like [systemd.services.\.restartIfChanged](#opt-systemd.services) is +respected. Since the activation script is run **after** services are already +stopped, [systemd.services.\.stopIfChanged](#opt-systemd.services) +cannot be taken into account anymore and the unit is always restarted instead +of being stopped and started afterwards. + +The files that can be written to are `/run/nixos/activation-restart-list` and +`/run/nixos/activation-reload-list` with their respective counterparts for +dry activation being `/run/nixos/dry-activation-restart-list` and +`/run/nixos/dry-activation-reload-list`. Those files can contain +newline-separated lists of unit names where duplicates are being ignored. These +files are not create automatically and activation scripts must take the +possiblility into account that they have to create them first. + +## NixOS snippets {#sec-activation-script-nixos-snippets} + +There are some snippets NixOS enables by default because disabling them would +most likely break you system. This section lists a few of them and what they +do: + +- `binsh` creates `/bin/sh` which points to the runtime shell +- `etc` sets up the contents of `/etc`, this includes systemd units and + excludes `/etc/passwd`, `/etc/group`, and `/etc/shadow` (which are managed by + the `users` snippet) +- `hostname` sets the system's hostname in the kernel (not in `/etc`) +- `modprobe` sets the path to the `modprobe` binary for module auto-loading +- `nix` prepares the nix store and adds a default initial channel +- `specialfs` is responsible for mounting filesystems like `/proc` and `sys` +- `users` creates and removes users and groups by managing `/etc/passwd`, + `/etc/group` and `/etc/shadow`. This also creates home directories +- `usrbinenv` creates `/usr/bin/env` +- `var` creates some directories in `/var` that are not service-specific +- `wrappers` creates setuid wrappers like `ping` and `sudo` diff --git a/nixos/doc/manual/development/development.xml b/nixos/doc/manual/development/development.xml index 0b2ad60a878b..21286cdbd2b4 100644 --- a/nixos/doc/manual/development/development.xml +++ b/nixos/doc/manual/development/development.xml @@ -12,6 +12,7 @@ + diff --git a/nixos/doc/manual/development/unit-handling.section.md b/nixos/doc/manual/development/unit-handling.section.md new file mode 100644 index 000000000000..d477f2c860f3 --- /dev/null +++ b/nixos/doc/manual/development/unit-handling.section.md @@ -0,0 +1,57 @@ +# Unit handling {#sec-unit-handling} + +To figure out what units need to be started/stopped/restarted/reloaded, the +script first checks the current state of the system, similar to what `systemctl +list-units` shows. For each of the units, the script goes through the following +checks: + +- Is the unit file still in the new system? If not, **stop** the service unless + it sets `X-StopOnRemoval` in the `[Unit]` section to `false`. + +- Is it a `.target` unit? If so, **start** it unless it sets + `RefuseManualStart` in the `[Unit]` section to `true` or `X-OnlyManualStart` + in the `[Unit]` section to `true`. Also **stop** the unit again unless it + sets `X-StopOnReconfiguration` to `false`. + +- Are the contents of the unit files different? They are compared by parsing + them and comparing their contents. If they are different but only + `X-Reload-Triggers` in the `[Unit]` section is changed, **reload** the unit. + The NixOS module system allows setting these triggers with the option + [systemd.services.\.reloadTriggers](#opt-systemd.services). If the + unit files differ in any way, the following actions are performed: + + - `.path` and `.slice` units are ignored. There is no need to restart them + since changes in their values are applied by systemd when systemd is + reloaded. + + - `.mount` units are **reload**ed. These mostly come from the `/etc/fstab` + parser. + + - `.socket` units are currently ignored. This is to be fixed at a later + point. + + - The rest of the units (mostly `.service` units) are then **reload**ed if + `X-ReloadIfChanged` in the `[Service]` section is set to `true` (exposed + via [systemd.services.\.reloadIfChanged](#opt-systemd.services)). + + - If the reload flag is not set, some more flags decide if the unit is + skipped. These flags are `X-RestartIfChanged` in the `[Service]` section + (exposed via + [systemd.services.\.restartIfChanged](#opt-systemd.services)), + `RefuseManualStop` in the `[Unit]` section, and `X-OnlyManualStart` in the + `[Unit]` section. + + - The rest of the behavior is decided whether the unit has `X-StopIfChanged` + in the `[Service]` section set (exposed via + [systemd.services.\.stopIfChanged](#opt-systemd.services)). This is + set to `true` by default and must be explicitly turned off if not wanted. + If the flag is enabled, the unit is **stop**ped and then **start**ed. If + not, the unit is **restart**ed. The goal of the flag is to make sure that + the new unit never runs in the old environment which is still in place + before the activation script is run. + + - The last thing that is taken into account is whether the unit is a service + and socket-activated. Due to a bug, this is currently only done when + `X-StopIfChanged` is set. If the unit is socket-activated, the socket is + stopped and started, and the service is stopped and to be started by socket + activation. diff --git a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md new file mode 100644 index 000000000000..aad82831a3c2 --- /dev/null +++ b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md @@ -0,0 +1,53 @@ +# What happens during a system switch? {#sec-switching-systems} + +Running `nixos-rebuild switch` is one of the more common tasks under NixOS. +This chapter explains some of the internals of this command to make it simpler +for new module developers to configure their units correctly and to make it +easier to understand what is happening and why for curious administrators. + +`nixos-rebuild`, like many deployment solutions, calls `switch-to-configuration` +which resides in a NixOS system at `$out/bin/switch-to-configuration`. The +script is called with the action that is to be performed like `switch`, `test`, +`boot`. There is also the `dry-activate` action which does not really perform +the actions but rather prints what it would do if you called it with `test`. +This feature can be used to check what service states would be changed if the +configuration was switched to. + +If the action is `switch` or `boot`, the bootloader is updated first so the +configuration will be the next one to boot. Unless `NIXOS_NO_SYNC` is set to +`1`, `/nix/store` is synced to disk. + +If the action is `switch` or `test`, the currently running system is inspected +and the actions to switch to the new system are calculated. This process takes +two data sources into account: `/etc/fstab` and the current systemd status. +Mounts and swaps are read from `/etc/fstab` and the corresponding actions are +generated. If a new mount is added, for example, the proper `.mount` unit is +marked to be started. The current systemd state is inspected, the difference +between the current system and the desired configuration is calculated and +actions are generated to get to this state. There are a lot of nuances that can +be controlled by the units which are explained here. + +After calculating what should be done, the actions are carried out. The order +of actions is always the same: +- Stop units (`systemctl stop`) +- Run activation script (`$out/activate`) +- See if the activation script requested more units to restart +- Restart systemd if needed (`systemd daemon-reexec`) +- Forget about the failed state of units (`systemctl reset-failed`) +- Reload systemd (`systemctl daemon-reload`) +- Reload systemd user instances (`systemctl --user daemon-reload`) +- Set up tmpfiles (`systemd-tmpfiles --create`) +- Reload units (`systemctl reload`) +- Restart units (`systemctl restart`) +- Start units (`systemctl start`) +- Inspect what changed during these actions and print units that failed and + that were newly started + +Most of these actions are either self-explaining but some of them have to do +with our units or the activation script. For this reason, these topics are +explained in the next sections. + +```{=docbook} + + +``` diff --git a/nixos/doc/manual/from_md/development/activation-script.section.xml b/nixos/doc/manual/from_md/development/activation-script.section.xml new file mode 100644 index 000000000000..0d9e911216ef --- /dev/null +++ b/nixos/doc/manual/from_md/development/activation-script.section.xml @@ -0,0 +1,150 @@ +
+ Activation script + + The activation script is a bash script called to activate the new + configuration which resides in a NixOS system in + $out/activate. Since its contents depend on your + system configuration, the contents may differ. This chapter explains + how the script works in general and some common NixOS snippets. + Please be aware that the script is executed on every boot and system + switch, so tasks that can be performed in other places should be + performed there (for example letting a directory of a service be + created by systemd using mechanisms like + StateDirectory, + CacheDirectory, … or if that’s not possible using + preStart of the service). + + + Activation scripts are defined as snippets using + . They can either be + a simple multiline string or an attribute set that can depend on + other snippets. The builder for the activation script will take + these dependencies into account and order the snippets accordingly. + As a simple example: + + +system.activationScripts.my-activation-script = { + deps = [ "etc" ]; + # supportsDryActivation = true; + text = '' + echo "Hallo i bims" + ''; +}; + + + This example creates an activation script snippet that is run after + the etc snippet. The special variable + supportsDryActivation can be set so the snippet + is also run when nixos-rebuild dry-activate is + run. To differentiate between real and dry activation, the + $NIXOS_ACTION environment variable can be read + which is set to dry-activate when a dry + activation is done. + + + An activation script can write to special files instructing + switch-to-configuration to restart/reload units. + The script will take these requests into account and will + incorperate the unit configuration as described above. This means + that the activation script will fake a modified unit + file and switch-to-configuration will act + accordingly. By doing so, configuration like + systemd.services.<name>.restartIfChanged + is respected. Since the activation script is run + after services are already + stopped, + systemd.services.<name>.stopIfChanged + cannot be taken into account anymore and the unit is always + restarted instead of being stopped and started afterwards. + + + The files that can be written to are + /run/nixos/activation-restart-list and + /run/nixos/activation-reload-list with their + respective counterparts for dry activation being + /run/nixos/dry-activation-restart-list and + /run/nixos/dry-activation-reload-list. Those + files can contain newline-separated lists of unit names where + duplicates are being ignored. These files are not create + automatically and activation scripts must take the possiblility into + account that they have to create them first. + +
+ NixOS snippets + + There are some snippets NixOS enables by default because disabling + them would most likely break you system. This section lists a few + of them and what they do: + + + + + binsh creates /bin/sh + which points to the runtime shell + + + + + etc sets up the contents of + /etc, this includes systemd units and + excludes /etc/passwd, + /etc/group, and + /etc/shadow (which are managed by the + users snippet) + + + + + hostname sets the system’s hostname in the + kernel (not in /etc) + + + + + modprobe sets the path to the + modprobe binary for module auto-loading + + + + + nix prepares the nix store and adds a + default initial channel + + + + + specialfs is responsible for mounting + filesystems like /proc and + sys + + + + + users creates and removes users and groups + by managing /etc/passwd, + /etc/group and + /etc/shadow. This also creates home + directories + + + + + usrbinenv creates + /usr/bin/env + + + + + var creates some directories in + /var that are not service-specific + + + + + wrappers creates setuid wrappers like + ping and sudo + + + +
+
diff --git a/nixos/doc/manual/from_md/development/unit-handling.section.xml b/nixos/doc/manual/from_md/development/unit-handling.section.xml new file mode 100644 index 000000000000..a6a654042f6f --- /dev/null +++ b/nixos/doc/manual/from_md/development/unit-handling.section.xml @@ -0,0 +1,119 @@ +
+ Unit handling + + To figure out what units need to be + started/stopped/restarted/reloaded, the script first checks the + current state of the system, similar to what + systemctl list-units shows. For each of the + units, the script goes through the following checks: + + + + + Is the unit file still in the new system? If not, + stop the service unless it + sets X-StopOnRemoval in the + [Unit] section to false. + + + + + Is it a .target unit? If so, + start it unless it sets + RefuseManualStart in the + [Unit] section to true or + X-OnlyManualStart in the + [Unit] section to true. + Also stop the unit again + unless it sets X-StopOnReconfiguration to + false. + + + + + Are the contents of the unit files different? They are compared + by parsing them and comparing their contents. If they are + different but only X-Reload-Triggers in the + [Unit] section is changed, + reload the unit. The NixOS + module system allows setting these triggers with the option + systemd.services.<name>.reloadTriggers. + If the unit files differ in any way, the following actions are + performed: + + + + + .path and .slice units + are ignored. There is no need to restart them since changes + in their values are applied by systemd when systemd is + reloaded. + + + + + .mount units are + reloaded. These mostly + come from the /etc/fstab parser. + + + + + .socket units are currently ignored. This + is to be fixed at a later point. + + + + + The rest of the units (mostly .service + units) are then reloaded + if X-ReloadIfChanged in the + [Service] section is set to + true (exposed via + systemd.services.<name>.reloadIfChanged). + + + + + If the reload flag is not set, some more flags decide if the + unit is skipped. These flags are + X-RestartIfChanged in the + [Service] section (exposed via + systemd.services.<name>.restartIfChanged), + RefuseManualStop in the + [Unit] section, and + X-OnlyManualStart in the + [Unit] section. + + + + + The rest of the behavior is decided whether the unit has + X-StopIfChanged in the + [Service] section set (exposed via + systemd.services.<name>.stopIfChanged). + This is set to true by default and must + be explicitly turned off if not wanted. If the flag is + enabled, the unit is + stopped and then + started. If not, the unit + is restarted. The goal of + the flag is to make sure that the new unit never runs in the + old environment which is still in place before the + activation script is run. + + + + + The last thing that is taken into account is whether the + unit is a service and socket-activated. Due to a bug, this + is currently only done when + X-StopIfChanged is set. If the unit is + socket-activated, the socket is stopped and started, and the + service is stopped and to be started by socket activation. + + + + + +
diff --git a/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml b/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml new file mode 100644 index 000000000000..66ba792ddacb --- /dev/null +++ b/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml @@ -0,0 +1,122 @@ + + What happens during a system switch? + + Running nixos-rebuild switch is one of the more + common tasks under NixOS. This chapter explains some of the + internals of this command to make it simpler for new module + developers to configure their units correctly and to make it easier + to understand what is happening and why for curious administrators. + + + nixos-rebuild, like many deployment solutions, + calls switch-to-configuration which resides in a + NixOS system at $out/bin/switch-to-configuration. + The script is called with the action that is to be performed like + switch, test, + boot. There is also the + dry-activate action which does not really perform + the actions but rather prints what it would do if you called it with + test. This feature can be used to check what + service states would be changed if the configuration was switched + to. + + + If the action is switch or + boot, the bootloader is updated first so the + configuration will be the next one to boot. Unless + NIXOS_NO_SYNC is set to 1, + /nix/store is synced to disk. + + + If the action is switch or + test, the currently running system is inspected + and the actions to switch to the new system are calculated. This + process takes two data sources into account: + /etc/fstab and the current systemd status. Mounts + and swaps are read from /etc/fstab and the + corresponding actions are generated. If a new mount is added, for + example, the proper .mount unit is marked to be + started. The current systemd state is inspected, the difference + between the current system and the desired configuration is + calculated and actions are generated to get to this state. There are + a lot of nuances that can be controlled by the units which are + explained here. + + + After calculating what should be done, the actions are carried out. + The order of actions is always the same: + + + + + Stop units (systemctl stop) + + + + + Run activation script ($out/activate) + + + + + See if the activation script requested more units to restart + + + + + Restart systemd if needed + (systemd daemon-reexec) + + + + + Forget about the failed state of units + (systemctl reset-failed) + + + + + Reload systemd (systemctl daemon-reload) + + + + + Reload systemd user instances + (systemctl --user daemon-reload) + + + + + Set up tmpfiles (systemd-tmpfiles --create) + + + + + Reload units (systemctl reload) + + + + + Restart units (systemctl restart) + + + + + Start units (systemctl start) + + + + + Inspect what changed during these actions and print units that + failed and that were newly started + + + + + Most of these actions are either self-explaining but some of them + have to do with our units or the activation script. For this reason, + these topics are explained in the next sections. + + + + -- cgit 1.4.1