Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bin/lib/host-support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// Thin CJS shim — implementation lives in src/lib/host-support.ts
module.exports = require("../../dist/lib/host-support");
11 changes: 11 additions & 0 deletions bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const {
getMemoryInfo,
planHostRemediation,
} = require("./preflight");
const { checkHostSupport } = require("./host-support");

// Typed modules (compiled from src/lib/*.ts → dist/lib/*.js)
const gatewayState = require("../../dist/lib/gateway-state");
Expand Down Expand Up @@ -1497,6 +1498,16 @@ function getNonInteractiveModel(providerKey) {
async function preflight() {
step(1, 8, "Preflight checks");

const hostSupport = checkHostSupport();
if (hostSupport.status === "ok") {
console.log(` ✓ ${hostSupport.message}`);
} else if (hostSupport.status === "warning") {
console.log(` ⚠ ${hostSupport.message}`);
} else {
console.error(` ${hostSupport.message}`);
process.exit(1);
}

const host = assessHost();

// Docker / runtime
Expand Down
110 changes: 110 additions & 0 deletions src/lib/host-support.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";
// Import through compiled dist output for consistent coverage attribution.
import {
checkHostSupport,
classifyLinuxHost,
parseOsRelease,
} from "../../dist/lib/host-support";

describe("parseOsRelease", () => {
it("parses quoted and unquoted values", () => {
const parsed = parseOsRelease([
'NAME="Ubuntu"',
"ID=ubuntu",
'VERSION_ID="24.04"',
].join("\n"));

expect(parsed).toEqual({ id: "ubuntu", versionId: "24.04" });
});

it("returns empty fields when ID/VERSION_ID are missing", () => {
const parsed = parseOsRelease("NAME=Ubuntu\nPRETTY_NAME=Ubuntu Linux");
expect(parsed).toEqual({ id: "", versionId: "" });
});
});

describe("classifyLinuxHost", () => {
it("marks Ubuntu 24.04 as supported", () => {
const result = classifyLinuxHost("ubuntu", "24.04");
expect(result.status).toBe("ok");
expect(result.code).toBe("SUPPORTED");
});

it("marks Ubuntu 22.04 as supported", () => {
const result = classifyLinuxHost("ubuntu", "22.04");
expect(result.status).toBe("ok");
expect(result.code).toBe("SUPPORTED");
});

it("marks Ubuntu 20.04 as near EOL warning", () => {
const result = classifyLinuxHost("ubuntu", "20.04");
expect(result.status).toBe("warning");
expect(result.code).toBe("NEAR_EOL");
});

it("marks Ubuntu 18.04 as EOL error", () => {
const result = classifyLinuxHost("ubuntu", "18.04");
expect(result.status).toBe("error");
expect(result.code).toBe("EOL");
});

it("marks unknown Linux distro as warning", () => {
const result = classifyLinuxHost("debian", "12");
expect(result.status).toBe("warning");
expect(result.code).toBe("UNSUPPORTED_OS");
});
});

describe("checkHostSupport", () => {
it("classifies unsupported platform as error", () => {
const result = checkHostSupport({ platform: "win32" });
expect(result.status).toBe("error");
expect(result.code).toBe("UNSUPPORTED_OS");
});

it("classifies linux from os-release data", () => {
const result = checkHostSupport({
platform: "linux",
readFileSyncImpl: () => 'ID=ubuntu\nVERSION_ID="24.04"\n',
});

expect(result.status).toBe("ok");
expect(result.code).toBe("SUPPORTED");
});

it("returns warning when /etc/os-release cannot be read", () => {
const result = checkHostSupport({
platform: "linux",
readFileSyncImpl: () => {
throw new Error("missing");
},
});

expect(result.status).toBe("warning");
expect(result.code).toBe("UNKNOWN_VERSION");
});

it("uses mocked macOS version detection", () => {
const result = checkHostSupport({
platform: "darwin",
getMacosVersionImpl: () => "14.5",
});

expect(result.status).toBe("warning");
expect(result.code).toBe("UNSUPPORTED_OS");
expect(result.message).toContain("macOS 14");
});

it("returns unknown-version warning when macOS version is unavailable", () => {
const result = checkHostSupport({
platform: "darwin",
getMacosVersionImpl: () => "",
});

expect(result.status).toBe("warning");
expect(result.code).toBe("UNKNOWN_VERSION");
});
});
222 changes: 222 additions & 0 deletions src/lib/host-support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import fs from "node:fs";
import os from "node:os";

// runner.js is CJS — use require so we don't pull it into the TS build.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { runCapture } = require("../../bin/lib/runner");

export type HostSupportStatus = "ok" | "warning" | "error";
export type HostSupportCode =
| "SUPPORTED"
| "NEAR_EOL"
| "EOL"
| "UNSUPPORTED_OS"
| "UNKNOWN_VERSION";

export type HostSupportResult = {
os: string;
version: string;
status: HostSupportStatus;
code: HostSupportCode;
message: string;
};

export interface OsReleaseInfo {
id: string;
versionId: string;
}

export interface CheckHostSupportOpts {
platform?: NodeJS.Platform;
osReleasePath?: string;
readFileSyncImpl?: (path: string, encoding: BufferEncoding) => string;
getMacosVersionImpl?: () => string;
}

function stripQuotes(value: string): string {
const trimmed = value.trim();
if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
return trimmed.slice(1, -1);
}
return trimmed;
}

export function parseOsRelease(content: string): OsReleaseInfo {
const lines = content.split("\n");
let id = "";
let versionId = "";

for (const line of lines) {
if (!line || line.startsWith("#")) continue;
const eq = line.indexOf("=");
if (eq <= 0) continue;

const key = line.slice(0, eq).trim();
const value = stripQuotes(line.slice(eq + 1));

if (key === "ID") id = value.toLowerCase();
if (key === "VERSION_ID") versionId = value;
}

return { id, versionId };
}

function parseUbuntuVersion(version: string): { major: number; minor: number } | null {
const match = String(version).trim().match(/^(\d+)\.(\d+)/);
if (!match) return null;

const major = parseInt(match[1], 10);
const minor = parseInt(match[2], 10);
if (isNaN(major) || isNaN(minor)) return null;

return { major, minor };
}

export function classifyLinuxHost(id: string, versionId: string): HostSupportResult {
if (!id) {
return {
os: "linux",
version: versionId || "unknown",
status: "warning",
code: "UNKNOWN_VERSION",
message: "Linux detected but distro/version could not be determined from /etc/os-release.",
};
}

if (id !== "ubuntu") {
return {
os: id,
version: versionId || "unknown",
status: "warning",
code: "UNSUPPORTED_OS",
message: `${id} ${versionId || "unknown"} detected: recognized Linux distro, but explicit host support policy is currently defined for Ubuntu only.`,
};
}

if (!versionId) {
return {
os: "ubuntu",
version: "unknown",
status: "warning",
code: "UNKNOWN_VERSION",
message: "Ubuntu detected but VERSION_ID is missing; support level could not be determined.",
};
}

const parsed = parseUbuntuVersion(versionId);
if (!parsed) {
return {
os: "ubuntu",
version: versionId,
status: "warning",
code: "UNKNOWN_VERSION",
message: `Ubuntu ${versionId} detected, but version format is unrecognized; support level could not be determined.`,
};
}

const { major, minor } = parsed;
const normalized = `${major}.${minor.toString().padStart(2, "0")}`;

if ((major === 24 && minor === 4) || (major === 22 && minor === 4)) {
return {
os: "ubuntu",
version: normalized,
status: "ok",
code: "SUPPORTED",
message: `Ubuntu ${normalized} detected: supported.`,
};
}

if (major === 20 && minor === 4) {
return {
os: "ubuntu",
version: normalized,
status: "warning",
code: "NEAR_EOL",
message: `Ubuntu ${normalized} detected: older host OS; upgrade is recommended.`,
};
}

if (major < 20) {
return {
os: "ubuntu",
version: normalized,
status: "error",
code: "EOL",
message: `Ubuntu ${normalized} detected: unsupported or end-of-life host OS.`,
};
}

return {
os: "ubuntu",
version: normalized,
status: "warning",
code: "UNSUPPORTED_OS",
message: `Ubuntu ${normalized} detected: recognized host OS, but this version is outside the current explicit support policy.`,
};
}

export function classifyMacosHost(version: string): HostSupportResult {
if (!version || version === "unknown") {
return {
os: "macos",
version: "unknown",
status: "warning",
code: "UNKNOWN_VERSION",
message: "macOS detected but product version could not be determined; explicit support policy is not yet fully defined.",
};
}

const major = version.split(".")[0] || version;
return {
os: "macos",
version,
status: "warning",
code: "UNSUPPORTED_OS",
message: `macOS ${major} detected: recognized host OS; explicit support policy not yet fully defined.`,
};
}

function defaultGetMacosVersion(): string {
const out = runCapture("sw_vers -productVersion", { ignoreError: true });
return String(out || "").trim();
}

export function checkHostSupport(opts: CheckHostSupportOpts = {}): HostSupportResult {
const platform = opts.platform || os.platform();
const osReleasePath = opts.osReleasePath || "/etc/os-release";
const readFileSyncImpl = opts.readFileSyncImpl || fs.readFileSync;
const getMacosVersionImpl = opts.getMacosVersionImpl || defaultGetMacosVersion;

if (platform === "linux") {
try {
const content = readFileSyncImpl(osReleasePath, "utf-8");
const { id, versionId } = parseOsRelease(content);
return classifyLinuxHost(id, versionId);
} catch {
return {
os: "linux",
version: "unknown",
status: "warning",
code: "UNKNOWN_VERSION",
message: "Linux detected but /etc/os-release is unavailable; support level could not be determined.",
};
}
}

if (platform === "darwin") {
const version = getMacosVersionImpl() || "unknown";
return classifyMacosHost(version);
}

return {
os: platform,
version: "unknown",
status: "error",
code: "UNSUPPORTED_OS",
message: `${platform} detected: unsupported host operating system for NemoClaw onboarding.`,
};
}
Loading