Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub struct NixInstallerCli {
#[async_trait::async_trait]
impl CommandExecute for NixInstallerCli {
#[tracing::instrument(level = "trace", skip_all)]
async fn execute<T>(self, mut feedback: T) -> eyre::Result<ExitCode>
async fn execute<T>(self, feedback: T) -> eyre::Result<ExitCode>
where
T: crate::feedback::Feedback,
{
Expand Down
99 changes: 99 additions & 0 deletions src/cli/subcommand/install/determinate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::io::IsTerminal as _;

use owo_colors::OwoColorize as _;

use crate::cli::interaction::PromptChoice;
use crate::feedback::Feedback;
use crate::planner::BuiltinPlanner;

const PRE_PKG_SUGGEST: &str = "For a more robust Nix installation, use the Determinate package for macOS: https://dtr.mn/determinate-nix";

const DETERMINATE_MSG_EXPLAINER: &str = "\
Determinate Nix is Determinate Systems' validated and secure downstream Nix distribution for enterprises. \
It comes bundled with Determinate Nixd, a helpful daemon that automates some otherwise-unpleasant aspects of using Nix, such as garbage collection, and enables you to easily authenticate with FlakeHub.

For more details: https://dtr.mn/determinate-nix\
";

pub(crate) async fn inform_macos_about_pkg<T: Feedback>(feedback: &T) {
if matches!(
target_lexicon::OperatingSystem::host(),
target_lexicon::OperatingSystem::MacOSX { .. } | target_lexicon::OperatingSystem::Darwin
) {
let msg = feedback
.get_feature_ptr_payload::<String>("dni-det-msg-start-pkg-ptr")
.await
.unwrap_or(PRE_PKG_SUGGEST.into());
tracing::info!("{}", msg.trim());
}
}

pub(crate) async fn prompt_for_determinate<T: Feedback>(
feedback: &mut T,
planner: &mut BuiltinPlanner,
no_confirm: bool,
) -> eyre::Result<Option<String>> {
let planner_settings = planner.common_settings_mut();

if !planner_settings.determinate_nix && std::io::stdin().is_terminal() && !no_confirm {
let base_prompt = feedback
.get_feature_ptr_payload::<String>("dni-det-msg-interactive-prompt-ptr")
.await
.unwrap_or("Install Determinate Nix?".into());

let mut explanation: Option<String> = None;

loop {
let prompt = if let Some(ref explanation) = explanation {
&format!("\n{}\n{}", base_prompt.trim().green(), explanation.trim())
} else {
&format!("\n{}", base_prompt.trim().green())
};

let response = crate::cli::interaction::prompt(
prompt.to_string(),
PromptChoice::Yes,
explanation.is_some(),
)
.await?;

match response {
PromptChoice::Explain => {
explanation = Some(
feedback
.get_feature_ptr_payload::<String>(
"dni-det-msg-interactive-explanation-ptr",
)
.await
.unwrap_or(DETERMINATE_MSG_EXPLAINER.into()),
);
},
PromptChoice::Yes => {
planner_settings.determinate_nix = true;
break;
},
PromptChoice::No => {
break;
},
}
}
}

let post_install_message_feature = match (
planner_settings.determinate_nix,
std::io::stdin().is_terminal() && !no_confirm,
) {
(true, true) => Some("dni-post-det-int-ptr"),
(true, false) => None,
(false, true) => Some("dni-post-ups-int-ptr"),
(false, false) => Some("dni-post-ups-scr-ptr"),
};

let msg = if let Some(feat) = post_install_message_feature {
feedback.get_feature_ptr_payload::<String>(feat).await
} else {
None
};

Ok(msg)
}
191 changes: 52 additions & 139 deletions src/cli/subcommand/install.rs → src/cli/subcommand/install/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod determinate;

use std::{
io::IsTerminal as _,
os::unix::prelude::PermissionsExt,
path::{Path, PathBuf},
process::ExitCode,
Expand Down Expand Up @@ -32,15 +33,6 @@ const EXISTING_INCOMPATIBLE_PLAN_GUIDANCE: &str = "\
If you are using `nix-installer` in an automated curing process and seeing this message, consider pinning the version you use via https://github.com/DeterminateSystems/nix-installer#accessing-other-versions.\
";

const PRE_PKG_SUGGEST: &str = "For a more robust Nix installation, use the Determinate package for macOS: https://dtr.mn/determinate-nix";

const DETERMINATE_MSG_EXPLAINER: &str = "\
Determinate Nix is Determinate Systems' validated and secure downstream Nix distribution for enterprises. \
It comes bundled with Determinate Nixd, a helpful daemon that automates some otherwise-unpleasant aspects of using Nix, such as garbage collection, and enables you to easily authenticate with FlakeHub.

For more details: https://dtr.mn/determinate-nix\
";

/**
Install Nix using a planner

Expand Down Expand Up @@ -82,14 +74,6 @@ pub struct Install {
pub planner: Option<BuiltinPlanner>,
}

#[derive(Eq, PartialEq)]
enum InstallCase {
DeterminateInteractive,
DeterminateScripted,
UpstreamInteractive,
UpstreamScripted,
}

#[async_trait::async_trait]
impl CommandExecute for Install {
#[tracing::instrument(level = "trace", skip_all)]
Expand Down Expand Up @@ -131,19 +115,9 @@ impl CommandExecute for Install {
return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense"));
}

if matches!(
target_lexicon::OperatingSystem::host(),
target_lexicon::OperatingSystem::MacOSX { .. }
| target_lexicon::OperatingSystem::Darwin
) {
let msg = feedback
.get_feature_ptr_payload::<String>("dni-det-msg-start-pkg-ptr")
.await
.unwrap_or(PRE_PKG_SUGGEST.into());
tracing::info!("{}", msg.trim());
}
determinate::inform_macos_about_pkg(&feedback).await;

let mut case: Option<InstallCase> = None;
let mut post_install_message = None;

let mut install_plan = if let Some(plan_path) = plan {
let install_plan_string = tokio::fs::read_to_string(&plan_path)
Expand All @@ -158,112 +132,52 @@ impl CommandExecute for Install {
.map_err(|e| eyre::eyre!(e))?,
};

match existing_receipt {
Some(existing_receipt) => {
if let Err(e) = existing_receipt.check_compatible() {
eprintln!(
"{}",
format!("\
{e}\n\
\n\
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
").red()
if let Some(existing_receipt) = existing_receipt {
if let Err(e) = existing_receipt.check_compatible() {
eprintln!(
"{}",
format!("\
{e}\n\
\n\
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
").red()
);
return Ok(ExitCode::FAILURE);
}
return Ok(ExitCode::FAILURE);
}

if existing_receipt.planner.typetag_name() != planner.typetag_name() {
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
return Ok(ExitCode::FAILURE);
}
if existing_receipt.planner.typetag_name() != planner.typetag_name() {
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
return Ok(ExitCode::FAILURE);
}

if existing_receipt.planner.settings().map_err(|e| eyre!(e))?
!= planner.settings().map_err(|e| eyre!(e))?
{
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
return Ok(ExitCode::FAILURE);
}
if existing_receipt.planner.settings().map_err(|e| eyre!(e))?
!= planner.settings().map_err(|e| eyre!(e))?
{
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
return Ok(ExitCode::FAILURE);
}

eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed. Try uninstalling (`{uninstall_command}`) and reinstalling if Nix isn't working").red());
return Ok(ExitCode::SUCCESS);
},
None => {
let planner_settings = planner.common_settings_mut();

if !planner_settings.determinate_nix
&& std::io::stdin().is_terminal()
&& !no_confirm
{
let base_prompt = feedback
.get_feature_ptr_payload::<String>("dni-det-msg-interactive-prompt-ptr")
.await
.unwrap_or("Install Determinate Nix?".into());

let mut explanation: Option<String> = None;

loop {
let prompt = if let Some(ref explanation) = explanation {
&format!("\n{}\n{}", base_prompt.trim().green(), explanation.trim())
} else {
&format!("\n{}", base_prompt.trim().green())
};

let response = interaction::prompt(
prompt.to_string(),
PromptChoice::Yes,
explanation.is_some(),
)
.await?;

match response {
PromptChoice::Explain => {
explanation = Some(
feedback
.get_feature_ptr_payload::<String>(
"dni-det-msg-interactive-explanation-ptr",
)
.await
.unwrap_or(DETERMINATE_MSG_EXPLAINER.into()),
);
},
PromptChoice::Yes => {
planner_settings.determinate_nix = true;
break;
},
PromptChoice::No => {
break;
},
}
}
}
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed. Try uninstalling (`{uninstall_command}`) and reinstalling if Nix isn't working").red());
return Ok(ExitCode::SUCCESS);
}

case = Some(
match (
planner_settings.determinate_nix,
std::io::stdin().is_terminal() && !no_confirm,
) {
(true, true) => InstallCase::DeterminateInteractive,
(true, false) => InstallCase::DeterminateScripted,
(false, true) => InstallCase::UpstreamInteractive,
(false, false) => InstallCase::UpstreamScripted,
},
);
post_install_message =
determinate::prompt_for_determinate(&mut feedback, &mut planner, no_confirm)
.await?;

feedback.set_planner(&planner).await?;
feedback.set_planner(&planner).await?;

let res = planner.plan().await;
match res {
Ok(plan) => plan,
Err(err) => {
feedback.planning_failed(&err).await;
if let Some(expected) = err.expected() {
eprintln!("{}", expected.red());
return Ok(ExitCode::FAILURE);
}
return Err(err)?;
},
let res = planner.plan().await;
match res {
Ok(plan) => plan,
Err(err) => {
feedback.planning_failed(&err).await;
if let Some(expected) = err.expected() {
eprintln!("{}", expected.red());
return Ok(ExitCode::FAILURE);
}
return Err(err)?;
},
}
};
Expand Down Expand Up @@ -366,6 +280,10 @@ impl CommandExecute for Install {
eprintln!("{}", expected.red());
return Ok(ExitCode::FAILURE);
}
if matches!(err, NixInstallerError::Cancelled) {
eprintln!("{}", err.red());
return Ok(ExitCode::FAILURE);
}
return Err(err)?;
},
_ => {
Expand All @@ -383,6 +301,10 @@ impl CommandExecute for Install {
eprintln!("{}", expected.red());
return Ok(ExitCode::FAILURE);
}
if matches!(err, NixInstallerError::Cancelled) {
eprintln!("{}", err.red());
return Ok(ExitCode::FAILURE);
}

let error = eyre!(err).wrap_err("Install failure");
return Err(error)?;
Expand Down Expand Up @@ -423,17 +345,8 @@ impl CommandExecute for Install {
},
);

let feat = match case {
Some(InstallCase::DeterminateInteractive) => Some("dni-post-det-int-ptr"),
Some(InstallCase::UpstreamInteractive) => Some("dni-post-ups-int-ptr"),
Some(InstallCase::UpstreamScripted) => Some("dni-post-ups-scr-ptr"),
_ => None,
};
if let Some(feat) = feat {
let msg = feedback.get_feature_ptr_payload::<String>(feat).await;
if let Some(msg) = msg {
println!("{}\n", msg.trim());
}
if let Some(msg) = post_install_message {
println!("{}\n", msg.trim());
}
},
}
Expand Down
2 changes: 1 addition & 1 deletion src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ impl ErrorDiagnostic for DiagnosticError {

impl crate::feedback::Feedback for DiagnosticData {
async fn get_feature_ptr_payload<T: serde::de::DeserializeOwned + Send + std::fmt::Debug>(
&mut self,
&self,
name: impl Into<String> + std::fmt::Debug + Send,
) -> Option<T> {
self.ids_client.get_feature_ptr_payload::<T>(name).await
Expand Down
2 changes: 1 addition & 1 deletion src/feedback/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub enum Client {

impl Feedback for Client {
async fn get_feature_ptr_payload<T: serde::de::DeserializeOwned + Send + std::fmt::Debug>(
&mut self,
&self,
name: impl Into<String> + core::marker::Send + std::fmt::Debug,
) -> Option<T> {
match self {
Expand Down
2 changes: 1 addition & 1 deletion src/feedback/devnull.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn dev_null() -> (Client, Worker) {
pub struct DevNull;
impl Feedback for DevNull {
async fn get_feature_ptr_payload<T: serde::de::DeserializeOwned + Send>(
&mut self,
&self,
_name: impl Into<String> + core::marker::Send,
) -> Option<T> {
None
Expand Down
Loading
Loading