diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml index afc159dbd5d7..c529737c3bf3 100644 --- a/nixos/doc/manual/man-nixos-rebuild.xml +++ b/nixos/doc/manual/man-nixos-rebuild.xml @@ -1,7 +1,7 @@ - + nixos-rebuild 8 @@ -22,7 +22,8 @@ - + + @@ -114,10 +115,22 @@ $ nix-build /path/to/nixpkgs/nixos -A system - + - Simply show what store paths would be built or downloaded - by any of the operations above. + Show what store paths would be built or downloaded by any + of the operations above, but otherwise do nothing. + + + + + + + Build the new configuration, but instead of activating it, + show what changes would be performed by the activation (i.e. by + nixos-rebuild test). For + instance, this command will print which systemd units would be + restarted. The list of changes is not guaranteed to be + complete. diff --git a/nixos/modules/installer/tools/nixos-rebuild.sh b/nixos/modules/installer/tools/nixos-rebuild.sh index 8157f8fc7da5..1d6df8cb3f71 100644 --- a/nixos/modules/installer/tools/nixos-rebuild.sh +++ b/nixos/modules/installer/tools/nixos-rebuild.sh @@ -26,7 +26,8 @@ while [ "$#" -gt 0 ]; do --help) showSyntax ;; - switch|boot|test|build|dry-run|build-vm|build-vm-with-bootloader) + switch|boot|test|build|dry-build|dry-run|dry-activate|build-vm|build-vm-with-bootloader) + if [ "$i" = dry-run ]; then i=dry-build; fi action="$i" ;; --install-grub) @@ -137,7 +138,7 @@ fi # First build Nix, since NixOS may require a newer version than the # current one. -if [ -n "$rollback" -o "$action" = dry-run ]; then +if [ -n "$rollback" -o "$action" = dry-build ]; then buildNix= fi @@ -180,7 +181,7 @@ if [ -n "$canRun" ]; then fi -if [ "$action" = dry-run ]; then +if [ "$action" = dry-build ]; then extraBuildFlags+=(--dry-run) fi @@ -193,7 +194,7 @@ if [ -z "$rollback" ]; then if [ "$action" = switch -o "$action" = boot ]; then nix-env "${extraBuildFlags[@]}" -p "$profile" -f '' --set -A system pathToConfig="$profile" - elif [ "$action" = test -o "$action" = build -o "$action" = dry-run ]; then + elif [ "$action" = test -o "$action" = build -o "$action" = dry-build -o "$action" = dry-activate ]; then nix-build '' -A system -k "${extraBuildFlags[@]}" > /dev/null pathToConfig=./result elif [ "$action" = build-vm ]; then @@ -224,7 +225,7 @@ fi # If we're not just building, then make the new configuration the boot # default and/or activate it now. -if [ "$action" = switch -o "$action" = boot -o "$action" = test ]; then +if [ "$action" = switch -o "$action" = boot -o "$action" = test -o "$action" = dry-activate ]; then if ! $pathToConfig/bin/switch-to-configuration "$action"; then echo "warning: error(s) occured while switching to the new configuration" >&2 exit 1 diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index dbe13c022f09..45fb752aaaa6 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -9,19 +9,21 @@ use Cwd 'abs_path'; my $out = "@out@"; +# To be robust against interruption, record what units need to be started etc. my $startListFile = "/run/systemd/start-list"; my $restartListFile = "/run/systemd/restart-list"; my $reloadListFile = "/run/systemd/reload-list"; my $action = shift @ARGV; -if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test")) { +if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) { print STDERR < 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. @@ -124,9 +129,18 @@ sub fingerprintUnit { return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : ""); } -# Stop all services that no longer exist or have changed in the new -# configuration. -my (@unitsToStop, @unitsToSkip); +# Figure out what units need to be stopped, started, restarted or reloaded. +my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload); + +$unitsToStart{$_} = 1 foreach + split('\n', read_file($startListFile, err_mode => 'quiet') // ""); + +$unitsToRestart{$_} = 1 foreach + split('\n', read_file($restartListFile, err_mode => 'quiet') // ""); + +$unitsToReload{$_} = 1 foreach + split '\n', read_file($reloadListFile, err_mode => 'quiet') // ""; + my $activePrev = getActiveUnits; while (my ($unit, $state) = each %{$activePrev}) { my $baseUnit = $unit; @@ -141,7 +155,7 @@ while (my ($unit, $state) = each %{$activePrev}) { if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) { if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") { - push @unitsToStop, $unit; + $unitsToStop{$unit} = 1; } elsif ($unit =~ /\.target$/) { @@ -155,7 +169,8 @@ while (my ($unit, $state) = each %{$activePrev}) { # should not be the case. Just ignore it. if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") { unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no")) { - write_file($startListFile, { append => 1 }, "$unit\n"); + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); } } @@ -171,7 +186,7 @@ while (my ($unit, $state) = each %{$activePrev}) { # (unless there is a PartOf dependency), so this is just a # bookkeeping thing to get systemd to do the right thing. if (boolIsTrue($unitInfo->{'X-StopOnReconfiguration'} // "no")) { - push @unitsToStop, $unit; + $unitsToStop{$unit} = 1; } } @@ -180,16 +195,18 @@ while (my ($unit, $state) = each %{$activePrev}) { # Do nothing. These cannot be restarted directly. } elsif ($unit =~ /\.mount$/) { # Reload the changed mount unit to force a remount. - write_file($reloadListFile, { append => 1 }, "$unit\n"); + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); } elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/ || $unit =~ /\.slice$/) { # FIXME: do something? } else { my $unitInfo = parseUnit($newUnitFile); if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) { - write_file($reloadListFile, { append => 1 }, "$unit\n"); + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); } elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") ) { - push @unitsToSkip, $unit; + $unitsToSkip{$unit} = 1; } else { # If this unit is socket-activated, then stop the # socket unit(s) as well, and restart the @@ -202,8 +219,9 @@ while (my ($unit, $state) = each %{$activePrev}) { } foreach my $socket (@sockets) { if (defined $activePrev->{$socket}) { - push @unitsToStop, $socket; - write_file($startListFile, { append => 1 }, "$socket\n"); + $unitsToStop{$unit} = 1; + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $socket); $socketActivated = 1; } } @@ -213,7 +231,8 @@ while (my ($unit, $state) = each %{$activePrev}) { # This unit should be restarted instead of # stopped and started. - write_file($restartListFile, { append => 1 }, "$unit\n"); + $unitsToRestart{$unit} = 1; + recordUnit($restartListFile, $unit); } else { @@ -222,10 +241,11 @@ while (my ($unit, $state) = each %{$activePrev}) { # We write this to a file to ensure that the # service gets restarted if we're interrupted. if (!$socketActivated) { - write_file($startListFile, { append => 1 }, "$unit\n"); + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); } - push @unitsToStop, $unit; + $unitsToStop{$unit} = 1; } } @@ -268,14 +288,16 @@ foreach my $mountPoint (keys %$prevFss) { my $unit = pathToUnitName($mountPoint) . ".mount"; if (!defined $new) { # Filesystem entry disappeared, so unmount it. - push @unitsToStop, $unit; + $unitsToStop{$unit} = 1; } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) { # Filesystem type or device changed, so unmount and mount it. - write_file($startListFile, { append => 1 }, "$unit\n"); - push @unitsToStop, $unit; + $unitsToStop{$unit} = 1; + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); } elsif ($prev->{options} ne $new->{options}) { # Mount options changes, so remount it. - write_file($reloadListFile, { append => 1 }, "$unit\n"); + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); } } @@ -294,14 +316,36 @@ foreach my $device (keys %$prevSwaps) { # FIXME: update swap options (i.e. its priority). } -if (scalar @unitsToStop > 0) { - @unitsToStop = unique(@unitsToStop); - print STDERR "stopping the following units: ", join(", ", sort(@unitsToStop)), "\n"; - system("systemctl", "stop", "--", @unitsToStop); # FIXME: ignore errors? + +# Should we have systemd re-exec itself? +my $restartSystemd = abs_path("/proc/1/exe") ne abs_path("@systemd@/lib/systemd/systemd"); + + +# Show dry-run actions. +if ($action eq "dry-activate") { + print STDERR "would stop the following units: ", join(", ", sort(keys %unitsToStop)), "\n" + if scalar(keys %unitsToStop) > 0; + print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" + if scalar(keys %unitsToSkip) > 0; + print STDERR "would restart systemd\n" if $restartSystemd; + print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n" + if scalar(keys %unitsToRestart) > 0; + print STDERR "would start the following units: ", join(", ", sort(keys %unitsToStart)), "\n"; + print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" + if scalar(keys %unitsToReload) > 0; + exit 0; } -print STDERR "NOT restarting the following units: ", join(", ", sort(@unitsToSkip)), "\n" - if scalar @unitsToSkip > 0; + +syslog(LOG_NOTICE, "switching to system configuration $out"); + +if (scalar (keys %unitsToStop) > 0) { + print STDERR "stopping the following units: ", join(", ", sort(keys %unitsToStop)), "\n"; + system("systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors? +} + +print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" + if scalar(keys %unitsToSkip) > 0; # Activate the new configuration (i.e., update /etc, make accounts, # and so on). @@ -310,7 +354,7 @@ print STDERR "activating the configuration...\n"; system("$out/activate", "$out") == 0 or $res = 2; # Restart systemd if necessary. -if (abs_path("/proc/1/exe") ne abs_path("@systemd@/lib/systemd/systemd")) { +if ($restartSystemd) { print STDERR "restarting systemd...\n"; system("@systemd@/bin/systemctl", "daemon-reexec") == 0 or $res = 2; } @@ -327,10 +371,9 @@ system("@systemd@/bin/systemctl", "reload-or-restart", "dbus.service"); # Restart changed services (those that have to be restarted rather # than stopped and started). -my @restart = unique(split('\n', read_file($restartListFile, err_mode => 'quiet') // "")); -if (scalar @restart > 0) { - print STDERR "restarting the following units: ", join(", ", sort(@restart)), "\n"; - system("@systemd@/bin/systemctl", "restart", "--", @restart) == 0 or $res = 4; +if (scalar(keys %unitsToRestart) > 0) { + print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"; + system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4; unlink($restartListFile); } @@ -340,17 +383,15 @@ if (scalar @restart > 0) { # that are symlinks to other units. We shouldn't start both at the # same time because we'll get a "Failed to add path to set" error from # systemd. -my @start = unique("default.target", "timers.target", "sockets.target", split('\n', read_file($startListFile, err_mode => 'quiet') // "")); -print STDERR "starting the following units: ", join(", ", sort(@start)), "\n"; -system("@systemd@/bin/systemctl", "start", "--", @start) == 0 or $res = 4; +print STDERR "starting the following units: ", join(", ", sort(keys %unitsToStart)), "\n"; +system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4; unlink($startListFile); # Reload units that need it. This includes remounting changed mount # units. -my @reload = unique(split '\n', read_file($reloadListFile, err_mode => 'quiet') // ""); -if (scalar @reload > 0) { - print STDERR "reloading the following units: ", join(", ", sort(@reload)), "\n"; - system("@systemd@/bin/systemctl", "reload", "--", @reload) == 0 or $res = 4; +if (scalar(keys %unitsToReload) > 0) { + print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n"; + system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4; unlink($reloadListFile); }