nixfleet_agent/host_facts/
linux.rs

1//! Linux/NixOS impl: `/proc` + `/run/booted-system`.
2
3use std::fs;
4
5use anyhow::{Context, Result};
6use nixfleet_proto::agent_wire::PendingGeneration;
7
8use super::{closure_hash_from_path, current_closure_hash};
9
10const BOOTED_SYSTEM: &str = "/run/booted-system";
11const BOOT_ID_PATH: &str = "/proc/sys/kernel/random/boot_id";
12
13pub fn boot_id() -> Result<String> {
14    let raw = fs::read_to_string(BOOT_ID_PATH).with_context(|| format!("read {BOOT_ID_PATH}"))?;
15    Ok(raw.trim().to_string())
16}
17
18pub fn pending_generation() -> Result<Option<PendingGeneration>> {
19    let current = current_closure_hash()?;
20    let booted = booted_closure_hash()?;
21    if current == booted {
22        return Ok(None);
23    }
24    Ok(Some(PendingGeneration {
25        closure_hash: current,
26    }))
27}
28
29fn booted_closure_hash() -> Result<String> {
30    let target =
31        fs::read_link(BOOTED_SYSTEM).with_context(|| format!("readlink {BOOTED_SYSTEM}"))?;
32    Ok(closure_hash_from_path(&target))
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn boot_id_returns_a_non_empty_string() {
41        let id = boot_id().expect("boot_id() must succeed on linux");
42        assert!(!id.is_empty(), "boot_id() returned an empty string");
43    }
44
45    #[test]
46    fn boot_id_is_stable_within_a_process() {
47        let a = boot_id().unwrap();
48        let b = boot_id().unwrap();
49        assert_eq!(a, b, "boot_id must be stable within the running process");
50    }
51}