From 19dd7a13d45c0c0ac067931a31481607608a1e6b Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Fri, 24 Feb 2023 10:11:12 -0800 Subject: [PATCH] Add diagnostics reporting (#264) * Add diagnostics reporting * Some tidying * Remove injected failure * Update URL * Fixups * Fix tests * Use triples instead of architecture --- Cargo.lock | 33 +++++++ Cargo.toml | 5 +- src/action/mod.rs | 11 ++- src/cli/subcommand/install.rs | 7 +- src/cli/subcommand/uninstall.rs | 2 + src/diagnostics.rs | 156 ++++++++++++++++++++++++++++++++ src/error.rs | 30 +++++- src/lib.rs | 2 + src/plan.rs | 102 ++++++++++++++++++--- src/planner/linux.rs | 9 ++ src/planner/macos.rs | 9 ++ src/planner/mod.rs | 56 +++++++++++- src/planner/steam_deck.rs | 9 ++ src/settings.rs | 50 +++++++++- 14 files changed, 460 insertions(+), 21 deletions(-) create mode 100644 src/diagnostics.rs diff --git a/Cargo.lock b/Cargo.lock index e0bd4d7..cef8250 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -927,6 +927,7 @@ dependencies = [ "eyre", "glob", "nix", + "os-release", "owo-colors", "plist", "rand 0.8.5", @@ -935,6 +936,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "strum", "tar", "target-lexicon", "tempdir", @@ -1004,6 +1006,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "os-release" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f29ae2f71b53ec19cc23385f8e4f3d90975195aa3d09171ba3bef7159bec27" +dependencies = [ + "lazy_static", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -1541,6 +1552,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "supports-color" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index e000482..2f1ff9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,9 @@ build-inputs = ["darwin.apple_sdk.frameworks.Security"] build-inputs = ["darwin.apple_sdk.frameworks.Security"] [features] -default = ["cli"] +default = ["cli", "diagnostics"] cli = ["eyre", "color-eyre", "clap", "tracing-subscriber", "tracing-error", "atty"] +diagnostics = ["os-release"] [[bin]] name = "nix-installer" @@ -53,6 +54,8 @@ rand = { version = "0.8.5", default-features = false, features = [ "std", "std_r semver = { version = "1.0.14", default-features = false, features = ["serde", "std"] } term = { version = "0.7.0", default-features = false } uuid = { version = "1.2.2", features = ["serde"] } +os-release = { version = "0.1.0", default-features = false, optional = true } +strum = { version = "0.24.1", features = ["derive"] } [dev-dependencies] eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] } diff --git a/src/action/mod.rs b/src/action/mod.rs index 6939704..384ecf3 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -133,6 +133,15 @@ impl Planner for MyPlanner { Ok(map) } + + #[cfg(feature = "diagnostics")] + async fn diagnostic_data(&self) -> Result { + Ok(nix_installer::diagnostics::DiagnosticData::new( + self.common.diagnostic_endpoint.clone(), + self.typetag_name().into(), + self.configured_settings().await?, + )) + } } # async fn custom_planner_install() -> color_eyre::Result<()> { @@ -244,7 +253,7 @@ impl ActionDescription { } /// An error occurring during an action -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, strum::IntoStaticStr)] pub enum ActionError { /// A custom error #[error(transparent)] diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index e9d7b0f..eee10b1 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -47,6 +47,7 @@ pub struct Install { global = true )] pub explain: bool, + #[clap(env = "NIX_INSTALLER_PLAN")] pub plan: Option, @@ -133,12 +134,12 @@ impl CommandExecute for Install { let res = builtin_planner.plan().await; match res { Ok(plan) => plan, - Err(e) => { - if let Some(expected) = e.expected() { + Err(err) => { + if let Some(expected) = err.expected() { eprintln!("{}", expected.red()); return Ok(ExitCode::FAILURE); } - return Err(e.into()) + return Err(err.into()) } } }, diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index ca78c14..def2067 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -28,6 +28,7 @@ pub struct Uninstall { global = true )] pub no_confirm: bool, + #[clap( long, env = "NIX_INSTALLER_EXPLAIN", @@ -36,6 +37,7 @@ pub struct Uninstall { global = true )] pub explain: bool, + #[clap(default_value = RECEIPT_LOCATION)] pub receipt: PathBuf, } diff --git a/src/diagnostics.rs b/src/diagnostics.rs new file mode 100644 index 0000000..58d07f4 --- /dev/null +++ b/src/diagnostics.rs @@ -0,0 +1,156 @@ +/*! Diagnostic reporting functionality + +When enabled with the `diagnostics` feature (default) this module provides automated install success/failure reporting to an endpoint. + +That endpoint can be a URL such as `https://our.project.org/nix-installer/diagnostics` or `file:///home/$USER/diagnostic.json` which receives a [`DiagnosticReport`] in JSON format. +*/ + +use std::time::Duration; + +use os_release::OsRelease; +use reqwest::Url; + +/// The static of an action attempt +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub enum DiagnosticStatus { + Cancelled, + Success, + /// This includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets) + Failure(String), + Pending, +} + +/// The action attempted +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy)] +pub enum DiagnosticAction { + Install, + Uninstall, +} + +/// A report sent to an endpoint +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct DiagnosticReport { + pub version: String, + pub planner: String, + pub configured_settings: Vec, + pub os_name: String, + pub os_version: String, + pub triple: String, + pub action: DiagnosticAction, + pub status: DiagnosticStatus, +} + +/// A preparation of data to be sent to the `endpoint`. +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Default)] +pub struct DiagnosticData { + version: String, + planner: String, + configured_settings: Vec, + os_name: String, + os_version: String, + triple: String, + endpoint: Option, +} + +impl DiagnosticData { + pub fn new(endpoint: Option, planner: String, configured_settings: Vec) -> Self { + let (os_name, os_version) = match OsRelease::new() { + Ok(os_release) => (os_release.name, os_release.version), + Err(_) => ("unknown".into(), "unknown".into()), + }; + Self { + endpoint, + version: env!("CARGO_PKG_VERSION").into(), + planner, + configured_settings, + os_name, + os_version, + triple: target_lexicon::HOST.to_string(), + } + } + + pub fn report(&self, action: DiagnosticAction, status: DiagnosticStatus) -> DiagnosticReport { + let Self { + version, + planner, + configured_settings, + os_name, + os_version, + triple, + endpoint: _, + } = self; + DiagnosticReport { + version: version.clone(), + planner: planner.clone(), + configured_settings: configured_settings.clone(), + os_name: os_name.clone(), + os_version: os_version.clone(), + triple: triple.clone(), + action, + status, + } + } + + #[tracing::instrument(level = "debug", skip_all)] + pub async fn send( + self, + action: DiagnosticAction, + status: DiagnosticStatus, + ) -> Result<(), DiagnosticError> { + let serialized = serde_json::to_string_pretty(&self.report(action, status))?; + + let endpoint = match self.endpoint { + Some(endpoint) => endpoint, + None => return Ok(()), + }; + + match endpoint.scheme() { + "https" | "http" => { + tracing::debug!("Sending diagnostic to `{endpoint}`"); + let client = reqwest::Client::new(); + let res = client + .post(endpoint.clone()) + .body(serialized) + .header("Content-Type", "application/json") + .timeout(Duration::from_millis(3000)) + .send() + .await; + + if let Err(_err) = res { + tracing::info!("Failed to send diagnostic to `{endpoint}`, continuing") + } + }, + "file" => { + let path = endpoint.path(); + tracing::debug!("Writing diagnostic to `{path}`"); + let res = tokio::fs::write(path, serialized).await; + + if let Err(_err) = res { + tracing::info!("Failed to send diagnostic to `{path}`, continuing") + } + }, + _ => return Err(DiagnosticError::UnknownUrlScheme), + }; + Ok(()) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum DiagnosticError { + #[error("Unknown url scheme")] + UnknownUrlScheme, + #[error("Request error")] + Reqwest( + #[from] + #[source] + reqwest::Error, + ), + #[error("Write path `{0}`")] + Write(std::path::PathBuf, #[source] std::io::Error), + #[error("Serializing receipt")] + Serializing( + #[from] + #[source] + serde_json::Error, + ), +} diff --git a/src/error.rs b/src/error.rs index ba464dd..815132c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use crate::{action::ActionError, planner::PlannerError, settings::InstallSettingsError}; /// An error occurring during a call defined in this crate -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, strum::IntoStaticStr)] pub enum NixInstallerError { /// An error originating from an [`Action`](crate::action::Action) #[error("Error executing action")] @@ -53,6 +53,15 @@ pub enum NixInstallerError { #[source] InstallSettingsError, ), + + #[cfg(feature = "diagnostics")] + /// Diagnostic error + #[error("Diagnostic error")] + Diagnostic( + #[from] + #[source] + crate::diagnostics::DiagnosticError, + ), } pub(crate) trait HasExpectedErrors: std::error::Error + Sized + Send + Sync { @@ -70,6 +79,25 @@ impl HasExpectedErrors for NixInstallerError { NixInstallerError::SemVer(_) => None, NixInstallerError::Planner(planner_error) => planner_error.expected(), NixInstallerError::InstallSettings(_) => None, + #[cfg(feature = "diagnostics")] + NixInstallerError::Diagnostic(_) => None, } } } + +// #[cfg(feature = "diagnostics")] +// impl NixInstallerError { +// pub fn diagnostic_synopsis(&self) -> &'static str { +// match self { +// NixInstallerError::Action(inner) => inner.into(), +// NixInstallerError::Planner(inner) => inner.into(), +// NixInstallerError::RecordingReceipt(_, _) +// | NixInstallerError::CopyingSelf(_) +// | NixInstallerError::SerializingReceipt(_) +// | NixInstallerError::Cancelled +// | NixInstallerError::SemVer(_) +// | NixInstallerError::Diagnostic(_) +// | NixInstallerError::InstallSettings(_) => self.into(), +// } +// } +// } diff --git a/src/lib.rs b/src/lib.rs index 1cba0a0..65cd6f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,8 @@ pub mod action; mod channel_value; #[cfg(feature = "cli")] pub mod cli; +#[cfg(feature = "diagnostics")] +pub mod diagnostics; mod error; mod os; mod plan; diff --git a/src/plan.rs b/src/plan.rs index 4cab190..4ff4cfc 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -24,17 +24,27 @@ pub struct InstallPlan { pub(crate) actions: Vec>>, pub(crate) planner: Box, + + #[cfg(feature = "diagnostics")] + pub(crate) diagnostic_data: Option, } impl InstallPlan { pub async fn default() -> Result { - let planner = BuiltinPlanner::default().await?.boxed(); + let planner = BuiltinPlanner::default().await?; + + #[cfg(feature = "diagnostics")] + let diagnostic_data = Some(planner.diagnostic_data().await?); + + let planner = planner.boxed(); let actions = planner.plan().await?; Ok(Self { planner, actions, version: current_version()?, + #[cfg(feature = "diagnostics")] + diagnostic_data, }) } @@ -42,11 +52,16 @@ impl InstallPlan { where P: Planner + 'static, { + #[cfg(feature = "diagnostics")] + let diagnostic_data = Some(planner.diagnostic_data().await?); + let actions = planner.plan().await?; Ok(Self { planner: planner.boxed(), actions, version: current_version()?, + #[cfg(feature = "diagnostics")] + diagnostic_data, }) } #[tracing::instrument(level = "debug", skip_all)] @@ -55,6 +70,7 @@ impl InstallPlan { planner, actions, version, + .. } = self; let buf = format!( "\ @@ -107,11 +123,7 @@ impl InstallPlan { &mut self, cancel_channel: impl Into>>, ) -> Result<(), NixInstallerError> { - let Self { - version: _, - actions, - planner: _, - } = self; + let Self { actions, .. } = self; let mut cancel_channel = cancel_channel.into(); // This is **deliberately sequential**. @@ -125,6 +137,18 @@ impl InstallPlan { if let Err(err) = write_receipt(self.clone()).await { tracing::error!("Error saving receipt: {:?}", err); } + + #[cfg(feature = "diagnostics")] + if let Some(diagnostic_data) = &self.diagnostic_data { + diagnostic_data + .clone() + .send( + crate::diagnostics::DiagnosticAction::Install, + crate::diagnostics::DiagnosticStatus::Cancelled, + ) + .await?; + } + return Err(NixInstallerError::Cancelled); } } @@ -134,11 +158,35 @@ impl InstallPlan { if let Err(err) = write_receipt(self.clone()).await { tracing::error!("Error saving receipt: {:?}", err); } + #[cfg(feature = "diagnostics")] + if let Some(diagnostic_data) = &self.diagnostic_data { + diagnostic_data + .clone() + .send( + crate::diagnostics::DiagnosticAction::Install, + crate::diagnostics::DiagnosticStatus::Failure({ + let x: &'static str = (&err).into(); + x.to_string() + }), + ) + .await?; + } + return Err(NixInstallerError::Action(err)); } } write_receipt(self.clone()).await?; + #[cfg(feature = "diagnostics")] + if let Some(diagnostic_data) = &self.diagnostic_data { + diagnostic_data + .clone() + .send( + crate::diagnostics::DiagnosticAction::Install, + crate::diagnostics::DiagnosticStatus::Success, + ) + .await?; + } Ok(()) } @@ -149,6 +197,7 @@ impl InstallPlan { version: _, planner, actions, + .. } = self; let buf = format!( "\ @@ -207,11 +256,7 @@ impl InstallPlan { &mut self, cancel_channel: impl Into>>, ) -> Result<(), NixInstallerError> { - let Self { - version: _, - actions, - planner: _, - } = self; + let Self { actions, .. } = self; let mut cancel_channel = cancel_channel.into(); // This is **deliberately sequential**. @@ -225,6 +270,17 @@ impl InstallPlan { if let Err(err) = write_receipt(self.clone()).await { tracing::error!("Error saving receipt: {:?}", err); } + + #[cfg(feature = "diagnostics")] + if let Some(diagnostic_data) = &self.diagnostic_data { + diagnostic_data + .clone() + .send( + crate::diagnostics::DiagnosticAction::Uninstall, + crate::diagnostics::DiagnosticStatus::Cancelled, + ) + .await?; + } return Err(NixInstallerError::Cancelled); } } @@ -234,10 +290,34 @@ impl InstallPlan { if let Err(err) = write_receipt(self.clone()).await { tracing::error!("Error saving receipt: {:?}", err); } + #[cfg(feature = "diagnostics")] + if let Some(diagnostic_data) = &self.diagnostic_data { + diagnostic_data + .clone() + .send( + crate::diagnostics::DiagnosticAction::Uninstall, + crate::diagnostics::DiagnosticStatus::Failure({ + let x: &'static str = (&err).into(); + x.to_string() + }), + ) + .await?; + } return Err(NixInstallerError::Action(err)); } } + #[cfg(feature = "diagnostics")] + if let Some(diagnostic_data) = &self.diagnostic_data { + diagnostic_data + .clone() + .send( + crate::diagnostics::DiagnosticAction::Uninstall, + crate::diagnostics::DiagnosticStatus::Success, + ) + .await?; + } + Ok(()) } } diff --git a/src/planner/linux.rs b/src/planner/linux.rs index cb6bc75..9eab5b3 100644 --- a/src/planner/linux.rs +++ b/src/planner/linux.rs @@ -96,6 +96,15 @@ impl Planner for Linux { Ok(map) } + + #[cfg(feature = "diagnostics")] + async fn diagnostic_data(&self) -> Result { + Ok(crate::diagnostics::DiagnosticData::new( + self.settings.diagnostic_endpoint.clone(), + self.typetag_name().into(), + self.configured_settings().await?, + )) + } } impl Into for Linux { diff --git a/src/planner/macos.rs b/src/planner/macos.rs index 6a03806..351f182 100644 --- a/src/planner/macos.rs +++ b/src/planner/macos.rs @@ -169,6 +169,15 @@ impl Planner for Macos { Ok(map) } + + #[cfg(feature = "diagnostics")] + async fn diagnostic_data(&self) -> Result { + Ok(crate::diagnostics::DiagnosticData::new( + self.settings.diagnostic_endpoint.clone(), + self.typetag_name().into(), + self.configured_settings().await?, + )) + } } impl Into for Macos { diff --git a/src/planner/mod.rs b/src/planner/mod.rs index 0bc6f24..cbb1563 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -52,6 +52,15 @@ impl Planner for MyPlanner { Ok(map) } + + #[cfg(feature = "diagnostics")] + async fn diagnostic_data(&self) -> Result { + Ok(nix_installer::diagnostics::DiagnosticData::new( + self.common.diagnostic_endpoint.clone(), + self.typetag_name().into(), + self.configured_settings().await?, + )) + } } # async fn custom_planner_install() -> color_eyre::Result<()> { @@ -101,6 +110,23 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone { async fn plan(&self) -> Result>>, PlannerError>; /// The settings being used by the planner fn settings(&self) -> Result, InstallSettingsError>; + + async fn configured_settings(&self) -> Result, PlannerError> + where + Self: Sized, + { + let default = Self::default().await?.settings()?; + let configured = self.settings()?; + + let mut keys: Vec = Vec::new(); + for (key, value) in configured.iter() { + if default.get(key) != Some(value) { + keys.push(key.clone()) + } + } + Ok(keys) + } + /// A boxed, type erased planner fn boxed(self) -> Box where @@ -108,6 +134,9 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone { { Box::new(self) } + + #[cfg(feature = "diagnostics")] + async fn diagnostic_data(&self) -> Result; } dyn_clone::clone_trait_object!(Planner); @@ -171,6 +200,17 @@ impl BuiltinPlanner { Ok(built) } + pub async fn configured_settings(&self) -> Result, PlannerError> { + match self { + #[cfg(target_os = "linux")] + BuiltinPlanner::Linux(inner) => inner.configured_settings().await, + #[cfg(target_os = "linux")] + BuiltinPlanner::SteamDeck(inner) => inner.configured_settings().await, + #[cfg(target_os = "macos")] + BuiltinPlanner::Macos(inner) => inner.configured_settings().await, + } + } + pub async fn plan(self) -> Result { match self { #[cfg(target_os = "linux")] @@ -213,10 +253,24 @@ impl BuiltinPlanner { BuiltinPlanner::Macos(i) => i.settings(), } } + + #[cfg(feature = "diagnostics")] + pub async fn diagnostic_data( + &self, + ) -> Result { + match self { + #[cfg(target_os = "linux")] + BuiltinPlanner::Linux(i) => i.diagnostic_data().await, + #[cfg(target_os = "linux")] + BuiltinPlanner::SteamDeck(i) => i.diagnostic_data().await, + #[cfg(target_os = "macos")] + BuiltinPlanner::Macos(i) => i.diagnostic_data().await, + } + } } /// An error originating from a [`Planner`] -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, strum::IntoStaticStr)] pub enum PlannerError { /// `nix-installer` does not have a default planner for the target architecture right now #[error("`nix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype")] diff --git a/src/planner/steam_deck.rs b/src/planner/steam_deck.rs index 1cedc83..3936452 100644 --- a/src/planner/steam_deck.rs +++ b/src/planner/steam_deck.rs @@ -252,6 +252,15 @@ impl Planner for SteamDeck { Ok(map) } + + #[cfg(feature = "diagnostics")] + async fn diagnostic_data(&self) -> Result { + Ok(crate::diagnostics::DiagnosticData::new( + self.settings.diagnostic_endpoint.clone(), + self.typetag_name().into(), + self.configured_settings().await?, + )) + } } impl Into for SteamDeck { diff --git a/src/settings.rs b/src/settings.rs index a160bdb..2b5bd51 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -202,6 +202,31 @@ pub struct CommonSettings { ) )] pub(crate) force: bool, + + #[cfg(feature = "diagnostics")] + /// The URL or file path for an installation diagnostic to be sent + /// + /// Sample of the data sent: + /// + /// { + /// "version": "0.3.0", + /// "planner": "linux", + /// "configured-settings": [ "modify_profile" ], + /// "os-name": "Ubuntu", + /// "os-version": "22.04.1 LTS (Jammy Jellyfish)", + /// "triple": "x86_64-unknown-linux-gnu", + /// "action": "Install", + /// "status": "Success" + /// } + /// + /// To disable diagnostic reporting, unset the default with `--diagnostic-endpoint=` + #[clap( + long, + env = "NIX_INSTALLER_DIAGNOSTIC_ENDPOINT", + global = true, + default_value = "https://install.determinate.systems/nix/diagnostic" + )] + pub diagnostic_endpoint: Option, } impl CommonSettings { @@ -217,19 +242,19 @@ impl CommonSettings { (Architecture::X86_64, OperatingSystem::Linux) => { url = NIX_X64_64_LINUX_URL; nix_build_user_prefix = "nixbld"; - nix_build_user_id_base = 3000; + nix_build_user_id_base = 30000; }, #[cfg(target_os = "linux")] (Architecture::X86_32(_), OperatingSystem::Linux) => { url = NIX_I686_LINUX_URL; nix_build_user_prefix = "nixbld"; - nix_build_user_id_base = 3000; + nix_build_user_id_base = 30000; }, #[cfg(target_os = "linux")] (Architecture::Aarch64(_), OperatingSystem::Linux) => { url = NIX_AARCH64_LINUX_URL; nix_build_user_prefix = "nixbld"; - nix_build_user_id_base = 3000; + nix_build_user_id_base = 30000; }, #[cfg(target_os = "macos")] (Architecture::X86_64, OperatingSystem::MacOSX { .. }) @@ -267,6 +292,10 @@ impl CommonSettings { nix_package_url: url.parse()?, extra_conf: Default::default(), force: false, + #[cfg(feature = "diagnostics")] + diagnostic_endpoint: Some( + "https://install.determinate.systems/diagnostics".try_into()?, + ), }) } @@ -283,6 +312,8 @@ impl CommonSettings { nix_package_url, extra_conf, force, + #[cfg(feature = "diagnostics")] + diagnostic_endpoint, } = self; let mut map = HashMap::default(); @@ -326,6 +357,12 @@ impl CommonSettings { map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?); map.insert("force".into(), serde_json::to_value(force)?); + #[cfg(feature = "diagnostics")] + map.insert( + "diagnostic_endpoint".into(), + serde_json::to_value(diagnostic_endpoint)?, + ); + Ok(map) } } @@ -418,6 +455,13 @@ impl CommonSettings { self.force = force; self } + + #[cfg(feature = "diagnostics")] + /// The URL or file path for an [`DiagnosticReport`][crate::diagnostics::DiagnosticReport] to be sent + pub fn diagnostic_endpoint(&mut self, diagnostic_endpoint: Option) -> &mut Self { + self.diagnostic_endpoint = diagnostic_endpoint; + self + } } #[serde_with::serde_as]