nixfleet_agent/
host_facts.rs

1//! Per-host OS primitives (`boot_id`, `pending_generation`); cfg-gated re-export.
2
3use std::path::Path;
4
5use anyhow::{Context, Result};
6use nixfleet_proto::agent_wire::GenerationRef;
7
8#[cfg(target_os = "macos")]
9mod darwin;
10#[cfg(target_os = "linux")]
11mod linux;
12
13#[cfg(target_os = "macos")]
14pub use darwin::{boot_id, pending_generation};
15#[cfg(target_os = "linux")]
16pub use linux::{boot_id, pending_generation};
17
18/// `/run/current-system` symlink target's basename = the activated
19/// system's closure hash. The agent doesn't trust CP's "what closure
20/// did you activate" reads; it always reports what the OS says.
21///
22/// Moved out of the deleted `checkin_state` module so `host_facts`
23/// stays self-contained.
24pub const CURRENT_SYSTEM: &str = "/run/current-system";
25
26pub fn current_closure_hash() -> Result<String> {
27    let target =
28        std::fs::read_link(CURRENT_SYSTEM).with_context(|| format!("readlink {CURRENT_SYSTEM}"))?;
29    Ok(closure_hash_from_path(&target))
30}
31
32/// FOOTGUN: returns full basename, NOT 32-char prefix — byte-equality
33/// required across CP / CI / agent.
34pub(crate) fn closure_hash_from_path(p: &Path) -> String {
35    let s = p.to_string_lossy();
36    s.rsplit('/')
37        .next()
38        .map(str::to_string)
39        .unwrap_or_else(|| s.to_string())
40}
41
42/// `channel_ref` is `None` until the projection correlates it.
43pub fn current_generation_ref() -> Result<GenerationRef> {
44    Ok(GenerationRef {
45        closure_hash: current_closure_hash()?,
46        channel_ref: None,
47        boot_id: boot_id()?,
48    })
49}