diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx
index 5c4dbe4fcde..8f0357c445f 100644
--- a/docs/inference/set-up-vllm.mdx
+++ b/docs/inference/set-up-vllm.mdx
@@ -92,40 +92,42 @@ The first run can take 10 to 30 minutes.
Later runs reuse the cached image and model weights.
The managed container uses Docker's `--restart unless-stopped` policy, so Docker restarts it unless an operator explicitly stops it.
-### Check Storage Before Downloads
+### Check Storage Before the Image Pull
-For an uncached image, NemoClaw checks Docker image storage before the watched image pull starts.
-It checks the Hugging Face model cache before that pull and checks it again afterward before `hf download` starts.
-The image estimate includes compressed content, unpacked layers, and pull-staging headroom.
-The model estimate includes the model's full published size plus temporary-write headroom because a local cache cannot prove which files a mutable remote revision will reuse.
+For an uncached pinned image, NemoClaw checks Docker image storage before the watched image pull starts.
+The estimate reserves three times the image's compressed registry size plus `3 GiB` for compressed content, unpacked layers, and pull staging.
For Docker's classic image store, NemoClaw checks the absolute `DockerRootDir` reported by `docker info`.
For Docker's containerd image store, it checks the top-level `root` from `/etc/containerd/config.toml`, or `/var/lib/containerd` when that configuration file is absent, and also checks `DockerRootDir` for pull staging.
The check uses the location with the least available capacity.
-The model-cache check uses `~/.cache/huggingface`, or its nearest existing parent filesystem when the cache directory does not exist yet.
NemoClaw recognizes an image cache hit only when Docker contains the exact platform digest.
-A cache hit skips the cold-pull capacity check, but the explicit watched `docker pull` still runs against that immutable digest.
-Before trusting the host model-cache capacity, NemoClaw also verifies that the selected Docker daemon maps bind mounts to the local host; a remote or unverifiable Docker host makes the model-cache check inconclusive.
-An explicit Unix socket is trusted only when it is Docker's default `/var/run/docker.sock`; any other socket can forward to a daemon with a different filesystem and is treated as unverifiable.
-Known container markers are rejected immediately.
-Before trusting client-side image-store capacity, NemoClaw requires the selected default Docker socket peer to have a visible PID and share the client's mount namespace; a nested PID namespace or unverifiable peer fails closed before the image pull.
-Before trusting model-cache capacity, NemoClaw also asks the pinned cached vLLM image to read a random mode-`0600` sentinel through a bounded, read-only Docker bind mount.
-The pull is idempotent for the cached digest and cannot select different layers from a moved release tag.
-The model-download helper and the long-lived vLLM container use `--pull=never`, so an implicit pull cannot bypass the storage gate.
-The second model-cache check detects capacity consumed by the image pull before the model download command can start.
-
-When capacity is insufficient or NemoClaw cannot verify it, the warning identifies the image or model, available capacity, approximate requirement, checked path and source, remediation, and diagnostic commands.
+A cache hit skips the capacity check, but the explicit watched `docker pull` still runs against that immutable digest.
+The model-download helper and the long-lived vLLM container use `--pull=never`, so an implicit pull cannot select another image.
+
+NemoClaw treats `/var/run/docker.sock` and `/run/docker.sock` as the local default Docker socket paths.
+It cannot inspect daemon storage through a remote endpoint, a named non-default Docker context, or another Unix socket.
+When both `DOCKER_CONTEXT` and `DOCKER_HOST` are set, NemoClaw follows Docker CLI precedence and uses `DOCKER_CONTEXT`.
+When the check is inconclusive, NemoClaw prints the reason, approximate image requirement, and diagnostic commands, then continues with the pull without claiming that capacity is sufficient.
+
+When verified capacity is insufficient, the warning identifies the image, available capacity, approximate requirement, checked path and source, remediation, and diagnostic commands.
Interactive setup continues only when you enter `y` or `yes` explicitly.
-Pressing **Enter**, entering `n` or `no`, or entering any other response stops before the guarded pull or model download starts.
+Pressing **Enter**, entering `n` or `no`, or entering any other response stops before the guarded image pull starts.
-`--yes` and `NEMOCLAW_YES=1` do not accept managed-vLLM storage warnings.
+`--yes` and `NEMOCLAW_YES=1` do not accept a verified insufficient-storage warning.
Non-interactive setup stops unless you set `NEMOCLAW_IGNORE_VLLM_DISK_SPACE=1` exactly.
-This override accepts both insufficient-capacity and inconclusive checks for Docker image storage and the model cache.
-Use it only after you independently verify that both storage locations can complete the downloads.
+The override applies only when NemoClaw verifies that Docker image storage is insufficient.
+Use it only after you independently verify that Docker storage can complete the image pull.
+Inconclusive checks continue automatically and do not require the override.
+
+This preflight checks only the pinned vLLM container image.
+It does not estimate or gate the Hugging Face model-weight download into `~/.cache/huggingface`.
+Before the first managed-vLLM run, verify that the filesystem backing your model cache has enough free space for the selected model.
+
+
If image pull output stops making progress, a watchdog stops the stalled pull without imposing a fixed wall-clock limit on slow but active downloads.
If vLLM does not become ready, NemoClaw prints a short tail of the container logs before exiting.
diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx
index 24b475a72c3..12145b2dfcb 100644
--- a/docs/reference/commands.mdx
+++ b/docs/reference/commands.mdx
@@ -3250,7 +3250,7 @@ Set them before running `$$nemoclaw onboard`.
| `NEMOCLAW_INSTALL_TAG` | release tag | For internal installer commands: the release tag to install. Defaults to the admin-promoted `lkg` tag when unset. Overridden by the `--install-tag` flag. |
| `NEMOCLAW_VLLM_MODEL` | registry slug or Hugging Face model id | Selects the model the managed-vLLM install path serves. Recognised slugs: `qwen3.6-27b`, `qwen3.6-35b-a3b-nvfp4`, `nemotron-3-nano-4b`, `deepseek-v4-flash`, `deepseek-r1-distill-70b`. Unset uses the per-platform profile default. Gated models (e.g. `deepseek-r1-distill-70b`) require `HF_TOKEN` or `HUGGING_FACE_HUB_TOKEN`. |
| `NEMOCLAW_VLLM_EXTRA_ARGS_JSON` | JSON array of non-blank strings | Appends advanced operator-owned tokens to the managed `vllm serve` command after NemoClaw's registry defaults. Example: `["--max-num-seqs","2"]`. Malformed JSON, non-string tokens, or blank tokens fail before Docker work starts. |
-| `NEMOCLAW_IGNORE_VLLM_DISK_SPACE` | exactly `1` | Allows managed vLLM to continue when Docker image-storage or model-cache capacity is insufficient or cannot be verified; other values do not bypass the checks. |
+| `NEMOCLAW_IGNORE_VLLM_DISK_SPACE` | exactly `1` | Allows managed vLLM to continue when verified Docker image-storage capacity is insufficient; other values do not bypass the check. Inconclusive image-storage checks report diagnostics and continue automatically. The check does not cover Hugging Face model-cache capacity. |
| `NEMOCLAW_MINIMAL_BOOTSTRAP` | `1` to enable | Skips default OpenClaw workspace-template seeding for new pristine workspaces. Existing files are not deleted; refer to [Understand Runtime Changes](../manage-sandboxes/configure-sandboxes/understand-runtime-changes). |
diff --git a/scripts/install.sh b/scripts/install.sh
index d49e9bd24e4..f487398830e 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -2655,7 +2655,7 @@ detect_express_platform() {
fi
case "$model" in
*DGX*Spark*) printf "DGX Spark" ;;
- *DGX*Station*) printf "DGX Station" ;;
+ *DGX*Station* | *Station*GB300*) printf "DGX Station" ;;
*) ;;
esac
}
diff --git a/src/lib/inference/vllm-models.test.ts b/src/lib/inference/vllm-models.test.ts
index 1eb1aacdd14..bbe843206e2 100644
--- a/src/lib/inference/vllm-models.test.ts
+++ b/src/lib/inference/vllm-models.test.ts
@@ -16,25 +16,6 @@ import {
} from "./vllm-models";
describe("vllm model registry", () => {
- it("records a finite positive Hugging Face download size for every model", () => {
- for (const model of VLLM_MODELS) {
- expect(Number.isFinite(model.downloadSizeBytes)).toBe(true);
- expect(model.downloadSizeBytes).toBeGreaterThan(0);
- }
- });
-
- it("pins the official Hugging Face repository totals", () => {
- expect(
- Object.fromEntries(VLLM_MODELS.map((model) => [model.envValue, model.downloadSizeBytes])),
- ).toEqual({
- "qwen3.6-27b": 30_900_000_000,
- "deepseek-r1-distill-70b": 141_000_000_000,
- "nemotron-3-nano-4b": 5_280_000_000,
- "deepseek-v4-flash": 160_000_000_000,
- "qwen3.6-35b-a3b-nvfp4": 23_500_000_000,
- });
- });
-
it("returns null when NEMOCLAW_VLLM_MODEL is unset so the caller can fall back to the profile default", () => {
expect(selectVllmModelFromEnv({} as NodeJS.ProcessEnv)).toBeNull();
});
diff --git a/src/lib/inference/vllm-models.ts b/src/lib/inference/vllm-models.ts
index d38f13dac8b..7359fc9bfd0 100644
--- a/src/lib/inference/vllm-models.ts
+++ b/src/lib/inference/vllm-models.ts
@@ -35,8 +35,6 @@ export interface VllmModelDef {
label: string;
/** Stable identifier accepted via `NEMOCLAW_VLLM_MODEL`. */
envValue: string;
- /** Approximate full Hugging Face repository download size in bytes. */
- downloadSizeBytes: number;
/** `--max-model-len` flag value. */
maxModelLen: number;
/** Model-specific flags appended after the shared serving flags. */
@@ -65,7 +63,6 @@ export const VLLM_MODELS: readonly VllmModelDef[] = [
id: "Qwen/Qwen3.6-27B-FP8",
label: "Qwen3.6 27B FP8",
envValue: "qwen3.6-27b",
- downloadSizeBytes: 30_900_000_000,
maxModelLen: 262144,
modelArgs: [
"--gpu-memory-utilization",
@@ -88,7 +85,6 @@ export const VLLM_MODELS: readonly VllmModelDef[] = [
id: "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
label: "DeepSeek-R1 Distill Llama 70B",
envValue: "deepseek-r1-distill-70b",
- downloadSizeBytes: 141_000_000_000,
maxModelLen: 32768,
modelArgs: [
"--gpu-memory-utilization",
@@ -108,7 +104,6 @@ export const VLLM_MODELS: readonly VllmModelDef[] = [
id: "nvidia/NVIDIA-Nemotron-3-Nano-4B-FP8",
label: "NVIDIA Nemotron-3 Nano 4B FP8",
envValue: "nemotron-3-nano-4b",
- downloadSizeBytes: 5_280_000_000,
// Matches the model card's `max_position_embeddings` and the vLLM
// example NVIDIA publishes for this checkpoint. The previous value
// (262000) was an undocumented round-down with no headroom rationale.
@@ -138,7 +133,6 @@ export const VLLM_MODELS: readonly VllmModelDef[] = [
id: "deepseek-ai/DeepSeek-V4-Flash",
label: "DeepSeek V4 Flash",
envValue: "deepseek-v4-flash",
- downloadSizeBytes: 160_000_000_000,
maxModelLen: 1048576,
modelArgs: [
"--kv-cache-dtype",
@@ -179,7 +173,6 @@ export const VLLM_MODELS: readonly VllmModelDef[] = [
id: "nvidia/Qwen3.6-35B-A3B-NVFP4",
label: "Qwen3.6 35B-A3B NVFP4",
envValue: "qwen3.6-35b-a3b-nvfp4",
- downloadSizeBytes: 23_500_000_000,
maxModelLen: 262144,
// Additive flags on top of the shared serving defaults. The shared flags
// already cover --tensor-parallel-size/--pipeline-parallel-size/
diff --git a/src/lib/inference/vllm-storage.test.ts b/src/lib/inference/vllm-storage.test.ts
index 2ac7bc1c6e8..c9b56d23c31 100644
--- a/src/lib/inference/vllm-storage.test.ts
+++ b/src/lib/inference/vllm-storage.test.ts
@@ -1,24 +1,15 @@
// 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";
-import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
- dockerSocketPeerSharesMountNamespace,
formatStorageBytes,
imageStorageRequirementBytes,
- modelStorageRequirementBytes,
- probeDockerBindIdentity,
- probeDockerHostLocality,
probeDockerStorage,
- probeModelCacheStorage,
resolveDockerStorageLocations,
} from "./vllm-storage";
const GIB = 1024n ** 3n;
-const tempDirs: string[] = [];
function nativeDockerInfo(overrides: Record = {}): string {
return JSON.stringify({
@@ -26,8 +17,6 @@ function nativeDockerInfo(overrides: Record = {}): string {
DockerRootDir: "/var/lib/docker",
Driver: "overlay2",
DriverStatus: [],
- Name: "dgx-station",
- OperatingSystem: "Ubuntu 24.04",
OSType: "linux",
SecurityOptions: [],
...overrides,
@@ -35,27 +24,22 @@ function nativeDockerInfo(overrides: Record = {}): string {
}
const nativeHost = {
- clientContainerized: false,
dockerContext: undefined,
dockerHost: undefined,
- osRelease: "6.8.0-generic",
platform: "linux" as NodeJS.Platform,
- dockerSocketPeerSharesMountNamespace: () => true,
};
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
- for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { force: true, recursive: true });
});
-describe("managed vLLM storage requirements", () => {
+describe("managed vLLM image-storage requirements", () => {
it("reserves compressed, unpacked, and staging space for an image pull (#6757)", () => {
expect(imageStorageRequirementBytes(1_000_000_000)).toBe(3_000_000_000n + 3n * GIB);
- });
-
- it("requires the full model size plus temporary-file headroom (#6757)", () => {
- expect(modelStorageRequirementBytes(Number(10n * GIB))).toBe(12n * GIB);
+ expect(() => imageStorageRequirementBytes(0)).toThrow(
+ "vLLM image download size must be a positive finite byte count",
+ );
});
it("formats available and required bytes as rounded GiB values", () => {
@@ -65,11 +49,24 @@ describe("managed vLLM storage requirements", () => {
});
describe("Docker image-storage detection", () => {
- it("uses DockerRootDir for the classic image store (#6757)", () => {
- expect(resolveDockerStorageLocations(nativeDockerInfo(), nativeHost)).toEqual({
+ it("measures DockerRootDir for the classic image store (#6757)", () => {
+ const statfs = vi.fn(() => ({ bavail: 7n, bsize: GIB }));
+
+ expect(
+ probeDockerStorage({
+ ...nativeHost,
+ dockerInfo: () => nativeDockerInfo(),
+ statfs,
+ }),
+ ).toEqual({
ok: true,
- locations: [{ path: "/var/lib/docker", source: "Docker root directory" }],
+ capacity: {
+ availableBytes: 7n * GIB,
+ path: "/var/lib/docker",
+ source: "Docker root directory",
+ },
});
+ expect(statfs).toHaveBeenCalledWith("/var/lib/docker");
});
it("checks both the configured containerd root and Docker pull staging (#6757)", () => {
@@ -77,19 +74,20 @@ describe("Docker image-storage detection", () => {
bavail: target === "/var/lib/docker" ? 2n : 40n,
bsize: GIB,
}));
- const result = probeDockerStorage({
- ...nativeHost,
- dockerInfo: () =>
- nativeDockerInfo({
- Driver: "overlayfs",
- DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
- }),
- exists: (target) => target === "/etc/containerd/config.toml",
- readFile: () => 'root = "/mnt/containerd"',
- statfs,
- });
- expect(result).toEqual({
+ expect(
+ probeDockerStorage({
+ ...nativeHost,
+ dockerInfo: () =>
+ nativeDockerInfo({
+ Driver: "overlayfs",
+ DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
+ }),
+ exists: (target) => target === "/etc/containerd/config.toml",
+ readFile: () => 'root = "/mnt/containerd"',
+ statfs,
+ }),
+ ).toEqual({
ok: true,
capacity: {
availableBytes: 2n * GIB,
@@ -102,15 +100,15 @@ describe("Docker image-storage detection", () => {
});
it("uses the documented containerd root when no config file exists (#6757)", () => {
- const result = resolveDockerStorageLocations(
- nativeDockerInfo({
- Driver: "overlayfs",
- DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
- }),
- { ...nativeHost, exists: () => false },
- );
-
- expect(result).toEqual({
+ expect(
+ resolveDockerStorageLocations(
+ nativeDockerInfo({
+ Driver: "overlayfs",
+ DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
+ }),
+ { ...nativeHost, exists: () => false },
+ ),
+ ).toEqual({
ok: true,
locations: [
{ path: "/var/lib/containerd", source: "containerd image store" },
@@ -119,58 +117,33 @@ describe("Docker image-storage detection", () => {
});
});
- it("fails closed for overlayfs without the exact containerd marker (#6757)", () => {
- const result = resolveDockerStorageLocations(
- nativeDockerInfo({
- Driver: "overlayfs",
- DriverStatus: [["note", "io.containerd.snapshotter.v1"]],
- }),
- nativeHost,
- );
-
- expect(result).toEqual({
- ok: false,
- reason: "docker reported ambiguous image-storage driver overlayfs",
- });
- });
+ it.each([
+ "/var/run/docker.sock",
+ "unix:///var/run/docker.sock",
+ "/run/docker.sock",
+ "unix:///run/docker.sock",
+ ])("measures storage through the local Docker socket %s (#6858)", (dockerHost) => {
+ const statfs = vi.fn(() => ({ bavail: 9n, bsize: GIB }));
- it("fails closed when containerd imports can override the configured root (#6757)", () => {
- const result = resolveDockerStorageLocations(
- nativeDockerInfo({
- Driver: "overlayfs",
- DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
- }),
- {
+ expect(
+ probeDockerStorage({
...nativeHost,
- exists: (target) => target === "/etc/containerd/config.toml",
- readFile: () => 'root = "/mnt/containerd"\nimports = ["/etc/containerd/conf.d/*.toml"]',
- },
- );
-
- expect(result).toEqual({
- ok: false,
- reason: "containerd config imports other files that can override its image-store root",
- });
- });
-
- it("fails closed with an actionable reason for rootless containerd (#6757)", () => {
- const result = resolveDockerStorageLocations(
- nativeDockerInfo({
- Driver: "overlayfs",
- DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
- SecurityOptions: ["name=rootless"],
+ dockerHost,
+ dockerInfo: () => nativeDockerInfo(),
+ statfs,
}),
- nativeHost,
- );
-
- expect(result).toEqual({
- ok: false,
- reason:
- "rootless Docker detected; managed vLLM cannot verify the containerd image-store location",
+ ).toEqual({
+ ok: true,
+ capacity: {
+ availableBytes: 9n * GIB,
+ path: "/var/lib/docker",
+ source: "Docker root directory",
+ },
});
+ expect(statfs).toHaveBeenCalledWith("/var/lib/docker");
});
- it("returns an inconclusive result for remote Docker contexts (#6757)", () => {
+ it("returns an inconclusive result for remote Docker endpoints (#6757)", () => {
expect(
resolveDockerStorageLocations(nativeDockerInfo(), {
...nativeHost,
@@ -182,25 +155,26 @@ describe("Docker image-storage detection", () => {
});
});
- it("honors DOCKER_HOST precedence over DOCKER_CONTEXT (#6757)", () => {
+ it("honors a named DOCKER_CONTEXT over DOCKER_HOST (#6757)", () => {
expect(
resolveDockerStorageLocations(nativeDockerInfo(), {
...nativeHost,
- dockerContext: "default",
- dockerHost: "ssh://builder.example.test",
+ dockerContext: "remote-builder",
+ dockerHost: "unix:///run/docker.sock",
}),
).toEqual({
ok: false,
- reason: "Docker uses a remote endpoint (ssh://builder.example.test)",
+ reason:
+ "Docker uses a named context (remote-builder) whose host filesystem cannot be inspected",
});
});
- it("ignores a named DOCKER_CONTEXT when DOCKER_HOST selects a local socket (#6757)", () => {
+ it("honors an explicit default DOCKER_CONTEXT over a remote DOCKER_HOST (#6757)", () => {
expect(
resolveDockerStorageLocations(nativeDockerInfo(), {
...nativeHost,
- dockerContext: "remote-builder",
- dockerHost: "unix:///var/run/docker.sock",
+ dockerContext: "default",
+ dockerHost: "ssh://builder.example.test",
}),
).toEqual({
ok: true,
@@ -208,7 +182,7 @@ describe("Docker image-storage detection", () => {
});
});
- it("fails closed for a named Docker context whose filesystem is not local (#6757)", () => {
+ it("returns an inconclusive result for a named Docker context (#6757)", () => {
expect(
resolveDockerStorageLocations(
nativeDockerInfo({ ClientInfo: { Context: "remote-builder" } }),
@@ -217,268 +191,114 @@ describe("Docker image-storage detection", () => {
).toEqual({
ok: false,
reason:
- "Docker uses a named context (remote-builder) whose host filesystem cannot be verified",
- });
- });
-
- it.each([
- {
- boundary: "non-Linux host",
- expected: "Docker runs behind a darwin host boundary",
- info: nativeDockerInfo(),
- overrides: { ...nativeHost, platform: "darwin" as NodeJS.Platform },
- },
- {
- boundary: "WSL kernel",
- expected: "Docker runs behind a WSL host boundary",
- info: nativeDockerInfo(),
- overrides: { ...nativeHost, osRelease: "5.15.153.1-microsoft-standard-WSL2" },
- },
- {
- boundary: "Docker Desktop VM",
- expected: "Docker runs inside a VM or compatibility layer",
- info: nativeDockerInfo({ OperatingSystem: "Docker Desktop" }),
- overrides: nativeHost,
- },
- {
- boundary: "Colima VM",
- expected: "Docker runs inside a VM or compatibility layer",
- info: nativeDockerInfo({ OperatingSystem: "Colima" }),
- overrides: nativeHost,
- },
- {
- boundary: "Podman compatibility layer",
- expected: "Docker runs inside a VM or compatibility layer",
- info: nativeDockerInfo({ OperatingSystem: "Podman Engine" }),
- overrides: nativeHost,
- },
- ])("fails closed across a $boundary (#6757)", ({ expected, info, overrides }) => {
- expect(resolveDockerStorageLocations(info, overrides)).toEqual({
- ok: false,
- reason: expected,
+ "Docker uses a named context (remote-builder) whose host filesystem cannot be inspected",
});
});
- it("checks Docker host locality without requiring image-store capacity (#6757)", () => {
+ it("returns an inconclusive result outside a native Linux engine (#6757)", () => {
expect(
- probeDockerHostLocality({
+ resolveDockerStorageLocations(nativeDockerInfo(), {
...nativeHost,
- dockerInfo: () => nativeDockerInfo({ Driver: "unrecognized" }),
+ platform: "darwin",
}),
- ).toEqual({ ok: true });
+ ).toEqual({ ok: false, reason: "Docker runs behind a darwin host boundary" });
+ expect(
+ resolveDockerStorageLocations(nativeDockerInfo({ OSType: "windows" }), nativeHost),
+ ).toEqual({ ok: false, reason: "Docker is not using a Linux engine" });
+ });
+ it("returns an inconclusive result for an ambiguous image-storage driver (#6757)", () => {
expect(
- probeDockerHostLocality({
- ...nativeHost,
- dockerHost: "ssh://builder.example.test",
- dockerInfo: () => nativeDockerInfo({ Driver: "unrecognized" }),
- }),
+ resolveDockerStorageLocations(
+ nativeDockerInfo({
+ Driver: "overlayfs",
+ DriverStatus: [["note", "io.containerd.snapshotter.v1"]],
+ }),
+ nativeHost,
+ ),
).toEqual({
ok: false,
- reason: "Docker uses a remote endpoint (ssh://builder.example.test)",
+ reason: "docker reported ambiguous image-storage driver overlayfs",
});
});
- it("fails closed for a non-default Unix socket that can forward a remote daemon (#6757)", () => {
- vi.stubEnv("DOCKER_HOST", "unix:///tmp/forwarded-remote.sock");
- vi.stubEnv("DOCKER_CONTEXT", "default");
+ it("does not guess a containerd root when configuration can override it (#6757)", () => {
+ const containerdInfo = nativeDockerInfo({
+ Driver: "overlayfs",
+ DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
+ });
expect(
- probeDockerHostLocality({
- clientContainerized: false,
- dockerInfo: () => nativeDockerInfo(),
- osRelease: nativeHost.osRelease,
- platform: nativeHost.platform,
- dockerSocketPeerSharesMountNamespace: () => true,
+ resolveDockerStorageLocations(containerdInfo, {
+ ...nativeHost,
+ exists: (target) => target === "/etc/containerd/config.toml",
+ readFile: () => 'root = "/mnt/containerd"\nimports = ["/etc/containerd/conf.d/*.toml"]',
}),
).toEqual({
ok: false,
- reason:
- "Docker uses a non-default socket (unix:///tmp/forwarded-remote.sock) whose daemon host filesystem cannot be verified",
+ reason: "containerd config imports other files that can override its image-store root",
});
- });
-
- it("fails closed when the default Docker socket is mounted into a client container (#6757)", () => {
- vi.stubEnv("DOCKER_HOST", "unix:///var/run/docker.sock");
- vi.stubEnv("DOCKER_CONTEXT", "default");
- vi.spyOn(fs, "existsSync").mockImplementation((target) => target === "/.dockerenv");
-
expect(
- probeDockerHostLocality({
- dockerInfo: () => nativeDockerInfo(),
- osRelease: nativeHost.osRelease,
- platform: nativeHost.platform,
+ resolveDockerStorageLocations(containerdInfo, {
+ ...nativeHost,
+ exists: (target) => target === "/etc/containerd/config.toml",
+ readFile: () => {
+ throw new Error("permission denied");
+ },
}),
).toEqual({
ok: false,
- reason:
- "Docker client runs inside a container, so daemon bind-mount storage cannot be verified",
+ reason: "could not read /etc/containerd/config.toml: permission denied",
});
});
- it("classifies the Docker socket peer mount namespace without trusting procfs PID 1", () => {
- const capture = vi.fn(() => "shared\n");
-
- expect(dockerSocketPeerSharesMountNamespace("/var/run/docker.sock", capture, "linux")).toBe(
- true,
- );
- expect(capture).toHaveBeenCalledWith(
- expect.arrayContaining(["python3", "-I", "-c", "/var/run/docker.sock"]),
- { ignoreError: true, timeout: 5_000 },
- );
- capture.mockReturnValueOnce("different\n");
- expect(dockerSocketPeerSharesMountNamespace("/var/run/docker.sock", capture, "linux")).toBe(
- false,
- );
- capture.mockReturnValueOnce("unknown\n");
- expect(
- dockerSocketPeerSharesMountNamespace("/var/run/docker.sock", capture, "linux"),
- ).toBeNull();
- });
-
- it("fails closed when the Docker socket peer has a different mount namespace (#6757)", () => {
+ it("returns an inconclusive result for rootless containerd (#6757)", () => {
expect(
- probeDockerHostLocality({
- ...nativeHost,
- dockerHost: "unix:///var/run/docker.sock",
- dockerInfo: () => nativeDockerInfo(),
- dockerSocketPeerSharesMountNamespace: () => false,
- }),
+ resolveDockerStorageLocations(
+ nativeDockerInfo({
+ Driver: "overlayfs",
+ DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
+ SecurityOptions: ["name=rootless"],
+ }),
+ nativeHost,
+ ),
).toEqual({
ok: false,
reason:
- "Docker client and socket peer use different mount namespaces, so daemon filesystem identity cannot be verified",
+ "rootless Docker detected; managed vLLM cannot inspect the containerd image-store location",
});
});
- it("fails closed before statfs when a nested PID namespace hides the socket peer (#6757)", () => {
- const statfs = vi.fn(() => ({ bavail: 1_000n, bsize: GIB }));
-
+ it("surfaces filesystem inspection failures without reporting low capacity (#6757)", () => {
expect(
probeDockerStorage({
...nativeHost,
- dockerHost: "unix:///var/run/docker.sock",
dockerInfo: () => nativeDockerInfo(),
- dockerSocketPeerSharesMountNamespace: () => null,
- statfs,
+ statfs: () => {
+ throw new Error("permission denied");
+ },
}),
).toEqual({
ok: false,
- reason: "Docker socket peer PID or mount namespace could not be verified",
+ path: "/var/lib/docker",
+ reason: "could not inspect /var/lib/docker: permission denied",
+ source: "Docker root directory",
});
- expect(statfs).not.toHaveBeenCalled();
});
- it("reads the Docker selection when each storage probe starts (#6757)", () => {
+ it("reads the Docker selection when each capacity probe starts (#6757)", () => {
vi.stubEnv("DOCKER_CONTEXT", "remote-builder");
vi.stubEnv("DOCKER_HOST", "");
expect(
probeDockerStorage({
- clientContainerized: false,
dockerInfo: () => nativeDockerInfo(),
- osRelease: nativeHost.osRelease,
- platform: nativeHost.platform,
- dockerSocketPeerSharesMountNamespace: () => true,
+ platform: "linux",
}),
).toEqual({
ok: false,
reason:
- "Docker uses a named context (remote-builder) whose host filesystem cannot be verified",
- });
- });
-
- it("does not fall back to DockerRootDir when containerd config is unreadable (#6757)", () => {
- const result = resolveDockerStorageLocations(
- nativeDockerInfo({
- Driver: "overlayfs",
- DriverStatus: [["driver-type", "io.containerd.snapshotter.v1"]],
- }),
- {
- ...nativeHost,
- exists: (target) => target === "/etc/containerd/config.toml",
- readFile: () => {
- throw new Error("permission denied");
- },
- },
- );
-
- expect(result).toEqual({
- ok: false,
- reason: "could not read /etc/containerd/config.toml: permission denied",
- });
- });
-});
-
-describe("Hugging Face model-cache storage", () => {
- it("proves Docker bind identity with a bounded read-only sentinel round trip (#6757)", () => {
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-vllm-bind-identity-"));
- tempDirs.push(root);
- const cacheDir = path.join(root, ".cache", "huggingface");
- const dockerReadBind = vi.fn((_image: string, sourcePath: string) => {
- const fd = fs.openSync(sourcePath, "r");
- try {
- expect(fs.fstatSync(fd).mode & 0o777).toBe(0o600);
- const token = fs.readFileSync(fd, "utf8");
- expect(sourcePath).not.toContain(token.slice("nemoclaw-storage-token:".length));
- return token;
- } finally {
- fs.closeSync(fd);
- }
- });
-
- expect(
- probeDockerBindIdentity(cacheDir, "example.test/vllm@sha256:pinned", {
- ...nativeHost,
- dockerInfo: () => nativeDockerInfo(),
- dockerReadBind,
- }),
- ).toEqual({ ok: true });
- expect(dockerReadBind).toHaveBeenCalledWith(
- "example.test/vllm@sha256:pinned",
- expect.stringMatching(/\.nemoclaw-storage-probe-/),
- );
- expect(fs.readdirSync(root)).toEqual([]);
- });
-
- it("fails closed when namespace-local PID 1 hides a private mount namespace (#6757)", () => {
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-vllm-bind-identity-"));
- tempDirs.push(root);
-
- expect(
- probeDockerBindIdentity(
- path.join(root, ".cache", "huggingface"),
- "example.test/vllm@sha256:pinned",
- {
- ...nativeHost,
- clientContainerized: false,
- dockerHost: "unix:///var/run/docker.sock",
- dockerInfo: () => nativeDockerInfo(),
- dockerReadBind: () => "",
- },
- ),
- ).toEqual({
- ok: false,
- reason:
- "Docker daemon could not read the client storage sentinel; bind-mount filesystem identity cannot be verified",
- });
- expect(fs.readdirSync(root)).toEqual([]);
- });
-
- it("checks the nearest existing filesystem before creating the cache directory (#6757)", () => {
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-vllm-storage-"));
- tempDirs.push(root);
- const cacheDir = path.join(root, ".cache", "huggingface");
- const statfs = vi.fn(() => ({ bavail: 7n, bsize: GIB }));
-
- expect(probeModelCacheStorage(cacheDir, { statfs })).toEqual({
- ok: true,
- capacity: {
- availableBytes: 7n * GIB,
- path: root,
- source: "model cache filesystem",
- },
+ "Docker uses a named context (remote-builder) whose host filesystem cannot be inspected",
});
- expect(statfs).toHaveBeenCalledWith(root);
});
});
diff --git a/src/lib/inference/vllm-storage.ts b/src/lib/inference/vllm-storage.ts
index afcabc3aedf..f197a0d78cc 100644
--- a/src/lib/inference/vllm-storage.ts
+++ b/src/lib/inference/vllm-storage.ts
@@ -1,115 +1,33 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { randomUUID } from "node:crypto";
import fs from "node:fs";
-import os from "node:os";
import path from "node:path";
import { parse as parseToml } from "smol-toml";
import { dockerCapture } from "../adapters/docker";
-import { runCapture } from "../runner";
import { buildVllmDockerEnv } from "./vllm-docker-env";
export const VLLM_STORAGE_OVERRIDE_ENV = "NEMOCLAW_IGNORE_VLLM_DISK_SPACE";
const GIB_BYTES = 1024n ** 3n;
const IMAGE_PULL_TEMP_HEADROOM_BYTES = 3n * GIB_BYTES;
-const MODEL_MINIMUM_HEADROOM_BYTES = 2n * GIB_BYTES;
const DEFAULT_CONTAINERD_ROOT = "/var/lib/containerd";
const DEFAULT_CONTAINERD_CONFIG = "/etc/containerd/config.toml";
-const DEFAULT_DOCKER_SOCKET = "/var/run/docker.sock";
-const DOCKER_SOCKET_PEER_PROBE_TIMEOUT_MS = 5_000;
-// SO_PEERCRED translates the daemon PID into the caller's PID namespace. A
-// hidden peer therefore exposes the namespace-local PID 1 topology directly,
-// while equal mountinfo snapshots prove that client-side statfs sees the same
-// mount table as the process serving the selected Docker socket.
-const DOCKER_SOCKET_PEER_PROBE = String.raw`
-import socket
-import struct
-import sys
-
-def read_mountinfo(target):
- with open(target, "rb") as stream:
- return stream.read()
-
-sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-sock.settimeout(2)
-try:
- sock.connect(sys.argv[1])
- size = struct.calcsize("3i")
- peer_pid, _, _ = struct.unpack(
- "3i", sock.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, size)
- )
- if peer_pid <= 0:
- print("unknown")
- else:
- own_before = read_mountinfo("/proc/self/mountinfo")
- peer = read_mountinfo(f"/proc/{peer_pid}/mountinfo")
- own_after = read_mountinfo("/proc/self/mountinfo")
- if own_before != own_after:
- print("unknown")
- else:
- print("shared" if own_before == peer else "different")
-except (OSError, ValueError):
- print("unknown")
-finally:
- sock.close()
-`;
+const DEFAULT_DOCKER_SOCKET_PATHS = new Set(["/var/run/docker.sock", "/run/docker.sock"]);
interface StorageProbeDeps {
- clientContainerized: boolean;
dockerContext: string | undefined;
dockerHost: string | undefined;
dockerInfo: () => string;
- dockerReadBind: (image: string, sourcePath: string) => string;
- dockerSocketPeerSharesMountNamespace: () => boolean | null;
exists: (target: string) => boolean;
- osRelease: string;
platform: NodeJS.Platform;
readFile: (target: string) => string;
statfs: (target: string) => { bavail: bigint; bsize: bigint };
}
-function clientLooksContainerized(): boolean {
- if (String(process.env.container ?? "").trim()) return true;
- if (
- fs.existsSync("/.dockerenv") ||
- fs.existsSync("/run/.containerenv") ||
- fs.existsSync("/run/systemd/container")
- ) {
- return true;
- }
- try {
- return /(?:^|[/:.-])(?:docker|kubepods|containerd|libpod|lxc)(?:$|[/:.-])/im.test(
- fs.readFileSync("/proc/self/cgroup", "utf8"),
- );
- } catch {
- return false;
- }
-}
-
-export function dockerSocketPeerSharesMountNamespace(
- socketPath: string,
- capture: (
- command: readonly string[],
- options: { ignoreError: true; timeout: number },
- ) => string = runCapture,
- platform: NodeJS.Platform = process.platform,
-): boolean | null {
- if (platform !== "linux") return null;
- const result = capture(["python3", "-I", "-c", DOCKER_SOCKET_PEER_PROBE, socketPath], {
- ignoreError: true,
- timeout: DOCKER_SOCKET_PEER_PROBE_TIMEOUT_MS,
- }).trim();
- if (result === "shared") return true;
- if (result === "different") return false;
- return null;
-}
-
function defaultStorageProbeDeps(): StorageProbeDeps {
const dockerEnv = buildVllmDockerEnv();
return {
- clientContainerized: clientLooksContainerized(),
dockerContext: dockerEnv.DOCKER_CONTEXT,
dockerHost: dockerEnv.DOCKER_HOST,
dockerInfo: () =>
@@ -118,39 +36,7 @@ function defaultStorageProbeDeps(): StorageProbeDeps {
ignoreError: true,
timeout: 10_000,
}),
- dockerReadBind: (image, sourcePath) =>
- dockerCapture(
- [
- "run",
- "--rm",
- "--network=none",
- "--pull=never",
- "--entrypoint",
- "/bin/cat",
- "--mount",
- `type=bind,src=${sourcePath},dst=/nemoclaw-storage-sentinel,readonly`,
- image,
- "/nemoclaw-storage-sentinel",
- ],
- {
- env: dockerEnv,
- ignoreError: true,
- timeout: 30_000,
- },
- ),
- dockerSocketPeerSharesMountNamespace: () => {
- const endpoint = dockerEnv.DOCKER_HOST?.trim() ?? "";
- const socketPath = endpoint.startsWith("unix://")
- ? endpoint.slice("unix://".length)
- : path.isAbsolute(endpoint)
- ? endpoint
- : endpoint
- ? ""
- : DEFAULT_DOCKER_SOCKET;
- return socketPath ? dockerSocketPeerSharesMountNamespace(socketPath) : null;
- },
exists: fs.existsSync,
- osRelease: os.release(),
platform: process.platform,
readFile: (target) => fs.readFileSync(target, "utf8"),
statfs: (target) => fs.statfsSync(target, { bigint: true }),
@@ -167,15 +53,11 @@ export type StorageProbeResult =
| { ok: true; capacity: StorageCapacity }
| { ok: false; reason: string; path?: string; source?: string };
-export type DockerHostLocalityResult = { ok: true } | { ok: false; reason: string };
-
interface DockerInfoShape {
ClientInfo?: { Context?: unknown };
DockerRootDir?: unknown;
Driver?: unknown;
DriverStatus?: unknown;
- Name?: unknown;
- OperatingSystem?: unknown;
OSType?: unknown;
SecurityOptions?: unknown;
}
@@ -205,19 +87,6 @@ export function imageStorageRequirementBytes(downloadSizeBytes: number): bigint
);
}
-/**
- * Hugging Face downloads need the published repository size plus temporary
- * write headroom. A local cache cannot prove which blobs a mutable remote
- * revision will reuse, so the estimate deliberately does not subtract it.
- */
-export function modelStorageRequirementBytes(downloadSizeBytes: number): bigint {
- const downloadBytes = positiveBytes(downloadSizeBytes, "vLLM model download size");
- const tenPercent = (downloadBytes + 9n) / 10n;
- const headroom =
- tenPercent > MODEL_MINIMUM_HEADROOM_BYTES ? tenPercent : MODEL_MINIMUM_HEADROOM_BYTES;
- return downloadBytes + headroom;
-}
-
export function formatStorageBytes(bytes: bigint): string {
const roundedTenths = (bytes * 10n + GIB_BYTES / 2n) / GIB_BYTES;
const whole = roundedTenths / 10n;
@@ -250,6 +119,11 @@ function absoluteString(value: unknown): string | null {
return typeof value === "string" && path.isAbsolute(value) ? value : null;
}
+function isDefaultDockerSocket(endpoint: string): boolean {
+ const socketPath = endpoint.startsWith("unix://") ? endpoint.slice("unix://".length) : endpoint;
+ return DEFAULT_DOCKER_SOCKET_PATHS.has(socketPath);
+}
+
type ContainerdRootResult = { ok: true; root: string } | { ok: false; reason: string };
function containerdRootFromConfig(deps: StorageProbeDeps): ContainerdRootResult {
@@ -286,59 +160,36 @@ function containerdRootFromConfig(deps: StorageProbeDeps): ContainerdRootResult
}
}
-function nativeDockerHostProblem(info: DockerInfoShape, deps: StorageProbeDeps): string | null {
+function localDockerHostProblem(info: DockerInfoShape, deps: StorageProbeDeps): string | null {
if (deps.platform !== "linux") return `Docker runs behind a ${deps.platform} host boundary`;
- if (/microsoft|wsl/i.test(deps.osRelease)) return "Docker runs behind a WSL host boundary";
- if (deps.clientContainerized) {
- return "Docker client runs inside a container, so daemon bind-mount storage cannot be verified";
- }
if (info.OSType !== "linux") return "Docker is not using a Linux engine";
- const product = `${String(info.Name ?? "")} ${String(info.OperatingSystem ?? "")}`;
- if (/docker desktop|colima|podman/i.test(product)) {
- return "Docker runs inside a VM or compatibility layer";
- }
-
const dockerHost = deps.dockerHost?.trim() ?? "";
- // DOCKER_HOST takes precedence over DOCKER_CONTEXT in the Docker CLI.
- const explicitContext = dockerHost ? "" : deps.dockerContext?.trim();
- const reportedContext =
- typeof info.ClientInfo?.Context === "string" ? info.ClientInfo.Context.trim() : "";
- const context = explicitContext || reportedContext;
- if (!context) return "docker info did not report the effective Docker context";
- if (context !== "default") {
- return `Docker uses a named context (${context}) whose host filesystem cannot be verified`;
+ // An explicit DOCKER_CONTEXT overrides DOCKER_HOST in the Docker CLI.
+ const explicitContext = deps.dockerContext?.trim() ?? "";
+ if (explicitContext) {
+ if (explicitContext !== "default") {
+ return `Docker uses a named context (${explicitContext}) whose host filesystem cannot be inspected`;
+ }
+ return null;
}
- const endpoint = dockerHost;
- if (endpoint) {
- const localSocket = endpoint.startsWith("unix://") || path.isAbsolute(endpoint);
- if (!localSocket) return `Docker uses a remote endpoint (${endpoint})`;
- if (endpoint !== DEFAULT_DOCKER_SOCKET && endpoint !== `unix://${DEFAULT_DOCKER_SOCKET}`) {
- return `Docker uses a non-default socket (${endpoint}) whose daemon host filesystem cannot be verified`;
+ if (dockerHost) {
+ if (isDefaultDockerSocket(dockerHost)) return null;
+ if (dockerHost.startsWith("unix://") || path.isAbsolute(dockerHost)) {
+ return `Docker uses a non-default socket (${dockerHost}) whose host filesystem cannot be inspected`;
}
+ return `Docker uses a remote endpoint (${dockerHost})`;
}
- const sharesPeerMountNamespace = deps.dockerSocketPeerSharesMountNamespace();
- if (sharesPeerMountNamespace === false) {
- return "Docker client and socket peer use different mount namespaces, so daemon filesystem identity cannot be verified";
- }
- if (sharesPeerMountNamespace === null) {
- return "Docker socket peer PID or mount namespace could not be verified";
+ const reportedContext =
+ typeof info.ClientInfo?.Context === "string" ? info.ClientInfo.Context.trim() : "";
+ if (reportedContext && reportedContext !== "default") {
+ return `Docker uses a named context (${reportedContext}) whose host filesystem cannot be inspected`;
}
return null;
}
-export function probeDockerHostLocality(
- overrides: Partial = {},
-): DockerHostLocalityResult {
- const deps = { ...defaultStorageProbeDeps(), ...overrides };
- const info = parseDockerInfo(deps.dockerInfo());
- if (!info) return { ok: false, reason: "docker info did not return valid JSON" };
- const hostProblem = nativeDockerHostProblem(info, deps);
- return hostProblem ? { ok: false, reason: hostProblem } : { ok: true };
-}
-
export function resolveDockerStorageLocations(
rawInfo: string,
overrides: Partial = {},
@@ -346,7 +197,7 @@ export function resolveDockerStorageLocations(
const deps = { ...defaultStorageProbeDeps(), ...overrides };
const info = parseDockerInfo(rawInfo);
if (!info) return { ok: false, reason: "docker info did not return valid JSON" };
- const hostProblem = nativeDockerHostProblem(info, deps);
+ const hostProblem = localDockerHostProblem(info, deps);
if (hostProblem) return { ok: false, reason: hostProblem };
const dockerRoot = absoluteString(info.DockerRootDir);
@@ -385,7 +236,7 @@ export function resolveDockerStorageLocations(
return {
ok: false,
reason:
- "rootless Docker detected; managed vLLM cannot verify the containerd image-store location",
+ "rootless Docker detected; managed vLLM cannot inspect the containerd image-store location",
};
}
const containerd = containerdRootFromConfig(deps);
@@ -420,8 +271,7 @@ function capacityForLocation(
export function probeDockerStorage(overrides: Partial = {}): StorageProbeResult {
const deps = { ...defaultStorageProbeDeps(), ...overrides };
- const rawInfo = deps.dockerInfo();
- const resolved = resolveDockerStorageLocations(rawInfo, deps);
+ const resolved = resolveDockerStorageLocations(deps.dockerInfo(), deps);
if (!resolved.ok) return resolved;
let limiting: StorageCapacity | null = null;
@@ -436,62 +286,3 @@ export function probeDockerStorage(overrides: Partial = {}): S
? { ok: true, capacity: limiting }
: { ok: false, reason: "docker info did not report a usable image-storage path" };
}
-
-function nearestExistingPath(target: string, exists: (candidate: string) => boolean): string {
- let candidate = path.resolve(target);
- while (!exists(candidate)) {
- const parent = path.dirname(candidate);
- if (parent === candidate) return candidate;
- candidate = parent;
- }
- return candidate;
-}
-
-export function probeDockerBindIdentity(
- cacheDir: string,
- image: string,
- overrides: Partial = {},
-): DockerHostLocalityResult {
- const deps = { ...defaultStorageProbeDeps(), ...overrides };
- const info = parseDockerInfo(deps.dockerInfo());
- if (!info) return { ok: false, reason: "docker info did not return valid JSON" };
- const hostProblem = nativeDockerHostProblem(info, deps);
- if (hostProblem) return { ok: false, reason: hostProblem };
-
- const target = nearestExistingPath(cacheDir, deps.exists);
- const token = `nemoclaw-storage-token:${randomUUID()}`;
- const sentinelPath = path.join(target, `.nemoclaw-storage-probe-${randomUUID()}`);
- try {
- fs.writeFileSync(sentinelPath, token, { encoding: "utf8", flag: "wx", mode: 0o600 });
- if (deps.dockerReadBind(image, sentinelPath) !== token) {
- return {
- ok: false,
- reason:
- "Docker daemon could not read the client storage sentinel; bind-mount filesystem identity cannot be verified",
- };
- }
- return { ok: true };
- } catch (err) {
- return {
- ok: false,
- reason: `could not verify Docker bind-mount filesystem identity at ${target}: ${(err as Error).message}`,
- };
- } finally {
- fs.rmSync(sentinelPath, { force: true });
- }
-}
-
-export function probeModelCacheStorage(
- cacheDir: string,
- overrides: Partial = {},
-): StorageProbeResult {
- const deps = { ...defaultStorageProbeDeps(), ...overrides };
- const target = nearestExistingPath(cacheDir, deps.exists);
- return capacityForLocation(
- {
- path: target,
- source: target === path.resolve(cacheDir) ? "model cache" : "model cache filesystem",
- },
- deps.statfs,
- );
-}
diff --git a/src/lib/inference/vllm.test.ts b/src/lib/inference/vllm.test.ts
index 5872c6e6a04..a31b3bee5ac 100644
--- a/src/lib/inference/vllm.test.ts
+++ b/src/lib/inference/vllm.test.ts
@@ -15,10 +15,7 @@ const mocks = vi.hoisted(() => ({
dockerSpawn: vi.fn(),
dockerStop: vi.fn(),
getGpuIndicesByName: vi.fn<(_pattern: RegExp) => number[]>(() => []),
- probeDockerBindIdentity: vi.fn(),
- probeDockerHostLocality: vi.fn(),
probeDockerStorage: vi.fn(),
- probeModelCacheStorage: vi.fn(),
runCapture: vi.fn(),
}));
@@ -44,10 +41,7 @@ vi.mock("./vllm-storage", async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
- probeDockerBindIdentity: mocks.probeDockerBindIdentity,
- probeDockerHostLocality: mocks.probeDockerHostLocality,
probeDockerStorage: mocks.probeDockerStorage,
- probeModelCacheStorage: mocks.probeModelCacheStorage,
};
});
@@ -64,16 +58,10 @@ import {
beforeEach(() => {
mocks.dockerImageInspectFormat.mockReturnValue("");
- mocks.probeDockerBindIdentity.mockReturnValue({ ok: true });
- mocks.probeDockerHostLocality.mockReturnValue({ ok: true });
mocks.probeDockerStorage.mockReturnValue({
ok: true,
capacity: { availableBytes: 1_000_000_000_000n, path: "/docker", source: "Docker" },
});
- mocks.probeModelCacheStorage.mockReturnValue({
- ok: true,
- capacity: { availableBytes: 1_000_000_000_000n, path: "/models", source: "model cache" },
- });
});
function mockDockerSpawnSuccess(): EventEmitter & {
@@ -136,14 +124,6 @@ function mockInconclusiveDockerStorage(): void {
});
}
-function mockInconclusiveModelCacheStorage(): void {
- mocks.dockerImageInspectFormat.mockReturnValue("sha256:cached-image");
- mocks.probeModelCacheStorage.mockReturnValue({
- ok: false,
- reason: "could not inspect the model cache: permission denied",
- });
-}
-
describe("vLLM served route identity", () => {
it("uses one safe served-model override and rejects ambiguous aliases (#6315)", () => {
expect(resolveVllmServedModelId("catalog/model", [])).toBe("catalog/model");
@@ -186,6 +166,7 @@ describe("vLLM profile detection", () => {
expect(profile!.image).toBe(
"nvcr.io/nvidia/vllm@sha256:9204569b17ee4c0eff75194b8e6e458479c8aee18953b5ab9cf359fcdac659e2",
);
+ expect(profile!.imageDownloadSizeBytes).toBe(9_603_085_145);
expect(profile!.defaultModel.id).toBe("nvidia/Qwen3.6-35B-A3B-NVFP4");
expect(profile!.defaultModel.envValue).toBe("qwen3.6-35b-a3b-nvfp4");
});
@@ -644,90 +625,27 @@ describe("installVllm model resolution", () => {
);
});
- it.each([
- {
- name: "inconclusive Docker storage in non-interactive mode",
- hasImage: false,
- nonInteractive: true,
- replies: [],
- expectedPromptCalls: 0,
- expectedWarning: "Unable to verify Docker storage for the managed vLLM image",
- setup: mockInconclusiveDockerStorage,
- },
- {
- name: "inconclusive Docker storage after an interactive decline",
- hasImage: false,
- nonInteractive: false,
- replies: ["y", "n"],
- expectedPromptCalls: 2,
- expectedWarning: "Unable to verify Docker storage for the managed vLLM image",
- setup: mockInconclusiveDockerStorage,
- },
- {
- name: "inconclusive model-cache storage in non-interactive mode",
- hasImage: true,
- nonInteractive: true,
- replies: [],
- expectedPromptCalls: 0,
- expectedWarning: "Unable to verify model-cache storage for managed vLLM",
- setup: mockInconclusiveModelCacheStorage,
- },
- {
- name: "inconclusive model-cache storage after an interactive decline",
- hasImage: true,
- nonInteractive: false,
- replies: ["y", "n"],
- expectedPromptCalls: 2,
- expectedWarning: "Unable to verify model-cache storage for managed vLLM",
- setup: mockInconclusiveModelCacheStorage,
- },
- ] as const)("$name stops before guarded downloads (#6757)", async (testCase) => {
+ it("reports an inconclusive capacity check without blocking the image pull (#6757)", async () => {
const profile = detectVllmProfile({ platform: "station", type: "nvidia" })!;
process.env.NEMOCLAW_VLLM_MODEL = profile.defaultModel.envValue;
mockSuccessfulVllmInstall(profile.containerName);
- testCase.setup();
- const replies = [...testCase.replies];
- const promptFn = vi.fn(async () => replies.shift() ?? "");
+ mockInconclusiveDockerStorage();
+ const promptFn = vi.fn();
const result = await installVllm(profile, {
- hasImage: testCase.hasImage,
- nonInteractive: testCase.nonInteractive,
+ hasImage: false,
+ nonInteractive: true,
promptFn,
});
- expect(result).toEqual({ ok: false });
- expect(promptFn).toHaveBeenCalledTimes(testCase.expectedPromptCalls);
- expect(mocks.dockerPullWithProgressWatchdog).not.toHaveBeenCalled();
- expect(mocks.dockerSpawn).not.toHaveBeenCalled();
+ expect(result).toEqual({ ok: true });
+ expect(promptFn).not.toHaveBeenCalled();
+ expect(mocks.dockerPullWithProgressWatchdog).toHaveBeenCalledTimes(1);
+ expect(mocks.dockerSpawn).toHaveBeenCalledTimes(1);
const errors = errSpy.mock.calls.map((call: unknown[]) => String(call[0])).join("\n");
- expect(errors).toContain(testCase.expectedWarning);
+ expect(errors).toContain("Unable to verify Docker storage for the managed vLLM image");
expect(errors).toContain("Available: unknown (");
- });
-
- it("does not pull an uncached image when a nested PID namespace hides the Docker peer (#6757)", async () => {
- const profile = detectVllmProfile({ platform: "station", type: "nvidia" })!;
- process.env.NEMOCLAW_VLLM_MODEL = profile.defaultModel.envValue;
- mockSuccessfulVllmInstall(profile.containerName);
- mocks.probeDockerStorage.mockReturnValue({
- ok: false,
- reason: "Docker socket peer PID or mount namespace could not be verified",
- });
-
- const result = await installVllm(profile, {
- hasImage: false,
- nonInteractive: true,
- promptFn: vi.fn(),
- });
-
- expect(result).toEqual({ ok: false });
- expect(mocks.probeDockerStorage).toHaveBeenCalledTimes(1);
- expect(mocks.dockerPullWithProgressWatchdog).not.toHaveBeenCalled();
- expect(mocks.probeDockerBindIdentity).not.toHaveBeenCalled();
- expect(mocks.dockerSpawn).not.toHaveBeenCalled();
- expect(mocks.dockerRunDetached).not.toHaveBeenCalled();
- expect(errSpy).toHaveBeenCalledWith(
- expect.stringContaining("Docker socket peer PID or mount namespace could not be verified"),
- );
+ expect(errors).toContain("Continuing because Docker storage capacity could not be verified");
});
it("honors only the dedicated disk-space override in non-interactive setup (#6757)", async () => {
@@ -739,11 +657,6 @@ describe("installVllm model resolution", () => {
ok: true,
capacity: { availableBytes: 1n, path: "/docker-low", source: "containerd image store" },
});
- mocks.probeModelCacheStorage.mockReturnValue({
- ok: true,
- capacity: { availableBytes: 1n, path: "/models-low", source: "model cache" },
- });
-
const result = await installVllm(profile, {
hasImage: false,
nonInteractive: true,
@@ -758,30 +671,6 @@ describe("installVllm model resolution", () => {
);
});
- it("stops before either download when model-cache capacity is declined (#6757)", async () => {
- const profile = detectVllmProfile({ platform: "spark", type: "nvidia" })!;
- process.env.NEMOCLAW_VLLM_MODEL = profile.defaultModel.envValue;
- mockSuccessfulVllmInstall(profile.containerName);
- mocks.probeModelCacheStorage.mockReturnValue({
- ok: true,
- capacity: { availableBytes: 1n, path: "/models-low", source: "model cache" },
- });
- const replies = ["y", "n"];
-
- const result = await installVllm(profile, {
- hasImage: false,
- nonInteractive: false,
- promptFn: vi.fn(async () => replies.shift() ?? ""),
- });
-
- expect(result).toEqual({ ok: false });
- expect(mocks.dockerPullWithProgressWatchdog).not.toHaveBeenCalled();
- expect(mocks.dockerSpawn).not.toHaveBeenCalled();
- const errors = errSpy.mock.calls.map((call: unknown[]) => String(call[0])).join("\n");
- expect(errors).toContain("Insufficient model-cache storage for managed vLLM");
- expect(errors).toContain(profile.defaultModel.id);
- });
-
it("reuses an authoritatively cached image without a cold-pull capacity check (#6757)", async () => {
const profile = detectVllmProfile({ platform: "spark", type: "nvidia" })!;
mockSuccessfulVllmInstall(profile.containerName);
@@ -832,176 +721,6 @@ describe("installVllm model resolution", () => {
expect(mocks.dockerSpawn).not.toHaveBeenCalled();
});
- it("rechecks model capacity after the image pull before hf download (#6757)", async () => {
- const profile = detectVllmProfile({ platform: "spark", type: "nvidia" })!;
- process.env.NEMOCLAW_VLLM_MODEL = profile.defaultModel.envValue;
- mockSuccessfulVllmInstall(profile.containerName);
- mocks.probeModelCacheStorage
- .mockReturnValueOnce({
- ok: true,
- capacity: {
- availableBytes: 1_000_000_000_000n,
- path: "/shared",
- source: "model cache",
- },
- })
- .mockReturnValueOnce({
- ok: true,
- capacity: { availableBytes: 1n, path: "/shared", source: "model cache" },
- });
-
- const result = await installVllm(profile, {
- hasImage: false,
- nonInteractive: true,
- promptFn: vi.fn(),
- });
-
- expect(result).toEqual({ ok: false });
- expect(mocks.dockerPullWithProgressWatchdog).toHaveBeenCalledTimes(1);
- expect(mocks.probeDockerHostLocality).toHaveBeenCalledTimes(1);
- expect(mocks.probeDockerBindIdentity).toHaveBeenCalledTimes(1);
- expect(mocks.probeModelCacheStorage).toHaveBeenCalledTimes(2);
- expect(mocks.dockerSpawn).not.toHaveBeenCalled();
- });
-
- it("rechecks model capacity for a cached image before hf download (#6757)", async () => {
- const profile = detectVllmProfile({ platform: "spark", type: "nvidia" })!;
- process.env.NEMOCLAW_VLLM_MODEL = profile.defaultModel.envValue;
- mockSuccessfulVllmInstall(profile.containerName);
- mocks.dockerImageInspectFormat.mockReturnValue("sha256:cached-image");
- mocks.probeModelCacheStorage
- .mockReturnValueOnce({
- ok: true,
- capacity: {
- availableBytes: 1_000_000_000_000n,
- path: "/shared",
- source: "model cache",
- },
- })
- .mockReturnValueOnce({
- ok: true,
- capacity: { availableBytes: 1n, path: "/shared", source: "model cache" },
- });
-
- const result = await installVllm(profile, {
- hasImage: true,
- nonInteractive: true,
- promptFn: vi.fn(),
- });
-
- expect(result).toEqual({ ok: false });
- expect(mocks.probeDockerStorage).not.toHaveBeenCalled();
- expect(mocks.dockerPullWithProgressWatchdog).toHaveBeenCalledTimes(1);
- expect(mocks.probeDockerHostLocality).not.toHaveBeenCalled();
- expect(mocks.probeDockerBindIdentity).toHaveBeenCalledTimes(2);
- expect(mocks.probeModelCacheStorage).toHaveBeenCalledTimes(2);
- expect(mocks.dockerSpawn).not.toHaveBeenCalled();
- });
-
- it.each([
- {
- reason: "Docker uses a remote endpoint (ssh://builder.example.test)",
- selectorDescription: "remote DOCKER_HOST",
- selectorName: "DOCKER_HOST" as const,
- selectorValue: "ssh://builder.example.test",
- },
- {
- reason:
- "Docker uses a non-default socket (unix:///tmp/forwarded-remote.sock) whose daemon host filesystem cannot be verified",
- selectorDescription: "forwarded Unix DOCKER_HOST",
- selectorName: "DOCKER_HOST" as const,
- selectorValue: "unix:///tmp/forwarded-remote.sock",
- },
- {
- reason:
- "Docker uses a named context (remote-builder) whose host filesystem cannot be verified",
- selectorDescription: "named DOCKER_CONTEXT",
- selectorName: "DOCKER_CONTEXT" as const,
- selectorValue: "remote-builder",
- },
- {
- reason:
- "Docker client runs inside a container, so daemon bind-mount storage cannot be verified",
- selectorDescription: "default socket mounted into a client container",
- selectorName: "DOCKER_HOST" as const,
- selectorValue: "unix:///var/run/docker.sock",
- },
- {
- reason:
- "Docker daemon could not read the client storage sentinel; bind-mount filesystem identity cannot be verified",
- selectorDescription: "namespace-local PID 1 bind mismatch",
- selectorName: "DOCKER_HOST" as const,
- selectorValue: "unix:///var/run/docker.sock",
- },
- ])("blocks a cached image for an unverifiable $selectorDescription (#6757)", async ({
- reason,
- selectorName,
- selectorValue,
- }) => {
- delete process.env.DOCKER_HOST;
- delete process.env.DOCKER_CONTEXT;
- process.env[selectorName] = selectorValue;
- const profile = detectVllmProfile({ platform: "spark", type: "nvidia" })!;
- mockSuccessfulVllmInstall(profile.containerName);
- mocks.dockerImageInspectFormat.mockReturnValue("sha256:cached-image");
- mocks.probeDockerBindIdentity.mockReturnValue({
- ok: false,
- reason,
- });
-
- const result = await installVllm(profile, {
- hasImage: true,
- nonInteractive: true,
- promptFn: vi.fn(),
- });
-
- expect(result).toEqual({ ok: false });
- expect(mocks.dockerImageInspectFormat).toHaveBeenCalledWith(
- "{{.Id}}",
- profile.image,
- expect.objectContaining({
- env: expect.objectContaining({ [selectorName]: selectorValue }),
- }),
- );
- expect(mocks.probeDockerBindIdentity).toHaveBeenCalledWith(
- path.join(os.homedir(), ".cache", "huggingface"),
- profile.image,
- );
- expect(mocks.probeModelCacheStorage).not.toHaveBeenCalled();
- expect(mocks.dockerPullWithProgressWatchdog).not.toHaveBeenCalled();
- expect(mocks.dockerSpawn).not.toHaveBeenCalled();
- expect(mocks.dockerRunDetached).not.toHaveBeenCalled();
- expect(errSpy).toHaveBeenCalledWith(expect.stringContaining(reason));
- });
-
- it("allows the dedicated override for an unverifiable cached-image host (#6757)", async () => {
- process.env.DOCKER_HOST = "ssh://builder.example.test";
- delete process.env.DOCKER_CONTEXT;
- process.env.NEMOCLAW_IGNORE_VLLM_DISK_SPACE = "1";
- const profile = detectVllmProfile({ platform: "spark", type: "nvidia" })!;
- mockSuccessfulVllmInstall(profile.containerName);
- mocks.dockerImageInspectFormat.mockReturnValue("sha256:cached-image");
- mocks.probeDockerBindIdentity.mockReturnValue({
- ok: false,
- reason: "Docker uses a remote endpoint (ssh://builder.example.test)",
- });
-
- const result = await installVllm(profile, {
- hasImage: true,
- nonInteractive: true,
- promptFn: vi.fn(),
- });
-
- expect(result).toEqual({ ok: true });
- expect(mocks.probeDockerBindIdentity).toHaveBeenCalledTimes(2);
- expect(mocks.probeModelCacheStorage).not.toHaveBeenCalled();
- expect(mocks.dockerPullWithProgressWatchdog).toHaveBeenCalledTimes(1);
- expect(mocks.dockerSpawn).toHaveBeenCalledTimes(1);
- expect(errSpy).toHaveBeenCalledWith(
- expect.stringContaining("NEMOCLAW_IGNORE_VLLM_DISK_SPACE=1"),
- );
- });
-
it("uses one Docker context throughout a successful managed install (#6757)", async () => {
process.env.DOCKER_CONTEXT = "local-test-context";
delete process.env.DOCKER_HOST;
diff --git a/src/lib/inference/vllm.ts b/src/lib/inference/vllm.ts
index 003dcc6083e..32a5e3023f7 100644
--- a/src/lib/inference/vllm.ts
+++ b/src/lib/inference/vllm.ts
@@ -35,11 +35,7 @@ import { resolveVllmInstallModel } from "./vllm-prompt";
import {
formatStorageBytes,
imageStorageRequirementBytes,
- modelStorageRequirementBytes,
- probeDockerBindIdentity,
- probeDockerHostLocality,
probeDockerStorage,
- probeModelCacheStorage,
type StorageProbeResult,
VLLM_STORAGE_OVERRIDE_ENV,
} from "./vllm-storage";
@@ -672,49 +668,51 @@ function containerStillRunning(profile: VllmProfile): boolean {
return out === profile.containerName;
}
-interface StorageWarning {
- item: string;
- itemLabel: "Image" | "Model";
- probe: StorageProbeResult;
- question: string;
- remediation: readonly string[];
- requiredBytes: bigint;
- subject: string;
-}
-
-function printStorageWarning(warning: StorageWarning): void {
- const insufficient =
- warning.probe.ok && warning.probe.capacity.availableBytes < warning.requiredBytes;
+function printImageStorageWarning(
+ profile: VllmProfile,
+ probe: StorageProbeResult,
+ requiredBytes: bigint,
+): void {
+ const insufficient = probe.ok && probe.capacity.availableBytes < requiredBytes;
console.error("");
- console.error(` ${insufficient ? "Insufficient" : "Unable to verify"} ${warning.subject}.`);
+ console.error(
+ ` ${insufficient ? "Insufficient" : "Unable to verify"} Docker storage for the managed vLLM image.`,
+ );
console.error("");
- console.error(` ${warning.itemLabel}: ${warning.item}`);
+ console.error(` Image: ${profile.image}`);
console.error(
` Available: ${
- warning.probe.ok
- ? formatStorageBytes(warning.probe.capacity.availableBytes)
- : `unknown (${warning.probe.reason})`
+ probe.ok ? formatStorageBytes(probe.capacity.availableBytes) : `unknown (${probe.reason})`
}`,
);
- console.error(` Required: approximately ${formatStorageBytes(warning.requiredBytes)}`);
- if (warning.probe.ok) {
- console.error(` Storage: ${warning.probe.capacity.source} (${warning.probe.capacity.path})`);
- } else if (warning.probe.path) {
- console.error(` Storage: ${warning.probe.source ?? "filesystem"} (${warning.probe.path})`);
+ console.error(` Required: approximately ${formatStorageBytes(requiredBytes)}`);
+ if (probe.ok) {
+ console.error(` Storage: ${probe.capacity.source} (${probe.capacity.path})`);
+ } else if (probe.path) {
+ console.error(` Storage: ${probe.source ?? "filesystem"} (${probe.path})`);
}
console.error("");
- for (const line of warning.remediation) console.error(` ${line}`);
+ if (insufficient) console.error(" Free or expand Docker storage before continuing.");
+ console.error(" Useful diagnostics:");
+ console.error(" docker system df");
+ console.error(" docker info --format '{{.DockerRootDir}}'");
}
-async function storageWarningAccepted(
- warning: StorageWarning,
+async function imageStorageAccepted(
+ profile: VllmProfile,
opts: InstallVllmOptions,
env: NodeJS.ProcessEnv = process.env,
): Promise {
- if (warning.probe.ok && warning.probe.capacity.availableBytes >= warning.requiredBytes) {
+ const probe = probeDockerStorage();
+ const requiredBytes = imageStorageRequirementBytes(profile.imageDownloadSizeBytes);
+ if (probe.ok && probe.capacity.availableBytes >= requiredBytes) {
+ return true;
+ }
+ printImageStorageWarning(profile, probe, requiredBytes);
+ if (!probe.ok) {
+ console.error(" Continuing because Docker storage capacity could not be verified.");
return true;
}
- printStorageWarning(warning);
if (env[VLLM_STORAGE_OVERRIDE_ENV] === "1") {
console.error(` Continuing because ${VLLM_STORAGE_OVERRIDE_ENV}=1.`);
return true;
@@ -725,58 +723,7 @@ async function storageWarningAccepted(
);
return false;
}
- return isAffirmativeAnswer(await opts.promptFn(warning.question));
-}
-
-async function imageStorageAccepted(
- profile: VllmProfile,
- opts: InstallVllmOptions,
-): Promise {
- return storageWarningAccepted(
- {
- item: profile.image,
- itemLabel: "Image",
- probe: probeDockerStorage(),
- question: " Continue with the pull anyway? [y/N]: ",
- remediation: [
- "Free or expand Docker storage before continuing.",
- "Useful diagnostics:",
- " docker system df",
- " docker info --format '{{.DockerRootDir}}'",
- ],
- requiredBytes: imageStorageRequirementBytes(profile.imageDownloadSizeBytes),
- subject: "Docker storage for the managed vLLM image",
- },
- opts,
- );
-}
-
-async function modelStorageAccepted(
- model: VllmModelDef,
- opts: InstallVllmOptions,
- bindProbeImage?: string,
-): Promise {
- const cacheDir = hostHfCacheDir();
- const dockerHost = bindProbeImage
- ? probeDockerBindIdentity(cacheDir, bindProbeImage)
- : probeDockerHostLocality();
- const probe: StorageProbeResult = dockerHost.ok ? probeModelCacheStorage(cacheDir) : dockerHost;
- return storageWarningAccepted(
- {
- item: model.id,
- itemLabel: "Model",
- probe,
- question: " Continue with the model download anyway? [y/N]: ",
- remediation: [
- `Free or expand storage for ${cacheDir} before continuing.`,
- "Useful diagnostic:",
- ' df -h "$HOME/.cache/huggingface"',
- ],
- requiredBytes: modelStorageRequirementBytes(model.downloadSizeBytes),
- subject: "model-cache storage for managed vLLM",
- },
- opts,
- );
+ return isAffirmativeAnswer(await opts.promptFn(" Continue with the pull anyway? [y/N]: "));
}
interface InstallVllmOptions {
@@ -886,9 +833,6 @@ export async function installVllm(
if (!hasImage && !(await imageStorageAccepted(profile, opts))) {
return { ok: false };
}
- if (!(await modelStorageAccepted(model, opts, hasImage ? profile.image : undefined))) {
- return { ok: false };
- }
const pull = await pullImage(profile);
if (!pull.ok) {
@@ -896,12 +840,6 @@ export async function installVllm(
return { ok: false };
}
- // The image now exists, so this re-probe also proves daemon/client bind
- // identity before their individually valid estimates can overcommit storage.
- if (!(await modelStorageAccepted(model, opts, profile.image))) {
- return { ok: false };
- }
-
const modelDownload = await downloadModel(profile, model);
if (!modelDownload.ok) {
console.error(` vLLM install failed: ${String(modelDownload.reason)}`);
diff --git a/test/install-express-prompt.test.ts b/test/install-express-prompt.test.ts
index f916e84c9d3..57873166cec 100644
--- a/test/install-express-prompt.test.ts
+++ b/test/install-express-prompt.test.ts
@@ -108,6 +108,43 @@ sys.exit(exit_code)
});
}
+ function detectExpressPlatformForProductName(productName: string) {
+ return spawnSync(
+ "bash",
+ [
+ "-c",
+ `
+source "$INSTALLER_UNDER_TEST" >/dev/null
+function [ {
+ if [[ "$#" -eq 3 && "$1" = "-r" && "$2" = "/sys/class/dmi/id/product_name" && "$3" = "]" ]]; then
+ return 0
+ fi
+ builtin [ "$@"
+}
+cat() {
+ if [[ "$#" -eq 1 && "$1" = "/sys/class/dmi/id/product_name" ]]; then
+ printf "%s" "$EXPRESS_PRODUCT_NAME"
+ return
+ fi
+ command cat "$@"
+}
+is_wsl_host() { return 1; }
+detect_express_platform
+`,
+ ],
+ {
+ cwd: path.join(import.meta.dirname, ".."),
+ encoding: "utf-8",
+ env: {
+ HOME: fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-platform-detect-")),
+ PATH: TEST_SYSTEM_PATH,
+ INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD,
+ EXPRESS_PRODUCT_NAME: productName,
+ },
+ },
+ );
+ }
+
it("offers express install when curl-piped stdin still has a controlling TTY", () => {
const result = runExpressPromptWithTty("y\n", "pipe");
const output = `${result.stdout}${result.stderr}`;
@@ -185,6 +222,22 @@ detect_express_platform
expect(result.stdout).toBe("Windows WSL");
});
+ it("recognizes Station GB300 OEM firmware as DGX Station", () => {
+ const result = detectExpressPlatformForProductName("Dell Pro Max with Station GB300");
+
+ expect(result.status, `${result.stdout}${result.stderr}`).toBe(0);
+ expect(result.stdout).toBe("DGX Station");
+ });
+
+ it("requires both Station and GB300 for the OEM firmware match", () => {
+ for (const productName of ["Dell Pro Max with Station GB200", "Dell Pro Max with GB300"]) {
+ const result = detectExpressPlatformForProductName(productName);
+
+ expect(result.status, `${result.stdout}${result.stderr}`).toBe(0);
+ expect(result.stdout).toBe("");
+ }
+ });
+
it("maps Windows WSL express install to Windows-host Ollama", () => {
const result = runExpressPromptWithTty("\n", "pipe", "Windows WSL");
const output = `${result.stdout}${result.stderr}`;