From d85fb91d0bce37a7de78b9575e82d00c9b67f46c Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 8 Nov 2024 11:51:54 -0800 Subject: [PATCH] Various logic fixups in preparation for phased uninstall (#1277) * EncryptApfsVolume: fix encryption detection, enable curing * fixup: `launchctl print` does not take a `-plist` flag * fixup: we always thought the service was started The string "not running" contains "running", so we'd always set `service_started = true;` here. That's wrong, so instead check for "does not contain 'not running'". This does open us up to thinking that anything that is not "not running" is running, but we can address that later, if it becomes an issue. Also I'm pretty sure we don't need this at all, since it's always "not running" after it mounts the volume... But I'll leave that for later. * BootstrapLaunchctlService: always run * KickstartLaunchctlService: use execute_command helper * fixup: get_uuid_for_label -> get_disk_info_for_label For a follow-up commit. * If a Nix Store volume already exists and is encrypted with FileVault, force `encrypt` to true * fixup: check_loaded should not print to stdout/stderr, should use DARWIN_LAUNCHD_DOMAIN const * fixup: cargo clippy * fixup: check_loaded_output -> check_loaded Even though the exit code is an output of running the program, it's not textual output on stdout / stderr. * fixup: make comment more readable --- src/action/common/configure_init_service.rs | 4 +- .../macos/bootstrap_launchctl_service.rs | 20 +++--- .../create_determinate_volume_service.rs | 40 ++++++------ src/action/macos/create_fstab_entry.rs | 10 +-- src/action/macos/create_nix_hook_service.rs | 35 ++++++----- src/action/macos/create_volume_service.rs | 56 +++++++++-------- src/action/macos/encrypt_apfs_volume.rs | 45 ++++++++------ .../macos/kickstart_launchctl_service.rs | 44 ++++++------- src/action/macos/mod.rs | 37 ++++++----- src/os/darwin/diskutil.rs | 2 +- src/planner/macos/mod.rs | 61 ++++++++++++++----- src/settings.rs | 50 +++++++-------- 12 files changed, 229 insertions(+), 175 deletions(-) diff --git a/src/action/common/configure_init_service.rs b/src/action/common/configure_init_service.rs index cd9871d74..128fd6550 100644 --- a/src/action/common/configure_init_service.rs +++ b/src/action/common/configure_init_service.rs @@ -270,7 +270,7 @@ impl Action for ConfigureInitService { Command::new("launchctl") .process_group(0) .arg("enable") - .arg(&format!("{domain}/{service}")) + .arg(format!("{domain}/{service}")) .stdin(std::process::Stdio::null()), ) .await @@ -283,7 +283,7 @@ impl Action for ConfigureInitService { .process_group(0) .arg("kickstart") .arg("-k") - .arg(&format!("{domain}/{service}")) + .arg(format!("{domain}/{service}")) .stdin(std::process::Stdio::null()), ) .await diff --git a/src/action/macos/bootstrap_launchctl_service.rs b/src/action/macos/bootstrap_launchctl_service.rs index 4d34ba44e..ca3a9c9b2 100644 --- a/src/action/macos/bootstrap_launchctl_service.rs +++ b/src/action/macos/bootstrap_launchctl_service.rs @@ -33,7 +33,6 @@ impl BootstrapLaunchctlService { command.process_group(0); command.arg("print"); command.arg(format!("{DARWIN_LAUNCHD_DOMAIN}/{service}")); - command.arg("-plist"); command.stdin(std::process::Stdio::null()); command.stdout(std::process::Stdio::piped()); command.stderr(std::process::Stdio::piped()); @@ -49,15 +48,6 @@ impl BootstrapLaunchctlService { .await .map_err(Self::error)?; - if is_present && !is_disabled { - return Ok(StatefulAction::completed(Self { - service, - path, - is_present, - is_disabled, - })); - } - Ok(StatefulAction::uncompleted(Self { service, path, @@ -110,19 +100,23 @@ impl Action for BootstrapLaunchctlService { Command::new("launchctl") .process_group(0) .arg("enable") - .arg(&format!("{DARWIN_LAUNCHD_DOMAIN}/{service}")) + .arg(format!("{DARWIN_LAUNCHD_DOMAIN}/{service}")) .stdin(std::process::Stdio::null()), ) .await .map_err(Self::error)?; } - if !*is_present { - crate::action::macos::retry_bootstrap(DARWIN_LAUNCHD_DOMAIN, service, path) + if *is_present { + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, service) .await .map_err(Self::error)?; } + crate::action::macos::retry_bootstrap(DARWIN_LAUNCHD_DOMAIN, service, path) + .await + .map_err(Self::error)?; + Ok(()) } diff --git a/src/action/macos/create_determinate_volume_service.rs b/src/action/macos/create_determinate_volume_service.rs index 13956d31e..bc6f95ac4 100644 --- a/src/action/macos/create_determinate_volume_service.rs +++ b/src/action/macos/create_determinate_volume_service.rs @@ -1,15 +1,19 @@ use serde::{Deserialize, Serialize}; use tracing::{span, Span}; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + process::Stdio, +}; use tokio::{ fs::{remove_file, OpenOptions}, io::AsyncWriteExt, process::Command, }; -use crate::action::{ - Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, +use crate::{ + action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction}, + execute_command, }; use super::DARWIN_LAUNCHD_DOMAIN; @@ -43,27 +47,27 @@ impl CreateDeterminateVolumeService { // If the service is currently loaded or running, we need to unload it during execute (since we will then recreate it and reload it) // This `launchctl` command may fail if the service isn't loaded - let mut check_loaded_command = Command::new("launchctl"); - check_loaded_command.arg("print"); - check_loaded_command.arg(format!("system/{}", this.mount_service_label)); - tracing::trace!( - command = format!("{:?}", check_loaded_command.as_std()), - "Executing" - ); - let check_loaded_output = check_loaded_command - .status() - .await - .map_err(|e| ActionErrorKind::command(&check_loaded_command, e)) - .map_err(Self::error)?; - - this.needs_bootout = check_loaded_output.success(); + let check_loaded = execute_command( + Command::new("launchctl") + .arg("print") + .arg(format!( + "{DARWIN_LAUNCHD_DOMAIN}/{}", + this.mount_service_label + )) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()), + ) + .await + .ok(); - if this.needs_bootout { + if check_loaded.is_some() { tracing::debug!( "Detected loaded service `{}` which needs unload before replacing `{}`", this.mount_service_label, this.path.display(), ); + this.needs_bootout = true; } if this.path.exists() { diff --git a/src/action/macos/create_fstab_entry.rs b/src/action/macos/create_fstab_entry.rs index eb61719a1..b44fae694 100644 --- a/src/action/macos/create_fstab_entry.rs +++ b/src/action/macos/create_fstab_entry.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use super::{get_uuid_for_label, CreateApfsVolume}; +use super::{get_disk_info_for_label, CreateApfsVolume}; use crate::action::{ Action, ActionDescription, ActionError, ActionErrorKind, ActionState, ActionTag, StatefulAction, }; @@ -123,11 +123,11 @@ impl Action for CreateFstabEntry { existing_entry, } = self; let fstab_path = Path::new(FSTAB_PATH); - let uuid = match get_uuid_for_label(apfs_volume_label) + let uuid = match get_disk_info_for_label(apfs_volume_label) .await .map_err(Self::error)? { - Some(uuid) => uuid, + Some(diskutil_info) => diskutil_info.volume_uuid, None => { return Err(Self::error(CreateFstabEntryError::CannotDetermineUuid( apfs_volume_label.clone(), @@ -237,11 +237,11 @@ impl Action for CreateFstabEntry { async fn revert(&mut self) -> Result<(), ActionError> { let fstab_path = Path::new(FSTAB_PATH); - if let Some(uuid) = get_uuid_for_label(&self.apfs_volume_label) + if let Some(diskutil_info) = get_disk_info_for_label(&self.apfs_volume_label) .await .map_err(Self::error)? { - let fstab_entry = fstab_lines(&uuid, &self.apfs_volume_label); + let fstab_entry = fstab_lines(&diskutil_info.volume_uuid, &self.apfs_volume_label); let mut file = OpenOptions::new() .create(false) diff --git a/src/action/macos/create_nix_hook_service.rs b/src/action/macos/create_nix_hook_service.rs index e4799b38b..3014e2ce2 100644 --- a/src/action/macos/create_nix_hook_service.rs +++ b/src/action/macos/create_nix_hook_service.rs @@ -1,15 +1,16 @@ use serde::{Deserialize, Serialize}; use tracing::{span, Span}; -use std::path::PathBuf; +use std::{path::PathBuf, process::Stdio}; use tokio::{ fs::{remove_file, OpenOptions}, io::AsyncWriteExt, process::Command, }; -use crate::action::{ - Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, +use crate::{ + action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction}, + execute_command, }; use super::DARWIN_LAUNCHD_DOMAIN; @@ -37,26 +38,24 @@ impl CreateNixHookService { // If the service is currently loaded or running, we need to unload it during execute (since we will then recreate it and reload it) // This `launchctl` command may fail if the service isn't loaded - let mut check_loaded_command = Command::new("launchctl"); - check_loaded_command.process_group(0); - check_loaded_command.arg("print"); - check_loaded_command.arg(format!("system/{}", this.service_label)); - tracing::trace!( - command = format!("{:?}", check_loaded_command.as_std()), - "Executing" - ); - let check_loaded_output = check_loaded_command - .output() - .await - .map_err(|e| ActionErrorKind::command(&check_loaded_command, e)) - .map_err(Self::error)?; - this.needs_bootout = check_loaded_output.status.success(); - if this.needs_bootout { + let check_loaded = execute_command( + Command::new("launchctl") + .arg("print") + .arg(format!("{DARWIN_LAUNCHD_DOMAIN}/{}", this.service_label)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()), + ) + .await + .ok(); + + if check_loaded.is_some() { tracing::debug!( "Detected loaded service `{}` which needs unload before replacing `{}`", this.service_label, this.path.display(), ); + this.needs_bootout = true; } if this.path.exists() { diff --git a/src/action/macos/create_volume_service.rs b/src/action/macos/create_volume_service.rs index 9b610cb9b..5e90e65af 100644 --- a/src/action/macos/create_volume_service.rs +++ b/src/action/macos/create_volume_service.rs @@ -1,19 +1,25 @@ use serde::{Deserialize, Serialize}; use tracing::{span, Span}; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + process::Stdio, +}; use tokio::{ fs::{remove_file, OpenOptions}, io::AsyncWriteExt, process::Command, }; -use crate::action::{ - macos::DARWIN_LAUNCHD_DOMAIN, Action, ActionDescription, ActionError, ActionErrorKind, - ActionTag, StatefulAction, +use crate::{ + action::{ + macos::DARWIN_LAUNCHD_DOMAIN, Action, ActionDescription, ActionError, ActionErrorKind, + ActionTag, StatefulAction, + }, + execute_command, }; -use super::get_uuid_for_label; +use super::get_disk_info_for_label; /** Create a plist for a `launchctl` service to mount the given `apfs_volume_label` on the given `mount_point`. */ @@ -51,39 +57,41 @@ impl CreateVolumeService { // If the service is currently loaded or running, we need to unload it during execute (since we will then recreate it and reload it) // This `launchctl` command may fail if the service isn't loaded - let mut check_loaded_command = Command::new("launchctl"); - check_loaded_command.arg("print"); - check_loaded_command.arg(format!("system/{}", this.mount_service_label)); - tracing::trace!( - command = format!("{:?}", check_loaded_command.as_std()), - "Executing" - ); - let check_loaded_output = check_loaded_command - .output() - .await - .map_err(|e| ActionErrorKind::command(&check_loaded_command, e)) - .map_err(Self::error)?; - this.needs_bootout = check_loaded_output.status.success(); - if this.needs_bootout { + let check_loaded = execute_command( + Command::new("launchctl") + .arg("print") + .arg(format!( + "{DARWIN_LAUNCHD_DOMAIN}/{}", + this.mount_service_label + )) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()), + ) + .await + .ok(); + + if check_loaded.is_some() { tracing::debug!( "Detected loaded service `{}` which needs unload before replacing `{}`", this.mount_service_label, this.path.display(), ); + this.needs_bootout = true; } if this.path.exists() { let discovered_plist: LaunchctlMountPlist = plist::from_file(&this.path).map_err(Self::error)?; - match get_uuid_for_label(&this.apfs_volume_label) + match get_disk_info_for_label(&this.apfs_volume_label) .await .map_err(Self::error)? { - Some(uuid) => { + Some(disk_info) => { let expected_plist = generate_mount_plist( &this.mount_service_label, &this.apfs_volume_label, - uuid, + disk_info.volume_uuid, &this.mount_point, encrypt, ) @@ -191,7 +199,7 @@ impl Action for CreateVolumeService { .map_err(Self::error)?; } - let uuid = match get_uuid_for_label(apfs_volume_label) + let disk_info = match get_disk_info_for_label(apfs_volume_label) .await .map_err(Self::error)? { @@ -205,7 +213,7 @@ impl Action for CreateVolumeService { let generated_plist = generate_mount_plist( mount_service_label, apfs_volume_label, - uuid, + disk_info.volume_uuid, mount_point, *encrypt, ) diff --git a/src/action/macos/encrypt_apfs_volume.rs b/src/action/macos/encrypt_apfs_volume.rs index 032ee2630..f0c55e97a 100644 --- a/src/action/macos/encrypt_apfs_volume.rs +++ b/src/action/macos/encrypt_apfs_volume.rs @@ -44,7 +44,7 @@ impl EncryptApfsVolume { command.arg("-s"); command.arg("Nix Store"); command.arg("-l"); - command.arg(&format!("{} encryption password", disk.display())); + command.arg(format!("{} encryption password", disk.display())); command.arg("-D"); command.arg("Encrypted volume password"); command.process_group(0); @@ -72,10 +72,27 @@ impl EncryptApfsVolume { name, disk, ))); } else if planned_create_apfs_volume.state == ActionState::Completed { - // The user has a volume already created, but a password not set. This means we probably can't decrypt the volume. - return Err(Self::error( - EncryptApfsVolumeError::MissingPasswordForExistingVolume(name, disk), - )); + #[derive(serde::Deserialize)] + #[serde(rename_all = "PascalCase")] + struct DiskUtilDiskInfoOutput { + file_vault: bool, + } + + let output = + execute_command(Command::new("/usr/sbin/diskutil").args(["info", "-plist", &name])) + .await + .map_err(Self::error)?; + + let parsed: DiskUtilDiskInfoOutput = + plist::from_bytes(&output.stdout).map_err(Self::error)?; + + // The user has an already-encrypted volume, but we couldn't retrieve the password. + // We won't be able to decrypt the volume. + if parsed.file_vault { + return Err(Self::error( + EncryptApfsVolumeError::MissingPasswordForExistingVolume(name, disk), + )); + } } // Ensure if the disk already exists, that it's encrypted @@ -88,18 +105,12 @@ impl EncryptApfsVolume { plist::from_bytes(&output.stdout).map_err(Self::error)?; for container in parsed.containers { for volume in container.volumes { - if volume.name.as_ref() == Some(&name) { - if volume.encryption { - return Err(Self::error( - EncryptApfsVolumeError::ExistingVolumeNotEncrypted(name, disk), - )); - } else { - return Ok(StatefulAction::completed(Self { - determinate_nix, - disk, - name, - })); - } + if volume.name.as_ref() == Some(&name) && volume.file_vault { + return Ok(StatefulAction::completed(Self { + determinate_nix, + disk, + name, + })); } } } diff --git a/src/action/macos/kickstart_launchctl_service.rs b/src/action/macos/kickstart_launchctl_service.rs index ba730e4d4..d08780d7a 100644 --- a/src/action/macos/kickstart_launchctl_service.rs +++ b/src/action/macos/kickstart_launchctl_service.rs @@ -7,6 +7,7 @@ use tracing::{span, Span}; use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}; use crate::action::{Action, ActionDescription}; +use crate::execute_command; /** Bootstrap and kickstart an APFS volume @@ -19,29 +20,26 @@ pub struct KickstartLaunchctlService { } impl KickstartLaunchctlService { - #[tracing::instrument(level = "debug", skip_all)] - pub async fn plan( - domain: impl AsRef, - service: impl AsRef, - ) -> Result, ActionError> { - let domain = domain.as_ref().to_string(); - let service = service.as_ref().to_string(); + #[tracing::instrument(level = "debug")] + pub async fn plan(domain: &str, service: &str) -> Result, ActionError> { + let domain = domain.to_string(); + let service = service.to_string(); let mut service_exists = false; let mut service_started = false; - let mut command = Command::new("launchctl"); - command.process_group(0); - command.arg("print"); - command.arg(&service); - command.arg("-plist"); - command.stdin(std::process::Stdio::null()); - command.stdout(std::process::Stdio::piped()); - command.stderr(std::process::Stdio::piped()); - let output = command - .output() - .await - .map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?; - if output.status.success() { + let output = execute_command( + Command::new("launchctl") + .process_group(0) + .arg("print") + .arg(format!("{domain}/{service}")) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()), + ) + .await + .ok(); + + if let Some(output) = output { service_exists = true; let output_string = String::from_utf8(output.stdout).map_err(Self::error)?; @@ -53,7 +51,7 @@ impl KickstartLaunchctlService { for output_line in output_string.lines() { let output_line_trimmed = output_line.trim(); if output_line_trimmed.starts_with("state") { - if output_line_trimmed.contains("running") { + if !output_line_trimmed.contains("not running") { service_started = true; } break; @@ -97,14 +95,12 @@ impl Action for KickstartLaunchctlService { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { domain, service } = self; - let mut retry_tokens: usize = 10; loop { let mut command = Command::new("launchctl"); command.process_group(0); command.args(["kickstart", "-k"]); - command.arg(format!("{domain}/{service}")); + command.arg(format!("{}/{}", self.domain, self.service)); command.stdin(std::process::Stdio::null()); command.stderr(std::process::Stdio::null()); command.stdout(std::process::Stdio::null()); diff --git a/src/action/macos/mod.rs b/src/action/macos/mod.rs index a7f352c23..445412afe 100644 --- a/src/action/macos/mod.rs +++ b/src/action/macos/mod.rs @@ -46,7 +46,9 @@ use super::ActionErrorKind; pub const DARWIN_LAUNCHD_DOMAIN: &str = "system"; -async fn get_uuid_for_label(apfs_volume_label: &str) -> Result, ActionErrorKind> { +pub(crate) async fn get_disk_info_for_label( + apfs_volume_label: &str, +) -> Result, ActionErrorKind> { let mut command = Command::new("/usr/sbin/diskutil"); command.process_group(0); command.arg("info"); @@ -63,32 +65,39 @@ async fn get_uuid_for_label(apfs_volume_label: &str) -> Result, Act .await .map_err(|e| ActionErrorKind::command(&command, e))?; - let parsed: DiskUtilApfsInfoOutput = plist::from_bytes(&output.stdout)?; + if let Ok(diskutil_info) = plist::from_bytes::(&output.stdout) { + return Ok(Some(diskutil_info)); + } - if let Some(error_message) = parsed.error_message { + if let Ok(diskutil_error) = plist::from_bytes::(&output.stdout) { + let error_message = diskutil_error.error_message; let expected_not_found = format!("Could not find disk: {apfs_volume_label}"); if error_message.contains(&expected_not_found) { - Ok(None) + return Ok(None); } else { - Err(ActionErrorKind::DiskUtilInfoError { + return Err(ActionErrorKind::DiskUtilInfoError { command: command_str, message: error_message, - }) + }); } - } else if let Some(uuid) = parsed.volume_uuid { - Ok(Some(uuid)) - } else { - Err(ActionErrorKind::command_output(&command, output)) } + + Err(ActionErrorKind::command_output(&command, output)) } #[derive(Deserialize, Clone, Debug)] #[serde(rename_all = "PascalCase")] -struct DiskUtilApfsInfoOutput { - #[serde(rename = "ErrorMessage")] - error_message: Option, +pub(crate) struct DiskUtilApfsInfoOutput { #[serde(rename = "VolumeUUID")] - volume_uuid: Option, + volume_uuid: Uuid, + pub(crate) file_vault: bool, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +struct DiskUtilApfsInfoError { + #[serde(rename = "ErrorMessage")] + error_message: String, } #[tracing::instrument] diff --git a/src/os/darwin/diskutil.rs b/src/os/darwin/diskutil.rs index 0d48e7383..a2ead29a6 100644 --- a/src/os/darwin/diskutil.rs +++ b/src/os/darwin/diskutil.rs @@ -34,7 +34,7 @@ pub struct DiskUtilApfsContainer { #[serde(rename_all = "PascalCase")] pub struct DiskUtilApfsListVolume { pub name: Option, - pub encryption: bool, + pub file_vault: bool, } #[derive(serde::Deserialize, Clone, Debug)] diff --git a/src/planner/macos/mod.rs b/src/planner/macos/mod.rs index 788b8b503..223151256 100644 --- a/src/planner/macos/mod.rs +++ b/src/planner/macos/mod.rs @@ -163,21 +163,54 @@ impl Planner for Macos { // This is a goofy thing to do, but it is in an attempt to make a more globally coherent plan / receipt. let encrypt = match (self.settings.determinate_nix, self.encrypt) { (true, _) => true, - (false, Some(choice)) => choice, + (false, Some(choice)) => { + if let Some(diskutil_info) = + crate::action::macos::get_disk_info_for_label(&self.volume_label) + .await + .ok() + .flatten() + { + if diskutil_info.file_vault { + tracing::warn!("Existing volume was encrypted with FileVault, forcing `encrypt` to true"); + true + } else { + choice + } + } else { + choice + } + }, (false, None) => { - let output = Command::new("/usr/bin/fdesetup") - .arg("isactive") - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .process_group(0) - .output() - .await - .map_err(|e| PlannerError::Custom(Box::new(e)))?; - - let stdout = String::from_utf8_lossy(&output.stdout); - let stdout_trimmed = stdout.trim(); - - stdout_trimmed == "true" + let root_disk_is_encrypted = { + let output = Command::new("/usr/bin/fdesetup") + .arg("isactive") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .process_group(0) + .output() + .await + .map_err(|e| PlannerError::Custom(Box::new(e)))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let stdout_trimmed = stdout.trim(); + + stdout_trimmed == "true" + }; + + let existing_store_volume_is_encrypted = { + if let Some(diskutil_info) = + crate::action::macos::get_disk_info_for_label(&self.volume_label) + .await + .ok() + .flatten() + { + diskutil_info.file_vault + } else { + false + } + }; + + root_disk_is_encrypted || existing_store_volume_is_encrypted }, }; diff --git a/src/settings.rs b/src/settings.rs index 6540c64ad..49482af07 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -645,6 +645,31 @@ impl crate::diagnostics::ErrorDiagnostic for InstallSettingsError { } } +pub fn determinate_nix_settings() -> nix_config_parser::NixConfig { + let mut cfg = nix_config_parser::NixConfig::new(); + let settings = cfg.settings_mut(); + + settings.insert("netrc-file".into(), "/nix/var/determinate/netrc".into()); + + let extra_substituters = ["https://cache.flakehub.com"]; + match settings.entry("extra-substituters".to_string()) { + Entry::Occupied(mut slot) => { + let slot_mut = slot.get_mut(); + for extra_substituter in extra_substituters { + if !slot_mut.contains(extra_substituter) { + *slot_mut += " "; + *slot_mut += extra_substituter; + } + } + }, + Entry::Vacant(slot) => { + let _ = slot.insert(extra_substituters.join(" ")); + }, + }; + + cfg +} + #[cfg(test)] mod tests { use super::{FromStr, PathBuf, Url, UrlOrPath, UrlOrPathOrString}; @@ -689,28 +714,3 @@ mod tests { Ok(()) } } - -pub fn determinate_nix_settings() -> nix_config_parser::NixConfig { - let mut cfg = nix_config_parser::NixConfig::new(); - let settings = cfg.settings_mut(); - - settings.insert("netrc-file".into(), "/nix/var/determinate/netrc".into()); - - let extra_substituters = ["https://cache.flakehub.com"]; - match settings.entry("extra-substituters".to_string()) { - Entry::Occupied(mut slot) => { - let slot_mut = slot.get_mut(); - for extra_substituter in extra_substituters { - if !slot_mut.contains(extra_substituter) { - *slot_mut += " "; - *slot_mut += extra_substituter; - } - } - }, - Entry::Vacant(slot) => { - let _ = slot.insert(extra_substituters.join(" ")); - }, - }; - - cfg -}