Files
OmniRoute/src/shared/utils/machineId.ts
Diego Rodrigues de Sa e Souza 0cd388efb8 Release v3.7.4 (#1730)
* chore(release): v3.7.4 — version bump, openapi and changelog sync

* fix: preserve previous_response_id and conversation_id fields on empty input array (#1729)

* fix: bypass UI validation block for optional API keys and fix string fallback typing (#1721)

* fix(proxy): disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent socket hang up

* feat(proxy): implement bulk proxy import via pipe-delimited parser with update-or-create logic

* docs: update changelog for v3.7.4 fixes and proxy features

* test: update responses store expectations for empty input arrays

* feat(pwa): add fullscreen installable PWA with manifest, service worker, and cross-platform app icons. (#1728)

Integrated into release/v3.7.4

* Fix image provider validation and Stability image requests (#1726)

Integrated into release/v3.7.4

* docs: add PR 1726 and PR 1728 to v3.7.4 changelog

* fix(security): replace insecure Math.random with crypto.getRandomValues for fallback UUID generation

* fix(migrations): intercept 007 migration to use IF NOT EXISTS logic on fresh installs

Fixes #1733

* test: fix typescript compilation errors in unit tests

* fix(db): reconcile legacy reasoning cache migration

* chore(release): bump to v3.7.4 — changelog, docs, version sync

* fix(cc-compatible): preserve Claude Code system skeleton (#1740)

Integrated into release/v3.7.4

* docs(changelog): update for PR #1740 merge

* docs(changelog): include workflow updates

* fix(db): reconcile legacy reasoning cache migration (#1734)

Integrated into release/v3.7.4

* Add endpoint tunnel visibility settings (#1743)

Integrated into release/v3.7.4

* Normalize max reasoning effort for Codex routing (#1744)

Integrated into release/v3.7.4

* Fix Claude Code gateway config helper (#1745)

Integrated into release/v3.7.4

* Refresh CLI fingerprint provider profiles (#1746)

Integrated into release/v3.7.4

* Integrated into release/v3.7.4 (PR #1742)

* docs(changelog): update for PRs 1742-1746

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Yash Ghule <y.ghule77@gmail.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: dhaern <manker_lol@hotmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Duncan L <leungd@gmail.com>
2026-04-28 20:46:25 -03:00

187 lines
5.7 KiB
TypeScript

import { execFileSync, execSync } from "child_process";
import { existsSync, readFileSync } from "fs";
/**
* Get raw machine ID using OS-specific methods.
*
* We use try/catch waterfall: try each OS method and fall through
* to the next on failure. Platform checks are INSIDE try blocks so they
* run at RUNTIME (not build time), avoiding Next.js SWC dead-code elimination.
*
* On Linux: skips Windows (REG.exe) and macOS (ioreg) strategies entirely.
*/
function getMachineIdRaw(): string {
// Strategy 1: Windows — REG.exe query for MachineGuid
try {
if (process.platform !== "win32") {
throw new Error("Not Windows");
}
const sysRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
const regPath = `${sysRoot}\\System32\\REG.exe`;
if (existsSync(/* turbopackIgnore: true */ regPath)) {
const output = execFileSync(
regPath,
["QUERY", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
{ encoding: "utf8", timeout: 5000 }
);
const id = output
.split("REG_SZ")[1]
?.replace(/\r+|\n+|\s+/gi, "")
?.toLowerCase();
if (id && id.length > 8) return id;
}
} catch {
// Not Windows or REG.exe failed — continue
}
// Strategy 2: macOS — ioreg IOPlatformUUID
try {
if (process.platform !== "darwin") {
throw new Error("Not macOS");
}
const output = execSync("ioreg -rd1 -c IOPlatformExpertDevice", {
encoding: "utf8",
timeout: 5000,
});
if (output.includes("IOPlatformUUID")) {
const id = output
.split("IOPlatformUUID")[1]
?.split("\n")[0]
?.replace(/=|\s+|"/gi, "")
?.toLowerCase();
if (id && id.length > 8) return id;
}
} catch {
// Not macOS or ioreg not available — continue
}
// Strategy 3: Linux — read machine-id files directly (no `head` or pipe)
try {
for (const filePath of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
try {
const content = readFileSync(/* turbopackIgnore: true */ filePath, "utf8")
.trim()
.toLowerCase();
if (content.length > 8) return content;
} catch {
// Try the next candidate file
}
}
} catch {
// Files not readable — continue
}
// Strategy 4: Hostname fallback (works on all platforms)
try {
const hostname = execSync("hostname", { encoding: "utf8", timeout: 5000 });
const id = hostname.trim().toLowerCase();
if (id) return id;
} catch {
// hostname failed — continue
}
// Strategy 5: Node.js os.hostname() (no exec needed)
try {
const os = require("os");
return os.hostname().toLowerCase();
} catch {
// Final fallback
}
return "unknown-machine";
}
/**
* Get consistent machine ID using native registry/OS query with salt
* This ensures the same physical machine gets the same ID across runs
*
* @param {string} salt - Optional salt to use (defaults to environment variable)
* @returns {Promise<string>} Machine ID (16-character base32)
*/
export async function getConsistentMachineId(salt = null) {
const saltValue = salt || process.env.MACHINE_ID_SALT || "endpoint-proxy-salt";
try {
const rawMachineId = getMachineIdRaw();
// Create consistent ID using salt
const crypto = await import("crypto");
const hashedMachineId = crypto
.createHash("sha256")
.update(rawMachineId + saltValue)
.digest("hex");
// Return only first 16 characters for brevity
return hashedMachineId.substring(0, 16);
} catch (error) {
console.log("Error getting machine ID:", error);
// Fallback to random ID if node-machine-id fails
try {
const cryptoFallback = await import("crypto");
return cryptoFallback.randomUUID();
} catch {
if (typeof globalThis !== "undefined" && globalThis.crypto && globalThis.crypto.randomUUID) {
return globalThis.crypto.randomUUID();
}
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
let r = 0;
if (
typeof globalThis !== "undefined" &&
globalThis.crypto &&
globalThis.crypto.getRandomValues
) {
const arr = new Uint8Array(1);
globalThis.crypto.getRandomValues(arr);
r = arr[0] % 16;
} else {
r = (Date.now() % 16) | 0;
}
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}
}
/**
* Get raw machine ID without hashing (for debugging purposes)
* @returns {Promise<string>} Raw machine ID
*/
export async function getRawMachineId() {
try {
return getMachineIdRaw();
} catch (error) {
console.log("Error getting raw machine ID:", error);
// Fallback to random ID if node-machine-id fails
try {
const cryptoFallback = await import("crypto");
return cryptoFallback.randomUUID();
} catch {
if (typeof globalThis !== "undefined" && globalThis.crypto && globalThis.crypto.randomUUID) {
return globalThis.crypto.randomUUID();
}
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
let r = 0;
if (
typeof globalThis !== "undefined" &&
globalThis.crypto &&
globalThis.crypto.getRandomValues
) {
const arr = new Uint8Array(1);
globalThis.crypto.getRandomValues(arr);
r = arr[0] % 16;
} else {
r = (Date.now() % 16) | 0;
}
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}
}
/**
* Check if we're running in browser or server environment
* @returns {boolean} True if in browser, false if in server
*/
export function isBrowser() {
return typeof window !== "undefined";
}