From 2594316750dfac9d5537861d680837ca62362401 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 6 Mar 2023 09:29:44 -0800 Subject: [PATCH] Integrate nix-config-parser (#263) Co-authored-by: Ana Hobden --- Cargo.lock | 11 + Cargo.toml | 1 + src/action/base/create_or_merge_nix_config.rs | 685 ++++++++++++++++++ src/action/base/mod.rs | 2 + src/action/common/place_nix_configuration.rs | 88 ++- tests/fixtures/linux/linux.json | 29 +- tests/fixtures/linux/steam-deck.json | 368 +++++----- tests/fixtures/macos/macos.json | 29 +- 8 files changed, 984 insertions(+), 229 deletions(-) create mode 100644 src/action/base/create_or_merge_nix_config.rs diff --git a/Cargo.lock b/Cargo.lock index 68fe405..841f37b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -932,6 +932,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "nix-config-parser" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd0a73a9c5f1f960f435be5ade7dd728d5039e6e92dbd5764f8c3d701491fc1" +dependencies = [ + "serde", + "thiserror", +] + [[package]] name = "nix-installer" version = "0.4.0" @@ -947,6 +957,7 @@ dependencies = [ "glob", "is_ci", "nix", + "nix-config-parser", "os-release", "owo-colors", "plist", diff --git a/Cargo.toml b/Cargo.toml index b742b8c..38df9a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ uuid = { version = "1.2.2", features = ["serde"] } os-release = { version = "0.1.0", default-features = false, optional = true } is_ci = { version = "1.1.1", default-features = false, optional = true } strum = { version = "0.24.1", features = ["derive"] } +nix-config-parser = { version = "0.1.2", features = ["serde"] } [dev-dependencies] eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] } diff --git a/src/action/base/create_or_merge_nix_config.rs b/src/action/base/create_or_merge_nix_config.rs new file mode 100644 index 0000000..6c209eb --- /dev/null +++ b/src/action/base/create_or_merge_nix_config.rs @@ -0,0 +1,685 @@ +use std::{ + os::unix::fs::PermissionsExt, + path::{Path, PathBuf}, +}; + +use nix_config_parser::NixConfig; +use rand::Rng; +use tokio::{ + fs::{remove_file, OpenOptions}, + io::AsyncWriteExt, +}; +use tracing::{span, Span}; + +use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction}; + +/// The `nix.conf` configuration names that are safe to merge. +// FIXME(@cole-h): make configurable by downstream users? +const MERGEABLE_CONF_NAMES: &[&str] = &["experimental-features"]; +const NIX_CONF_MODE: u32 = 0o644; +const NIX_CONF_COMMENT_CHAR: char = '#'; + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum CreateOrMergeNixConfigError { + #[error(transparent)] + ParseNixConfig(#[from] nix_config_parser::ParseError), + #[error("Could not merge Nix configuration for key(s) {}; consider removing them from `{1}` in your editor, or removing your existing configuration with `rm {1}`", + .0 + .iter() + .map(|v| format!("`{v}`")) + .collect::>() + .join(", "))] + UnmergeableConfig(Vec, std::path::PathBuf), +} + +/// Create or merge an existing `nix.conf` at the specified path. +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct CreateOrMergeNixConfig { + pub(crate) path: PathBuf, + pending_nix_config: NixConfig, +} + +impl CreateOrMergeNixConfig { + #[tracing::instrument(level = "debug", skip_all)] + pub async fn plan( + path: impl AsRef, + pending_nix_config: NixConfig, + ) -> Result, ActionError> { + let path = path.as_ref().to_path_buf(); + + let this = Self { + path, + pending_nix_config, + }; + + if this.path.exists() { + let (merged_nix_config, _) = + Self::validate_existing_nix_config(&this.pending_nix_config, &this.path)?; + + if !merged_nix_config.settings().is_empty() { + return Ok(StatefulAction::uncompleted(this)); + } else { + tracing::debug!( + "Setting Nix configurations in `{}` already complete", + this.path.display() + ); + return Ok(StatefulAction::completed(this)); + } + } + + Ok(StatefulAction::uncompleted(this)) + } + + fn merge_pending_and_existing_nix_config( + pending_nix_config: &NixConfig, + existing_nix_config: &NixConfig, + path: &Path, + ) -> Result<(NixConfig, NixConfig), CreateOrMergeNixConfigError> { + let mut merged_nix_config = NixConfig::new(); + let mut unmergeable_config_names = Vec::new(); + + for (pending_conf_name, pending_conf_value) in pending_nix_config.settings() { + if let Some(existing_conf_value) = existing_nix_config.settings().get(pending_conf_name) + { + let pending_conf_value = pending_conf_value.split(' ').collect::>(); + let existing_conf_value = existing_conf_value.split(' ').collect::>(); + + if pending_conf_value + .iter() + .all(|e| existing_conf_value.contains(e)) + { + // If _all_ the values we want are present in the existing config, + // merged_nix_config will be empty and this will be marked as completed. We + // don't return early here because there may be more config options to + // check. + } else if MERGEABLE_CONF_NAMES.contains(&pending_conf_name.as_str()) { + let mut merged_conf_value = + Vec::with_capacity(pending_conf_value.len() + existing_conf_value.len()); + merged_conf_value.extend(pending_conf_value); + merged_conf_value.extend(existing_conf_value); + merged_conf_value.dedup(); + let merged_conf_value = merged_conf_value.join(" "); + let merged_conf_value = merged_conf_value.trim(); + + merged_nix_config + .settings_mut() + .insert(pending_conf_name.to_owned(), merged_conf_value.to_owned()); + } else { + unmergeable_config_names.push(pending_conf_name.to_owned()); + } + } else { + merged_nix_config + .settings_mut() + .insert(pending_conf_name.to_owned(), pending_conf_value.to_owned()); + } + } + + if !unmergeable_config_names.is_empty() { + return Err(CreateOrMergeNixConfigError::UnmergeableConfig( + unmergeable_config_names, + path.to_path_buf(), + )); + } + + Ok((merged_nix_config, existing_nix_config.clone())) + } + + fn validate_existing_nix_config( + pending_nix_config: &NixConfig, + path: &Path, + ) -> Result<(NixConfig, NixConfig), ActionError> { + let path = path.to_path_buf(); + let metadata = path + .metadata() + .map_err(|e| ActionError::GettingMetadata(path.clone(), e))?; + + if !metadata.is_file() { + return Err(ActionError::PathWasNotFile(path)); + } + + // Does the file have the right permissions? + let discovered_mode = metadata.permissions().mode(); + // We only care about user-group-other permissions + let discovered_mode = discovered_mode & 0o777; + + if discovered_mode != NIX_CONF_MODE { + return Err(ActionError::PathModeMismatch( + path, + discovered_mode, + NIX_CONF_MODE, + )); + } + + let existing_nix_config = NixConfig::parse_file(&path) + .map_err(CreateOrMergeNixConfigError::ParseNixConfig) + .map_err(|e| ActionError::Custom(Box::new(e)))?; + + let (merged_nix_config, existing_nix_config) = Self::merge_pending_and_existing_nix_config( + &pending_nix_config, + &existing_nix_config, + &path, + ) + .map_err(|e| ActionError::Custom(Box::new(e)))?; + + Ok((merged_nix_config, existing_nix_config)) + } +} + +#[async_trait::async_trait] +#[typetag::serde(name = "create_or_merge_nix_config")] +impl Action for CreateOrMergeNixConfig { + fn action_tag() -> ActionTag { + ActionTag("create_or_merge_nix_config") + } + fn tracing_synopsis(&self) -> String { + format!( + "Merge or create nix.conf file `{path}`", + path = self.path.display(), + ) + } + + fn tracing_span(&self) -> Span { + let span = span!( + tracing::Level::DEBUG, + "create_or_merge_nix_config", + path = tracing::field::display(self.path.display()), + mode = tracing::field::display(format!("{:#o}", NIX_CONF_MODE)), + pending_nix_config = tracing::field::Empty, + ); + + if tracing::enabled!(tracing::Level::TRACE) { + span.record( + "pending_nix_config", + &self + .pending_nix_config + .settings() + .iter() + .map(|(k, v)| format!("{k}=\"{v}\"")) + .collect::>() + .join(","), + ); + } + span + } + + fn execute_description(&self) -> Vec { + vec![ActionDescription::new( + self.tracing_synopsis(), + vec![format!( + "Added settings: {settings}", + settings = self + .pending_nix_config + .settings() + .iter() + .map(|(k, v)| format!("{k}=\"{v}\"")) + .collect::>() + .join(", "), + )], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn execute(&mut self) -> Result<(), ActionError> { + let Self { + path, + pending_nix_config, + } = self; + + if tracing::enabled!(tracing::Level::TRACE) { + let span = tracing::Span::current(); + span.record( + "pending_nix_config", + pending_nix_config + .settings() + .iter() + .map(|(k, v)| format!("{k}='{v}'")) + .collect::>() + .join(" "), + ); + } + + // Create a temporary file in the same directory as the one + // that the final file goes in, so that we can rename it + // atomically + let parent_dir = path.parent().expect("File must be in a directory"); + let mut temp_file_path = parent_dir.to_owned(); + { + let mut rng = rand::thread_rng(); + temp_file_path.push(format!("nix-installer-tmp.{}", rng.gen::())); + } + let mut temp_file = OpenOptions::new() + .create(true) + .write(true) + // If the file is created, ensure that it has harmless + // permissions regardless of whether the mode will be + // changed later (if we ever create setuid executables, + // they should only become setuid once they are owned by + // the appropriate user) + .mode(0o600) + .open(&temp_file_path) + .await + .map_err(|e| { + ActionError::Open(temp_file_path.clone(), e) + })?; + + let (mut merged_nix_config, mut existing_nix_config) = if path.exists() { + let (merged_nix_config, existing_nix_config) = + Self::validate_existing_nix_config(&pending_nix_config, &path)?; + (merged_nix_config, Some(existing_nix_config)) + } else { + (pending_nix_config.clone(), None) + }; + + let mut new_config = String::new(); + + if let Some(existing_nix_config) = existing_nix_config.as_mut() { + let mut discovered_buf = tokio::fs::read_to_string(&path) + .await + .map_err(|e| ActionError::Read(path.to_path_buf(), e))?; + + // We append a newline to ensure that, in the case there are comments at the end of the + // file and _NO_ trailing newline, we still preserve the entire block of comments. + discovered_buf.push('\n'); + + let (associated_lines, _, _) = discovered_buf.split('\n').fold( + (Vec::new(), Vec::new(), false), + |(mut all_assoc, mut current_assoc, mut associating): ( + Vec>, + Vec, + bool, + ), + line| { + let line = line.trim(); + + // Don't associate our "Generated by" comment if it appears + if line.starts_with("# Generated by") { + return (all_assoc, current_assoc, associating); + } + + if line.starts_with(NIX_CONF_COMMENT_CHAR) { + associating = true; + } else if line.is_empty() || !line.starts_with(NIX_CONF_COMMENT_CHAR) { + associating = false; + } + + current_assoc.push(line.to_string()); + + if !associating { + all_assoc.push(current_assoc); + current_assoc = Vec::new(); + } + + (all_assoc, current_assoc, associating) + }, + ); + + for line_group in associated_lines { + if line_group.is_empty() || line_group.iter().all(|line| line.is_empty()) { + continue; + } + + // This expect should never reasonably panic, because we would need a line group + // consisting solely of a comment and nothing else, but unconditionally appending a + // newline to the config string before grouping above prevents this from occurring. + let line_idx = line_group + .iter() + .position(|line| !line.starts_with(NIX_CONF_COMMENT_CHAR)) + .expect("There should always be one line without a comment character"); + + let setting_line = &line_group[line_idx]; + let comments = line_group[..line_idx].join("\n"); + + // If we're here, but the line without a comment char is empty, we have + // standalone comments to preserve, but no settings with inline comments. + if setting_line.is_empty() { + for line in &line_group { + new_config.push_str(&line); + new_config.push('\n'); + } + + continue; + } + + // Preserve inline comments for settings we've merged + let to_remove = if let Some((name, value)) = existing_nix_config + .settings() + .iter() + .find(|(name, _value)| setting_line.starts_with(*name)) + { + let inline_comment_idx = + if let Some(idx) = setting_line.find(NIX_CONF_COMMENT_CHAR) { + idx + } else { + continue; + }; + + let inline_comment = &setting_line[inline_comment_idx..]; + + new_config.push_str(&comments); + new_config.push('\n'); + new_config.push_str(name); + new_config.push_str(" = "); + + if let Some(merged_value) = merged_nix_config.settings_mut().remove(name) { + new_config.push_str(&merged_value); + new_config.push(' '); + } else { + new_config.push_str(value); + } + + new_config.push_str(inline_comment); + new_config.push('\n'); + + Some(name.clone()) + } else { + new_config.push_str(&comments); + new_config.push('\n'); + new_config.push_str(setting_line); + new_config.push('\n'); + + None + }; + + if let Some(to_remove) = to_remove { + existing_nix_config.settings_mut().remove(&to_remove); + } + } + + // Add the leftover existing nix config + for (name, value) in existing_nix_config.settings() { + if merged_nix_config.settings().get(name).is_some() { + continue; + } + + new_config.push_str(name); + new_config.push_str(" = "); + new_config.push_str(value); + new_config.push('\n'); + } + + new_config.push('\n'); + } + + new_config.push_str(&format!( + "# Generated by https://github.com/DeterminateSystems/nix-installer, version {version}.\n", + version = env!("CARGO_PKG_VERSION"), + )); + + for (name, value) in merged_nix_config.settings() { + new_config.push_str(name); + new_config.push_str(" = "); + new_config.push_str(value); + new_config.push('\n'); + } + + temp_file + .write_all(new_config.as_bytes()) + .await + .map_err(|e| ActionError::Write(temp_file_path.clone(), e))?; + tokio::fs::set_permissions(&temp_file_path, PermissionsExt::from_mode(NIX_CONF_MODE)) + .await + .map_err(|e| ActionError::SetPermissions(NIX_CONF_MODE, path.to_owned(), e))?; + tokio::fs::rename(&temp_file_path, &path) + .await + .map_err(|e| ActionError::Rename(temp_file_path.to_owned(), path.to_owned(), e))?; + + Ok(()) + } + + fn revert_description(&self) -> Vec { + let Self { + path, + pending_nix_config: _, + } = &self; + + vec![ActionDescription::new( + format!("Delete file `{}`", path.display()), + vec![format!("Delete file `{}`", path.display())], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn revert(&mut self) -> Result<(), ActionError> { + let Self { + path, + pending_nix_config: _, + } = self; + + remove_file(&path) + .await + .map_err(|e| ActionError::Remove(path.to_owned(), e))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use eyre::eyre; + use tokio::fs::write; + + #[tokio::test] + async fn creates_and_deletes_file() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir.path().join("creates_and_deletes_file"); + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "ca-references".into()); + let mut action = CreateOrMergeNixConfig::plan(&test_file, nix_config).await?; + + action.try_execute().await?; + + let s = std::fs::read_to_string(&test_file)?; + assert!(s.contains("# Generated by")); + assert!(s.contains("ca-references")); + assert!(NixConfig::parse_file(&test_file).is_ok()); + + action.try_revert().await?; + + assert!(!test_file.exists(), "File should have been deleted"); + + Ok(()) + } + + #[tokio::test] + async fn creates_and_deletes_file_even_if_edited() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir + .path() + .join("creates_and_deletes_file_even_if_edited"); + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "ca-references".into()); + let mut action = CreateOrMergeNixConfig::plan(&test_file, nix_config).await?; + + action.try_execute().await?; + + write(test_file.as_path(), "More content").await?; + + action.try_revert().await?; + + assert!(!test_file.exists(), "File should have been deleted"); + + Ok(()) + } + + #[tokio::test] + async fn recognizes_existing_exact_files_and_reverts_them() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir + .path() + .join("recognizes_existing_exact_files_and_reverts_them"); + + let test_content = "experimental-features = flakes"; + write(test_file.as_path(), test_content).await?; + tokio::fs::set_permissions(&test_file, PermissionsExt::from_mode(NIX_CONF_MODE)).await?; + + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "flakes".into()); + let mut action = CreateOrMergeNixConfig::plan(&test_file, nix_config).await?; + + action.try_execute().await?; + + action.try_revert().await?; + + assert!(!test_file.exists(), "File should have been deleted"); + + Ok(()) + } + + #[tokio::test] + async fn recognizes_existing_different_files_and_merges() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir + .path() + .join("recognizes_existing_different_files_and_merges"); + + write( + test_file.as_path(), + "experimental-features = flakes\nwarn-dirty = true\n", + ) + .await?; + tokio::fs::set_permissions(&test_file, PermissionsExt::from_mode(NIX_CONF_MODE)).await?; + + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "nix-command flakes".into()); + nix_config + .settings_mut() + .insert("allow-dirty".into(), "false".into()); + let mut action = CreateOrMergeNixConfig::plan(&test_file, nix_config).await?; + + action.try_execute().await?; + + let s = std::fs::read_to_string(&test_file)?; + assert!(s.contains("# Generated by")); + assert!(s.contains("flakes")); + assert!(s.contains("nix-command")); + assert_eq!( + s.matches("flakes").count(), + 1, + "we should not duplicate strings" + ); + assert!(s.contains("allow-dirty = false")); + assert!(s.contains("warn-dirty = true")); + assert!(NixConfig::parse_file(&test_file).is_ok()); + + action.try_revert().await?; + + assert!(!test_file.exists(), "File should have been deleted"); + + Ok(()) + } + + #[tokio::test] + async fn recognizes_existing_different_files_and_fails_to_merge() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir + .path() + .join("recognizes_existing_different_files_and_fails_to_merge"); + + write( + test_file.as_path(), + "experimental-features = flakes\nwarn-dirty = true\n", + ) + .await?; + + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "nix-command flakes".into()); + nix_config + .settings_mut() + .insert("warn-dirty".into(), "false".into()); + match CreateOrMergeNixConfig::plan(&test_file, nix_config).await { + Err(ActionError::Custom(e)) => match e.downcast_ref::() { + Some(CreateOrMergeNixConfigError::UnmergeableConfig(_, path)) => { + assert_eq!(path, test_file.as_path()) + }, + _ => { + return Err(eyre!( + "Should have returned CreateOrMergeNixConfigError::UnmergeableConfig" + )) + }, + }, + _ => { + return Err(eyre!( + "Should have returned CreateOrMergeNixConfigError::UnmergeableConfig" + )) + }, + } + + assert!(test_file.exists(), "File should not have been deleted"); + + Ok(()) + } + + #[tokio::test] + async fn preserves_comments() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir.path().join("preserves_comments"); + + write( + test_file.as_path(), + "# test 2\n# test\nexperimental-features = flakes # some inline comment about experimental-features\n# the following line should be warn-dirty = true\nwarn-dirty = true # this is an inline comment\n# this is an ungrouped comment\n# this too", + ) + .await?; + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "ca-references".into()); + let mut action = CreateOrMergeNixConfig::plan(&test_file, nix_config).await?; + + action.try_execute().await?; + + let s = std::fs::read_to_string(&test_file)?; + assert!(s.contains("# the following line should be warn-dirty = true\nwarn-dirty = true")); + assert!(s.contains("# test 2\n# test\nexperimental-features")); + assert!(s.contains("# this is an inline comment")); + assert!(s.contains("# some inline comment about experimental-features")); + assert!(s.contains("# Generated by")); + assert!(s.contains("# this is an ungrouped comment\n# this too")); + assert!(s.contains("ca-references")); + assert!(NixConfig::parse_file(&test_file).is_ok()); + + action.try_revert().await?; + + assert!(!test_file.exists(), "File should have been deleted"); + + Ok(()) + } + + #[tokio::test] + async fn preserves_comments_edge_case() -> eyre::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let test_file = temp_dir.path().join("preserves_comments"); + + write(test_file.as_path(), " a = b\n c = d# lol\n# e = f").await?; + let mut nix_config = NixConfig::new(); + nix_config + .settings_mut() + .insert("experimental-features".into(), "ca-references".into()); + let mut action = CreateOrMergeNixConfig::plan(&test_file, nix_config).await?; + + action.try_execute().await?; + + let s = std::fs::read_to_string(&test_file)?; + assert!(s.contains("# Generated by")); + assert!(s.contains("ca-references")); + assert_eq!(s.matches("a = b").count(), 1); + assert!(NixConfig::parse_file(&test_file).is_ok()); + + action.try_revert().await?; + + assert!(!test_file.exists(), "File should have been deleted"); + + Ok(()) + } +} diff --git a/src/action/base/mod.rs b/src/action/base/mod.rs index 0c54c1a..1344dc5 100644 --- a/src/action/base/mod.rs +++ b/src/action/base/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod create_directory; pub(crate) mod create_file; pub(crate) mod create_group; pub(crate) mod create_or_insert_into_file; +pub(crate) mod create_or_merge_nix_config; pub(crate) mod create_user; pub(crate) mod fetch_and_unpack_nix; pub(crate) mod move_unpacked_nix; @@ -15,6 +16,7 @@ pub use create_directory::CreateDirectory; pub use create_file::CreateFile; pub use create_group::CreateGroup; pub use create_or_insert_into_file::CreateOrInsertIntoFile; +pub use create_or_merge_nix_config::CreateOrMergeNixConfig; pub use create_user::CreateUser; pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError}; pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError}; diff --git a/src/action/common/place_nix_configuration.rs b/src/action/common/place_nix_configuration.rs index e7bc637..2fe447c 100644 --- a/src/action/common/place_nix_configuration.rs +++ b/src/action/common/place_nix_configuration.rs @@ -1,6 +1,7 @@ use tracing::{span, Span}; -use crate::action::base::{CreateDirectory, CreateFile}; +use crate::action::base::create_or_merge_nix_config::CreateOrMergeNixConfigError; +use crate::action::base::{CreateDirectory, CreateOrMergeNixConfig}; use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction}; const NIX_CONF_FOLDER: &str = "/etc/nix"; @@ -12,7 +13,7 @@ Place the `/etc/nix.conf` file #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct PlaceNixConfiguration { create_directory: StatefulAction, - create_file: StatefulAction, + create_or_merge_nix_config: StatefulAction, } impl PlaceNixConfiguration { @@ -22,34 +23,38 @@ impl PlaceNixConfiguration { extra_conf: Vec, force: bool, ) -> Result, ActionError> { - let buf = format!( - "\ - # Generated by https://github.com/DeterminateSystems/nix-installer, version {version}.\n\ - \n\ - {extra_conf}\n\ - \n\ - build-users-group = {nix_build_group_name}\n\ - \n\ - experimental-features = nix-command flakes\n\ - \n\ - auto-optimise-store = true\n\ - \n\ - bash-prompt-prefix = (nix:$name)\\040\n\ - \n\ - extra-nix-path = nixpkgs=flake:nixpkgs\n\ - ", - extra_conf = extra_conf.join("\n"), - version = env!("CARGO_PKG_VERSION"), + let extra_conf = extra_conf.join("\n"); + let mut nix_config = nix_config_parser::NixConfig::parse_string(extra_conf, None) + .map_err(CreateOrMergeNixConfigError::ParseNixConfig) + .map_err(|e| ActionError::Custom(Box::new(e)))?; + let settings = nix_config.settings_mut(); + + settings.insert("build-users-group".to_string(), nix_build_group_name); + settings.insert( + "experimental-features".to_string(), + "nix-command flakes".to_string(), ); + settings.insert("auto-optimise-store".to_string(), "true".to_string()); + settings.insert( + "bash-prompt-prefix".to_string(), + "(nix:$name)\\040".to_string(), + ); + settings.insert( + "extra-nix-path".to_string(), + "nixpkgs=flake:nixpkgs".to_string(), + ); + let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force) .await .map_err(|e| ActionError::Child(CreateDirectory::action_tag(), Box::new(e)))?; - let create_file = CreateFile::plan(NIX_CONF, None, None, 0o0664, buf, force) + let create_or_merge_nix_config = CreateOrMergeNixConfig::plan(NIX_CONF, nix_config) .await - .map_err(|e| ActionError::Child(CreateFile::action_tag(), Box::new(e)))?; + .map_err(|e| { + ActionError::Child(CreateOrMergeNixConfig::action_tag(), Box::new(e)) + })?; Ok(Self { create_directory, - create_file, + create_or_merge_nix_config, } .into()) } @@ -70,13 +75,24 @@ impl Action for PlaceNixConfiguration { } fn execute_description(&self) -> Vec { - vec![ActionDescription::new( - self.tracing_synopsis(), - vec![ - "This file is read by the Nix daemon to set its configuration options at runtime." - .to_string(), - ], - )] + let Self { + create_or_merge_nix_config, + create_directory, + } = self; + + let mut explanation = vec![ + "This file is read by the Nix daemon to set its configuration options at runtime." + .to_string(), + ]; + + if let Some(val) = create_directory.describe_execute().iter().next() { + explanation.push(val.description.clone()) + } + for val in create_or_merge_nix_config.describe_execute().iter() { + explanation.push(val.description.clone()) + } + + vec![ActionDescription::new(self.tracing_synopsis(), explanation)] } #[tracing::instrument(level = "debug", skip_all)] @@ -85,10 +101,12 @@ impl Action for PlaceNixConfiguration { .try_execute() .await .map_err(|e| ActionError::Child(self.create_directory.action_tag(), Box::new(e)))?; - self.create_file + self.create_or_merge_nix_config .try_execute() .await - .map_err(|e| ActionError::Child(self.create_file.action_tag(), Box::new(e)))?; + .map_err(|e| { + ActionError::Child(self.create_or_merge_nix_config.action_tag(), Box::new(e)) + })?; Ok(()) } @@ -105,10 +123,12 @@ impl Action for PlaceNixConfiguration { #[tracing::instrument(level = "debug", skip_all)] async fn revert(&mut self) -> Result<(), ActionError> { - self.create_file + self.create_or_merge_nix_config .try_revert() .await - .map_err(|e| ActionError::Child(self.create_file.action_tag(), Box::new(e)))?; + .map_err(|e| { + ActionError::Child(self.create_or_merge_nix_config.action_tag(), Box::new(e)) + })?; self.create_directory .try_revert() .await diff --git a/tests/fixtures/linux/linux.json b/tests/fixtures/linux/linux.json index bd69213..f8b864f 100644 --- a/tests/fixtures/linux/linux.json +++ b/tests/fixtures/linux/linux.json @@ -17,7 +17,7 @@ "action": "provision_nix", "fetch_nix": { "action": { - "url": "https://releases.nixos.org/nix/nix-2.12.0/nix-2.12.0-x86_64-linux.tar.xz", + "url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-x86_64-linux.tar.xz", "dest": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -788,7 +788,7 @@ "path": "/etc/bashrc", "user": null, "group": null, - "mode": 493, + "mode": 33188, "buf": "\n# Nix\nif [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then\n . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'\nfi\n# End Nix\n\n \n", "position": "Beginning" }, @@ -799,7 +799,7 @@ "path": "/etc/profile.d/nix.sh", "user": null, "group": null, - "mode": 493, + "mode": 33188, "buf": "\n# Nix\nif [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then\n . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'\nfi\n# End Nix\n\n \n", "position": "Beginning" }, @@ -810,7 +810,7 @@ "path": "/etc/zshenv", "user": null, "group": null, - "mode": 493, + "mode": 33188, "buf": "\n# Nix\nif [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then\n . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'\nfi\n# End Nix\n\n \n", "position": "Beginning" }, @@ -821,7 +821,7 @@ "path": "/etc/bash.bashrc", "user": null, "group": null, - "mode": 493, + "mode": 33188, "buf": "\n# Nix\nif [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then\n . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'\nfi\n# End Nix\n\n \n", "position": "Beginning" }, @@ -841,7 +841,7 @@ ], "create_file": { "action": { - "path": "/home/ana/.nix-channels", + "path": "/root/.nix-channels", "user": null, "group": null, "mode": 436, @@ -865,14 +865,17 @@ }, "state": "Uncompleted" }, - "create_file": { + "create_or_merge_nix_config": { "action": { "path": "/etc/nix/nix.conf", - "user": null, - "group": null, - "mode": 436, - "buf": "# Generated by https://github.com/DeterminateSystems/nix-installer, version 0.1.0-unreleased.\n\n\n\nbuild-users-group = nixbld\n\nexperimental-features = nix-command flakes\n\nauto-optimise-store = true\n\nbash-prompt-prefix = (nix:$name)\\040\n", - "force": false + "pending_nix_config": { + "settings": { + "bash-prompt-prefix": "(nix:$name)\\040", + "build-users-group": "nixbld", + "auto-optimise-store": "true", + "experimental-features": "nix-command flakes" + } + } }, "state": "Uncompleted" } @@ -906,7 +909,7 @@ "nix_build_group_id": 3000, "nix_build_user_prefix": "nixbld", "nix_build_user_id_base": 3000, - "nix_package_url": "https://releases.nixos.org/nix/nix-2.12.0/nix-2.12.0-x86_64-linux.tar.xz", + "nix_package_url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-x86_64-linux.tar.xz", "extra_conf": [], "force": false }, diff --git a/tests/fixtures/linux/steam-deck.json b/tests/fixtures/linux/steam-deck.json index 26cc16e..ddf5219 100644 --- a/tests/fixtures/linux/steam-deck.json +++ b/tests/fixtures/linux/steam-deck.json @@ -4,7 +4,7 @@ { "action": { "action": "create_directory", - "path": "/nix", + "path": "/home/nix", "user": null, "group": null, "mode": 493, @@ -12,6 +12,50 @@ }, "state": "Uncompleted" }, + { + "action": { + "action": "create_file", + "path": "/etc/systemd/system/nix-directory.service", + "user": null, + "group": null, + "mode": 420, + "buf": "[Unit]\nDescription=Create a `/nix` directory to be used for bind mounting\nPropagatesStopTo=nix-daemon.service\nPropagatesStopTo=nix.mount\nDefaultDependencies=no\nAfter=grub-recordfail.service\nAfter=steamos-finish-oobe-migration.service\n\n[Service]\nType=oneshot\nExecStart=steamos-readonly disable\nExecStart=mkdir -vp /nix\nExecStart=chmod -v 0755 /nix\nExecStart=chown -v root /nix\nExecStart=chgrp -v root /nix\nExecStart=steamos-readonly enable\nExecStop=steamos-readonly disable\nExecStop=rmdir /nix\nExecStop=steamos-readonly enable\nRemainAfterExit=true\n", + "force": false + }, + "state": "Uncompleted" + }, + { + "action": { + "action": "create_file", + "path": "/etc/systemd/system/nix.mount", + "user": null, + "group": null, + "mode": 420, + "buf": "[Unit]\nDescription=Mount `/home/nix` on `/nix`\nPropagatesStopTo=nix-daemon.service\nPropagatesStopTo=nix-directory.service\nAfter=nix-directory.service\nRequires=nix-directory.service\nConditionPathIsDirectory=/nix\nDefaultDependencies=no\nRequiredBy=nix-daemon.service\nRequiredBy=nix-daemon.socket\n\n[Mount]\nWhat=/home/nix\nWhere=/nix\nType=none\nDirectoryMode=0755\nOptions=bind\n", + "force": false + }, + "state": "Uncompleted" + }, + { + "action": { + "action": "create_file", + "path": "/etc/systemd/system/ensure-symlinked-units-resolve.service", + "user": null, + "group": null, + "mode": 420, + "buf": "[Unit]\nDescription=Ensure Nix related units which are symlinked resolve\nAfter=nix.mount\nRequires=nix-directory.service\nRequires=nix.mount\nDefaultDependencies=no\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/usr/bin/systemctl daemon-reload\nExecStart=/usr/bin/systemctl restart --no-block nix-daemon.socket\n\n[Install]\nWantedBy=sysinit.target\n", + "force": false + }, + "state": "Uncompleted" + }, + { + "action": { + "action": "start_systemd_unit", + "unit": "nix.mount", + "enable": false + }, + "state": "Uncompleted" + }, { "action": { "action": "provision_nix", @@ -26,13 +70,13 @@ "action": { "nix_build_user_count": 32, "nix_build_group_name": "nixbld", - "nix_build_group_id": 3000, + "nix_build_group_id": 30000, "nix_build_user_prefix": "nixbld", - "nix_build_user_id_base": 3000, + "nix_build_user_id_base": 30000, "create_group": { "action": { "name": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, @@ -40,288 +84,288 @@ { "action": { "name": "nixbld0", - "uid": 3000, + "uid": 30000, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld1", - "uid": 3001, + "uid": 30001, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld2", - "uid": 3002, + "uid": 30002, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld3", - "uid": 3003, + "uid": 30003, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld4", - "uid": 3004, + "uid": 30004, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld5", - "uid": 3005, + "uid": 30005, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld6", - "uid": 3006, + "uid": 30006, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld7", - "uid": 3007, + "uid": 30007, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld8", - "uid": 3008, + "uid": 30008, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld9", - "uid": 3009, + "uid": 30009, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld10", - "uid": 3010, + "uid": 30010, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld11", - "uid": 3011, + "uid": 30011, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld12", - "uid": 3012, + "uid": 30012, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld13", - "uid": 3013, + "uid": 30013, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld14", - "uid": 3014, + "uid": 30014, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld15", - "uid": 3015, + "uid": 30015, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld16", - "uid": 3016, + "uid": 30016, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld17", - "uid": 3017, + "uid": 30017, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld18", - "uid": 3018, + "uid": 30018, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld19", - "uid": 3019, + "uid": 30019, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld20", - "uid": 3020, + "uid": 30020, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld21", - "uid": 3021, + "uid": 30021, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld22", - "uid": 3022, + "uid": 30022, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld23", - "uid": 3023, + "uid": 30023, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld24", - "uid": 3024, + "uid": 30024, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld25", - "uid": 3025, + "uid": 30025, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld26", - "uid": 3026, + "uid": 30026, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld27", - "uid": 3027, + "uid": 30027, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld28", - "uid": 3028, + "uid": 30028, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld29", - "uid": 3029, + "uid": 30029, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld30", - "uid": 3030, + "uid": 30030, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld31", - "uid": 3031, + "uid": 30031, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" } @@ -330,288 +374,288 @@ { "action": { "name": "nixbld0", - "uid": 3000, + "uid": 30000, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld1", - "uid": 3001, + "uid": 30001, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld2", - "uid": 3002, + "uid": 30002, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld3", - "uid": 3003, + "uid": 30003, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld4", - "uid": 3004, + "uid": 30004, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld5", - "uid": 3005, + "uid": 30005, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld6", - "uid": 3006, + "uid": 30006, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld7", - "uid": 3007, + "uid": 30007, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld8", - "uid": 3008, + "uid": 30008, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld9", - "uid": 3009, + "uid": 30009, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld10", - "uid": 3010, + "uid": 30010, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld11", - "uid": 3011, + "uid": 30011, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld12", - "uid": 3012, + "uid": 30012, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld13", - "uid": 3013, + "uid": 30013, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld14", - "uid": 3014, + "uid": 30014, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld15", - "uid": 3015, + "uid": 30015, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld16", - "uid": 3016, + "uid": 30016, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld17", - "uid": 3017, + "uid": 30017, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld18", - "uid": 3018, + "uid": 30018, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld19", - "uid": 3019, + "uid": 30019, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld20", - "uid": 3020, + "uid": 30020, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld21", - "uid": 3021, + "uid": 30021, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld22", - "uid": 3022, + "uid": 30022, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld23", - "uid": 3023, + "uid": 30023, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld24", - "uid": 3024, + "uid": 30024, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld25", - "uid": 3025, + "uid": 30025, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld26", - "uid": 3026, + "uid": 30026, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld27", - "uid": 3027, + "uid": 30027, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld28", - "uid": 3028, + "uid": 30028, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld29", - "uid": 3029, + "uid": 30029, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld30", - "uid": 3030, + "uid": 30030, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" }, { "action": { "name": "nixbld31", - "uid": 3031, + "uid": 30031, "groupname": "nixbld", - "gid": 3000 + "gid": 30000 }, "state": "Uncompleted" } @@ -781,18 +825,7 @@ }, "configure_shell_profile": { "action": { - "create_directories": [ - { - "action": { - "path": "/etc/fish/conf.d", - "user": null, - "group": null, - "mode": 420, - "force_prune_on_revert": false - }, - "state": "Skipped" - } - ], + "create_directories": [], "create_or_insert_into_files": [ { "action": { @@ -837,17 +870,6 @@ "position": "Beginning" }, "state": "Uncompleted" - }, - { - "action": { - "path": "/etc/fish/conf.d/nix.fish", - "user": null, - "group": null, - "mode": 33188, - "buf": "\n# Nix\nif test -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish'\n . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish'\nend\n# End Nix\n\n", - "position": "Beginning" - }, - "state": "Uncompleted" } ] }, @@ -863,7 +885,7 @@ ], "create_file": { "action": { - "path": "/home/deck/.nix-channels", + "path": "/home/ubuntu/.nix-channels", "user": null, "group": null, "mode": 436, @@ -887,14 +909,17 @@ }, "state": "Uncompleted" }, - "create_file": { + "create_or_merge_nix_config": { "action": { "path": "/etc/nix/nix.conf", - "user": null, - "group": null, - "mode": 436, - "buf": "# Generated by https://github.com/DeterminateSystems/nix-installer, version 0.3.0.\n\n\n\nbuild-users-group = nixbld\n\nexperimental-features = nix-command flakes\n\nauto-optimise-store = true\n\nbash-prompt-prefix = (nix:$name)\\040\n", - "force": false + "pending_nix_config": { + "settings": { + "bash-prompt-prefix": "(nix:$name)\\040", + "build-users-group": "nixbld", + "auto-optimise-store": "true", + "experimental-features": "nix-command flakes" + } + } }, "state": "Uncompleted" } @@ -911,10 +936,19 @@ "start_daemon": true }, "state": "Uncompleted" + }, + { + "action": { + "action": "start_systemd_unit", + "unit": "ensure-symlinked-units-resolve.service", + "enable": true + }, + "state": "Uncompleted" } ], "planner": { - "planner": "linux", + "planner": "steam-deck", + "persistence": "/home/nix", "settings": { "channels": [ [ @@ -925,16 +959,12 @@ "modify_profile": true, "nix_build_user_count": 32, "nix_build_group_name": "nixbld", - "nix_build_group_id": 3000, + "nix_build_group_id": 30000, "nix_build_user_prefix": "nixbld", - "nix_build_user_id_base": 3000, + "nix_build_user_id_base": 30000, "nix_package_url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-x86_64-linux.tar.xz", "extra_conf": [], "force": false - }, - "init": { - "init": "Systemd", - "start_daemon": true } } } diff --git a/tests/fixtures/macos/macos.json b/tests/fixtures/macos/macos.json index 3b943b1..7a3f2c1 100644 --- a/tests/fixtures/macos/macos.json +++ b/tests/fixtures/macos/macos.json @@ -4,7 +4,7 @@ { "action": { "action": "create_apfs_volume", - "disk": "disk3", + "disk": "disk1", "name": "Nix Store", "case_sensitive": false, "encrypt": true, @@ -25,14 +25,14 @@ }, "unmount_volume": { "action": { - "disk": "disk3", + "disk": "disk1", "name": "Nix Store" }, "state": "Uncompleted" }, "create_volume": { "action": { - "disk": "disk3", + "disk": "disk1", "name": "Nix Store", "case_sensitive": false }, @@ -46,7 +46,7 @@ }, "encrypt_volume": { "action": { - "disk": "disk3", + "disk": "disk1", "name": "Nix Store" }, "state": "Uncompleted" @@ -82,7 +82,7 @@ "action": "provision_nix", "fetch_nix": { "action": { - "url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-aarch64-darwin.tar.xz", + "url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-x86_64-darwin.tar.xz", "dest": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -919,14 +919,17 @@ }, "state": "Uncompleted" }, - "create_file": { + "create_or_merge_nix_config": { "action": { "path": "/etc/nix/nix.conf", - "user": null, - "group": null, - "mode": 436, - "buf": "# Generated by https://github.com/DeterminateSystems/nix-installer, version 0.3.0.\n\n\n\nbuild-users-group = nixbld\n\nexperimental-features = nix-command flakes\n\nauto-optimise-store = true\n\nbash-prompt-prefix = (nix:$name)\\040\n", - "force": false + "pending_nix_config": { + "settings": { + "bash-prompt-prefix": "(nix:$name)\\040", + "auto-optimise-store": "true", + "build-users-group": "nixbld", + "experimental-features": "nix-command flakes" + } + } }, "state": "Uncompleted" } @@ -960,13 +963,13 @@ "nix_build_group_id": 3000, "nix_build_user_prefix": "_nixbld", "nix_build_user_id_base": 300, - "nix_package_url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-aarch64-darwin.tar.xz", + "nix_package_url": "https://releases.nixos.org/nix/nix-2.13.2/nix-2.13.2-x86_64-darwin.tar.xz", "extra_conf": [], "force": false }, "encrypt": null, "case_sensitive": false, "volume_label": "Nix Store", - "root_disk": "disk3" + "root_disk": "disk1" } }