nixos/activation: Add pre-switch checks (#236375)
This commit is contained in:
commit
4e54bbdea1
@ -1621,6 +1621,7 @@
|
||||
./services/x11/xserver.nix
|
||||
./system/activation/activatable-system.nix
|
||||
./system/activation/activation-script.nix
|
||||
./system/activation/pre-switch-check.nix
|
||||
./system/activation/specialisation.nix
|
||||
./system/activation/switchable-system.nix
|
||||
./system/activation/bootspec.nix
|
||||
|
44
nixos/modules/system/activation/pre-switch-check.nix
Normal file
44
nixos/modules/system/activation/pre-switch-check.nix
Normal file
@ -0,0 +1,44 @@
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
preSwitchCheckScript =
|
||||
set:
|
||||
lib.concatLines (
|
||||
lib.mapAttrsToList (name: text: ''
|
||||
# pre-switch check ${name}
|
||||
(
|
||||
${text}
|
||||
)
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Pre-switch check '${name}' failed"
|
||||
exit 1
|
||||
fi
|
||||
'') set
|
||||
);
|
||||
in
|
||||
{
|
||||
options.system.preSwitchChecks = lib.mkOption {
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{ failsEveryTime =
|
||||
'''
|
||||
false
|
||||
''';
|
||||
}
|
||||
'';
|
||||
|
||||
description = ''
|
||||
A set of shell script fragments that are executed before the switch to a
|
||||
new NixOS system configuration. A failure in any of these fragments will
|
||||
cause the switch to fail and exit early.
|
||||
'';
|
||||
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
|
||||
apply =
|
||||
set:
|
||||
set
|
||||
// {
|
||||
script = pkgs.writeShellScript "pre-switch-checks" (preSwitchCheckScript set);
|
||||
};
|
||||
};
|
||||
}
|
@ -78,10 +78,11 @@ if ("@localeArchive@" ne "") {
|
||||
$ENV{LOCALE_ARCHIVE} = "@localeArchive@";
|
||||
}
|
||||
|
||||
if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
|
||||
if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate" && $action ne "check")) {
|
||||
print STDERR <<"EOF";
|
||||
Usage: $0 [switch|boot|test|dry-activate]
|
||||
Usage: $0 [check|switch|boot|test|dry-activate]
|
||||
|
||||
check: run pre-switch checks and exit
|
||||
switch: make the configuration the boot default and activate now
|
||||
boot: make the configuration the boot default
|
||||
test: activate the configuration, but don\'t make it the boot default
|
||||
@ -101,6 +102,17 @@ open(my $stc_lock, '>>', '/run/nixos/switch-to-configuration.lock') or die "Coul
|
||||
flock($stc_lock, LOCK_EX) or die "Could not acquire lock - $!";
|
||||
openlog("nixos", "", LOG_USER);
|
||||
|
||||
# run pre-switch checks
|
||||
if (($ENV{"NIXOS_NO_CHECK"} // "") ne "1") {
|
||||
chomp(my $pre_switch_checks = <<'EOFCHECKS');
|
||||
@preSwitchCheck@
|
||||
EOFCHECKS
|
||||
system("$pre_switch_checks $out") == 0 or exit 1;
|
||||
if ($action eq "check") {
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
|
||||
# Install or update the bootloader.
|
||||
if ($action eq "switch" || $action eq "boot") {
|
||||
chomp(my $install_boot_loader = <<'EOFBOOTLOADER');
|
||||
|
@ -61,6 +61,7 @@ in
|
||||
--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 preSwitchCheck ${lib.escapeShellArg config.system.preSwitchChecks.script} \
|
||||
--subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \
|
||||
--subst-var-by perl "${perlWrapped}" \
|
||||
--subst-var-by shell "${pkgs.bash}/bin/sh" \
|
||||
@ -93,6 +94,7 @@ in
|
||||
--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.preSwitchChecks.script} \
|
||||
--set LOCALE_ARCHIVE ${config.i18n.glibcLocales}/lib/locale/locale-archive \
|
||||
--set SYSTEMD ${config.systemd.package}
|
||||
)
|
||||
|
@ -342,6 +342,7 @@ in
|
||||
perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
|
||||
# End if legacy environment variables
|
||||
|
||||
preSwitchCheck = config.system.preSwitchChecks.script;
|
||||
|
||||
# Not actually used in the builder. `passedChecks` is just here to create
|
||||
# the build dependencies. Checks are similar to build dependencies in the
|
||||
|
@ -612,6 +612,10 @@ in {
|
||||
other = {
|
||||
system.switch.enable = true;
|
||||
users.mutableUsers = true;
|
||||
specialisation.failingCheck.configuration.system.preSwitchChecks.failEveryTime = ''
|
||||
echo this will fail
|
||||
false
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
@ -684,6 +688,11 @@ in {
|
||||
|
||||
boot_loader_text = "Warning: do not know how to make this configuration bootable; please enable a boot loader."
|
||||
|
||||
with subtest("pre-switch checks"):
|
||||
machine.succeed("${stderrRunner} ${otherSystem}/bin/switch-to-configuration check")
|
||||
out = switch_to_specialisation("${otherSystem}", "failingCheck", action="check", fail=True)
|
||||
assert_contains(out, "this will fail")
|
||||
|
||||
with subtest("actions"):
|
||||
# boot action
|
||||
out = switch_to_specialisation("${machine}", "simpleService", action="boot")
|
||||
|
@ -79,6 +79,7 @@ const DRY_RELOAD_BY_ACTIVATION_LIST_FILE: &str = "/run/nixos/dry-activation-relo
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum Action {
|
||||
Switch,
|
||||
Check,
|
||||
Boot,
|
||||
Test,
|
||||
DryActivate,
|
||||
@ -93,6 +94,7 @@ impl std::str::FromStr for Action {
|
||||
"boot" => Self::Boot,
|
||||
"test" => Self::Test,
|
||||
"dry-activate" => Self::DryActivate,
|
||||
"check" => Self::Check,
|
||||
_ => bail!("invalid action {s}"),
|
||||
})
|
||||
}
|
||||
@ -105,6 +107,7 @@ impl Into<&'static str> for &Action {
|
||||
Action::Boot => "boot",
|
||||
Action::Test => "test",
|
||||
Action::DryActivate => "dry-activate",
|
||||
Action::Check => "check",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,6 +132,28 @@ fn parse_os_release() -> Result<HashMap<String, String>> {
|
||||
}))
|
||||
}
|
||||
|
||||
fn do_pre_switch_check(command: &str, toplevel: &Path) -> Result<()> {
|
||||
let mut cmd_split = command.split_whitespace();
|
||||
let Some(argv0) = cmd_split.next() else {
|
||||
bail!("missing first argument in install bootloader commands");
|
||||
};
|
||||
|
||||
match std::process::Command::new(argv0)
|
||||
.args(cmd_split.collect::<Vec<&str>>())
|
||||
.arg(toplevel)
|
||||
.spawn()
|
||||
.map(|mut child| child.wait())
|
||||
{
|
||||
Ok(Ok(status)) if status.success() => {}
|
||||
_ => {
|
||||
eprintln!("Pre-switch checks failed");
|
||||
die()
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_install_bootloader(command: &str, toplevel: &Path) -> Result<()> {
|
||||
let mut cmd_split = command.split_whitespace();
|
||||
let Some(argv0) = cmd_split.next() else {
|
||||
@ -939,7 +964,8 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
|
||||
fn usage(argv0: &str) -> ! {
|
||||
eprintln!(
|
||||
r#"Usage: {} [switch|boot|test|dry-activate]
|
||||
r#"Usage: {} [check|switch|boot|test|dry-activate]
|
||||
check: run pre-switch checks and exit
|
||||
switch: make the configuration the boot default and activate now
|
||||
boot: make the configuration the boot default
|
||||
test: activate the configuration, but don't make it the boot default
|
||||
@ -955,6 +981,7 @@ fn do_system_switch(action: Action) -> anyhow::Result<()> {
|
||||
let out = PathBuf::from(required_env("OUT")?);
|
||||
let toplevel = PathBuf::from(required_env("TOPLEVEL")?);
|
||||
let distro_id = required_env("DISTRO_ID")?;
|
||||
let pre_switch_check = required_env("PRE_SWITCH_CHECK")?;
|
||||
let install_bootloader = required_env("INSTALL_BOOTLOADER")?;
|
||||
let locale_archive = required_env("LOCALE_ARCHIVE")?;
|
||||
let new_systemd = PathBuf::from(required_env("SYSTEMD")?);
|
||||
@ -1013,6 +1040,18 @@ fn do_system_switch(action: Action) -> anyhow::Result<()> {
|
||||
bail!("Failed to initialize logger");
|
||||
}
|
||||
|
||||
if std::env::var("NIXOS_NO_CHECK")
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
!= "1"
|
||||
{
|
||||
do_pre_switch_check(&pre_switch_check, &toplevel)?;
|
||||
}
|
||||
|
||||
if *action == Action::Check {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Install or update the bootloader.
|
||||
if matches!(action, Action::Switch | Action::Boot) {
|
||||
do_install_bootloader(&install_bootloader, &toplevel)?;
|
||||
|
Loading…
Reference in New Issue
Block a user