nixfleet_agent/activation/
realise.rs

1//! `nix-store --realise` wrapper. Signature-error detection is a stderr
2//! string match because nix has no distinct exit code for trust-failure;
3//! per-phrasing tests guard against silent downgrade to RealiseFailed.
4
5use anyhow::{Context, anyhow};
6use tokio::process::Command;
7
8pub enum RealiseError {
9    SignatureMismatch { stderr_tail: String },
10    Other(anyhow::Error),
11}
12
13impl From<anyhow::Error> for RealiseError {
14    fn from(err: anyhow::Error) -> Self {
15        RealiseError::Other(err)
16    }
17}
18
19/// Covers 2.18+ stable phrasings plus legacy 2.x.
20pub fn looks_like_signature_error(stderr: &str) -> bool {
21    let lower = stderr.to_lowercase();
22    [
23        "lacks a valid signature",
24        "no signature is trusted",
25        "is not signed by any of the keys",
26        "no signatures matched",
27        "signature mismatch",
28        "untrusted signature",
29    ]
30    .iter()
31    .any(|needle| lower.contains(needle))
32}
33
34pub(super) async fn realise(store_path: &str) -> Result<String, RealiseError> {
35    let output = Command::new("nix-store")
36        .arg("--realise")
37        .arg(store_path)
38        .output()
39        .await
40        .with_context(|| format!("spawn nix-store --realise {store_path}"))?;
41
42    if !output.status.success() {
43        let stderr = String::from_utf8_lossy(&output.stderr);
44        if looks_like_signature_error(&stderr) {
45            let tail_start = stderr.len().saturating_sub(500);
46            let tail = stderr[tail_start..].to_string();
47            return Err(RealiseError::SignatureMismatch { stderr_tail: tail });
48        }
49        return Err(anyhow!(
50            "nix-store --realise {store_path} exited {:?}: {stderr}",
51            output.status.code()
52        )
53        .into());
54    }
55
56    let stdout = String::from_utf8(output.stdout)
57        .map_err(|e| anyhow!("nix-store --realise stdout not utf-8: {e}"))?;
58    let line = stdout
59        .lines()
60        .next()
61        .ok_or_else(|| anyhow!("nix-store --realise produced no output"))?;
62    Ok(line.trim().to_string())
63}