aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaximilian Bosch2022-10-09 18:37:10 +0200
committerMaximilian Bosch2022-10-09 18:47:08 +0200
commit068372aad18f04122bbdb836e36c655c157ebe71 (patch)
treea32513f56334765e68c64a680f0acb6e67f0ecb5
parent41f15759dd8b638e7b4f299730d94d5aa46ab7eb (diff)
Add new activation strategy `boot` as equivalent to `nixos-rebuild boot`
This can be useful when e.g. deploying a kernel update to a target host. You usually plan a reboot (or kexec) after that to activate the new kernel. However you don't want to wait for services to be restarted first since these will be "restarted" anyways on the reboot. In cases like GitLab or the Atlassian stack this actually makes a difference. This patch changes the following things: * If `--boot` is provided, `nix-env -p profile-to-activate --set` is called for each deployed profile to make sure that it is activated automatically after a reboot. * However, the actual activation (e.g. `switch-to-configuration switch`) is skipped. Instead: * For NixOS, `switch-to-configuration boot` is called to set the new profile as default in the bootloader. * For everything else, nothing else is done. The profile is already the new default (and thus picked up on the next boot).
-rw-r--r--flake.nix31
-rw-r--r--src/bin/activate.rs9
-rw-r--r--src/cli.rs11
-rw-r--r--src/deploy.rs15
4 files changed, 51 insertions, 15 deletions
diff --git a/flake.nix b/flake.nix
index 66ba60b..2d520b6 100644
--- a/flake.nix
+++ b/flake.nix
@@ -63,6 +63,9 @@
if [[ "''${DRY_ACTIVATE:-}" == "1" ]]
then
${customSelf.dryActivate or "echo ${final.writeScript "activate" activate}"}
+ elif [[ "''${BOOT:-}" == "1" ]]
+ then
+ ${customSelf.boot or "echo ${final.writeScript "activate" activate}"}
else
${activate}
fi
@@ -83,17 +86,23 @@
};
};
- nixos = base: (custom // { dryActivate = "$PROFILE/bin/switch-to-configuration dry-activate"; }) base.config.system.build.toplevel ''
- # work around https://github.com/NixOS/nixpkgs/issues/73404
- cd /tmp
-
- $PROFILE/bin/switch-to-configuration switch
-
- # https://github.com/serokell/deploy-rs/issues/31
- ${with base.config.boot.loader;
- final.lib.optionalString systemd-boot.enable
- "sed -i '/^default /d' ${efi.efiSysMountPoint}/loader/loader.conf"}
- '';
+ nixos = base:
+ (custom // {
+ dryActivate = "$PROFILE/bin/switch-to-configuration dry-activate";
+ boot = "$PROFILE/bin/switch-to-configuration boot";
+ })
+ base.config.system.build.toplevel
+ ''
+ # work around https://github.com/NixOS/nixpkgs/issues/73404
+ cd /tmp
+
+ $PROFILE/bin/switch-to-configuration switch
+
+ # https://github.com/serokell/deploy-rs/issues/31
+ ${with base.config.boot.loader;
+ final.lib.optionalString systemd-boot.enable
+ "sed -i '/^default /d' ${efi.efiSysMountPoint}/loader/loader.conf"}
+ '';
home-manager = base: custom base.activationPackage "$PROFILE/activate";
diff --git a/src/bin/activate.rs b/src/bin/activate.rs
index d0cfbe1..4c865f1 100644
--- a/src/bin/activate.rs
+++ b/src/bin/activate.rs
@@ -69,6 +69,10 @@ struct ActivateOpts {
#[clap(long)]
dry_activate: bool,
+ /// Don't activate, but update the boot loader to boot into the new profile
+ #[clap(long)]
+ boot: bool,
+
/// Path for any temporary files that may be needed during activation
#[clap(long)]
temp_path: String,
@@ -363,6 +367,7 @@ pub async fn activate(
confirm_timeout: u16,
magic_rollback: bool,
dry_activate: bool,
+ boot: bool,
) -> Result<(), ActivateError> {
if !dry_activate {
info!("Activating profile");
@@ -396,6 +401,7 @@ pub async fn activate(
let activate_status = match Command::new(format!("{}/deploy-rs-activate", activation_location))
.env("PROFILE", activation_location)
.env("DRY_ACTIVATE", if dry_activate { "1" } else { "0" })
+ .env("BOOT", if boot { "1" } else { "0" })
.current_dir(activation_location)
.status()
.await
@@ -425,7 +431,7 @@ pub async fn activate(
info!("Activation succeeded!");
}
- if magic_rollback {
+ if magic_rollback && !boot {
info!("Magic rollback is enabled, setting up confirmation hook...");
match activation_confirmation(profile_path.clone(), temp_path, confirm_timeout, closure)
@@ -479,6 +485,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
activate_opts.confirm_timeout,
activate_opts.magic_rollback,
activate_opts.dry_activate,
+ activate_opts.boot,
)
.await
.map_err(|x| Box::new(x) as Box<dyn std::error::Error>),
diff --git a/src/cli.rs b/src/cli.rs
index cc5a3ac..eb9094d 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -86,6 +86,9 @@ pub struct Opts {
/// Show what will be activated on the machines
#[clap(long)]
dry_activate: bool,
+ /// Don't activate, but update the boot loader to boot into the new profile
+ #[clap(long)]
+ boot: bool,
/// Revoke all previously succeeded deploys when deploying multiple profiles
#[clap(long)]
rollback_succeeded: Option<bool>,
@@ -409,6 +412,7 @@ async fn run_deploy(
extra_build_args: &[String],
debug_logs: bool,
dry_activate: bool,
+ boot: bool,
log_dir: &Option<String>,
rollback_succeeded: bool,
) -> Result<(), RunDeployError> {
@@ -560,7 +564,7 @@ async fn run_deploy(
// Rollbacks adhere to the global seeting to auto_rollback and secondary
// the profile's configuration
for (_, deploy_data, deploy_defs) in &parts {
- if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate).await
+ if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate, boot).await
{
error!("{}", e);
if dry_activate {
@@ -617,6 +621,10 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
&deploy::LoggerType::Deploy,
)?;
+ if opts.dry_activate && opts.boot {
+ error!("Cannot use both --dry-activate & --boot!");
+ }
+
let deploys = opts
.clone()
.targets
@@ -666,6 +674,7 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
&opts.extra_build_args,
opts.debug_logs,
opts.dry_activate,
+ opts.boot,
&opts.log_dir,
opts.rollback_succeeded.unwrap_or(true),
)
diff --git a/src/deploy.rs b/src/deploy.rs
index 5c4b656..cc5d862 100644
--- a/src/deploy.rs
+++ b/src/deploy.rs
@@ -22,6 +22,7 @@ struct ActivateCommandData<'a> {
debug_logs: bool,
log_dir: Option<&'a str>,
dry_activate: bool,
+ boot: bool,
}
fn build_activate_command(data: &ActivateCommandData) -> String {
@@ -57,6 +58,10 @@ fn build_activate_command(data: &ActivateCommandData) -> String {
self_activate_command = format!("{} --dry-activate", self_activate_command);
}
+ if data.boot {
+ self_activate_command = format!("{} --boot", self_activate_command);
+ }
+
if let Some(sudo_cmd) = &data.sudo {
self_activate_command = format!("{} {}", sudo_cmd, self_activate_command);
}
@@ -71,6 +76,7 @@ fn test_activation_command_builder() {
let closure = "/nix/store/blah/etc";
let auto_rollback = true;
let dry_activate = false;
+ let boot = false;
let temp_path = "/tmp";
let confirm_timeout = 30;
let magic_rollback = true;
@@ -88,7 +94,8 @@ fn test_activation_command_builder() {
magic_rollback,
debug_logs,
log_dir,
- dry_activate
+ dry_activate,
+ boot,
}),
"sudo -u test /nix/store/blah/etc/activate-rs --debug-logs --log-dir /tmp/something.txt activate '/nix/store/blah/etc' '/blah/profiles/test' --temp-path '/tmp' --confirm-timeout 30 --magic-rollback --auto-rollback"
.to_string(),
@@ -270,6 +277,7 @@ pub async fn deploy_profile(
deploy_data: &super::DeployData<'_>,
deploy_defs: &super::DeployDefs,
dry_activate: bool,
+ boot: bool,
) -> Result<(), DeployProfileError> {
if !dry_activate {
info!(
@@ -300,6 +308,7 @@ pub async fn deploy_profile(
debug_logs: deploy_data.debug_logs,
log_dir: deploy_data.log_dir,
dry_activate,
+ boot,
});
debug!("Constructed activation command: {}", self_activate_command);
@@ -318,7 +327,7 @@ pub async fn deploy_profile(
ssh_activate_command.arg(&ssh_opt);
}
- if !magic_rollback || dry_activate {
+ if !magic_rollback || dry_activate || boot {
let ssh_activate_exit_status = ssh_activate_command
.arg(self_activate_command)
.status()
@@ -332,6 +341,8 @@ pub async fn deploy_profile(
if dry_activate {
info!("Completed dry-activate!");
+ } else if boot {
+ info!("Success activating for next boot, done!");
} else {
info!("Success activating, done!");
}