diff --git a/src/action/base/setup_default_profile.rs b/src/action/base/setup_default_profile.rs index 6a67cd3d1..f8ba75f4c 100644 --- a/src/action/base/setup_default_profile.rs +++ b/src/action/base/setup_default_profile.rs @@ -1,12 +1,10 @@ use std::path::PathBuf; use crate::{ - action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}, + action::{common::ConfigureNix, ActionError, ActionErrorKind, ActionTag, StatefulAction}, execute_command, set_env, }; -use glob::glob; - use tokio::{io::AsyncWriteExt, process::Command}; use tracing::{span, Span}; @@ -51,63 +49,8 @@ impl Action for SetupDefaultProfile { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - // Find an `nix` package - let nix_pkg_glob = format!("{}/nix-*/store/*-nix-*.*.*", self.unpacked_path.display()); - let mut found_nix_pkg = None; - for entry in glob(&nix_pkg_glob).map_err(Self::error)? { - match entry { - Ok(path) => { - // If we are curing, the user may have multiple of these installed - if let Some(_existing) = found_nix_pkg { - return Err(Self::error(SetupDefaultProfileError::MultipleNixPackages))?; - } else { - found_nix_pkg = Some(path); - } - break; - }, - Err(_) => continue, /* Ignore it */ - }; - } - let nix_pkg = if let Some(nix_pkg) = found_nix_pkg { - tokio::fs::read_link(&nix_pkg) - .await - .map_err(|e| ActionErrorKind::ReadSymlink(nix_pkg, e)) - .map_err(Self::error)? - } else { - return Err(Self::error(SetupDefaultProfileError::NoNix)); - }; - - // Find an `nss-cacert` package, add it too. - let nss_ca_cert_pkg_glob = format!( - "{}/nix-*/store/*-nss-cacert-*.*", - self.unpacked_path.display() - ); - let mut found_nss_ca_cert_pkg = None; - for entry in glob(&nss_ca_cert_pkg_glob).map_err(Self::error)? { - match entry { - Ok(path) => { - // If we are curing, the user may have multiple of these installed - if let Some(_existing) = found_nss_ca_cert_pkg { - return Err(Self::error( - SetupDefaultProfileError::MultipleNssCaCertPackages, - ))?; - } else { - found_nss_ca_cert_pkg = Some(path); - } - break; - }, - Err(_) => continue, /* Ignore it */ - }; - } - let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg { - tokio::fs::read_link(&nss_ca_cert_pkg) - .await - .map_err(|e| ActionErrorKind::ReadSymlink(nss_ca_cert_pkg, e)) - .map_err(Self::error)? - } else { - return Err(Self::error(SetupDefaultProfileError::NoNssCacert)); - }; - + let (nix_pkg, nss_ca_cert_pkg) = + ConfigureNix::find_nix_and_ca_cert(&self.unpacked_path).await?; let found_nix_paths = glob::glob(&format!("{}/nix-*", self.unpacked_path.display())) .map_err(Self::error)? .collect::, _>>() @@ -236,16 +179,8 @@ impl Action for SetupDefaultProfile { #[non_exhaustive] #[derive(Debug, thiserror::Error)] pub enum SetupDefaultProfileError { - #[error("Unarchived Nix store did not appear to include a `nss-cacert` location")] - NoNssCacert, - #[error("Unarchived Nix store did not appear to include a `nix` location")] - NoNix, #[error("No root home found to place channel configuration in")] NoRootHome, - #[error("Unarchived Nix store appears to contain multiple `nss-ca-cert` packages, cannot select one")] - MultipleNssCaCertPackages, - #[error("Unarchived Nix store appears to contain multiple `nix` packages, cannot select one")] - MultipleNixPackages, } impl From for ActionErrorKind { diff --git a/src/action/common/configure_nix.rs b/src/action/common/configure_nix.rs index 9088c22e3..8f56eadb6 100644 --- a/src/action/common/configure_nix.rs +++ b/src/action/common/configure_nix.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::{ action::{ @@ -9,6 +9,9 @@ use crate::{ planner::ShellProfileLocations, settings::{CommonSettings, SCRATCH_DIR}, }; +use glob::glob; + +use crate::action::common::SetupChannels; use tracing::{span, Instrument, Span}; @@ -20,6 +23,7 @@ pub struct ConfigureNix { setup_default_profile: StatefulAction, configure_shell_profile: Option>, place_nix_configuration: StatefulAction, + setup_channels: Option>, } impl ConfigureNix { @@ -51,13 +55,83 @@ impl ConfigureNix { .await .map_err(Self::error)?; + let setup_channels = if settings.no_channel_add { + None + } else { + Some( + SetupChannels::plan(PathBuf::from(SCRATCH_DIR)) + .await + .map_err(Self::error)?, + ) + }; + Ok(Self { place_nix_configuration, setup_default_profile, configure_shell_profile, + setup_channels, } .into()) } + + pub async fn find_nix_and_ca_cert( + unpacked_path: &Path, + ) -> Result<(PathBuf, PathBuf), ActionError> { + // Find a `nix` package + let nix_pkg_glob = format!("{}/nix-*/store/*-nix-*.*.*", unpacked_path.display()); + let mut found_nix_pkg = None; + for entry in glob(&nix_pkg_glob).map_err(Self::error)? { + match entry { + Ok(path) => { + // If we are curing, the user may have multiple of these installed + if let Some(_existing) = found_nix_pkg { + return Err(Self::error(ConfigureNixError::MultipleNixPackages))?; + } else { + found_nix_pkg = Some(path); + } + break; + }, + Err(_) => continue, /* Ignore it */ + }; + } + let nix_pkg = if let Some(nix_pkg) = found_nix_pkg { + tokio::fs::read_link(&nix_pkg) + .await + .map_err(|e| ActionErrorKind::ReadSymlink(nix_pkg, e)) + .map_err(Self::error)? + } else { + return Err(Self::error(ConfigureNixError::NoNix)); + }; + + // Find an `nss-cacert` package + let nss_ca_cert_pkg_glob = + format!("{}/nix-*/store/*-nss-cacert-*.*", unpacked_path.display()); + let mut found_nss_ca_cert_pkg = None; + for entry in glob(&nss_ca_cert_pkg_glob).map_err(Self::error)? { + match entry { + Ok(path) => { + // If we are curing, the user may have multiple of these installed + if let Some(_existing) = found_nss_ca_cert_pkg { + return Err(Self::error(ConfigureNixError::MultipleNssCaCertPackages))?; + } else { + found_nss_ca_cert_pkg = Some(path); + } + break; + }, + Err(_) => continue, /* Ignore it */ + }; + } + let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg { + tokio::fs::read_link(&nss_ca_cert_pkg) + .await + .map_err(|e| ActionErrorKind::ReadSymlink(nss_ca_cert_pkg, e)) + .map_err(Self::error)? + } else { + return Err(Self::error(ConfigureNixError::NoNssCacert)); + }; + + Ok((nix_pkg, nss_ca_cert_pkg)) + } } #[async_trait::async_trait] @@ -79,10 +153,14 @@ impl Action for ConfigureNix { setup_default_profile, place_nix_configuration, configure_shell_profile, + setup_channels, } = &self; let mut buf = setup_default_profile.describe_execute(); buf.append(&mut place_nix_configuration.describe_execute()); + if let Some(setup_channels) = setup_channels { + buf.append(&mut setup_channels.describe_execute()); + } if let Some(configure_shell_profile) = configure_shell_profile { buf.append(&mut configure_shell_profile.describe_execute()); } @@ -95,57 +173,51 @@ impl Action for ConfigureNix { setup_default_profile, place_nix_configuration, configure_shell_profile, + setup_channels, } = self; - if let Some(configure_shell_profile) = configure_shell_profile { - let setup_default_profile_span = tracing::Span::current().clone(); - let (place_nix_configuration_span, configure_shell_profile_span) = ( - setup_default_profile_span.clone(), - setup_default_profile_span.clone(), - ); - tokio::try_join!( - async move { - setup_default_profile - .try_execute() - .instrument(setup_default_profile_span) - .await - .map_err(Self::error) - }, - async move { - place_nix_configuration - .try_execute() - .instrument(place_nix_configuration_span) - .await - .map_err(Self::error) - }, - async move { - configure_shell_profile + let setup_default_profile_span = tracing::Span::current().clone(); + let (place_nix_configuration_span, configure_shell_profile_span, setup_channels_span) = ( + setup_default_profile_span.clone(), + setup_default_profile_span.clone(), + setup_default_profile_span.clone(), + ); + tokio::try_join!( + async move { + setup_default_profile + .try_execute() + .instrument(setup_default_profile_span) + .await + .map_err(Self::error) + }, + async move { + place_nix_configuration + .try_execute() + .instrument(place_nix_configuration_span) + .await + .map_err(Self::error) + }, + async move { + match configure_shell_profile { + Some(configure_shell_profile) => configure_shell_profile .try_execute() .instrument(configure_shell_profile_span) .await - .map_err(Self::error) - }, - )?; - } else { - let setup_default_profile_span = tracing::Span::current().clone(); - let place_nix_configuration_span = setup_default_profile_span.clone(); - tokio::try_join!( - async move { - setup_default_profile - .try_execute() - .instrument(setup_default_profile_span) - .await - .map_err(Self::error) - }, - async move { - place_nix_configuration + .map_err(Self::error), + None => Ok(()), + } + }, + async move { + match setup_channels { + Some(setup_channels) => setup_channels .try_execute() - .instrument(place_nix_configuration_span) + .instrument(setup_channels_span) .await - .map_err(Self::error) - }, - )?; - }; + .map_err(Self::error), + None => Ok(()), + } + }, + )?; Ok(()) } @@ -155,6 +227,7 @@ impl Action for ConfigureNix { setup_default_profile, place_nix_configuration, configure_shell_profile, + setup_channels, } = &self; let mut buf = Vec::default(); @@ -163,6 +236,9 @@ impl Action for ConfigureNix { } buf.append(&mut place_nix_configuration.describe_revert()); buf.append(&mut setup_default_profile.describe_revert()); + if let Some(setup_channels) = setup_channels { + buf.append(&mut setup_channels.describe_revert()); + } buf } @@ -181,6 +257,11 @@ impl Action for ConfigureNix { if let Err(err) = self.setup_default_profile.try_revert().await { errors.push(err); } + if let Some(setup_channels) = &mut self.setup_channels { + if let Err(err) = setup_channels.try_revert().await { + errors.push(err); + } + } if errors.is_empty() { Ok(()) @@ -194,3 +275,22 @@ impl Action for ConfigureNix { } } } + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum ConfigureNixError { + #[error("Unarchived Nix store did not appear to include a `nss-cacert` location")] + NoNssCacert, + #[error("Unarchived Nix store did not appear to include a `nix` location")] + NoNix, + #[error("Unarchived Nix store appears to contain multiple `nss-ca-cert` packages, cannot select one")] + MultipleNssCaCertPackages, + #[error("Unarchived Nix store appears to contain multiple `nix` packages, cannot select one")] + MultipleNixPackages, +} + +impl From for ActionErrorKind { + fn from(val: ConfigureNixError) -> Self { + ActionErrorKind::Custom(Box::new(val)) + } +} diff --git a/src/action/common/mod.rs b/src/action/common/mod.rs index 9ca619835..1ba5dcbae 100644 --- a/src/action/common/mod.rs +++ b/src/action/common/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod create_users_and_groups; pub(crate) mod delete_users; pub(crate) mod place_nix_configuration; pub(crate) mod provision_nix; +pub(crate) mod setup_channels; pub use configure_init_service::{ConfigureInitService, ConfigureNixDaemonServiceError}; pub use configure_nix::ConfigureNix; @@ -17,3 +18,4 @@ pub use create_users_and_groups::CreateUsersAndGroups; pub use delete_users::DeleteUsersInGroup; pub use place_nix_configuration::PlaceNixConfiguration; pub use provision_nix::ProvisionNix; +pub use setup_channels::SetupChannels; diff --git a/src/action/common/setup_channels.rs b/src/action/common/setup_channels.rs new file mode 100644 index 000000000..c4d496de6 --- /dev/null +++ b/src/action/common/setup_channels.rs @@ -0,0 +1,126 @@ +use std::path::PathBuf; + +use crate::{ + action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}, + execute_command, +}; + +use tokio::process::Command; +use tracing::{span, Span}; + +use crate::action::{Action, ActionDescription}; + +use crate::action::base::CreateFile; + +use super::ConfigureNix; + +/** +Setup the default system channel with nixpkgs-unstable. + */ +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct SetupChannels { + create_file: StatefulAction, + unpacked_path: PathBuf, +} + +impl SetupChannels { + #[tracing::instrument(level = "debug", skip_all)] + pub async fn plan(unpacked_path: PathBuf) -> Result, ActionError> { + let create_file = CreateFile::plan( + dirs::home_dir() + .ok_or_else(|| Self::error(SetupChannelsError::NoRootHome))? + .join(".nix-channels"), + None, + None, + 0o664, + "https://nixos.org/channels/nixpkgs-unstable nixpkgs\n".to_string(), + false, + ) + .await?; + Ok(Self { + create_file, + unpacked_path, + } + .into()) + } +} + +#[async_trait::async_trait] +#[typetag::serde(name = "setup_channels")] +impl Action for SetupChannels { + fn action_tag() -> ActionTag { + ActionTag("setup_channels") + } + fn tracing_synopsis(&self) -> String { + "Setup the default system channel".to_string() + } + + fn tracing_span(&self) -> Span { + span!( + tracing::Level::DEBUG, + "setup_channels", + unpacked_path = %self.unpacked_path.display(), + ) + } + + fn execute_description(&self) -> Vec { + vec![ActionDescription::new(self.tracing_synopsis(), vec![])] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn execute(&mut self) -> Result<(), ActionError> { + // Place channel configuration + self.create_file.try_execute().await?; + + let (nix_pkg, nss_ca_cert_pkg) = + ConfigureNix::find_nix_and_ca_cert(&self.unpacked_path).await?; + // Update nixpkgs channel + execute_command( + Command::new(nix_pkg.join("bin/nix-channel")) + .process_group(0) + .arg("--update") + .arg("nixpkgs") + .stdin(std::process::Stdio::null()) + .env( + "HOME", + dirs::home_dir().ok_or_else(|| Self::error(SetupChannelsError::NoRootHome))?, + ) + .env( + "NIX_SSL_CERT_FILE", + nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"), + ), /* This is apparently load bearing... */ + ) + .await + .map_err(Self::error)?; + + Ok(()) + } + + fn revert_description(&self) -> Vec { + vec![ActionDescription::new( + "Remove system channel configuration".to_string(), + vec![], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn revert(&mut self) -> Result<(), ActionError> { + self.create_file.try_revert().await?; + // TODO: we could try to rollback /nix/var/nix/profiles/per-user/root/channels + + Ok(()) + } +} + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum SetupChannelsError { + #[error("No root home found to place channel configuration in")] + NoRootHome, +} + +impl From for ActionErrorKind { + fn from(val: SetupChannelsError) -> Self { + ActionErrorKind::Custom(Box::new(val)) + } +} diff --git a/src/settings.rs b/src/settings.rs index 31529c61e..791b98eee 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -245,6 +245,18 @@ pub struct CommonSettings { default_value = "https://install.determinate.systems/nix/diagnostic" )] pub diagnostic_endpoint: Option, + + /// Whether to setup system channels + #[cfg_attr( + all(feature = "cli", feature = "nix-community"), + clap( + long, + default_value = "false", + env = "NIX_INSTALLER_NO_CHANNEL_ADD", + global = true + ) + )] + pub no_channel_add: bool, } impl CommonSettings { @@ -317,6 +329,7 @@ impl CommonSettings { diagnostic_attribution: None, #[cfg(feature = "diagnostics")] diagnostic_endpoint: Some("https://install.determinate.systems/nix/diagnostic".into()), + no_channel_add: false, }) } @@ -338,6 +351,7 @@ impl CommonSettings { diagnostic_attribution: _, #[cfg(feature = "diagnostics")] diagnostic_endpoint, + no_channel_add, } = self; let mut map = HashMap::default(); @@ -380,6 +394,11 @@ impl CommonSettings { serde_json::to_value(diagnostic_endpoint)?, ); + map.insert( + "no_channel_add".into(), + serde_json::to_value(no_channel_add)?, + ); + Ok(map) } }