use std::process::Command;
pub fn device_label() -> String {
let hostname = std::env::var("HOSTNAME")
.or_else(|_| {
std::fs::read_to_string("/etc/hostname")
.map(|s| s.trim().to_string())
.or_else(|_| {
Command::new("hostname")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
})
})
.unwrap_or_else(|_| "unknown".to_string());
let user = std::env::var("USER")
.or_else(|_| std::env::var("USERNAME"))
.unwrap_or_else(|_| "unknown".to_string());
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let raw = format!("{}|{}|{}|{}", hostname, user, os, arch);
let hash = hash_string(&raw);
format!("{}-{}", &hash[..8], hostname)
}
fn hash_string(s: &str) -> String {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in s.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
format!("{:016x}", hash)
}
pub fn activate(key: &str, label: &str) -> Result<String, String> {
let api_url = crate::shim::config::api_url();
let activate_url = api_url.replace("/v1/mcp", "/v1/activate");
let body = format!(
r#"{{"key":"{}","label":"{}"}}"#,
key.replace('"', "\\\""),
label.replace('"', "\\\"")
);
let result = Command::new("curl")
.arg("-s")
.arg("-X")
.arg("POST")
.arg(&activate_url)
.arg("-H")
.arg("Content-Type: application/json")
.arg("-H")
.arg(format!("X-API-KEY: {}", key))
.arg("-d")
.arg(&body)
.arg("--max-time")
.arg("30")
.output();
match result {
Ok(output) if output.status.success() => {
let body = String::from_utf8_lossy(&output.stdout);
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
if let Some(id) = v.get("activation_id").and_then(|i| i.as_str()) {
return Ok(id.to_string());
}
if let Some(id) = v.get("id").and_then(|i| i.as_str()) {
return Ok(id.to_string());
}
if let Some(err) = v.get("detail").and_then(|d| d.as_str()) {
return Err(err.to_string());
}
}
Err(format!("invalid response: {}", body))
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("request failed: {}", stderr))
}
Err(e) => Err(format!("failed to send request: {}", e)),
}
}
pub fn deactivate(key: &str, activation_id: &str) -> Result<(), String> {
let api_url = crate::shim::config::api_url();
let deactivate_url = api_url.replace("/v1/mcp", "/v1/deactivate");
let body = format!(
r#"{{"key":"{}","activation_id":"{}"}}"#,
key.replace('"', "\\\""),
activation_id.replace('"', "\\\"")
);
let result = Command::new("curl")
.arg("-s")
.arg("-X")
.arg("POST")
.arg(&deactivate_url)
.arg("-H")
.arg("Content-Type: application/json")
.arg("-H")
.arg(format!("X-API-KEY: {}", key))
.arg("-d")
.arg(&body)
.arg("--max-time")
.arg("30")
.output();
match result {
Ok(output) if output.status.success() => Ok(()),
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
Err(format!("request failed: {} {}", stderr, stdout))
}
Err(e) => Err(format!("failed to send request: {}", e)),
}
}