Skip to content

Commit 068372a

Browse files
committed
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).
1 parent 41f1575 commit 068372a

File tree

4 files changed

+51
-15
lines changed

4 files changed

+51
-15
lines changed

flake.nix

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
if [[ "''${DRY_ACTIVATE:-}" == "1" ]]
6464
then
6565
${customSelf.dryActivate or "echo ${final.writeScript "activate" activate}"}
66+
elif [[ "''${BOOT:-}" == "1" ]]
67+
then
68+
${customSelf.boot or "echo ${final.writeScript "activate" activate}"}
6669
else
6770
${activate}
6871
fi
@@ -83,17 +86,23 @@
8386
};
8487
};
8588

86-
nixos = base: (custom // { dryActivate = "$PROFILE/bin/switch-to-configuration dry-activate"; }) base.config.system.build.toplevel ''
87-
# work around https://github.com/NixOS/nixpkgs/issues/73404
88-
cd /tmp
89-
90-
$PROFILE/bin/switch-to-configuration switch
91-
92-
# https://github.com/serokell/deploy-rs/issues/31
93-
${with base.config.boot.loader;
94-
final.lib.optionalString systemd-boot.enable
95-
"sed -i '/^default /d' ${efi.efiSysMountPoint}/loader/loader.conf"}
96-
'';
89+
nixos = base:
90+
(custom // {
91+
dryActivate = "$PROFILE/bin/switch-to-configuration dry-activate";
92+
boot = "$PROFILE/bin/switch-to-configuration boot";
93+
})
94+
base.config.system.build.toplevel
95+
''
96+
# work around https://github.com/NixOS/nixpkgs/issues/73404
97+
cd /tmp
98+
99+
$PROFILE/bin/switch-to-configuration switch
100+
101+
# https://github.com/serokell/deploy-rs/issues/31
102+
${with base.config.boot.loader;
103+
final.lib.optionalString systemd-boot.enable
104+
"sed -i '/^default /d' ${efi.efiSysMountPoint}/loader/loader.conf"}
105+
'';
97106

98107
home-manager = base: custom base.activationPackage "$PROFILE/activate";
99108

