Skip to content

Commit 52f89b0

Browse files
committed
feat: add --remote-bin-path option (#298)
1 parent 6bc76b8 commit 52f89b0

File tree

6 files changed

+261
-102
lines changed

6 files changed

+261
-102
lines changed

src/bin/activate.rs

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ struct ActivateOpts {
9090
/// Path for any temporary files that may be needed during activation
9191
#[arg(long)]
9292
temp_path: PathBuf,
93+
94+
/// Path to where the nix-store and nix-env binaries are stored
95+
#[arg(long)]
96+
bin_path: Option<PathBuf>,
9397
}
9498

9599
/// Wait for profile activation
@@ -119,6 +123,10 @@ struct RevokeOpts {
119123
/// The profile name
120124
#[arg(long, requires = "profile_user")]
121125
profile_name: Option<String>,
126+
127+
/// Path to where the nix-store and nix-env binaries are stored
128+
#[arg(long)]
129+
bin_path: Option<PathBuf>,
122130
}
123131

124132
#[derive(Error, Debug)]
@@ -143,10 +151,17 @@ pub enum DeactivateError {
143151
ReactivateExit(Option<i32>),
144152
}
145153

146-
pub async fn deactivate(profile_path: &str) -> Result<(), DeactivateError> {
154+
pub async fn deactivate(
155+
profile_path: &str,
156+
bin_path: Option<PathBuf>,
157+
) -> Result<(), DeactivateError> {
147158
warn!("De-activating due to error");
148159

149-
let nix_env_rollback_exit_status = Command::new("nix-env")
160+
let program = match bin_path {
161+
Some(path) => path.join("nix-env"),
162+
None => PathBuf::from("nix-env"),
163+
};
164+
let nix_env_rollback_exit_status = Command::new(&program)
150165
.arg("-p")
151166
.arg(&profile_path)
152167
.arg("--rollback")
@@ -161,7 +176,7 @@ pub async fn deactivate(profile_path: &str) -> Result<(), DeactivateError> {
161176

162177
debug!("Listing generations");
163178

164-
let nix_env_list_generations_out = Command::new("nix-env")
179+
let nix_env_list_generations_out = Command::new(&program)
165180
.arg("-p")
166181
.arg(&profile_path)
167182
.arg("--list-generations")
@@ -190,7 +205,7 @@ pub async fn deactivate(profile_path: &str) -> Result<(), DeactivateError> {
190205
debug!("Removing generation entry {}", last_generation_line);
191206
warn!("Removing generation by ID {}", last_generation_id);
192207

193-
let nix_env_delete_generation_exit_status = Command::new("nix-env")
208+
let nix_env_delete_generation_exit_status = Command::new(program)
194209
.arg("-p")
195210
.arg(&profile_path)
196211
.arg("--delete-generations")
@@ -315,7 +330,11 @@ pub enum WaitError {
315330
#[error("Error waiting for activation: {0}")]
316331
Waiting(#[from] DangerZoneError),
317332
}
318-
pub async fn wait(temp_path: PathBuf, closure: String, activation_timeout: Option<u16>) -> Result<(), WaitError> {
333+
pub async fn wait(
334+
temp_path: PathBuf,
335+
closure: String,
336+
activation_timeout: Option<u16>,
337+
) -> Result<(), WaitError> {
319338
let lock_path = deploy::make_lock_path(&temp_path, &closure);
320339

321340
let (created, done) = mpsc::channel(1);
@@ -386,14 +405,20 @@ pub async fn activate(
386405
closure: String,
387406
auto_rollback: bool,
388407
temp_path: PathBuf,
408+
bin_path: Option<PathBuf>,
389409
confirm_timeout: u16,
390410
magic_rollback: bool,
391411
dry_activate: bool,
392412
boot: bool,
393413
) -> Result<(), ActivateError> {
394414
if !dry_activate {
395415
info!("Activating profile");
396-
let nix_env_set_exit_status = Command::new("nix-env")
416+
let program = match &bin_path {
417+
Some(path) => path.join("nix-env"),
418+
None => PathBuf::from("nix-env"),
419+
};
420+
421+
let nix_env_set_exit_status = Command::new(program)
397422
.arg("-p")
398423
.arg(&profile_path)
399424
.arg("--set")
@@ -405,7 +430,7 @@ pub async fn activate(
405430
Some(0) => (),
406431
a => {
407432
if auto_rollback && !dry_activate {
408-
deactivate(&profile_path).await?;
433+
deactivate(&profile_path, bin_path).await?;
409434
}
410435
return Err(ActivateError::SetProfileExit(a));
411436
}
@@ -432,7 +457,7 @@ pub async fn activate(
432457
Ok(x) => x,
433458
Err(e) => {
434459
if auto_rollback && !dry_activate {
435-
deactivate(&profile_path).await?;
460+
deactivate(&profile_path, bin_path).await?;
436461
}
437462
return Err(e);
438463
}
@@ -443,7 +468,7 @@ pub async fn activate(
443468
Some(0) => (),
444469
a => {
445470
if auto_rollback {
446-
deactivate(&profile_path).await?;
471+
deactivate(&profile_path, bin_path).await?;
447472
}
448473
return Err(ActivateError::RunActivateExit(a));
449474
}
@@ -456,7 +481,7 @@ pub async fn activate(
456481
if magic_rollback && !boot {
457482
info!("Magic rollback is enabled, setting up confirmation hook...");
458483
if let Err(err) = activation_confirmation(temp_path, confirm_timeout, closure).await {
459-
deactivate(&profile_path).await?;
484+
deactivate(&profile_path, bin_path).await?;
460485
return Err(ActivateError::ActivationConfirmation(err));
461486
}
462487
}
@@ -465,8 +490,8 @@ pub async fn activate(
465490
Ok(())
466491
}
467492

468-
async fn revoke(profile_path: String) -> Result<(), DeactivateError> {
469-
deactivate(profile_path.as_str()).await?;
493+
async fn revoke(profile_path: String, bin_path: Option<PathBuf>) -> Result<(), DeactivateError> {
494+
deactivate(profile_path.as_str(), bin_path).await?;
470495
Ok(())
471496
}
472497

@@ -557,6 +582,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
557582
activate_opts.closure,
558583
activate_opts.auto_rollback,
559584
activate_opts.temp_path,
585+
activate_opts.bin_path,
560586
activate_opts.confirm_timeout,
561587
activate_opts.magic_rollback,
562588
activate_opts.dry_activate,
@@ -565,15 +591,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
565591
.await
566592
.map_err(|x| Box::new(x) as Box<dyn std::error::Error>),
567593

568-
SubCommand::Wait(wait_opts) => wait(wait_opts.temp_path, wait_opts.closure, wait_opts.activation_timeout)
569-
.await
570-
.map_err(|x| Box::new(x) as Box<dyn std::error::Error>),
594+
SubCommand::Wait(wait_opts) => wait(
595+
wait_opts.temp_path,
596+
wait_opts.closure,
597+
wait_opts.activation_timeout,
598+
)
599+
.await
600+
.map_err(|x| Box::new(x) as Box<dyn std::error::Error>),
571601

572-
SubCommand::Revoke(revoke_opts) => revoke(get_profile_path(
573-
revoke_opts.profile_path,
574-
revoke_opts.profile_user,
575-
revoke_opts.profile_name,
576-
)?)
602+
SubCommand::Revoke(revoke_opts) => revoke(
603+
get_profile_path(
604+
revoke_opts.profile_path,
605+
revoke_opts.profile_user,
606+
revoke_opts.profile_name,
607+
)?,
608+
revoke_opts.bin_path,
609+
)
577610
.await
578611
.map_err(|x| Box::new(x) as Box<dyn std::error::Error>),
579612
};

src/cli.rs

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use std::collections::HashMap;
77
use std::io::{stdin, stdout, Write};
88

9-
use clap::{ArgMatches, Parser, FromArgMatches};
9+
use clap::{ArgMatches, FromArgMatches, Parser};
1010

1111
use crate as deploy;
1212

@@ -109,6 +109,9 @@ pub struct Opts {
109109
/// Prompt for sudo password during activation.
110110
#[arg(long)]
111111
interactive_sudo: Option<bool>,
112+
/// Path to where the nix-store and nix-env binaries are stored on the remote host
113+
#[arg(long)]
114+
remote_bin_path: Option<PathBuf>,
112115
}
113116

114117
/// Returns if the available Nix installation supports flakes
@@ -386,9 +389,9 @@ pub enum RunDeployError {
386389
#[error("Failed to deploy profile to node {0}: {1}")]
387390
DeployProfile(String, deploy::deploy::DeployProfileError),
388391
#[error("Failed to build profile on node {0}: {0}")]
389-
BuildProfile(String, deploy::push::PushProfileError),
392+
BuildProfile(String, deploy::push::PushProfileError),
390393
#[error("Failed to push profile to node {0}: {0}")]
391-
PushProfile(String, deploy::push::PushProfileError),
394+
PushProfile(String, deploy::push::PushProfileError),
392395
#[error("No profile named `{0}` was found")]
393396
ProfileNotFound(String),
394397
#[error("No node named `{0}` was found")]
@@ -404,7 +407,7 @@ pub enum RunDeployError {
404407
#[error("Failed to revoke profile for node {0}: {1}")]
405408
RevokeProfile(String, deploy::deploy::RevokeProfileError),
406409
#[error("Deployment to node {0} failed, rolled back to previous generation")]
407-
Rollback(String)
410+
Rollback(String),
408411
}
409412

410413
type ToDeploy<'a> = Vec<(
@@ -548,7 +551,11 @@ async fn run_deploy(
548551

549552
let mut deploy_defs = deploy_data.defs()?;
550553

551-
if deploy_data.merged_settings.interactive_sudo.unwrap_or(false) {
554+
if deploy_data
555+
.merged_settings
556+
.interactive_sudo
557+
.unwrap_or(false)
558+
{
552559
warn!("Interactive sudo is enabled! Using a sudo password is less secure than correctly configured SSH keys.\nPlease use keys in production environments.");
553560

554561
if deploy_data.merged_settings.sudo.is_some() {
@@ -560,8 +567,15 @@ async fn run_deploy(
560567
deploy_defs.sudo = Some(format!("{} -S -p \"\"", original));
561568
}
562569

563-
info!("You will now be prompted for the sudo password for {}.", node.node_settings.hostname);
564-
let sudo_password = rpassword::prompt_password(format!("(sudo for {}) Password: ", node.node_settings.hostname)).unwrap_or("".to_string());
570+
info!(
571+
"You will now be prompted for the sudo password for {}.",
572+
node.node_settings.hostname
573+
);
574+
let sudo_password = rpassword::prompt_password(format!(
575+
"(sudo for {}) Password: ",
576+
node.node_settings.hostname
577+
))
578+
.unwrap_or("".to_string());
565579

566580
deploy_defs.sudo_password = Some(sudo_password);
567581
}
@@ -592,16 +606,16 @@ async fn run_deploy(
592606

593607
for data in data_iter() {
594608
let node_name: String = data.deploy_data.node_name.to_string();
595-
deploy::push::build_profile(data).await.map_err(|e| {
596-
RunDeployError::BuildProfile(node_name, e)
597-
})?;
609+
deploy::push::build_profile(data)
610+
.await
611+
.map_err(|e| RunDeployError::BuildProfile(node_name, e))?;
598612
}
599613

600614
for data in data_iter() {
601615
let node_name: String = data.deploy_data.node_name.to_string();
602-
deploy::push::push_profile(data).await.map_err(|e| {
603-
RunDeployError::PushProfile(node_name, e)
604-
})?;
616+
deploy::push::push_profile(data)
617+
.await
618+
.map_err(|e| RunDeployError::PushProfile(node_name, e))?;
605619
}
606620

607621
let mut succeeded: Vec<(&deploy::DeployData, &deploy::DeployDefs)> = vec![];
@@ -611,7 +625,8 @@ async fn run_deploy(
611625
// Rollbacks adhere to the global seeting to auto_rollback and secondary
612626
// the profile's configuration
613627
for (_, deploy_data, deploy_defs) in &parts {
614-
if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate, boot).await
628+
if let Err(e) =
629+
deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate, boot).await
615630
{
616631
error!("{}", e);
617632
if dry_activate {
@@ -624,14 +639,19 @@ async fn run_deploy(
624639
// the command line)
625640
for (deploy_data, deploy_defs) in &succeeded {
626641
if deploy_data.merged_settings.auto_rollback.unwrap_or(true) {
627-
deploy::deploy::revoke(*deploy_data, *deploy_defs).await.map_err(|e| {
628-
RunDeployError::RevokeProfile(deploy_data.node_name.to_string(), e)
629-
})?;
642+
deploy::deploy::revoke(*deploy_data, *deploy_defs)
643+
.await
644+
.map_err(|e| {
645+
RunDeployError::RevokeProfile(deploy_data.node_name.to_string(), e)
646+
})?;
630647
}
631648
}
632649
return Err(RunDeployError::Rollback(deploy_data.node_name.to_string()));
633650
}
634-
return Err(RunDeployError::DeployProfile(deploy_data.node_name.to_string(), e))
651+
return Err(RunDeployError::DeployProfile(
652+
deploy_data.node_name.to_string(),
653+
e,
654+
));
635655
}
636656
succeeded.push((deploy_data, deploy_defs))
637657
}
@@ -682,18 +702,16 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
682702
.targets
683703
.unwrap_or_else(|| vec![opts.clone().target.unwrap_or_else(|| ".".to_string())]);
684704

685-
let deploy_flakes: Vec<DeployFlake> =
686-
if let Some(file) = &opts.file {
687-
deploys
688-
.iter()
689-
.map(|f| deploy::parse_file(file.as_str(), f.as_str()))
690-
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?
691-
}
692-
else {
705+
let deploy_flakes: Vec<DeployFlake> = if let Some(file) = &opts.file {
693706
deploys
694-
.iter()
695-
.map(|f| deploy::parse_flake(f.as_str()))
696-
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?
707+
.iter()
708+
.map(|f| deploy::parse_file(file.as_str(), f.as_str()))
709+
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?
710+
} else {
711+
deploys
712+
.iter()
713+
.map(|f| deploy::parse_flake(f.as_str()))
714+
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?
697715
};
698716

699717
let cmd_overrides = deploy::CmdOverrides {
@@ -710,7 +728,8 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
710728
dry_activate: opts.dry_activate,
711729
remote_build: opts.remote_build,
712730
sudo: opts.sudo,
713-
interactive_sudo: opts.interactive_sudo
731+
interactive_sudo: opts.interactive_sudo,
732+
remote_bin_path: opts.remote_bin_path,
714733
};
715734

716735
let supports_flakes = test_flake_support().await.map_err(RunError::FlakeTest)?;

src/data.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ pub struct GenericSettings {
3333
pub magic_rollback: Option<bool>,
3434
#[serde(rename(deserialize = "sudo"))]
3535
pub sudo: Option<String>,
36-
#[serde(default,rename(deserialize = "remoteBuild"))]
36+
#[serde(default, rename(deserialize = "remoteBuild"))]
3737
pub remote_build: Option<bool>,
3838
#[serde(rename(deserialize = "interactiveSudo"))]
3939
pub interactive_sudo: Option<bool>,
40+
#[serde(default, rename(deserialize = "remotePath"))]
41+
pub remote_bin_path: Option<PathBuf>,
4042
}
4143

4244
#[derive(Deserialize, Debug, Clone)]

0 commit comments

Comments
 (0)