nixos/activation: Add pre-switch checks
Add an option for shell script fragments that are ran before switching to a new NixOS system configuration (pre installation of bootloader or system activation). Also add a new subcommand for switch-to-configuration called "check" that will cause the program to always exit after checks are ran.
This commit is contained in:
parent
b0d941edb4
commit
6e192c4489
@ -1612,6 +1612,7 @@
|
|||||||
./services/x11/xserver.nix
|
./services/x11/xserver.nix
|
||||||
./system/activation/activatable-system.nix
|
./system/activation/activatable-system.nix
|
||||||
./system/activation/activation-script.nix
|
./system/activation/activation-script.nix
|
||||||
|
./system/activation/pre-switch-check.nix
|
||||||
./system/activation/specialisation.nix
|
./system/activation/specialisation.nix
|
||||||
./system/activation/switchable-system.nix
|
./system/activation/switchable-system.nix
|
||||||
./system/activation/bootspec.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@";
|
$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";
|
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
|
switch: make the configuration the boot default and activate now
|
||||||
boot: make the configuration the boot default
|
boot: make the configuration the boot default
|
||||||
test: activate the configuration, but don\'t make it 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 - $!";
|
flock($stc_lock, LOCK_EX) or die "Could not acquire lock - $!";
|
||||||
openlog("nixos", "", LOG_USER);
|
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.
|
# Install or update the bootloader.
|
||||||
if ($action eq "switch" || $action eq "boot") {
|
if ($action eq "switch" || $action eq "boot") {
|
||||||
chomp(my $install_boot_loader = <<'EOFBOOTLOADER');
|
chomp(my $install_boot_loader = <<'EOFBOOTLOADER');
|
||||||
|
@ -61,6 +61,7 @@ in
|
|||||||
--subst-var-by coreutils "${pkgs.coreutils}" \
|
--subst-var-by coreutils "${pkgs.coreutils}" \
|
||||||
--subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \
|
--subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \
|
||||||
--subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \
|
--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 localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \
|
||||||
--subst-var-by perl "${perlWrapped}" \
|
--subst-var-by perl "${perlWrapped}" \
|
||||||
--subst-var-by shell "${pkgs.bash}/bin/sh" \
|
--subst-var-by shell "${pkgs.bash}/bin/sh" \
|
||||||
@ -93,6 +94,7 @@ in
|
|||||||
--set TOPLEVEL ''${!toplevelVar} \
|
--set TOPLEVEL ''${!toplevelVar} \
|
||||||
--set DISTRO_ID ${lib.escapeShellArg config.system.nixos.distroId} \
|
--set DISTRO_ID ${lib.escapeShellArg config.system.nixos.distroId} \
|
||||||
--set INSTALL_BOOTLOADER ${lib.escapeShellArg config.system.build.installBootLoader} \
|
--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 LOCALE_ARCHIVE ${config.i18n.glibcLocales}/lib/locale/locale-archive \
|
||||||
--set SYSTEMD ${config.systemd.package}
|
--set SYSTEMD ${config.systemd.package}
|
||||||
)
|
)
|
||||||
|
@ -342,6 +342,7 @@ in
|
|||||||
perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
|
perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
|
||||||
# End if legacy environment variables
|
# End if legacy environment variables
|
||||||
|
|
||||||
|
preSwitchCheck = config.system.preSwitchChecks.script;
|
||||||
|
|
||||||
# Not actually used in the builder. `passedChecks` is just here to create
|
# Not actually used in the builder. `passedChecks` is just here to create
|
||||||
# the build dependencies. Checks are similar to build dependencies in the
|
# the build dependencies. Checks are similar to build dependencies in the
|
||||||
|
@ -612,6 +612,10 @@ in {
|
|||||||
other = {
|
other = {
|
||||||
system.switch.enable = true;
|
system.switch.enable = true;
|
||||||
users.mutableUsers = 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."
|
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"):
|
with subtest("actions"):
|
||||||
# boot action
|
# boot action
|
||||||
out = switch_to_specialisation("${machine}", "simpleService", action="boot")
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum Action {
|
enum Action {
|
||||||
Switch,
|
Switch,
|
||||||
|
Check,
|
||||||
Boot,
|
Boot,
|
||||||
Test,
|
Test,
|
||||||
DryActivate,
|
DryActivate,
|
||||||
@ -93,6 +94,7 @@ impl std::str::FromStr for Action {
|
|||||||
"boot" => Self::Boot,
|
"boot" => Self::Boot,
|
||||||
"test" => Self::Test,
|
"test" => Self::Test,
|
||||||
"dry-activate" => Self::DryActivate,
|
"dry-activate" => Self::DryActivate,
|
||||||
|
"check" => Self::Check,
|
||||||
_ => bail!("invalid action {s}"),
|
_ => bail!("invalid action {s}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -105,6 +107,7 @@ impl Into<&'static str> for &Action {
|
|||||||
Action::Boot => "boot",
|
Action::Boot => "boot",
|
||||||
Action::Test => "test",
|
Action::Test => "test",
|
||||||
Action::DryActivate => "dry-activate",
|
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<()> {
|
fn do_install_bootloader(command: &str, toplevel: &Path) -> Result<()> {
|
||||||
let mut cmd_split = command.split_whitespace();
|
let mut cmd_split = command.split_whitespace();
|
||||||
let Some(argv0) = cmd_split.next() else {
|
let Some(argv0) = cmd_split.next() else {
|
||||||
@ -939,7 +964,8 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
fn usage(argv0: &str) -> ! {
|
fn usage(argv0: &str) -> ! {
|
||||||
eprintln!(
|
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
|
switch: make the configuration the boot default and activate now
|
||||||
boot: make the configuration the boot default
|
boot: make the configuration the boot default
|
||||||
test: activate the configuration, but don't make it 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 out = PathBuf::from(required_env("OUT")?);
|
||||||
let toplevel = PathBuf::from(required_env("TOPLEVEL")?);
|
let toplevel = PathBuf::from(required_env("TOPLEVEL")?);
|
||||||
let distro_id = required_env("DISTRO_ID")?;
|
let distro_id = required_env("DISTRO_ID")?;
|
||||||
|
let pre_switch_check = required_env("PRE_SWITCH_CHECK")?;
|
||||||
let install_bootloader = required_env("INSTALL_BOOTLOADER")?;
|
let install_bootloader = required_env("INSTALL_BOOTLOADER")?;
|
||||||
let locale_archive = required_env("LOCALE_ARCHIVE")?;
|
let locale_archive = required_env("LOCALE_ARCHIVE")?;
|
||||||
let new_systemd = PathBuf::from(required_env("SYSTEMD")?);
|
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");
|
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.
|
// Install or update the bootloader.
|
||||||
if matches!(action, Action::Switch | Action::Boot) {
|
if matches!(action, Action::Switch | Action::Boot) {
|
||||||
do_install_bootloader(&install_bootloader, &toplevel)?;
|
do_install_bootloader(&install_bootloader, &toplevel)?;
|
||||||
|
Loading…
Reference in New Issue
Block a user