Skip to content
14 changes: 9 additions & 5 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ static PASSWORD_CACHE: OnceLock<Mutex<HashMap<String, SecretString>>> =

fn get_cached_password(host: &str) -> Option<SecretString> {
let cache = PASSWORD_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let guard = cache.lock().unwrap_or_else(|e| e.into_inner());
let guard = cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.get(host).cloned()
}

fn cache_password(host: &str, password: SecretString) {
let cache = PASSWORD_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut guard = cache.lock().unwrap_or_else(|e| e.into_inner());
let mut guard = cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.insert(host.to_string(), password);
}

Expand Down Expand Up @@ -449,7 +453,7 @@ impl Command {
Some(cached_password)
} else {
let password =
inquire::Password::new(&format!("[sudo] password for {}:", host))
inquire::Password::new(&format!("[sudo] password for {host}:"))
.without_confirmation()
.prompt()
.context("Failed to read sudo password")?;
Expand Down Expand Up @@ -492,11 +496,11 @@ impl Command {
for (key, action) in &self.env_vars {
match action {
EnvAction::Set(value) => {
elev_cmd = elev_cmd.arg(format!("{}={}", key, value));
elev_cmd = elev_cmd.arg(format!("{key}={value}"));
},
EnvAction::Preserve => {
if let Ok(value) = std::env::var(key) {
elev_cmd = elev_cmd.arg(format!("{}={}", key, value));
elev_cmd = elev_cmd.arg(format!("{key}={value}"));
}
},
_ => {},
Expand Down
7 changes: 6 additions & 1 deletion src/darwin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,13 @@ impl DarwinRebuildArgs {
}

if matches!(variant, Switch) {
let profile_path = self.profile.as_ref().map_or_else(
|| std::ffi::OsStr::new(SYSTEM_PROFILE),
|p| p.as_os_str(),
);
Command::new("nix")
.args(["build", "--no-link", "--profile", SYSTEM_PROFILE])
.args(["build", "--no-link", "--profile"])
.arg(profile_path)
.arg(&out_path)
.elevate(Some(elevation.clone()))
.dry(self.common.dry)
Expand Down
32 changes: 21 additions & 11 deletions src/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,27 @@ impl HomeRebuildArgs {
.run()
.wrap_err("Failed to build Home-Manager configuration")?;

let prev_generation: Option<PathBuf> = [
PathBuf::from("/nix/var/nix/profiles/per-user")
.join(env::var("USER").map_err(|_| eyre!("Couldn't get username"))?)
.join("home-manager"),
PathBuf::from(
env::var("HOME").map_err(|_| eyre!("Couldn't get home directory"))?,
)
.join(".local/state/nix/profiles/home-manager"),
]
.into_iter()
.find(|next| next.exists());
let prev_generation: Option<PathBuf> = if let Some(ref profile) =
self.profile
{
if profile.exists() {
Some(profile.clone())
} else {
None
}
} else {
[
PathBuf::from("/nix/var/nix/profiles/per-user")
.join(env::var("USER").map_err(|_| eyre!("Couldn't get username"))?)
.join("home-manager"),
PathBuf::from(
env::var("HOME").map_err(|_| eyre!("Couldn't get home directory"))?,
)
.join(".local/state/nix/profiles/home-manager"),
]
.into_iter()
.find(|next| next.exists())
};

debug!("Previous generation: {prev_generation:?}");

Expand Down
16 changes: 16 additions & 0 deletions src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ pub struct OsRebuildArgs {
/// Build the configuration to a different host over ssh
#[arg(long)]
pub build_host: Option<String>,

/// Path to Nix' system profile
#[arg(long, short = 'P')]
pub profile: Option<std::path::PathBuf>,
}

impl OsRebuildArgs {
Expand Down Expand Up @@ -285,6 +289,10 @@ pub struct OsRollbackArgs {
/// Whether to display a package diff
#[arg(long, short, value_enum, default_value_t = DiffType::Auto)]
pub diff: DiffType,

/// Path to Nix' system profile for rollback
#[arg(long, short = 'P')]
pub profile: Option<std::path::PathBuf>,
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -514,6 +522,10 @@ pub struct HomeRebuildArgs {
/// Move existing files by backing up with this file extension
#[arg(long, short = 'b')]
pub backup_extension: Option<String>,

/// Path to Home-Manager profile
#[arg(long, short = 'P')]
pub profile: Option<std::path::PathBuf>,
}

impl HomeRebuildArgs {
Expand Down Expand Up @@ -623,6 +635,10 @@ pub struct DarwinRebuildArgs {
/// Don't panic if calling nh as root
#[arg(short = 'R', long, env = "NH_BYPASS_ROOT_CHECK")]
pub bypass_root_check: bool,

/// Path to Darwin system profile
#[arg(long, short = 'P')]
pub profile: Option<std::path::PathBuf>,
}

impl DarwinRebuildArgs {
Expand Down
25 changes: 21 additions & 4 deletions src/nixos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,14 @@ impl OsRebuildArgs {
.canonicalize()
.context("Failed to resolve output path")?;

let system_profile_path = self.profile.as_ref().map_or_else(
|| std::ffi::OsStr::new(SYSTEM_PROFILE),
|p| p.as_os_str(),
);
Command::new("nix")
.elevate(elevate.then_some(elevation.clone()))
.args(["build", "--no-link", "--profile", SYSTEM_PROFILE])
.args(["build", "--no-link", "--profile"])
.arg(system_profile_path)
.arg(&canonical_out_path)
.ssh(self.target_host.clone())
.with_required_env()
Expand Down Expand Up @@ -401,7 +406,11 @@ impl OsRollbackArgs {
info!("Rolling back to generation {}", target_generation.number);

// Construct path to the generation
let profile_dir = Path::new(SYSTEM_PROFILE).parent().unwrap_or_else(|| {
let system_profile_path = self
.profile
.as_deref()
.unwrap_or_else(|| Path::new(SYSTEM_PROFILE));
let profile_dir = system_profile_path.parent().unwrap_or_else(|| {
tracing::warn!(
"SYSTEM_PROFILE has no parent, defaulting to /nix/var/nix/profiles"
);
Expand Down Expand Up @@ -469,10 +478,14 @@ impl OsRollbackArgs {
info!("Setting system profile...");

// Instead of direct symlink operations, use a command with proper elevation
let system_profile_path = self
.profile
.as_deref()
.unwrap_or_else(|| Path::new(SYSTEM_PROFILE));
Command::new("ln")
.arg("-sfn") // force, symbolic link
.arg(&generation_link)
.arg(SYSTEM_PROFILE)
.arg(system_profile_path)
.elevate(elevate.then_some(elevation.clone()))
.message("Setting system profile")
.with_required_env()
Expand Down Expand Up @@ -534,10 +547,14 @@ impl OsRollbackArgs {
let current_gen_link =
profile_dir.join(format!("system-{current_gen_number}-link"));

let system_profile_path = self
.profile
.as_deref()
.unwrap_or_else(|| Path::new(SYSTEM_PROFILE));
Command::new("ln")
.arg("-sfn") // Force, symbolic link
.arg(&current_gen_link)
.arg(SYSTEM_PROFILE)
.arg(system_profile_path)
.elevate(elevate.then_some(elevation))
.message("Rolling back system profile")
.with_required_env()
Expand Down