From 75627bcd91853008e8eac62f0107f01775e8c03e Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Mon, 10 Apr 2023 16:16:46 -0700 Subject: [PATCH] Provide users a better error message if systemd is not active (#412) * Provide users a better error message if systemd is not active * Fixups * Fix mac * Fixup --- src/cli/subcommand/install.rs | 12 ++- src/planner/linux.rs | 148 ++++++++++++++++++++++++++-------- src/planner/mod.rs | 15 +++- src/settings.rs | 2 +- 4 files changed, 139 insertions(+), 38 deletions(-) diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index b0fdc7b..0e033a0 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -106,7 +106,17 @@ impl CommandExecute for Install { return Ok(ExitCode::FAILURE) } , None => { - planner.plan().await.map_err(|e| eyre!(e))? + let res = planner.plan().await; + match res { + Ok(plan) => plan, + Err(err) => { + if let Some(expected) = err.expected() { + eprintln!("{}", expected.red()); + return Ok(ExitCode::FAILURE); + } + return Err(err)?; + } + } }, } }, diff --git a/src/planner/linux.rs b/src/planner/linux.rs index 0bf8f7e..a230b9c 100644 --- a/src/planner/linux.rs +++ b/src/planner/linux.rs @@ -4,9 +4,10 @@ use crate::{ common::{ConfigureInitService, ConfigureNix, ProvisionNix}, StatefulAction, }, + error::HasExpectedErrors, planner::{Planner, PlannerError}, settings::CommonSettings, - settings::{InitSettings, InstallSettingsError}, + settings::{InitSettings, InitSystem, InstallSettingsError}, Action, BuiltinPlanner, }; use std::{collections::HashMap, path::Path}; @@ -35,42 +36,16 @@ impl Planner for Linux { } async fn plan(&self) -> Result>>, PlannerError> { - // If on NixOS, running `nix_installer` is pointless - // NixOS always sets up this file as part of setting up /etc itself: https://github.com/NixOS/nixpkgs/blob/bdd39e5757d858bd6ea58ed65b4a2e52c8ed11ca/nixos/modules/system/etc/setup-etc.pl#L145 - if Path::new("/etc/NIXOS").exists() { - return Err(PlannerError::NixOs); - } + check_not_nixos()?; - if std::env::var("WSL_DISTRO_NAME").is_ok() && std::env::var("WSL_INTEROP").is_err() { - return Err(PlannerError::Wsl1); - } + check_nix_not_already_installed().await?; - // We currently do not support SELinux - match Command::new("getenforce").output().await { - Ok(output) => { - let stdout_string = String::from_utf8(output.stdout).map_err(PlannerError::Utf8)?; - tracing::trace!(getenforce_stdout = stdout_string, "SELinux detected"); - match stdout_string.trim() { - "Enforcing" => return Err(PlannerError::SelinuxEnforcing), - _ => (), - } - }, - // The device doesn't have SELinux set up - Err(e) if e.kind() == std::io::ErrorKind::NotFound => (), - // Some unknown error - Err(e) => { - tracing::warn!(error = ?e, "Got an error checking for SELinux setting, this install may fail if SELinux is set to `Enforcing`") - }, - } + check_not_wsl1()?; - // For now, we don't try to repair the user's Nix install or anything special. - if let Ok(_) = Command::new("nix-env") - .arg("--version") - .stdin(std::process::Stdio::null()) - .status() - .await - { - return Err(PlannerError::NixExists); + check_not_selinux().await?; + + if self.init.init == InitSystem::Systemd && self.init.start_daemon { + check_systemd_active()?; } Ok(vec![ @@ -146,3 +121,108 @@ impl Into for Linux { BuiltinPlanner::Linux(self) } } + +// If on NixOS, running `nix_installer` is pointless +fn check_not_nixos() -> Result<(), PlannerError> { + // NixOS always sets up this file as part of setting up /etc itself: https://github.com/NixOS/nixpkgs/blob/bdd39e5757d858bd6ea58ed65b4a2e52c8ed11ca/nixos/modules/system/etc/setup-etc.pl#L145 + if Path::new("/etc/NIXOS").exists() { + return Err(PlannerError::NixOs); + } + Ok(()) +} + +fn check_not_wsl1() -> Result<(), PlannerError> { + // Detection strategies: https://patrickwu.space/wslconf/ + if std::env::var("WSL_DISTRO_NAME").is_ok() && std::env::var("WSL_INTEROP").is_err() { + return Err(PlannerError::Wsl1); + } + Ok(()) +} + +async fn check_not_selinux() -> Result<(), PlannerError> { + // We currently do not support SELinux + match Command::new("getenforce").output().await { + Ok(output) => { + let stdout_string = String::from_utf8(output.stdout).map_err(PlannerError::Utf8)?; + tracing::trace!(getenforce_stdout = stdout_string, "SELinux detected"); + match stdout_string.trim() { + "Enforcing" => return Err(PlannerError::SelinuxEnforcing), + _ => (), + } + }, + // The device doesn't have SELinux set up + Err(e) if e.kind() == std::io::ErrorKind::NotFound => (), + // Some unknown error + Err(e) => { + tracing::warn!(error = ?e, "Got an error checking for SELinux setting, this install may fail if SELinux is set to `Enforcing`") + }, + } + + Ok(()) +} + +async fn check_nix_not_already_installed() -> Result<(), PlannerError> { + // For now, we don't try to repair the user's Nix install or anything special. + if let Ok(_) = Command::new("nix-env") + .arg("--version") + .stdin(std::process::Stdio::null()) + .status() + .await + { + return Err(PlannerError::NixExists); + } + + Ok(()) +} + +fn check_systemd_active() -> Result<(), PlannerError> { + if !Path::new("/run/systemd/system").exists() { + if std::env::var("WSL_DISTRO_NAME").is_ok() { + return Err(LinuxErrorKind::Wsl2SystemdNotActive)?; + } else { + return Err(LinuxErrorKind::SystemdNotActive)?; + } + } + + Ok(()) +} + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum LinuxErrorKind { + #[error( + "\ + systemd was not active.\n\ + \n\ + If it will be started later consider, passing `--no-start-daemon`.\n\ + \n\ + To use a `root`-only Nix install, consider passing `--init none`." + )] + SystemdNotActive, + #[error( + "\ + systemd was not active.\n\ + \n\ + On WSL2, systemd is not enabled by default. Consider enabling it by adding it to your `/etc/wsl.conf` with `echo -e '[boot]\\nsystemd=true'` then restarting WSL2 with `wsl.exe --shutdown` and re-entering the WSL shell. For more information, see https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/.\n\ + \n\ + If it will be started later consider, passing `--no-start-daemon`.\n\ + \n\ + To use a `root`-only Nix install, consider passing `--init none`." + )] + Wsl2SystemdNotActive, +} + +impl HasExpectedErrors for LinuxErrorKind { + fn expected<'a>(&'a self) -> Option> { + match self { + LinuxErrorKind::SystemdNotActive => Some(Box::new(self)), + LinuxErrorKind::Wsl2SystemdNotActive => Some(Box::new(self)), + } + } +} + +impl From for PlannerError { + fn from(v: LinuxErrorKind) -> PlannerError { + PlannerError::Custom(Box::new(v)) + } +} diff --git a/src/planner/mod.rs b/src/planner/mod.rs index 9e996f6..3a1553a 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -367,7 +367,12 @@ pub enum PlannerError { #[error("Detected that this process is running under Rosetta, using Nix in Rosetta is not supported (Please open an issue with your use case)")] RosettaDetected, /// A Linux SELinux related error - #[error("This installer doesn't yet support SELinux in `Enforcing` mode. If SELinux is important to you, please see https://github.com/DeterminateSystems/nix-installer/issues/124. You can also try again after setting SELinux to `Permissive` mode with `setenforce Permissive`")] + #[error("\ + This installer doesn't yet support SELinux in `Enforcing` mode.\n + \n\ + If desirable, consider setting SELinux to `Permissive` mode with `setenforce Permissive`.\n\ + \n\ + If SELinux is important to you, please see https://github.com/DeterminateSystems/nix-installer/issues/124.")] SelinuxEnforcing, /// A UTF-8 related error #[error("UTF-8 error")] @@ -397,7 +402,13 @@ impl HasExpectedErrors for PlannerError { this @ PlannerError::RosettaDetected => Some(Box::new(this)), PlannerError::Utf8(_) => None, PlannerError::SelinuxEnforcing => Some(Box::new(self)), - PlannerError::Custom(_) => None, + PlannerError::Custom(e) => { + #[cfg(target_os = "linux")] + if let Some(err) = e.downcast_ref::() { + return err.expected(); + } + None + }, this @ PlannerError::NixOs => Some(Box::new(this)), this @ PlannerError::NixExists => Some(Box::new(this)), this @ PlannerError::Wsl1 => Some(Box::new(this)), diff --git a/src/settings.rs b/src/settings.rs index 8fb1e5e..138c030 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -24,7 +24,7 @@ pub const NIX_X64_64_DARWIN_URL: &str = pub const NIX_AARCH64_DARWIN_URL: &str = "https://releases.nixos.org/nix/nix-2.13.3/nix-2.13.3-aarch64-darwin.tar.xz"; -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy)] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "cli", derive(clap::ValueEnum))] pub enum InitSystem { #[cfg(not(target_os = "macos"))]