diff --git a/devenv/src/cnix.rs b/devenv/src/cnix.rs index 7f881f735..bfdfec575 100644 --- a/devenv/src/cnix.rs +++ b/devenv/src/cnix.rs @@ -607,17 +607,23 @@ impl<'a> Nix<'a> { ..self.options }; let caches_raw = self.eval(&["devenv.cachix"]).await?; - let cachix = serde_json::from_str(&caches_raw).expect("Failed to parse JSON"); - let known_keys = if let Ok(known_keys) = - std::fs::read_to_string(self.cachix_trusted_keys.as_path()) - { - serde_json::from_str(&known_keys).expect("Failed to parse JSON") - } else { - HashMap::new() - }; + let cachix_config: CachixConfig = + serde_json::from_str(&caches_raw).expect("Failed to parse JSON"); + + // Return empty caches if the Cachix integration is disabled + if !cachix_config.enable { + *self.cachix_caches.borrow_mut() = Some(CachixCaches::default()); + return Ok(Ref::map(self.cachix_caches.borrow(), |option| { + option.as_ref().unwrap() + })); + } + + let known_keys = std::fs::read_to_string(self.cachix_trusted_keys.as_path()) + .map(|known_keys| serde_json::from_str(&known_keys).expect("Failed to parse JSON")) + .unwrap_or_default(); let mut caches = CachixCaches { - caches: cachix, + caches: cachix_config.caches, known_keys, }; @@ -626,7 +632,7 @@ impl<'a> Nix<'a> { for name in caches.caches.pull.iter() { if !caches.known_keys.contains_key(name) { let mut request = - client.get(&format!("https://cachix.org/api/v1/cache/{}", name)); + client.get(format!("https://cachix.org/api/v1/cache/{}", name)); if let Ok(ret) = env::var("CACHIX_AUTH_TOKEN") { request = request.bearer_auth(ret); } @@ -691,35 +697,11 @@ impl<'a> Nix<'a> { // 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) { - 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()); - } - } - } - } + let (missing_caches, missing_public_keys) = self + .get_nix_config() + .await + .map(|nix_conf| detect_missing_caches(&caches, nix_conf)) + .unwrap_or_default(); if !missing_caches.is_empty() || !missing_public_keys.is_empty() { if !Path::new("/etc/NIXOS").exists() { @@ -857,13 +839,21 @@ fn display_command(cmd: &std::process::Command) -> String { format!("{command} {args}") } -#[derive(Deserialize, Clone)] +/// The Cachix module configuration +#[derive(Deserialize, Default, Clone)] +pub struct CachixConfig { + enable: bool, + #[serde(flatten)] + caches: Cachix, +} + +#[derive(Deserialize, Default, Clone)] pub struct Cachix { - pull: Vec, - push: Option, + pub pull: Vec, + pub push: Option, } -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Default, Clone)] pub struct CachixCaches { caches: Cachix, known_keys: HashMap, @@ -880,6 +870,50 @@ struct StorePing { trusted: Option, } +fn detect_missing_caches(caches: &CachixCaches, nix_conf: NixConf) -> (Vec, Vec) { + let mut missing_caches = Vec::new(); + let mut missing_public_keys = Vec::new(); + + let substituters = nix_conf + .get("substituters") + .map(|s| s.split_whitespace().collect::>()); + let extra_substituters = nix_conf + .get("extra-substituters") + .map(|s| s.split_whitespace().collect::>()); + let all_substituters = substituters + .into_iter() + .flatten() + .chain(extra_substituters.into_iter().flatten()) + .collect::>(); + + for cache in caches.caches.pull.iter() { + let cache_url = format!("https://{}.cachix.org", cache); + if !all_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::>()); + let extra_trusted_public_keys = nix_conf + .get("extra-trusted-public-keys") + .map(|s| s.split_whitespace().collect::>()); + let all_trusted_public_keys = trusted_public_keys + .into_iter() + .flatten() + .chain(extra_trusted_public_keys.into_iter().flatten()) + .collect::>(); + + for (_name, key) in caches.known_keys.iter() { + if !all_trusted_public_keys.iter().any(|p| p == key) { + missing_public_keys.push(key.clone()); + } + } + + (missing_caches, missing_public_keys) +} + #[cfg(test)] mod tests { use super::*; @@ -904,4 +938,58 @@ mod tests { let store_ping: StorePing = serde_json::from_str(store_ping).unwrap(); assert_eq!(store_ping.trusted, Some(0)); } + + #[test] + fn test_missing_substituters() { + let mut cachix = CachixCaches::default(); + cachix.caches.pull = vec!["cache1".to_string(), "cache2".to_string()]; + cachix + .known_keys + .insert("cache1".to_string(), "key1".to_string()); + cachix + .known_keys + .insert("cache2".to_string(), "key2".to_string()); + let nix_conf = NixConf::parse_stdout( + r#" + substituters = https://cache1.cachix.org https://cache3.cachix.org + trusted-public-keys = key1 key3 + "# + .as_bytes(), + ) + .expect("Failed to parse NixConf"); + assert_eq!( + detect_missing_caches(&cachix, nix_conf), + ( + vec!["https://cache2.cachix.org".to_string()], + vec!["key2".to_string()] + ) + ); + } + + #[test] + fn test_extra_missing_substituters() { + let mut cachix = CachixCaches::default(); + cachix.caches.pull = vec!["cache1".to_string(), "cache2".to_string()]; + cachix + .known_keys + .insert("cache1".to_string(), "key1".to_string()); + cachix + .known_keys + .insert("cache2".to_string(), "key2".to_string()); + let nix_conf = NixConf::parse_stdout( + r#" + extra-substituters = https://cache1.cachix.org https://cache3.cachix.org + extra-trusted-public-keys = key1 key3 + "# + .as_bytes(), + ) + .expect("Failed to parse NixConf"); + assert_eq!( + detect_missing_caches(&cachix, nix_conf), + ( + vec!["https://cache2.cachix.org".to_string()], + vec!["key2".to_string()] + ) + ); + } } diff --git a/docs/reference/options.md b/docs/reference/options.md index f3c20806d..a6b69ac0f 100644 --- a/docs/reference/options.md +++ b/docs/reference/options.md @@ -923,7 +923,7 @@ package -What caches to pull from. +Which Cachix caches to pull from. @@ -933,7 +933,7 @@ list of string *Default:* -` [ ] ` +` [ "devenv" ] ` *Declared by:* - [https://github.com/cachix/devenv/blob/main/src/modules/cachix.nix](https://github.com/cachix/devenv/blob/main/src/modules/cachix.nix) @@ -944,7 +944,7 @@ list of string -What cache to push to. Automatically also adds it to the list of caches to pull from. +Which Cachix cache to push to. This cache is also added to ` cachix.pull `. diff --git a/src/modules/cachix.nix b/src/modules/cachix.nix index 054e084fb..f1e087615 100644 --- a/src/modules/cachix.nix +++ b/src/modules/cachix.nix @@ -12,13 +12,14 @@ in pull = lib.mkOption { type = lib.types.listOf lib.types.str; - description = "What caches to pull from."; + description = "Which Cachix caches to pull from."; default = [ ]; + defaultText = lib.literalExpression ''[ "devenv" ]''; }; push = lib.mkOption { type = lib.types.nullOr lib.types.str; - description = "What cache to push to. Automatically also adds it to the list of caches to pull from."; + description = "Which Cachix cache to push to. This cache is also added to `cachix.pull`."; default = null; }; @@ -30,7 +31,7 @@ in config = lib.mkIf cfg.enable { cachix.pull = [ "devenv" ] - ++ (lib.optionals (cfg.push != null) [ config.cachix.push ]); + ++ (lib.optional (cfg.push != null) config.cachix.push); warnings = lib.optionals (!config.devenv.flakesIntegration && lib.versionOlder config.devenv.cliVersion "1.0") [ ''