Skip to content
2 changes: 1 addition & 1 deletion ci/platform-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"status": "tested",
"prd_priority": "P1",
"ci_tested": true,
"notes": "Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local Ollama inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw)."
"notes": "Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw)."
},
{
"name": "Windows WSL2",
Expand Down
2 changes: 1 addition & 1 deletion ci/test-file-size-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"src/lib/onboard/preflight.test.ts": 1905,
"test/channels-add-preset.test.ts": 1871,
"test/generate-openclaw-config.test.ts": 1984,
"test/install-preflight.test.ts": 4207,
"test/install-preflight.test.ts": 4006,
"test/nemoclaw-start.test.ts": 5043,
"test/onboard-messaging.test.ts": 2062,
"test/onboard-selection.test.ts": 6888,
Expand Down
2 changes: 1 addition & 1 deletion docs/get-started/prerequisites.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The table comes from [`ci/platform-matrix.json`](https://github.com/NVIDIA/NemoC
|----|-------------------|--------|-------|
| Linux | Docker | Tested | Primary tested path. Ubuntu 24.04 is the validated distro in production source (`DEFAULT_COMPAT_IMAGE` in `src/lib/onboard/docker-driver-gateway-launch.ts:10` and the preflight tests pin 24.04 only); the installer's package-manager probes assume apt-get. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. |
| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | Start the container runtime (Colima or Docker Desktop) before running the installer. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. |
| DGX Spark | Docker | Tested | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local Ollama inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). |
| DGX Spark | Docker | Tested | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). |
| Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | Requires WSL2 with Docker Desktop backend. |
{/* platform-matrix:end */}

Expand Down
1 change: 1 addition & 0 deletions docs/get-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash

On DGX Spark, DGX Station, and Windows WSL, an interactive installer offers express install after you accept the third-party software notice.
Express install switches onboarding to non-interactive mode, allows `sudo` password prompts for required host changes, and selects the managed local inference path for that platform.
On DGX Spark and DGX Station, express install selects managed vLLM; DGX Spark uses `qwen3.6-35b-a3b-nvfp4` by default.
Unless `NEMOCLAW_POLICY_TIER` is set, it applies sandbox policy in `suggested` mode with the `balanced` tier by default, using the base sandbox policy plus supported package, model, web-search, local-inference, and read-only weather presets.
On DGX Spark, express install uses `my-spark-assistant` as the sandbox name unless `NEMOCLAW_SANDBOX_NAME` is already set.
On WSL, express install selects the Windows-host Ollama setup path.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/platform-support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ For the onboarding-time supported set without deferred rows, see [Prerequisites]
|----|-------------------|--------|--------------|----|-------|
| Linux | Docker | Tested | P0 | Yes | Primary tested path. Ubuntu 24.04 is the validated distro in production source (`DEFAULT_COMPAT_IMAGE` in `src/lib/onboard/docker-driver-gateway-launch.ts:10` and the preflight tests pin 24.04 only); the installer's package-manager probes assume apt-get. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. |
| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | P0 | Yes | Start the container runtime (Colima or Docker Desktop) before running the installer. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. |
| DGX Spark | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local Ollama inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). |
| DGX Spark | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). |
| Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | P1 | No | Requires WSL2 with Docker Desktop backend. |
| DGX Station | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Onboard path not yet validated end-to-end on the hardware; vLLM has a placeholder default model defined for this host class (`Qwen/Qwen3.6-27B-FP8`) that will move out of `deferred` once the hardware run is signed off. |
| NVIDIA RTX (consumer and Pro workstation GPUs) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Covers RTX consumer cards and RTX Pro workstation cards on Linux hosts that meet the generic-Linux-GPU requirements (NVIDIA Container Toolkit + CDI present). The provider menu emits managed vLLM behind `NEMOCLAW_EXPERIMENTAL=1` or `NEMOCLAW_PROVIDER=install-vllm` for this host class today; the end-to-end onboard path on this hardware is not yet validated in CI. |
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,7 @@ Use `--follow` to stream logs in real time while debugging.
<a id="dgx-spark"></a>
## DGX Spark

For an end-to-end Ollama walkthrough on DGX Spark, refer to the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw).
For an end-to-end walkthrough with local inference on DGX Spark, refer to the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw).

### CoreDNS CrashLoop after onboarding

