use std::io::IsTerminal;
use serde_json::json;
fn env_any(keys: &[&str]) -> bool {
keys.iter().any(|k| std::env::var_os(k).is_some())
}
fn deployment_vendor() -> Option<&'static str> {
if env_any(&["CF_PAGES", "CF_PAGES_URL", "CF_ACCOUNT_ID"]) {
return Some("cloudflare");
}
if env_any(&["VERCEL", "VERCEL_URL", "VERCEL_ENV"]) {
return Some("vercel");
}
if env_any(&["NETLIFY", "NETLIFY_URL"]) {
return Some("netlify");
}
if env_any(&[
"RENDER",
"RENDER_URL",
"RENDER_INTERNAL_HOSTNAME",
"RENDER_SERVICE_ID",
]) {
return Some("render");
}
if env_any(&[
"AWS_LAMBDA_FUNCTION_NAME",
"AWS_EXECUTION_ENV",
"LAMBDA_TASK_ROOT",
]) {
return Some("aws");
}
if env_any(&[
"GOOGLE_CLOUD_FUNCTION_NAME",
"GOOGLE_CLOUD_PROJECT",
"GCP_PROJECT",
"K_SERVICE",
]) {
return Some("gcp");
}
if env_any(&[
"AZURE_FUNCTION_NAME",
"FUNCTIONS_WORKER_RUNTIME",
"WEBSITE_INSTANCE_ID",
"WEBSITE_SITE_NAME",
]) {
return Some("azure");
}
if env_any(&["DENO_DEPLOYMENT_ID", "DENO_REGION"]) {
return Some("deno-deploy");
}
if env_any(&["FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID"]) {
return Some("fly-io");
}
if env_any(&["RAILWAY_STATIC_URL", "RAILWAY_ENVIRONMENT_NAME"]) {
return Some("railway");
}
if env_any(&["DYNO", "HEROKU_APP_NAME"]) {
return Some("heroku");
}
if env_any(&["DO_DEPLOYMENT_ID", "DO_APP_NAME", "DIGITALOCEAN"]) {
return Some("digitalocean");
}
if env_any(&["KOYEB", "KOYEB_DEPLOYMENT_ID", "KOYEB_APP_NAME"]) {
return Some("koyeb");
}
None
}
pub fn detect_system_info() -> serde_json::Value {
json!({
"deploymentVendor": deployment_vendor(),
"systemPlatform": std::env::consts::OS,
"systemRelease": system_release(),
"systemArchitecture": std::env::consts::ARCH,
"cpuCount": std::thread::available_parallelism().ok().map(|count| count.get()),
"cpuModel": serde_json::Value::Null,
"cpuSpeed": serde_json::Value::Null,
"memory": serde_json::Value::Null,
"isWSL": is_wsl(),
"isDocker": is_docker(),
"isTTY": std::io::stdout().is_terminal(),
"isCI": crate::env::is_ci(),
})
}
fn system_release() -> serde_json::Value {
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.ok()
.map(|release| serde_json::Value::String(release.trim().to_owned()))
.unwrap_or(serde_json::Value::Null)
}
fn is_docker() -> bool {
std::path::Path::new("/.dockerenv").exists()
|| std::path::Path::new("/run/.containerenv").exists()
|| std::fs::read_to_string("/proc/self/cgroup")
.ok()
.is_some_and(|content| content.contains("docker"))
}
fn is_wsl() -> bool {
if std::env::consts::OS != "linux" || is_docker() {
return false;
}
let release = std::fs::read_to_string("/proc/sys/kernel/osrelease").unwrap_or_default();
let version = std::fs::read_to_string("/proc/version").unwrap_or_default();
release.to_lowercase().contains("microsoft") || version.to_lowercase().contains("microsoft")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_info_reports_available_rust_host_fields() {
let info = detect_system_info();
assert_eq!(info["systemPlatform"], std::env::consts::OS);
assert_eq!(info["systemArchitecture"], std::env::consts::ARCH);
assert!(info["cpuCount"].as_u64().is_some());
assert!(info["isTTY"].as_bool().is_some());
}
}