Skip to content

Commit 2542680

Browse files
authored
Grahamc/fh 560 suggest dn (#1415)
1 parent 78bd674 commit 2542680

File tree

4 files changed

+199
-101
lines changed

4 files changed

+199
-101
lines changed

src/cli/mod.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ use url::Url;
1515

1616
use self::subcommand::NixInstallerSubcommand;
1717

18+
const FAIL_PKG_SUGGEST: &str = "\
19+
The Determinate Nix Installer failed.
20+
21+
Try our macOS-native package instead, which can handle almost anything: https://dtr.mn/determinate-nix\
22+
";
23+
1824
#[async_trait::async_trait]
1925
pub trait CommandExecute {
2026
async fn execute<T>(self, feedback: T) -> eyre::Result<ExitCode>
@@ -80,14 +86,35 @@ pub struct NixInstallerCli {
8086
#[async_trait::async_trait]
8187
impl CommandExecute for NixInstallerCli {
8288
#[tracing::instrument(level = "trace", skip_all)]
83-
async fn execute<T>(self, feedback: T) -> eyre::Result<ExitCode>
89+
async fn execute<T>(self, mut feedback: T) -> eyre::Result<ExitCode>
8490
where
8591
T: crate::feedback::Feedback,
8692
{
8793
match self.subcommand {
8894
NixInstallerSubcommand::Plan(plan) => plan.execute(feedback).await,
8995
NixInstallerSubcommand::SelfTest(self_test) => self_test.execute(feedback).await,
90-
NixInstallerSubcommand::Install(install) => install.execute(feedback).await,
96+
NixInstallerSubcommand::Install(install) => {
97+
let ret = install.execute(feedback.clone()).await;
98+
99+
if matches!(
100+
target_lexicon::OperatingSystem::host(),
101+
target_lexicon::OperatingSystem::MacOSX { .. }
102+
| target_lexicon::OperatingSystem::Darwin
103+
) {
104+
#[allow(clippy::collapsible_if)]
105+
if ret.is_err() || ret.as_ref().is_ok_and(|code| code == &ExitCode::FAILURE) {
106+
let msg = feedback
107+
.get_feature_ptr_payload::<String>("dni-det-msg-fail-pkg-ptr")
108+
.await
109+
.unwrap_or(FAIL_PKG_SUGGEST.into());
110+
tracing::warn!("{}\n", msg.trim());
111+
112+
return Ok(ExitCode::FAILURE);
113+
}
114+
}
115+
116+
ret
117+
},
91118
NixInstallerSubcommand::Repair(repair) => repair.execute(feedback).await,
92119
NixInstallerSubcommand::Uninstall(revert) => revert.execute(feedback).await,
93120
NixInstallerSubcommand::SplitReceipt(split_receipt) => {

src/cli/subcommand/install.rs

Lines changed: 149 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::{
2+
io::IsTerminal as _,
23
os::unix::prelude::PermissionsExt,
34
path::{Path, PathBuf},
45
process::ExitCode,
56
};
67

78
use crate::{
8-
action::ActionState,
99
cli::{
1010
ensure_root,
1111
interaction::{self, PromptChoice},
@@ -15,7 +15,6 @@ use crate::{
1515
},
1616
error::HasExpectedErrors,
1717
plan::RECEIPT_LOCATION,
18-
planner::Planner,
1918
settings::CommonSettings,
2019
util::OnMissing,
2120
BuiltinPlanner, InstallPlan, NixInstallerError,
@@ -33,6 +32,15 @@ const EXISTING_INCOMPATIBLE_PLAN_GUIDANCE: &str = "\
3332
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.\
3433
";
3534

35+
const PRE_PKG_SUGGEST: &str = "For a more robust Nix installation, use the Determinate package for macOS: https://dtr.mn/determinate-nix";
36+
37+
const DETERMINATE_MSG_EXPLAINER: &str = "\
38+
Determinate Nix is Determinate Systems' validated and secure downstream Nix distribution for enterprises. \
39+
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.
40+
41+
For more details: https://dtr.mn/determinate-nix\
42+
";
43+
3644
/**
3745
Install Nix using a planner
3846
@@ -77,14 +85,14 @@ pub struct Install {
7785
#[async_trait::async_trait]
7886
impl CommandExecute for Install {
7987
#[tracing::instrument(level = "trace", skip_all)]
80-
async fn execute<T>(self, feedback: T) -> eyre::Result<ExitCode>
88+
async fn execute<T>(self, mut feedback: T) -> eyre::Result<ExitCode>
8189
where
8290
T: crate::feedback::Feedback,
8391
{
8492
let Self {
8593
no_confirm,
8694
plan,
87-
planner,
95+
planner: maybe_planner,
8896
settings,
8997
explain,
9098
} = self;
@@ -111,107 +119,147 @@ impl CommandExecute for Install {
111119
false => format!("curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/v{} | sh -s -- uninstall", env!("CARGO_PKG_VERSION")),
112120
};
113121

114-
let mut install_plan = match (planner, plan) {
115-
(Some(planner), None) => {
116-
let chosen_planner: Box<dyn Planner> = planner.clone().boxed();
117-
118-
match existing_receipt {
119-
Some(existing_receipt) => {
120-
if let Err(e) = existing_receipt.check_compatible() {
121-
eprintln!(
122-
"{}",
123-
format!("\
124-
{e}\n\
125-
\n\
126-
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
127-
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
128-
").red()
129-
);
130-
return Ok(ExitCode::FAILURE)
131-
}
132-
if existing_receipt.planner.typetag_name() != chosen_planner.typetag_name() {
133-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
134-
return Ok(ExitCode::FAILURE)
135-
}
136-
if existing_receipt.planner.settings().map_err(|e| eyre!(e))? != chosen_planner.settings().map_err(|e| eyre!(e))? {
137-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
138-
return Ok(ExitCode::FAILURE)
139-
}
140-
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());
141-
return Ok(ExitCode::SUCCESS)
142-
},
143-
None => {
144-
let res = planner.plan().await;
145-
match res {
146-
Ok(plan) => plan,
147-
Err(err) => {
148-
if let Some(expected) = err.expected() {
149-
eprintln!("{}", expected.red());
150-
return Ok(ExitCode::FAILURE);
151-
}
152-
return Err(err)?;
153-
}
154-
}
155-
},
156-
}
157-
},
158-
(None, Some(plan_path)) => {
159-
let install_plan_string = tokio::fs::read_to_string(&plan_path)
122+
if plan.is_some() && maybe_planner.is_some() {
123+
return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense"));
124+
}
125+
126+
if matches!(
127+
target_lexicon::OperatingSystem::host(),
128+
target_lexicon::OperatingSystem::MacOSX { .. }
129+
| target_lexicon::OperatingSystem::Darwin
130+
) {
131+
let msg = feedback
132+
.get_feature_ptr_payload::<String>("dni-det-msg-start-pkg-ptr")
133+
.await
134+
.unwrap_or(PRE_PKG_SUGGEST.into());
135+
tracing::info!("{}", msg.trim());
136+
}
137+
138+
let mut post_install_message = None;
139+
140+
let mut install_plan = if let Some(plan_path) = plan {
141+
let install_plan_string = tokio::fs::read_to_string(&plan_path)
160142
.await
161143
.wrap_err("Reading plan")?;
162-
serde_json::from_str(&install_plan_string)?
163-
},
164-
(None, None) => {
165-
let builtin_planner = BuiltinPlanner::from_common_settings(settings.clone())
144+
serde_json::from_str(&install_plan_string)?
145+
} else {
146+
let mut planner = match maybe_planner {
147+
Some(planner) => planner,
148+
None => BuiltinPlanner::from_common_settings(settings.clone())
166149
.await
167-
.map_err(|e| eyre::eyre!(e))?;
168-
169-
match existing_receipt {
170-
Some(existing_receipt) => {
171-
if let Err(e) = existing_receipt.check_compatible() {
172-
eprintln!(
173-
"{}",
174-
format!("\
175-
{e}\n\
176-
\n\
177-
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
178-
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
179-
").red()
180-
);
181-
return Ok(ExitCode::FAILURE)
182-
}
183-
if existing_receipt.planner.typetag_name() != builtin_planner.typetag_name() {
184-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
185-
return Ok(ExitCode::FAILURE)
186-
}
187-
if existing_receipt.planner.settings().map_err(|e| eyre!(e))? != builtin_planner.settings().map_err(|e| eyre!(e))? {
188-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
189-
return Ok(ExitCode::FAILURE)
190-
}
191-
if existing_receipt.actions.iter().all(|v| v.state == ActionState::Completed) {
192-
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").yellow());
193-
return Ok(ExitCode::SUCCESS)
194-
}
195-
existing_receipt
196-
},
197-
None => {
198-
let res = builtin_planner.plan().await;
199-
match res {
200-
Ok(plan) => plan,
201-
Err(err) => {
202-
if let Some(expected) = err.expected() {
203-
eprintln!("{}", expected.red());
204-
return Ok(ExitCode::FAILURE);
150+
.map_err(|e| eyre::eyre!(e))?,
151+
};
152+
153+
match existing_receipt {
154+
Some(existing_receipt) => {
155+
if let Err(e) = existing_receipt.check_compatible() {
156+
eprintln!(
157+
"{}",
158+
format!("\
159+
{e}\n\
160+
\n\
161+
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
162+
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
163+
").red()
164+
);
165+
return Ok(ExitCode::FAILURE);
166+
}
167+
168+
if existing_receipt.planner.typetag_name() != planner.typetag_name() {
169+
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
170+
return Ok(ExitCode::FAILURE);
171+
}
172+
173+
if existing_receipt.planner.settings().map_err(|e| eyre!(e))?
174+
!= planner.settings().map_err(|e| eyre!(e))?
175+
{
176+
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
177+
return Ok(ExitCode::FAILURE);
178+
}
179+
180+
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());
181+
return Ok(ExitCode::SUCCESS);
182+
},
183+
None => {
184+
let planner_settings = planner.common_settings_mut();
185+
186+
if !planner_settings.determinate_nix {
187+
if !std::io::stdin().is_terminal() || no_confirm {
188+
let msg = feedback
189+
.get_feature_ptr_payload::<String>("dni-det-msg-noninteractive-ptr")
190+
.await
191+
.unwrap_or("Consider using Determinate Nix, for less fuss: https://dtr.mn/determinate-nix\n".into());
192+
post_install_message = Some(msg);
193+
} else {
194+
let base_prompt = feedback
195+
.get_feature_ptr_payload::<String>(
196+
"dni-det-msg-interactive-prompt-ptr",
197+
)
198+
.await
199+
.unwrap_or("Install Determinate Nix?".into());
200+
let explanation = feedback
201+
.get_feature_ptr_payload::<String>(
202+
"dni-det-msg-interactive-explanation-ptr",
203+
)
204+
.await
205+
.unwrap_or(DETERMINATE_MSG_EXPLAINER.into());
206+
207+
let mut currently_explaining = explain;
208+
209+
loop {
210+
let prompt = if currently_explaining {
211+
&format!(
212+
"\n{}\n{}\n",
213+
base_prompt.trim().green(),
214+
explanation.trim()
215+
)
216+
} else {
217+
&format!("\n{}", base_prompt.trim().green())
218+
};
219+
220+
let response = interaction::prompt(
221+
prompt.to_string(),
222+
PromptChoice::Yes,
223+
currently_explaining,
224+
)
225+
.await?;
226+
227+
match response {
228+
PromptChoice::Explain => {
229+
currently_explaining = true;
230+
},
231+
PromptChoice::Yes => {
232+
planner_settings.determinate_nix = true;
233+
break;
234+
},
235+
PromptChoice::No => {
236+
break;
237+
},
205238
}
206-
return Err(err)?;
207239
}
208240
}
209-
},
210-
}
211-
},
212-
(Some(_), Some(_)) => return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense")),
241+
}
242+
243+
feedback.set_planner(&planner).await?;
244+
245+
let res = planner.plan().await;
246+
match res {
247+
Ok(plan) => plan,
248+
Err(err) => {
249+
feedback.planning_failed(&err).await;
250+
if let Some(expected) = err.expected() {
251+
eprintln!("{}", expected.red());
252+
return Ok(ExitCode::FAILURE);
253+
}
254+
return Err(err)?;
255+
},
256+
}
257+
},
258+
}
213259
};
214260

261+
feedback.planning_succeeded().await;
262+
215263
if let Err(err) = install_plan.pre_install_check().await {
216264
if let Some(expected) = err.expected() {
217265
eprintln!("{}", expected.red());
@@ -358,6 +406,10 @@ impl CommandExecute for Install {
358406
". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh".bold(),
359407
},
360408
);
409+
410+
if let Some(post_message) = post_install_message {
411+
println!("{}", post_message.trim());
412+
}
361413
},
362414
}
363415

src/diagnostics.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,21 @@ impl crate::feedback::Feedback for DiagnosticData {
221221
.add_fact("planner", planner.typetag_name().into())
222222
.await;
223223

224-
if let Ok(settings) = planner.configured_settings().await {
224+
if let Ok(ref settings) = planner.configured_settings().await {
225225
self.ids_client
226226
.add_fact(
227227
"configured_settings",
228-
settings.into_keys().collect::<Vec<_>>().into(),
228+
settings.keys().cloned().collect::<Vec<_>>().into(),
229+
)
230+
.await;
231+
232+
self.ids_client
233+
.add_fact(
234+
"install_determinate_nix",
235+
settings
236+
.get("determinate_nix")
237+
.cloned()
238+
.unwrap_or(serde_json::Value::Bool(false)),
229239
)
230240
.await;
231241
}

src/planner/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,15 @@ impl BuiltinPlanner {
226226
Ok(built)
227227
}
228228

229+
pub fn common_settings_mut(&mut self) -> &mut CommonSettings {
230+
match self {
231+
BuiltinPlanner::Linux(inner) => &mut inner.settings,
232+
BuiltinPlanner::SteamDeck(inner) => &mut inner.settings,
233+
BuiltinPlanner::Ostree(inner) => &mut inner.settings,
234+
BuiltinPlanner::Macos(inner) => &mut inner.settings,
235+
}
236+
}
237+
229238
pub async fn configured_settings(
230239
&self,
231240
) -> Result<HashMap<String, serde_json::Value>, PlannerError> {

0 commit comments

Comments
 (0)