Expand Down
18 changes: 15 additions & 3 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2429,17 +2429,24 @@ detect_express_platform() {
describe_express_install() {
local platform="$1"
local inference_summary=""
local inference_disclosure=""
local sandbox_summary=""
local tier="${NEMOCLAW_POLICY_TIER:-balanced}"
local policy_summary=""

case "$platform" in
"DGX Spark")
inference_summary="managed local Ollama with model qwen3.6:35b"
if [ -n "${NEMOCLAW_VLLM_MODEL:-}" ]; then
inference_summary="managed local vLLM with model ${NEMOCLAW_VLLM_MODEL}"
else
inference_summary="managed local vLLM using the DGX Spark profile default model"
fi
inference_disclosure="Managed vLLM pulls the configured vLLM image/model and runs a local vLLM inference container."
sandbox_summary="${NEMOCLAW_SANDBOX_NAME:-my-spark-assistant}"
;;
"DGX Station")
inference_summary="managed local vLLM"
inference_disclosure="Managed vLLM pulls the configured vLLM image/model and runs a local vLLM inference container."
sandbox_summary="${NEMOCLAW_SANDBOX_NAME:-my-assistant}"
;;
"Windows WSL")
Expand Down Expand Up @@ -2471,6 +2478,9 @@ describe_express_install() {
esac

printf " Express install will configure %s.\n" "$inference_summary"
if [ -n "$inference_disclosure" ]; then
printf " %s\n" "$inference_disclosure"
fi
printf " Sandbox name: %s.\n" "$sandbox_summary"
printf " It runs onboarding non-interactively, but still prompts for sudo when host setup needs it.\n"
printf " Sandbox policy: suggested mode, tier '%s'. This uses the %s.\n" "$tier" "$policy_summary"
Expand Down Expand Up @@ -2532,8 +2542,10 @@ maybe_offer_express_install() {
case "$platform" in
"DGX Spark")
export NEMOCLAW_SANDBOX_NAME="${NEMOCLAW_SANDBOX_NAME:-my-spark-assistant}"
export NEMOCLAW_PROVIDER=install-ollama
export NEMOCLAW_MODEL=qwen3.6:35b
export NEMOCLAW_PROVIDER=install-vllm
if [ -n "${NEMOCLAW_VLLM_MODEL:-}" ]; then
export NEMOCLAW_VLLM_MODEL
fi
;;
"DGX Station")
export NEMOCLAW_PROVIDER=install-vllm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface RootConfig {
}

