From e68699babea3324b72a0d2c890f66371925a9cfb Mon Sep 17 00:00:00 2001 From: Sander Date: Sun, 24 Nov 2024 15:37:26 +0400 Subject: [PATCH 1/4] devenv: indent cache error messages to disambiguate steps --- devenv/src/cnix.rs | 80 +++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/devenv/src/cnix.rs b/devenv/src/cnix.rs index e3b0fd865..9cb50c546 100644 --- a/devenv/src/cnix.rs +++ b/devenv/src/cnix.rs @@ -646,7 +646,7 @@ impl<'a> Nix<'a> { .trusted; if trusted.is_none() { self.logger.warn( - "You're using very old version of Nix, please upgrade and restart nix-daemon.", + "You're using an outdated version of Nix. Please upgrade and restart the nix-daemon.", ); } let restart_command = if cfg!(target_os = "linux") { @@ -676,64 +676,78 @@ impl<'a> Nix<'a> { if trusted == Some(0) { if !Path::new("/etc/NIXOS").exists() { self.logger.error(&indoc::formatdoc!( - "You're not a trusted user of the Nix store. You have the following options: + "Failed to set up binary caches. - a) Add yourself to the trusted-users list in /etc/nix/nix.conf for devenv to manage caches for you. + devenv is configured to automatically manage binary caches with `cachix.enable = true`, but cannot do so because you are not a trusted user of the Nix store. - trusted-users = root {} + You have two options: - Restart nix-daemon with: + a) Add yourself to the trusted-users list in /etc/nix/nix.conf to allow devenv to set up the caches for you: - $ {restart_command} + trusted-users = root {} - b) Add binary caches to /etc/nix/nix.conf yourself: + Then restart the nix-daemon: - extra-substituters = {} - extra-trusted-public-keys = {} + $ {restart_command} - And disable automatic cache configuration in `devenv.nix`: + b) Add the binary caches to /etc/nix/nix.conf yourself: - {{ - cachix.enable = false; - }} + extra-substituters = {} + extra-trusted-public-keys = {} + + Then disable automatic cache configuration in `devenv.nix`: + + {{ + cachix.enable = false; + }} ", whoami::username() , caches.caches.pull.iter().map(|cache| format!("https://{}.cachix.org", cache)).collect::>().join(" ") , caches.known_keys.values().cloned().collect::>().join(" ") )); } else { self.logger.error(&indoc::formatdoc!( - "You're not a trusted user of the Nix store. You have the following options: + "Failed to set up binary caches. + + devenv is configured to automatically manage binary caches with `cachix.enable = true`, but cannot do so because you are not a trusted user of the Nix store. + + You have two options: - a) Add yourself to the trusted-users list in /etc/nix/nix.conf by editing configuration.nix for devenv to manage caches for you. + a) Add yourself to the trusted-users list in /etc/nix/nix.conf by editing configuration.nix to let devenv set up the caches for you: - {{ - nix.extraOptions = '' - trusted-users = root {} - ''; - }} + {{ + nix.extraOptions = '' + trusted-users = root {} + ''; + }} - b) Add binary caches to /etc/nix/nix.conf yourself by editing configuration.nix: - {{ - nix.extraOptions = '' - extra-substituters = {} - extra-trusted-public-keys = {} - ''; - }} + Rebuild your system: - Disable automatic cache configuration in `devenv.nix`: + $ sudo nixos-rebuild switch - {{ - cachix.enable = false; - }} + b) Add the binary caches to /etc/nix/nix.conf yourself by editing configuration.nix: - Lastly, rebuild your system: + {{ + nix.extraOptions = '' + extra-substituters = {} + extra-trusted-public-keys = {} + ''; + }} - $ sudo nixos-rebuild switch + Disable automatic cache configuration in `devenv.nix`: + + {{ + cachix.enable = false; + }} + + Rebuild your system: + + $ sudo nixos-rebuild switch ", whoami::username() , caches.caches.pull.iter().map(|cache| format!("https://{}.cachix.org", cache)).collect::>().join(" ") , caches.known_keys.values().cloned().collect::>().join(" ") )); } + bail!("You're not a trusted user of the Nix store.") } } From e6deeef23c5221ee2d491ab5902e305a571df52d Mon Sep 17 00:00:00 2001 From: Sander Date: Sun, 24 Nov 2024 18:01:44 +0400 Subject: [PATCH 2/4] nix-conf-parser: implement a basic nix.conf parser --- Cargo.lock | 24 ++++++++-- Cargo.toml | 3 +- nix-conf-parser/Cargo.toml | 9 ++++ nix-conf-parser/src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 nix-conf-parser/Cargo.toml create mode 100644 nix-conf-parser/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3449868c5..0e8841dcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -973,13 +973,19 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1135,12 +1141,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.1", "serde", ] @@ -1361,6 +1367,14 @@ dependencies = [ "libc", ] +[[package]] +name = "nix-conf-parser" +version = "0.0.1" +dependencies = [ + "indexmap", + "thiserror", +] + [[package]] name = "nom" version = "7.1.3" @@ -2211,7 +2225,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index 8d3b48f2f..78663a3bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["devenv", "devenv-eval-cache", "devenv-run-tests", "devenv-tasks", "xtask"] +members = ["devenv", "devenv-eval-cache", "devenv-run-tests", "devenv-tasks", "nix-conf-parser", "xtask"] [workspace.package] edition = "2021" @@ -24,6 +24,7 @@ dotlock = "0.5.0" futures = "0.3.30" hex = "0.4.3" include_dir = "0.7.3" +indexmap = "2.6.0" indoc = "2.0.4" lazy_static = "1.5.0" miette = { version = "7.1.0", features = ["fancy"] } diff --git a/nix-conf-parser/Cargo.toml b/nix-conf-parser/Cargo.toml new file mode 100644 index 000000000..06cf56403 --- /dev/null +++ b/nix-conf-parser/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "nix-conf-parser" +version = "0.0.1" +edition.workspace = true +license.workspace = true + +[dependencies] +indexmap.workspace = true +thiserror.workspace = true diff --git a/nix-conf-parser/src/lib.rs b/nix-conf-parser/src/lib.rs new file mode 100644 index 000000000..a5e0bfa24 --- /dev/null +++ b/nix-conf-parser/src/lib.rs @@ -0,0 +1,97 @@ +use indexmap::IndexMap; +use thiserror::Error; + +#[derive(Debug)] +pub struct NixConf { + settings: IndexMap, +} + +impl NixConf { + /// Parse a string into an ordered map of key-value string pairs. + /// + /// Closely follows the upstream implementation: + /// https://github.com/NixOS/nix/blob/acb60fc3594edcc54dae9a10d2a0dc3f3b3be0da/src/libutil/config.cc#L104-L161 + /// + /// Only intended to work on the output of `nix config show`. + /// Therefore, this intentionally leaves out: + /// - includes and !includes + /// - comments + /// - formatting + pub fn parse(input: &str) -> Result { + let mut settings = IndexMap::new(); + + for mut line in input.lines() { + // Trim comments + if let Some(pos) = line.find('#') { + line = &line[..pos]; + } + + if line.trim().is_empty() { + continue; + } + + let mut tokens = line.split_whitespace().collect::>(); + tokens.retain(|t| !t.is_empty()); + + if tokens.is_empty() { + continue; + } + + if tokens.len() < 2 { + return Err(ParseError::IllegalConfiguration(line.to_string())); + } + + // Skip includes if they make it into the input + match tokens[0] { + "include" | "!include" => continue, + _ => {} + } + + if tokens[1] != "=" { + return Err(ParseError::IllegalConfiguration(line.to_string())); + } + + let name = tokens[0]; + let value = tokens[2..].join(" "); + + settings.insert(name.to_string(), value); + } + + Ok(Self { settings }) + } + + pub fn get(&self, key: &str) -> Option<&String> { + self.settings.get(key) + } +} + +#[derive(Debug, Error)] +pub enum ParseError { + #[error("illegal configuration line '{0}'")] + IllegalConfiguration(String), +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse() { + let input = r#" + # This is a comment + include /etc/nixos/hardware-configuration.nix + !include /etc/nixos/hardware-configuration.nix + single = foo + space = foo bar + list = foo bar baz + comment = foo # comment + tab = foo + "#; + let nix_conf = NixConf::parse(input).unwrap(); + assert_eq!(nix_conf.get("single"), Some(&"foo".into())); + assert_eq!(nix_conf.get("space"), Some(&"foo bar".into())); + assert_eq!(nix_conf.get("list"), Some(&"foo bar baz".into())); + assert_eq!(nix_conf.get("comment"), Some(&"foo".into())); + assert_eq!(nix_conf.get("tab"), Some(&"foo".into())); + } +} From a01689a0c7c763f8d82d96869014906545e98b4c Mon Sep 17 00:00:00 2001 From: Sander Date: Sun, 24 Nov 2024 18:56:26 +0400 Subject: [PATCH 3/4] devenv: parse nix config to skip cache error if they're already set up --- Cargo.lock | 2 ++ Cargo.toml | 1 + devenv/Cargo.toml | 1 + devenv/src/cnix.rs | 66 ++++++++++++++++++++++++++++++++------ nix-conf-parser/Cargo.toml | 1 + nix-conf-parser/src/lib.rs | 31 +++++++++++------- package.nix | 1 + 7 files changed, 81 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e8841dcf..e102c7898 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,6 +546,7 @@ dependencies = [ "indoc", "miette", "nix", + "nix-conf-parser", "petgraph", "regex", "reqwest", @@ -1372,6 +1373,7 @@ name = "nix-conf-parser" version = "0.0.1" dependencies = [ "indexmap", + "miette", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 78663a3bd..43be1d6e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ devenv = { path = "devenv" } devenv-eval-cache = { path = "devenv-eval-cache" } devenv-run-tests = { path = "devenv-run-tests" } devenv-tasks = { path = "devenv-tasks" } +nix-conf-parser = { path = "nix-conf-parser" } xtask = { path = "xtask" } ansiterm = "0.12.2" diff --git a/devenv/Cargo.toml b/devenv/Cargo.toml index 4c088f42b..89d4f9755 100644 --- a/devenv/Cargo.toml +++ b/devenv/Cargo.toml @@ -12,6 +12,7 @@ default-run = "devenv" [dependencies] devenv-eval-cache.workspace = true devenv-tasks.workspace = true +nix-conf-parser.workspace = true clap.workspace = true cli-table.workspace = true diff --git a/devenv/src/cnix.rs b/devenv/src/cnix.rs index 9cb50c546..aa53713da 100644 --- a/devenv/src/cnix.rs +++ b/devenv/src/cnix.rs @@ -1,5 +1,6 @@ use crate::{cli, config, log}; use miette::{bail, IntoDiagnostic, Result, WrapErr}; +use nix_conf_parser::NixConf; use serde::Deserialize; use sqlx::SqlitePool; use std::cell::{Ref, RefCell}; @@ -588,6 +589,16 @@ impl<'a> Nix<'a> { Ok(cmd) } + async fn get_nix_config(&self) -> Result { + let options = Options { + logging: false, + ..self.options + }; + let raw_conf = self.run_nix("nix", &["config", "show"], &options).await?; + let nix_conf = NixConf::parse_stdout(&raw_conf.stdout)?; + Ok(nix_conf) + } + async fn get_cachix_caches(&self) -> Result> { if self.cachix_caches.borrow().is_none() { let no_logging = Options { @@ -673,9 +684,43 @@ impl<'a> Nix<'a> { ) .expect("Failed to write cachix caches to file"); + // If the user is not a trusted user, we can't set up the caches for them. + // Check if all of the requested caches and their public keys are in the substituters and trusted-public-keys lists. + // If not, suggest actions to remedy the issue. if trusted == Some(0) { - if !Path::new("/etc/NIXOS").exists() { - self.logger.error(&indoc::formatdoc!( + let mut missing_caches = Vec::new(); + let mut missing_public_keys = Vec::new(); + + if let Ok(nix_conf) = self.get_nix_config().await { + let substituters = nix_conf + .get("substituters") + .map(|s| s.split_whitespace().collect::>()); + + if let Some(substituters) = substituters { + for cache in caches.caches.pull.iter() { + let cache_url = format!("https://{}.cachix.org", cache); + if !substituters.iter().any(|s| s == &cache_url) { + missing_caches.push(cache_url); + } + } + } + + let trusted_public_keys = nix_conf + .get("trusted-public-keys") + .map(|s| s.split_whitespace().collect::>()); + + if let Some(trusted_public_keys) = trusted_public_keys { + for (_name, key) in caches.known_keys.iter() { + if !trusted_public_keys.iter().any(|p| p == key) { + missing_public_keys.push(key.clone()); + } + } + } + } + + if !missing_caches.is_empty() || !missing_public_keys.is_empty() { + if !Path::new("/etc/NIXOS").exists() { + self.logger.error(&indoc::formatdoc!( "Failed to set up binary caches. devenv is configured to automatically manage binary caches with `cachix.enable = true`, but cannot do so because you are not a trusted user of the Nix store. @@ -701,11 +746,11 @@ impl<'a> Nix<'a> { cachix.enable = false; }} ", whoami::username() - , caches.caches.pull.iter().map(|cache| format!("https://{}.cachix.org", cache)).collect::>().join(" ") - , caches.known_keys.values().cloned().collect::>().join(" ") + , missing_caches.join(" ") + , missing_public_keys.join(" ") )); - } else { - self.logger.error(&indoc::formatdoc!( + } else { + self.logger.error(&indoc::formatdoc!( "Failed to set up binary caches. devenv is configured to automatically manage binary caches with `cachix.enable = true`, but cannot do so because you are not a trusted user of the Nix store. @@ -743,12 +788,13 @@ impl<'a> Nix<'a> { $ sudo nixos-rebuild switch ", whoami::username() - , caches.caches.pull.iter().map(|cache| format!("https://{}.cachix.org", cache)).collect::>().join(" ") - , caches.known_keys.values().cloned().collect::>().join(" ") + , missing_caches.join(" ") + , missing_public_keys.join(" ") )); - } + } - bail!("You're not a trusted user of the Nix store.") + bail!("You're not a trusted user of the Nix store.") + } } } diff --git a/nix-conf-parser/Cargo.toml b/nix-conf-parser/Cargo.toml index 06cf56403..7fc922e8d 100644 --- a/nix-conf-parser/Cargo.toml +++ b/nix-conf-parser/Cargo.toml @@ -6,4 +6,5 @@ license.workspace = true [dependencies] indexmap.workspace = true +miette.workspace = true thiserror.workspace = true diff --git a/nix-conf-parser/src/lib.rs b/nix-conf-parser/src/lib.rs index a5e0bfa24..ba155cfca 100644 --- a/nix-conf-parser/src/lib.rs +++ b/nix-conf-parser/src/lib.rs @@ -1,4 +1,15 @@ +/// Parse a nix.conf into an ordered map of key-value string pairs. +/// +/// Closely follows the upstream implementation: +/// https://github.com/NixOS/nix/blob/acb60fc3594edcc54dae9a10d2a0dc3f3b3be0da/src/libutil/config.cc#L104-L161 +/// +/// Only intended to work on the output of `nix config show`. +/// Therefore, this intentionally leaves out: +/// - includes and !includes +/// - comments +/// - formatting use indexmap::IndexMap; +use miette::Diagnostic; use thiserror::Error; #[derive(Debug)] @@ -7,17 +18,13 @@ pub struct NixConf { } impl NixConf { + pub fn parse_stdout(input: &[u8]) -> Result { + let input = String::from_utf8_lossy(input); + Self::parse_str(&input) + } + /// Parse a string into an ordered map of key-value string pairs. - /// - /// Closely follows the upstream implementation: - /// https://github.com/NixOS/nix/blob/acb60fc3594edcc54dae9a10d2a0dc3f3b3be0da/src/libutil/config.cc#L104-L161 - /// - /// Only intended to work on the output of `nix config show`. - /// Therefore, this intentionally leaves out: - /// - includes and !includes - /// - comments - /// - formatting - pub fn parse(input: &str) -> Result { + pub fn parse_str(input: &str) -> Result { let mut settings = IndexMap::new(); for mut line in input.lines() { @@ -65,7 +72,7 @@ impl NixConf { } } -#[derive(Debug, Error)] +#[derive(Debug, Diagnostic, Error)] pub enum ParseError { #[error("illegal configuration line '{0}'")] IllegalConfiguration(String), @@ -87,7 +94,7 @@ mod test { comment = foo # comment tab = foo "#; - let nix_conf = NixConf::parse(input).unwrap(); + let nix_conf = NixConf::parse_str(input).unwrap(); assert_eq!(nix_conf.get("single"), Some(&"foo".into())); assert_eq!(nix_conf.get("space"), Some(&"foo bar".into())); assert_eq!(nix_conf.get("list"), Some(&"foo bar baz".into())); diff --git a/package.nix b/package.nix index 2909442a1..7d22c4226 100644 --- a/package.nix +++ b/package.nix @@ -14,6 +14,7 @@ pkgs.rustPlatform.buildRustPackage { ".*devenv-eval-cache(/.*)?" ".*devenv-run-tests(/.*)?" ".*devenv-tasks(/.*)?" + ".*nix-conf-parser(/.*)?" ".*xtask(/.*)?" ]; From 8056ceb68ff9adc72c3d5349936a616ae4b52ae0 Mon Sep 17 00:00:00 2001 From: Sander Date: Sun, 24 Nov 2024 19:32:39 +0400 Subject: [PATCH 4/4] devenv: improve cache setup warnings --- devenv/src/cnix.rs | 68 +++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/devenv/src/cnix.rs b/devenv/src/cnix.rs index aa53713da..e39f8339d 100644 --- a/devenv/src/cnix.rs +++ b/devenv/src/cnix.rs @@ -721,73 +721,79 @@ impl<'a> Nix<'a> { if !missing_caches.is_empty() || !missing_public_keys.is_empty() { if !Path::new("/etc/NIXOS").exists() { self.logger.error(&indoc::formatdoc!( - "Failed to set up binary caches. + "Failed to set up binary caches: + + {} devenv is configured to automatically manage binary caches with `cachix.enable = true`, but cannot do so because you are not a trusted user of the Nix store. - You have two options: + You have several options: - a) Add yourself to the trusted-users list in /etc/nix/nix.conf to allow devenv to set up the caches for you: + a) To let devenv set up the caches for you, add yourself to the trusted-users list in /etc/nix/nix.conf: - trusted-users = root {} + trusted-users = root {} Then restart the nix-daemon: - $ {restart_command} + $ {restart_command} - b) Add the binary caches to /etc/nix/nix.conf yourself: + b) Add the missing binary caches to /etc/nix/nix.conf yourself: extra-substituters = {} extra-trusted-public-keys = {} - Then disable automatic cache configuration in `devenv.nix`: + c) Disable automatic cache management in your devenv configuration: - {{ + {{ cachix.enable = false; - }} - ", whoami::username() + }} + " + , missing_caches.join(" ") + , whoami::username() , missing_caches.join(" ") , missing_public_keys.join(" ") )); } else { self.logger.error(&indoc::formatdoc!( - "Failed to set up binary caches. + "Failed to set up binary caches: + + {} devenv is configured to automatically manage binary caches with `cachix.enable = true`, but cannot do so because you are not a trusted user of the Nix store. - You have two options: + You have several options: - a) Add yourself to the trusted-users list in /etc/nix/nix.conf by editing configuration.nix to let devenv set up the caches for you: + a) To let devenv set up the caches for you, add yourself to the trusted-users list in /etc/nix/nix.conf by editing configuration.nix. - {{ - nix.extraOptions = '' - trusted-users = root {} - ''; - }} + {{ + nix.settings.trusted-users = [ \"root\" \"{}\" ]; + }} Rebuild your system: - $ sudo nixos-rebuild switch + $ sudo nixos-rebuild switch - b) Add the binary caches to /etc/nix/nix.conf yourself by editing configuration.nix: + b) Add the missing binary caches to /etc/nix/nix.conf yourself by editing configuration.nix: - {{ + {{ nix.extraOptions = '' - extra-substituters = {} - extra-trusted-public-keys = {} + extra-substituters = {} + extra-trusted-public-keys = {} ''; - }} + }} - Disable automatic cache configuration in `devenv.nix`: + Rebuild your system: - {{ - cachix.enable = false; - }} + $ sudo nixos-rebuild switch - Rebuild your system: + c) Disable automatic cache management in your devenv configuration: - $ sudo nixos-rebuild switch - ", whoami::username() + {{ + cachix.enable = false; + }} + " + , missing_caches.join(" ") + , whoami::username() , missing_caches.join(" ") , missing_public_keys.join(" ") ));