use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema,
)]
#[schemars(rename = "machine.MachineIdentity")]
pub struct MachineIdentity {
pub id: String,
pub os: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub hostname: Option<String>,
}
#[cfg(any(feature = "lockfile", feature = "machine"))]
pub fn machine_identity(objectiveai_dir: &std::path::Path) -> MachineIdentity {
MachineIdentity {
id: machine_id(objectiveai_dir),
os: std::env::consts::OS.to_string(),
hostname: hostname::get()
.ok()
.and_then(|h| h.into_string().ok())
.filter(|h| !h.is_empty()),
}
}
#[cfg(any(feature = "lockfile", feature = "machine"))]
pub fn machine_id(objectiveai_dir: &std::path::Path) -> String {
use sha2::{Digest, Sha256};
let raw = raw_machine_id()
.or_else(|| persisted_machine_id(objectiveai_dir))
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let mut hasher = Sha256::new();
hasher.update(b"objectiveai-machine-id");
hasher.update(raw.trim().as_bytes());
hex::encode(hasher.finalize())
}
#[cfg(all(any(feature = "lockfile", feature = "machine"), target_os = "linux"))]
fn raw_machine_id() -> Option<String> {
["/etc/machine-id", "/var/lib/dbus/machine-id"]
.iter()
.find_map(|p| std::fs::read_to_string(p).ok())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
#[cfg(all(any(feature = "lockfile", feature = "machine"), target_os = "macos"))]
fn raw_machine_id() -> Option<String> {
let output = std::process::Command::new("ioreg")
.args(["-rd1", "-c", "IOPlatformExpertDevice"])
.output()
.ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let line = stdout.lines().find(|l| l.contains("IOPlatformUUID"))?;
let value = line.split('"').nth(3)?;
(!value.is_empty()).then(|| value.to_string())
}
#[cfg(all(any(feature = "lockfile", feature = "machine"), windows))]
fn raw_machine_id() -> Option<String> {
use windows_sys::Win32::System::Registry::{
HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ, RegGetValueW,
};
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
let subkey = wide("SOFTWARE\\Microsoft\\Cryptography");
let value = wide("MachineGuid");
let mut buf = [0u16; 128];
let mut size = (buf.len() * 2) as u32;
let rc = unsafe {
RegGetValueW(
HKEY_LOCAL_MACHINE,
subkey.as_ptr(),
value.as_ptr(),
RRF_RT_REG_SZ,
std::ptr::null_mut(),
buf.as_mut_ptr() as *mut _,
&mut size,
)
};
if rc != 0 {
return None;
}
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
let guid = String::from_utf16_lossy(&buf[..len]);
(!guid.trim().is_empty()).then(|| guid.trim().to_string())
}
#[cfg(all(
any(feature = "lockfile", feature = "machine"),
not(any(target_os = "linux", target_os = "macos", windows))
))]
fn raw_machine_id() -> Option<String> {
None
}
#[cfg(any(feature = "lockfile", feature = "machine"))]
fn persisted_machine_id(objectiveai_dir: &std::path::Path) -> Option<String> {
let path = objectiveai_dir.join("bin").join("machine-id");
if let Ok(existing) = std::fs::read_to_string(&path) {
let existing = existing.trim();
if !existing.is_empty() {
return Some(existing.to_string());
}
}
let fresh = uuid::Uuid::new_v4().to_string();
std::fs::create_dir_all(path.parent()?).ok()?;
std::fs::write(&path, &fresh).ok()?;
Some(fresh)
}