nixos/switchable-system: introduce a standard pre-switch check to prevent switching under certain conditions
This commit introduces "switch inhibitors" which are derivations that prevent a switch of a system to a new configuration if those derivations don't have the same hash in both configurations. This means that we can for instance add the systemd and dbus derivations such that users will be instructed to reboot their system when those derivations have changed instead of switching. This feature should be used sparingly, but it can make NixOS more robust by avoiding users switching to a configuration that can make their system unstable (like major updates of systemd, or new versions of dbus since the dbus and dbus-broker daemons cannot be restarted). The user can still force the switch by setting an env var.
This commit is contained in:
@@ -174,6 +174,10 @@ in
|
||||
permissions = "u+rx,g+rx,o-rx";
|
||||
};
|
||||
|
||||
system.switch.inhibitors = [
|
||||
cfg.dbusPackage
|
||||
];
|
||||
|
||||
systemd.services.dbus = {
|
||||
aliases = [
|
||||
# hack aiding to prevent dbus from restarting when switching from dbus-broker back to dbus
|
||||
@@ -212,6 +216,10 @@ in
|
||||
cfg.brokerPackage
|
||||
];
|
||||
|
||||
system.switch.inhibitors = [
|
||||
cfg.brokerPackage
|
||||
];
|
||||
|
||||
# Just to be sure we don't restart through the unit alias
|
||||
systemd.services.dbus.reloadIfChanged = true;
|
||||
systemd.user.services.dbus.reloadIfChanged = true;
|
||||
|
||||
@@ -16,38 +16,129 @@
|
||||
'')
|
||||
];
|
||||
|
||||
options.system.switch.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to include the capability to switch configurations.
|
||||
options.system.switch = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to include the capability to switch configurations.
|
||||
|
||||
Disabling this makes the system unable to be reconfigured via `nixos-rebuild`.
|
||||
Disabling this makes the system unable to be reconfigured via `nixos-rebuild`.
|
||||
|
||||
This is good for image based appliances where updates are handled
|
||||
outside the image. Reducing features makes the image lighter and
|
||||
slightly more secure.
|
||||
'';
|
||||
This is good for image based appliances where updates are handled
|
||||
outside the image. Reducing features makes the image lighter and
|
||||
slightly more secure.
|
||||
'';
|
||||
};
|
||||
|
||||
inhibitors = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.pathInStore;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of derivations that will prevent switching into a configuration when
|
||||
they change.
|
||||
This can be manually overridden on the command line if required.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.system.switch.enable {
|
||||
# Use a subshell so we can source makeWrapper's setup hook without
|
||||
# affecting the rest of activatableSystemBuilderCommands.
|
||||
system.activatableSystemBuilderCommands = ''
|
||||
(
|
||||
source ${pkgs.buildPackages.makeWrapper}/nix-support/setup-hook
|
||||
system = {
|
||||
activatableSystemBuilderCommands = ''
|
||||
(
|
||||
source ${pkgs.buildPackages.makeWrapper}/nix-support/setup-hook
|
||||
|
||||
mkdir $out/bin
|
||||
ln -sf ${lib.getExe pkgs.switch-to-configuration-ng} $out/bin/switch-to-configuration
|
||||
wrapProgram $out/bin/switch-to-configuration \
|
||||
--set OUT $out \
|
||||
--set TOPLEVEL ''${!toplevelVar} \
|
||||
--set DISTRO_ID ${lib.escapeShellArg config.system.nixos.distroId} \
|
||||
--set INSTALL_BOOTLOADER ${lib.escapeShellArg config.system.build.installBootLoader} \
|
||||
--set PRE_SWITCH_CHECK ${lib.escapeShellArg config.system.preSwitchChecksScript} \
|
||||
--set LOCALE_ARCHIVE ${config.i18n.glibcLocales}/lib/locale/locale-archive \
|
||||
--set SYSTEMD ${config.systemd.package}
|
||||
)
|
||||
'';
|
||||
mkdir $out/bin
|
||||
ln -sf ${lib.getExe pkgs.switch-to-configuration-ng} $out/bin/switch-to-configuration
|
||||
wrapProgram $out/bin/switch-to-configuration \
|
||||
--set OUT $out \
|
||||
--set TOPLEVEL ''${!toplevelVar} \
|
||||
--set DISTRO_ID ${lib.escapeShellArg config.system.nixos.distroId} \
|
||||
--set INSTALL_BOOTLOADER ${lib.escapeShellArg config.system.build.installBootLoader} \
|
||||
--set PRE_SWITCH_CHECK ${lib.escapeShellArg config.system.preSwitchChecksScript} \
|
||||
--set LOCALE_ARCHIVE ${config.i18n.glibcLocales}/lib/locale/locale-archive \
|
||||
--set SYSTEMD ${config.systemd.package}
|
||||
)
|
||||
'';
|
||||
|
||||
systemBuilderCommands = ''
|
||||
ln -s ${config.system.build.inhibitSwitch} $out/switch-inhibitors
|
||||
'';
|
||||
|
||||
build.inhibitSwitch = pkgs.writeTextFile {
|
||||
name = "switch-inhibitors";
|
||||
text = lib.concatMapStringsSep "\n" (drv: drv.outPath) config.system.switch.inhibitors;
|
||||
};
|
||||
|
||||
preSwitchChecks.switchInhibitors =
|
||||
let
|
||||
realpath = lib.getExe' pkgs.coreutils "realpath";
|
||||
sha256sum = lib.getExe' pkgs.coreutils "sha256sum";
|
||||
diff = lib.getExe' pkgs.diffutils "diff";
|
||||
in
|
||||
# bash
|
||||
''
|
||||
incoming="''${1-}"
|
||||
action="''${2-}"
|
||||
|
||||
if [ "$action" == "boot" ]; then
|
||||
echo "Not checking switch inhibitors (action = $action)"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo -n "Checking switch inhibitors..."
|
||||
|
||||
booted_inhibitors="$(${realpath} /run/booted-system)/switch-inhibitors"
|
||||
booted_inhibitors_sha="$(
|
||||
if [ -f "$booted_inhibitors" ]; then
|
||||
${sha256sum} - < "$booted_inhibitors"
|
||||
else
|
||||
echo 'none'
|
||||
fi
|
||||
)"
|
||||
|
||||
if [ "$booted_inhibitors_sha" == "none" ]; then
|
||||
echo
|
||||
echo "The previous configuration did not specify switch inhibitors, nothing to check."
|
||||
exit
|
||||
fi
|
||||
|
||||
new_inhibitors="$(${realpath} "$incoming")/switch-inhibitors"
|
||||
new_inhibitors_sha="$(
|
||||
if [ -f "$new_inhibitors" ]; then
|
||||
${sha256sum} - < "$new_inhibitors"
|
||||
else
|
||||
echo 'none'
|
||||
fi
|
||||
)"
|
||||
|
||||
if [ "$new_inhibitors_sha" == "none" ]; then
|
||||
echo
|
||||
echo "The new configuration does not specify switch inhibitors, nothing to check."
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$new_inhibitors_sha" != "$booted_inhibitors_sha" ]; then
|
||||
echo
|
||||
echo "Found diff in switch inhibitors:"
|
||||
echo
|
||||
${diff} --color "$booted_inhibitors" "$new_inhibitors"
|
||||
echo
|
||||
echo "The new configuration contains changes to packages that were"
|
||||
echo "listed as switch inhibitors."
|
||||
echo
|
||||
echo "If you really want to switch into this configuration directly, then"
|
||||
echo "you can set NIXOS_NO_CHECK=1 to ignore these pre-switch checks."
|
||||
echo
|
||||
echo "WARNING: doing so might cause the switch to fail or your system to become unstable."
|
||||
echo
|
||||
exit 1
|
||||
else
|
||||
echo " done"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -567,6 +567,10 @@ in
|
||||
);
|
||||
};
|
||||
|
||||
system.switch.inhibitors = [
|
||||
cfg.package
|
||||
];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
environment.etc =
|
||||
|
||||
Reference in New Issue
Block a user