import type { EnvironmentMachineKind } from "@t3tools/contracts"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as ProcessRunner from "../processRunner.ts"; /** * Best-effort hardware detection for the environment icon. Every probe is * allowed to fail: a null result means "no signal", and the client draws a * generic server until the user picks something in Settings → Connections. */ const DMI_ROOT = "/sys/class/dmi/id"; const KERNEL_RELEASE_PATH = "/proc/sys/kernel/osrelease"; // SMBIOS 3.x System Enclosure types (table 17). Codes that describe a shape // rather than a machine (docking stations, blades enclosures, IoT gateways) // fall through to null on purpose. const DMI_CHASSIS_KINDS: Readonly> = { "3": "desktop", // Desktop "4": "desktop", // Low Profile Desktop "5": "desktop", // Pizza Box "6": "desktop", // Mini Tower "7": "desktop", // Tower "8": "laptop", // Portable "9": "laptop", // Laptop "10": "laptop", // Notebook "13": "desktop", // All in One "14": "laptop", // Sub Notebook "15": "desktop", // Space-saving "16": "desktop", // Lunch Box "17": "server", // Main Server Chassis "18": "server", // Expansion Chassis "19": "server", // SubChassis "20": "server", // Bus Expansion Chassis "21": "server", // Peripheral Chassis "22": "server", // RAID Chassis "23": "server", // Rack Mount Chassis "24": "server", // Sealed-case PC "28": "server", // Blade "31": "laptop", // Convertible "32": "laptop", // Detachable "35": "desktop", // Mini PC }; // Hypervisors and cloud providers write themselves into the DMI vendor or // product strings; any hit means the box is a VM, and a VM reads as "cloud" // regardless of the chassis type the hypervisor fakes. Hyper-V is matched on // its "Virtual Machine" product, not the "Microsoft Corporation" vendor that // physical Surface devices share. const VIRTUALIZATION_MARKERS = [ "qemu", "kvm", "bochs", "vmware", "virtualbox", "innotek", "xen", "parallels", "amazon ec2", "google compute engine", "digitalocean", "hetzner", "linode", "vultr", "scaleway", "openstack", "cloud", "virtual machine", ]; function normalize(value: string | null | undefined): string | null { const trimmed = value?.trim(); return trimmed && trimmed.length > 0 ? trimmed : null; } /** Marketing names and Intel-era model identifiers share these prefixes. */ export function machineKindFromAppleProductName(name: string): EnvironmentMachineKind | null { const normalized = name.trim().toLowerCase().replaceAll(/\s+/g, ""); if (normalized.startsWith("macmini")) return "mac-mini"; if (normalized.startsWith("macstudio")) return "mac-studio"; if (normalized.startsWith("macbook")) return "laptop"; if (normalized.startsWith("imac") || normalized.startsWith("macpro")) return "desktop"; return null; } export function machineKindFromDmi(input: { readonly chassisType: string | null; readonly sysVendor: string | null; readonly productName: string | null; }): EnvironmentMachineKind | null { const productName = input.productName ?? ""; const vendorAndProduct = `${input.sysVendor ?? ""} ${productName}`.toLowerCase(); if (VIRTUALIZATION_MARKERS.some((marker) => vendorAndProduct.includes(marker))) { return "cloud"; } // Apple hardware booting Linux (Asahi) still reports the Apple product name. const appleKind = machineKindFromAppleProductName(productName); if (appleKind !== null) { return appleKind; } return input.chassisType === null ? null : (DMI_CHASSIS_KINDS[input.chassisType] ?? null); } const readOptionalFile = Effect.fn("readOptionalFile")(function* (path: string) { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.readFileString(path).pipe( Effect.map(normalize), Effect.catch(() => Effect.succeed(null)), ); }); const runProbe = Effect.fn("runMachineProbe")(function* (input: { readonly command: string; readonly args: ReadonlyArray; }) { const processRunner = yield* ProcessRunner.ProcessRunner; return yield* processRunner .run({ command: input.command, args: input.args, timeout: "5 seconds", timeoutBehavior: "timedOutResult", }) .pipe( Effect.map((result) => (result.code === 0 ? normalize(result.stdout) : null)), Effect.catch(() => Effect.succeed(null)), ); }); // IOKit's `product` node carries the marketing name ("Mac mini (2024)") on // Apple silicon; Intel Macs lack it, so `hw.model` ("Macmini8,1") is the // fallback. Both are single-digit-millisecond calls. const detectDarwinMachineKind = Effect.fn("detectDarwinMachineKind")(function* () { const ioreg = yield* runProbe({ command: "ioreg", args: ["-rd1", "-n", "product"] }); const productName = ioreg?.match(/"product-name"\s*=\s*<"([^"]+)">/)?.[1] ?? null; const fromProductName = productName === null ? null : machineKindFromAppleProductName(productName); if (fromProductName !== null) { return fromProductName; } const model = yield* runProbe({ command: "sysctl", args: ["-n", "hw.model"] }); return model === null ? null : machineKindFromAppleProductName(model); }); const detectLinuxMachineKind = Effect.fn("detectLinuxMachineKind")(function* () { const [kernelRelease, chassisType, sysVendor, productName] = yield* Effect.all([ readOptionalFile(KERNEL_RELEASE_PATH), readOptionalFile(`${DMI_ROOT}/chassis_type`), readOptionalFile(`${DMI_ROOT}/sys_vendor`), readOptionalFile(`${DMI_ROOT}/product_name`), ]); // WSL exposes Microsoft in its kernel release on both WSL 1 and WSL 2. // Check it before DMI because WSL 2 presents as a Hyper-V VM. if (kernelRelease?.toLowerCase().includes("microsoft")) { return "linux"; } return machineKindFromDmi({ chassisType, sysVendor, productName }); }); export const detectServerEnvironmentMachineKind = Effect.fn("detectServerEnvironmentMachineKind")( function* () { const platform = yield* HostProcessPlatform; switch (platform) { case "darwin": return yield* detectDarwinMachineKind(); case "linux": return yield* detectLinuxMachineKind(); default: return null; } }, );