import { execFileSync } from "node:child_process"; import { randomUUID } from "node:crypto"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir, platform, tmpdir } from "node:os"; import { dirname, join } from "node:path"; export interface Logger { warn(msg: string, err?: unknown): void; } const noopLogger: Logger = { warn: () => {} }; function readLinuxMachineId(): string | undefined { for (const path of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) { try { const id = readFileSync(path, "utf8").trim(); if (id) return id; } catch { // try the next candidate path } } return undefined; } function readDarwinMachineId(): string | undefined { try { const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], { encoding: "utf8", timeout: 2000, }); return out.match(/"IOPlatformUUID"\s*=\s*"([0-9A-Fa-f-]+)"/)?.[1]; } catch { return undefined; } } function readWindowsMachineId(): string | undefined { try { const out = execFileSync( "reg", ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"], { encoding: "utf8", timeout: 2000 }, ); return out.match(/MachineGuid\s+REG_SZ\s+([0-9A-Fa-f-]+)/)?.[1]; } catch { return undefined; } } /** detectOsMachineId reads the real OS machine ID, or undefined if unavailable/unsupported. */ export function detectOsMachineId(): string | undefined { switch (platform()) { case "linux": return readLinuxMachineId(); case "darwin": return readDarwinMachineId(); case "win32": return readWindowsMachineId(); default: return undefined; } } /** * machineIdDetector is a mutable indirection over detectOsMachineId so * tests can stub OS machine-id lookup without depending on the actual * host's state. It's a settable-property holder rather than a plain * exported `let` because ES module namespace bindings can't be reassigned * from outside the module. */ export const machineIdDetector: { current: () => string | undefined } = { current: detectOsMachineId, }; /** defaultInstallIdPath returns the fallback-UUID persistence path used when no explicit path is given. */ export function defaultInstallIdPath(): string { const base = process.env.XDG_CONFIG_HOME || join(homedir() || tmpdir(), ".config"); return join(base, "origin-client", "install-id"); } /** * resolveMachineId returns the unique install ID to report to Origin, in * priority order: * * 1. explicit, if non-empty - a caller-supplied override. * 2. the real OS machine ID (/etc/machine-id on Linux, IOPlatformUUID on * macOS, MachineGuid on Windows), if readable. * 3. the ID already persisted at path, if present. * 4. a newly generated UUID, which resolveMachineId attempts to persist * to path for future runs. * * OS-ID and persistence failures are logged as warnings, not thrown: * resolveMachineId always returns a usable ID. */ export function resolveMachineId(explicit?: string, path?: string, logger: Logger = noopLogger): string { const trimmedExplicit = explicit?.trim(); if (trimmedExplicit) return trimmedExplicit; const osId = machineIdDetector.current(); if (osId) { const trimmed = osId.trim(); if (trimmed) return trimmed; } else { logger.warn("machine id: OS machine id unavailable, falling back to a generated id"); } const resolvedPath = path && path.length > 0 ? path : defaultInstallIdPath(); try { const existing = readFileSync(resolvedPath, "utf8").trim(); if (existing) return existing; } catch { // no persisted id yet } const id = randomUUID(); try { mkdirSync(dirname(resolvedPath), { recursive: true, mode: 0o750 }); writeFileSync(resolvedPath, id + "\n", { mode: 0o600 }); } catch (err) { logger.warn("machine id: failed to persist generated id, using an ephemeral id for this run", err); } return id; }