src/bin/activate.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ struct ActivateOpts {
6969
#[clap(long)]
7070
dry_activate: bool,
7171

72+
/// Don't activate, but update the boot loader to boot into the new profile
73+
#[clap(long)]
74+
boot: bool,
75+
7276
/// Path for any temporary files that may be needed during activation
7377
#[clap(long)]
7478
temp_path: String,
@@ -363,6 +367,7 @@ pub async fn activate(
363367
confirm_timeout: u16,
364368
magic_rollback: bool,
365369
dry_activate: bool,
370+
boot: bool,
366371
) -> Result<(), ActivateError> {
367372
if !dry_activate {
368373
info!("Activating profile");
@@ -396,6 +401,7 @@ pub async fn activate(
396401
let activate_status = match Command::new(format!("{}/deploy-rs-activate", activation_location))
397402
.env("PROFILE", activation_location)
398403
.env("DRY_ACTIVATE", if dry_activate { "1" } else { "0" })
404+
.env("BOOT", if boot { "1" } else { "0" })
399405
.current_dir(activation_location)
400406
.status()
401407
.await
@@ -425,7 +431,7 @@ pub async fn activate(
425431
info!("Activation succeeded!");
426432
}
427433

428-
if magic_rollback {
434+
if magic_rollback && !boot {
429435
info!("Magic rollback is enabled, setting up confirmation hook...");
430436

431437
match activation_confirmation(profile_path.clone(), temp_path, confirm_timeout, closure)
@@ -479,6 +485,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
479485
activate_opts.confirm_timeout,
480486
activate_opts.magic_rollback,
481487
activate_opts.dry_activate,
488+
activate_opts.boot,
482489
)
483490
.await
484491
.map_err(|x| Box::new(x) as Box<dyn std::error::Error>),

src/cli.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ pub struct Opts {
8686
/// Show what will be activated on the machines
8787
#[clap(long)]
8888
dry_activate: bool,
89+
/// Don't activate, but update the boot loader to boot into the new profile
90+
#[clap(long)]
91+
boot: bool,
8992
/// Revoke all previously succeeded deploys when deploying multiple profiles
9093
#[clap(long)]
9194
rollback_succeeded: Option<bool>,
@@ -409,6 +412,7 @@ async fn run_deploy(
409412
extra_build_args: &[String],
410413
debug_logs: bool,
411414
dry_activate: bool,
415+
boot: bool,
412416
log_dir: &Option<String>,
413417
rollback_succeeded: bool,
414418
) -> Result<(), RunDeployError> {
@@ -560,7 +564,7 @@ async fn run_deploy(
560564
// Rollbacks adhere to the global seeting to auto_rollback and secondary
561565
// the profile's configuration
562566
for (_, deploy_data, deploy_defs) in &parts {
563-
if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate).await
567+
if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate, boot).await
564568
{
565569
error!("{}", e);
566570
if dry_activate {
@@ -617,6 +621,10 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
617621
&deploy::LoggerType::Deploy,
618622
)?;
619623

624+
if opts.dry_activate && opts.boot {
625+
error!("Cannot use both --dry-activate & --boot!");
626+
}
627+
620628
let deploys = opts
621629
.clone()
622630
.targets
@@ -666,6 +674,7 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
666674
&opts.extra_build_args,
667675
opts.debug_logs,
668676
opts.dry_activate,
677+
opts.boot,
669678
&opts.log_dir,
670679
opts.rollback_succeeded.unwrap_or(true),
671680
)

src/deploy.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct ActivateCommandData<'a> {
2222
debug_logs: bool,
2323
log_dir: Option<&'a str>,
2424
dry_activate: bool,
25+
boot: bool,
2526
}
2627

2728
fn build_activate_command(data: &ActivateCommandData) -> String {
@@ -57,6 +58,10 @@ fn build_activate_command(data: &ActivateCommandData) -> String {
5758
self_activate_command = format!("{} --dry-activate", self_activate_command);
5859
}
5960

61+
if data.boot {
62+
self_activate_command = format!("{} --boot", self_activate_command);
63+
}
64+
6065
if let Some(sudo_cmd) = &data.sudo {
6166
self_activate_command = format!("{} {}", sudo_cmd, self_activate_command);
6267
}
@@ -71,6 +76,7 @@ fn test_activation_command_builder() {
7176
let closure = "/nix/store/blah/etc";
7277
let auto_rollback = true;
7378
let dry_activate = false;
79+
let boot = false;
7480
let temp_path = "/tmp";
7581
let confirm_timeout = 30;
7682
let magic_rollback = true;
@@ -88,7 +94,8 @@ fn test_activation_command_builder() {
8894
magic_rollback,
8995
debug_logs,
9096
log_dir,
91-
dry_activate
97+
dry_activate,
98+
boot,
9299
}),
93100
"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"
94101
.to_string(),
@@ -270,6 +277,7 @@ pub async fn deploy_profile(
270277
deploy_data: &super::DeployData<'_>,
271278
deploy_defs: &super::DeployDefs,
272279
dry_activate: bool,
280+
boot: bool,
273281
) -> Result<(), DeployProfileError> {
274282
if !dry_activate {
275283
info!(
@@ -300,6 +308,7 @@ pub async fn deploy_profile(
300308
debug_logs: deploy_data.debug_logs,
301309
log_dir: deploy_data.log_dir,
302310
dry_activate,
311+
boot,
303312
});
304313

305314
debug!("Constructed activation command: {}", self_activate_command);
@@ -318,7 +327,7 @@ pub async fn deploy_profile(
318327
ssh_activate_command.arg(&ssh_opt);
319328
}
320329

321-
if !magic_rollback || dry_activate {
330+
if !magic_rollback || dry_activate || boot {
322331
let ssh_activate_exit_status = ssh_activate_command
323332
.arg(self_activate_command)
324333
.status()
@@ -332,6 +341,8 @@ pub async fn deploy_profile(
332341

333342
if dry_activate {
334343
info!("Completed dry-activate!");
344+
} else if boot {
345+
info!("Success activating for next boot, done!");
335346
} else {
336347
info!("Success activating, done!");
337348
}

0 commit comments

Comments
 (0)