const INSTALLER_INTEGRATION_TESTS = [
"test/install-express-prompt.test.ts",
"test/install-preflight.test.ts",
"test/install-preflight-docker-bootstrap.test.ts",
"test/install-openshell-version-check.test.ts",
Expand Down
233 changes: 233 additions & 0 deletions test/install-express-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { INSTALLER_PAYLOAD, TEST_SYSTEM_PATH } from "./helpers/installer-sourced-env";

describe("installer express install prompt (sourced)", () => {
function runExpressPromptWithTty(
answer: string,
stdinMode: "pipe" | "tty",
platform = "DGX Spark",
extraEnv: Record<string, string> = {},
) {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-prompt-"));
const python =
spawnSync("bash", ["--noprofile", "--norc", "-c", "command -v python3"], {
encoding: "utf-8",
}).stdout.trim() || "python3";
const ptyRunner = `
import os
import pty
import select
import signal
import sys
import time

installer = sys.argv[1]
answer = sys.argv[2].encode()
stdin_mode = sys.argv[3]
platform = sys.argv[4]
script = r'''
source "$INSTALLER_UNDER_TEST" >/dev/null
detect_express_platform() { printf "$EXPRESS_PLATFORM"; }
NON_INTERACTIVE=""
NEMOCLAW_PROVIDER=""
NEMOCLAW_NO_EXPRESS=""
maybe_offer_express_install
printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s VLLM_MODEL=%s POLICY=%s YES=%s SANDBOX=%s\\n" \\
"\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \\
"\${NEMOCLAW_VLLM_MODEL:-}" "\${NEMOCLAW_POLICY_MODE:-}" "\${NEMOCLAW_YES:-}" "\${NEMOCLAW_SANDBOX_NAME:-}"
'''
env = dict(os.environ)
env["INSTALLER_UNDER_TEST"] = installer
env["EXPRESS_PLATFORM"] = platform
pid, fd = pty.fork()
if pid == 0:
if stdin_mode == "pipe":
devnull = os.open(os.devnull, os.O_RDONLY)
os.dup2(devnull, 0)
os.close(devnull)
os.execvpe("bash", ["bash", "-c", script], env)

output = bytearray()
os.set_blocking(fd, False)
sent = False
exit_code = 124
deadline = time.time() + 10
while True:
ready, _, _ = select.select([fd], [], [], 0.1)
if ready:
try:
chunk = os.read(fd, 4096)
except BlockingIOError:
chunk = b""
except OSError:
chunk = b""
if chunk:
output.extend(chunk)
if (not sent) and b"[Y/n]" in output:
os.write(fd, answer)
sent = True
waited = os.waitpid(pid, os.WNOHANG)
if waited[0] == pid:
exit_code = os.waitstatus_to_exitcode(waited[1])
break
if time.time() > deadline:
try:
os.kill(pid, signal.SIGKILL)
except ProcessLookupError:
pass
try:
os.waitpid(pid, 0)
except ChildProcessError:
pass
break

try:
os.close(fd)
except OSError:
pass
sys.stdout.buffer.write(output)
sys.exit(exit_code)
`;
return spawnSync(python, ["-c", ptyRunner, INSTALLER_PAYLOAD, answer, stdinMode, platform], {
cwd: tmp,
encoding: "utf-8",
timeout: 15_000,
killSignal: "SIGKILL",
env: {
HOME: tmp,
PATH: TEST_SYSTEM_PATH,
...extraEnv,
},
});
}

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}`;
expect(result.status, output).toBe(0);
expect(output).toMatch(/Detected DGX Spark/);
expect(output).toMatch(
/Express install will configure managed local vLLM using the DGX Spark profile default model/,
);
expect(output).toMatch(
/Managed vLLM pulls the configured vLLM image\/model and runs a local vLLM inference container/,
);
expect(output).toMatch(/Sandbox name: my-spark-assistant/);
expect(output).toMatch(/Sandbox policy: suggested mode, tier 'balanced'/);
expect(output).toMatch(/Run express install/);
expect(output).toMatch(/Using express install for DGX Spark/);
expect(output).toMatch(
/RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-vllm MODEL= VLLM_MODEL= POLICY=suggested YES=1 SANDBOX=my-spark-assistant/,
);
});

it("preserves a preset Spark vLLM model in the prompt and exported env", () => {
const result = runExpressPromptWithTty("y\n", "pipe", "DGX Spark", {
NEMOCLAW_VLLM_MODEL: "custom-qwen3.6",
});
const output = `${result.stdout}${result.stderr}`;
expect(result.status, output).toBe(0);
expect(output).toMatch(/Detected DGX Spark/);
expect(output).toMatch(
/Express install will configure managed local vLLM with model custom-qwen3\.6/,
);
expect(output).toMatch(
/Managed vLLM pulls the configured vLLM image\/model and runs a local vLLM inference container/,
);
expect(output).toMatch(
/RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-vllm MODEL= VLLM_MODEL=custom-qwen3\.6 POLICY=suggested YES=1 SANDBOX=my-spark-assistant/,
);
});

it("detects Windows WSL as an express install platform", () => {
const result = spawnSync(
"bash",
[
"-c",
`
source "$INSTALLER_UNDER_TEST" >/dev/null
detect_express_platform
`,
],
{
cwd: path.join(import.meta.dirname, ".."),
encoding: "utf-8",
env: {
HOME: fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-wsl-detect-")),
PATH: TEST_SYSTEM_PATH,
INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD,
WSL_DISTRO_NAME: "Ubuntu",
},
},
);

expect(result.status, `${result.stdout}${result.stderr}`).toBe(0);
expect(result.stdout).toBe("Windows WSL");
});

it("maps Windows WSL express install to Windows-host Ollama", () => {
const result = runExpressPromptWithTty("\n", "pipe", "Windows WSL");
const output = `${result.stdout}${result.stderr}`;
expect(result.status, output).toBe(0);
expect(output).toMatch(/Detected Windows WSL/);
expect(output).toMatch(
/Express install will configure Windows-host Ollama through host\.docker\.internal/,
);
expect(output).toMatch(/Sandbox policy: suggested mode, tier 'balanced'/);
expect(output).toMatch(/Run express install/);
expect(output).toMatch(/Using express install for Windows WSL/);
expect(output).toMatch(
/RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-windows-ollama MODEL= VLLM_MODEL= POLICY=suggested YES=1 SANDBOX=/,
);
});

it.skipIf(process.platform === "darwin")(
"skips express install without a controlling TTY",
() => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-no-tty-"));
const result = spawnSync(
"setsid",
[
"bash",
"-c",
`
source "$INSTALLER_UNDER_TEST" >/dev/null
detect_express_platform() { printf "DGX Spark"; }
NON_INTERACTIVE=""
NEMOCLAW_PROVIDER=""
NEMOCLAW_NO_EXPRESS=""
maybe_offer_express_install
printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s VLLM_MODEL=%s POLICY=%s YES=%s SANDBOX=%s\\n" \\
"\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \\
"\${NEMOCLAW_VLLM_MODEL:-}" "\${NEMOCLAW_POLICY_MODE:-}" "\${NEMOCLAW_YES:-}" "\${NEMOCLAW_SANDBOX_NAME:-}"
`,
],
{
cwd: tmp,
encoding: "utf-8",
input: "",
env: {
HOME: tmp,
PATH: TEST_SYSTEM_PATH,
INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD,
},
},
);
const output = `${result.stdout}${result.stderr}`;
expect(result.status, output).toBe(0);
expect(output).toMatch(/Detected DGX Spark/);
expect(output).toMatch(/Skipping express prompt \(no TTY\)/);
expect(output).not.toMatch(/Run express install/);
expect(output).toMatch(
/RESULT NON_INTERACTIVE= SUDO_MODE= PROVIDER= MODEL= VLLM_MODEL= POLICY= YES= SANDBOX=/,
);
},
);
});
Loading
Loading