objectiveai_sdk/machine/
machine.rs1use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22
23#[derive(
26 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema,
27)]
28#[schemars(rename = "machine.MachineIdentity")]
29pub struct MachineIdentity {
30 pub id: String,
33 pub os: String,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
38 #[schemars(extend("omitempty" = true))]
39 pub hostname: Option<String>,
40}
41
42#[cfg(feature = "lockfile")]
45pub fn machine_identity(objectiveai_dir: &std::path::Path) -> MachineIdentity {
46 MachineIdentity {
47 id: machine_id(objectiveai_dir),
48 os: std::env::consts::OS.to_string(),
49 hostname: hostname::get()
50 .ok()
51 .and_then(|h| h.into_string().ok())
52 .filter(|h| !h.is_empty()),
53 }
54}
55
56#[cfg(feature = "lockfile")]
58pub fn machine_id(objectiveai_dir: &std::path::Path) -> String {
59 use sha2::{Digest, Sha256};
60 let raw = raw_machine_id()
61 .or_else(|| persisted_machine_id(objectiveai_dir))
62 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
66 let mut hasher = Sha256::new();
67 hasher.update(b"objectiveai-machine-id");
68 hasher.update(raw.trim().as_bytes());
69 hex::encode(hasher.finalize())
70}
71
72#[cfg(all(feature = "lockfile", target_os = "linux"))]
74fn raw_machine_id() -> Option<String> {
75 ["/etc/machine-id", "/var/lib/dbus/machine-id"]
76 .iter()
77 .find_map(|p| std::fs::read_to_string(p).ok())
78 .map(|s| s.trim().to_string())
79 .filter(|s| !s.is_empty())
80}
81
82#[cfg(all(feature = "lockfile", target_os = "macos"))]
83fn raw_machine_id() -> Option<String> {
84 let output = std::process::Command::new("ioreg")
91 .args(["-rd1", "-c", "IOPlatformExpertDevice"])
92 .output()
93 .ok()?;
94 let stdout = String::from_utf8_lossy(&output.stdout);
95 let line = stdout.lines().find(|l| l.contains("IOPlatformUUID"))?;
96 let value = line.split('"').nth(3)?;
97 (!value.is_empty()).then(|| value.to_string())
98}
99
100#[cfg(all(feature = "lockfile", windows))]
101fn raw_machine_id() -> Option<String> {
102 use windows_sys::Win32::System::Registry::{
106 HKEY_LOCAL_MACHINE, RRF_RT_REG_SZ, RegGetValueW,
107 };
108 fn wide(s: &str) -> Vec<u16> {
109 s.encode_utf16().chain(std::iter::once(0)).collect()
110 }
111 let subkey = wide("SOFTWARE\\Microsoft\\Cryptography");
112 let value = wide("MachineGuid");
113 let mut buf = [0u16; 128];
114 let mut size = (buf.len() * 2) as u32;
115 let rc = unsafe {
116 RegGetValueW(
117 HKEY_LOCAL_MACHINE,
118 subkey.as_ptr(),
119 value.as_ptr(),
120 RRF_RT_REG_SZ,
121 std::ptr::null_mut(),
122 buf.as_mut_ptr() as *mut _,
123 &mut size,
124 )
125 };
126 if rc != 0 {
127 return None;
128 }
129 let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
130 let guid = String::from_utf16_lossy(&buf[..len]);
131 (!guid.trim().is_empty()).then(|| guid.trim().to_string())
132}
133
134#[cfg(all(
135 feature = "lockfile",
136 not(any(target_os = "linux", target_os = "macos", windows))
137))]
138fn raw_machine_id() -> Option<String> {
139 None
140}
141
142#[cfg(feature = "lockfile")]
144fn persisted_machine_id(objectiveai_dir: &std::path::Path) -> Option<String> {
145 let path = objectiveai_dir.join("bin").join("machine-id");
146 if let Ok(existing) = std::fs::read_to_string(&path) {
147 let existing = existing.trim();
148 if !existing.is_empty() {
149 return Some(existing.to_string());
150 }
151 }
152 let fresh = uuid::Uuid::new_v4().to_string();
153 std::fs::create_dir_all(path.parent()?).ok()?;
154 std::fs::write(&path, &fresh).ok()?;
155 Some(fresh)
156}