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
This commit is contained in:
Ana Hobden 2023-04-10 16:16:46 -07:00 committed by GitHub
parent cb48a7261b
commit 75627bcd91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 38 deletions

View file

@ -106,7 +106,17 @@ impl CommandExecute for Install {
return Ok(ExitCode::FAILURE) return Ok(ExitCode::FAILURE)
} , } ,
None => { 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)?;
}
}
}, },
} }
}, },

View file

@ -4,9 +4,10 @@ use crate::{
common::{ConfigureInitService, ConfigureNix, ProvisionNix}, common::{ConfigureInitService, ConfigureNix, ProvisionNix},
StatefulAction, StatefulAction,
}, },
error::HasExpectedErrors,
planner::{Planner, PlannerError}, planner::{Planner, PlannerError},
settings::CommonSettings, settings::CommonSettings,
settings::{InitSettings, InstallSettingsError}, settings::{InitSettings, InitSystem, InstallSettingsError},
Action, BuiltinPlanner, Action, BuiltinPlanner,
}; };
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
@ -35,42 +36,16 @@ impl Planner for Linux {
} }
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> { async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
// If on NixOS, running `nix_installer` is pointless check_not_nixos()?;
// 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);
}
if std::env::var("WSL_DISTRO_NAME").is_ok() && std::env::var("WSL_INTEROP").is_err() { check_nix_not_already_installed().await?;
return Err(PlannerError::Wsl1);
}
// We currently do not support SELinux check_not_wsl1()?;
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`")
},
}
// For now, we don't try to repair the user's Nix install or anything special. check_not_selinux().await?;
if let Ok(_) = Command::new("nix-env")
.arg("--version") if self.init.init == InitSystem::Systemd && self.init.start_daemon {
.stdin(std::process::Stdio::null()) check_systemd_active()?;
.status()
.await
{
return Err(PlannerError::NixExists);
} }
Ok(vec![ Ok(vec![
@ -146,3 +121,108 @@ impl Into<BuiltinPlanner> for Linux {
BuiltinPlanner::Linux(self) 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<Box<dyn std::error::Error + 'a>> {
match self {
LinuxErrorKind::SystemdNotActive => Some(Box::new(self)),
LinuxErrorKind::Wsl2SystemdNotActive => Some(Box::new(self)),
}
}
}
impl From<LinuxErrorKind> for PlannerError {
fn from(v: LinuxErrorKind) -> PlannerError {
PlannerError::Custom(Box::new(v))
}
}

View file

@ -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)")] #[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, RosettaDetected,
/// A Linux SELinux related error /// 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, SelinuxEnforcing,
/// A UTF-8 related error /// A UTF-8 related error
#[error("UTF-8 error")] #[error("UTF-8 error")]
@ -397,7 +402,13 @@ impl HasExpectedErrors for PlannerError {
this @ PlannerError::RosettaDetected => Some(Box::new(this)), this @ PlannerError::RosettaDetected => Some(Box::new(this)),
PlannerError::Utf8(_) => None, PlannerError::Utf8(_) => None,
PlannerError::SelinuxEnforcing => Some(Box::new(self)), PlannerError::SelinuxEnforcing => Some(Box::new(self)),
PlannerError::Custom(_) => None, PlannerError::Custom(e) => {
#[cfg(target_os = "linux")]
if let Some(err) = e.downcast_ref::<linux::LinuxErrorKind>() {
return err.expected();
}
None
},
this @ PlannerError::NixOs => Some(Box::new(this)), this @ PlannerError::NixOs => Some(Box::new(this)),
this @ PlannerError::NixExists => Some(Box::new(this)), this @ PlannerError::NixExists => Some(Box::new(this)),
this @ PlannerError::Wsl1 => Some(Box::new(this)), this @ PlannerError::Wsl1 => Some(Box::new(this)),

View file

@ -24,7 +24,7 @@ pub const NIX_X64_64_DARWIN_URL: &str =
pub const NIX_AARCH64_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"; "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))] #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
pub enum InitSystem { pub enum InitSystem {
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]