Skip to content
Merged
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
3 changes: 2 additions & 1 deletion docs/get-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ $ newgrp docker
$ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash
```

On DGX Spark and DGX Station, an interactive installer can offer express install after you accept the third-party software notice.
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, applies the suggested security policy, and selects the managed local inference path for that platform.
On WSL, express install selects the Windows-host Ollama setup path.
Set `NEMOCLAW_NO_EXPRESS=1` to skip the express prompt, or set `NEMOCLAW_PROVIDER` before launching the installer when you want to choose a provider yourself.

The installer auto-launches `nemoclaw onboard` when it can locate the freshly-installed binary.
Expand Down
3 changes: 2 additions & 1 deletion docs/get-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ $ newgrp docker
$ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash
```

On DGX Spark and DGX Station, an interactive installer can offer express install after you accept the third-party software notice.
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, applies the suggested security policy, and selects the managed local inference path for that platform.
On WSL, express install selects the Windows-host Ollama setup path.
Set `NEMOCLAW_NO_EXPRESS=1` to skip the express prompt, or set `NEMOCLAW_PROVIDER` before launching the installer when you want to choose a provider yourself.

The installer auto-launches `nemoclaw onboard` when it can locate the freshly-installed binary.
Expand Down
1 change: 1 addition & 0 deletions docs/get-started/windows-preparation.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ You can also start it yourself beforehand with `ollama serve`.

You can also use Ollama for Windows.
During onboarding, NemoClaw can use an already-running Windows-host daemon, start or restart an installed daemon, or install Ollama on the Windows host.
If the installer offers express install on WSL, accepting it selects this Windows-host Ollama path automatically.
When Ollama runs on the Windows host, NemoClaw detects it from WSL through `host.docker.internal` and pulls missing models through the Ollama HTTP API.
Do not run both the Windows and WSL Ollama instances on port `11434` at the same time.
Use one instance, or move one of them to a different port before running `nemoclaw onboard`.
Expand Down
1 change: 1 addition & 0 deletions docs/get-started/windows-preparation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ You can also start it yourself beforehand with `ollama serve`.

You can also use Ollama for Windows.
During onboarding, NemoClaw can use an already-running Windows-host daemon, start or restart an installed daemon, or install Ollama on the Windows host.
If the installer offers express install on WSL, accepting it selects this Windows-host Ollama path automatically.
When Ollama runs on the Windows host, NemoClaw detects it from WSL through `host.docker.internal` and pulls missing models through the Ollama HTTP API.
Do not run both the Windows and WSL Ollama instances on port `11434` at the same time.
Use one instance, or move one of them to a different port before running `nemoclaw onboard`.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,7 @@ These flags toggle optional behaviors during onboarding; set them before running
|----------|--------|--------|
| `NEMOCLAW_YES` | `1` to enable | Auto-accepts confirmation prompts (`--yes` equivalent) including in helpers like the Ollama proxy auth setup. |
| `NEMOCLAW_NON_INTERACTIVE_SUDO_MODE` | `prompt` or empty/unset | When set to `prompt`, allows non-interactive onboarding to use prompt-capable `sudo` for host setup steps that require elevation, which can ask for a password. Empty/unset is the default and uses `sudo -n`, which fails instead of asking for a password. Any other value is rejected. |
| `NEMOCLAW_NO_EXPRESS` | `1` to enable | Installer-only. Skips the DGX Spark and DGX Station express install prompt and continues with the normal interactive onboarding flow. |
| `NEMOCLAW_NO_EXPRESS` | `1` to enable | Installer-only. Skips the DGX Spark, DGX Station, and Windows WSL express install prompt and continues with the normal interactive onboarding flow. |
| `NEMOCLAW_EXPERIMENTAL` | `1` to enable | Surfaces experimental providers and flows in onboarding. |
| `NEMOCLAW_IGNORE_RUNTIME_RESOURCES` | `1` to enable | Suppresses the under-provisioned runtime warning during preflight. Use only when you know the sandbox host meets the minimums. |
| `NEMOCLAW_DISABLE_OVERLAY_FIX` | `1` to enable | Skips the Docker overlay-fix step during sandbox build. For environments where the fix is incompatible. |
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1165,7 +1165,7 @@ These flags toggle optional behaviors during onboarding; set them before running
|----------|--------|--------|
| `NEMOCLAW_YES` | `1` to enable | Auto-accepts confirmation prompts (`--yes` equivalent) including in helpers like the Ollama proxy auth setup. |
| `NEMOCLAW_NON_INTERACTIVE_SUDO_MODE` | `prompt` or empty/unset | When set to `prompt`, allows non-interactive onboarding to use prompt-capable `sudo` for host setup steps that require elevation, which can ask for a password. Empty/unset is the default and uses `sudo -n`, which fails instead of asking for a password. Any other value is rejected. |
| `NEMOCLAW_NO_EXPRESS` | `1` to enable | Installer-only. Skips the DGX Spark and DGX Station express install prompt and continues with the normal interactive onboarding flow. |
| `NEMOCLAW_NO_EXPRESS` | `1` to enable | Installer-only. Skips the DGX Spark, DGX Station, and Windows WSL express install prompt and continues with the normal interactive onboarding flow. |
| `NEMOCLAW_EXPERIMENTAL` | `1` to enable | Surfaces experimental providers and flows in onboarding. |
| `NEMOCLAW_IGNORE_RUNTIME_RESOURCES` | `1` to enable | Suppresses the under-provisioned runtime warning during preflight. Use only when you know the sandbox host meets the minimums. |
| `NEMOCLAW_DISABLE_OVERLAY_FIX` | `1` to enable | Skips the Docker overlay-fix step during sandbox build. For environments where the fix is incompatible. |
Expand Down
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ bootstrap_usage() {
printf " NEMOCLAW_NON_INTERACTIVE=1 Same as --non-interactive\n"
printf " NEMOCLAW_FRESH=1 Same as --fresh\n"
printf " NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 Same as --yes-i-accept-third-party-software\n"
printf " NEMOCLAW_NO_EXPRESS=1 Skip express install prompt on supported platforms\n"
printf " NEMOCLAW_SANDBOX_NAME Sandbox name to create/use\n"
printf " NEMOCLAW_ACCEPT_EXPERIMENTAL_OPENSHELL_UPGRADE=1\n"
printf " Allow automatic pre-0.0.37 OpenShell gateway upgrade\n"
Expand Down
34 changes: 29 additions & 5 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ usage() {
printf " NEMOCLAW_NON_INTERACTIVE=1 Same as --non-interactive\n"
printf " NEMOCLAW_NON_INTERACTIVE_SUDO_MODE=prompt Allow sudo prompts during non-interactive onboarding\n"
printf " NEMOCLAW_FRESH=1 Same as --fresh\n"
printf " NEMOCLAW_NO_EXPRESS=1 Skip express install prompt on supported platforms\n"
printf " NEMOCLAW_SANDBOX_NAME Sandbox name to create/use\n"
printf " NEMOCLAW_SINGLE_SESSION=1 Abort if active sandbox sessions exist\n"
printf " NEMOCLAW_ACCEPT_EXPERIMENTAL_OPENSHELL_UPGRADE=1\n"
Expand Down Expand Up @@ -2037,7 +2038,7 @@ ensure_docker() {
case "$(uname -s)" in
Darwin | MINGW* | MSYS*) return 0 ;;
esac
if [ -n "${WSL_DISTRO_NAME:-}" ] || [ -n "${WSL_INTEROP:-}" ]; then
if is_wsl_host; then
return 0
fi
# Fast path: docker info works → already set up (root, or already-active group).
Expand Down Expand Up @@ -2117,11 +2118,31 @@ ensure_docker() {
fi
}

# Detect DGX Spark / DGX Station from firmware (DMI first, devicetree fallback).
# Echoes "DGX Spark", "DGX Station", or empty. Used to gate the express
# install prompt; only platforms with a known sensible default are offered.
is_wsl_host() {
if [ -n "${WSL_DISTRO_NAME:-}" ] || [ -n "${WSL_INTEROP:-}" ]; then
return 0
fi
if [ -r /proc/sys/kernel/osrelease ] \
&& grep -qiE 'microsoft|wsl' /proc/sys/kernel/osrelease 2>/dev/null; then
return 0
fi
if [ -r /proc/version ] \
&& grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then
return 0
fi
return 1
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Detect DGX Spark / DGX Station from firmware (DMI first, devicetree fallback)
# and Windows WSL from the host environment. Echoes "DGX Spark",
# "DGX Station", "Windows WSL", or empty. Used to gate the express install
# prompt; only platforms with a known sensible default are offered.
detect_express_platform() {
local model=""
if is_wsl_host; then
printf "Windows WSL"
return
fi
if [ -r /sys/class/dmi/id/product_name ]; then
model="$(cat /sys/class/dmi/id/product_name 2>/dev/null || true)"
fi
Expand All @@ -2135,7 +2156,7 @@ detect_express_platform() {
esac
}

# Prompt the user to opt into express install on Spark/Station. Sets the
# Prompt the user to opt into express install on supported platforms. Sets the
# non-interactive + provider/model env vars when accepted. Skipped when
# the user already passed --non-interactive, set NEMOCLAW_PROVIDER, or has
# no TTY.
Expand Down Expand Up @@ -2198,6 +2219,9 @@ maybe_offer_express_install() {
"DGX Station")
export NEMOCLAW_PROVIDER=install-vllm
;;
"Windows WSL")
export NEMOCLAW_PROVIDER=install-windows-ollama
;;
esac
;;
*)
Expand Down
16 changes: 6 additions & 10 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ const {
const {
getSelectionDrift,
}: typeof import("./onboard/selection-drift") = require("./onboard/selection-drift");
const {
resolveProviderKeyFallback,
}: typeof import("./onboard/provider-key-fallback") = require("./onboard/provider-key-fallback");
const { isLinuxDockerDriverGatewayEnabled }: typeof import("./onboard/docker-driver-platform") = require("./onboard/docker-driver-platform");
const {
reconcileGatewayGpuReuseForGpuIntent,
Expand Down Expand Up @@ -6393,16 +6396,9 @@ async function setupNim(
}
selected = options.find((o) => o.key === providerKey);
if (!selected) {
// Install action keys fall back to the equivalent running-provider
// key when the menu only emits the running entry (the install would
// have been a no-op anyway).
if (providerKey === "install-ollama") {
selected = options.find((o) => o.key === "ollama");
} else if (providerKey === "install-vllm") {
selected = options.find((o) => o.key === "vllm");
} else if (providerKey === "ollama") {
selected = options.find((o) => o.key === "install-ollama");
}
selected = resolveProviderKeyFallback(options, providerKey, {
isWindowsHostOllama: ollamaHost === OLLAMA_HOST_DOCKER_INTERNAL,
});
if (!selected) {
if (providerKey === "hermesProvider" && !hermesProviderAvailable) {
console.error(" Hermes Provider is only available when onboarding Hermes Agent.");
Expand Down
69 changes: 69 additions & 0 deletions src/lib/onboard/provider-key-fallback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import assert from "node:assert/strict";
import { describe, it } from "vitest";

import { resolveProviderKeyFallback } from "../../../dist/lib/onboard/provider-key-fallback";

const option = (key: string) => ({ key, label: key });

describe("resolveProviderKeyFallback", () => {
it("maps generic install action keys to already-running provider options", () => {
const options = [option("ollama"), option("vllm")];

assert.equal(
resolveProviderKeyFallback(options, "install-ollama", { isWindowsHostOllama: false })?.key,
"ollama",
);
assert.equal(
resolveProviderKeyFallback(options, "install-vllm", { isWindowsHostOllama: false })?.key,
"vllm",
);
});

it("prefers Windows-host start when install-windows-ollama is requested after install already exists", () => {
const options = [option("ollama"), option("start-windows-ollama")];

assert.equal(
resolveProviderKeyFallback(options, "install-windows-ollama", {
isWindowsHostOllama: false,
})?.key,
"start-windows-ollama",
);
});

it("allows Windows-host install/start requests to collapse to ollama only for Windows-host Ollama", () => {
const options = [option("ollama")];

assert.equal(
resolveProviderKeyFallback(options, "install-windows-ollama", {
isWindowsHostOllama: true,
})?.key,
"ollama",
);
assert.equal(
resolveProviderKeyFallback(options, "start-windows-ollama", {
isWindowsHostOllama: true,
})?.key,
"ollama",
);
});

it("does not satisfy Windows-host requests with WSL or Linux local Ollama", () => {
const options = [option("ollama")];

assert.equal(
resolveProviderKeyFallback(options, "install-windows-ollama", {
isWindowsHostOllama: false,
}),
undefined,
);
assert.equal(
resolveProviderKeyFallback(options, "start-windows-ollama", {
isWindowsHostOllama: false,
}),
undefined,
);
});
});
41 changes: 41 additions & 0 deletions src/lib/onboard/provider-key-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

export interface ProviderOption {
key: string;
label?: string;
}

export interface ProviderKeyFallbackContext {
isWindowsHostOllama: boolean;
}

export function resolveProviderKeyFallback<T extends ProviderOption>(
options: T[],
providerKey: string | null | undefined,
context: ProviderKeyFallbackContext,
): T | undefined {
const find = (key: string) => options.find((option) => option.key === key);

switch (providerKey) {
case "install-ollama":
return find("ollama");
case "install-vllm":
return find("vllm");
case "install-windows-ollama":
// Windows-host Ollama requests may arrive from NEMOCLAW_PROVIDER before
// the dynamic menu knows whether Windows Ollama needs install,
// start/restart, or is already reachable. Collapse only to later-state
// entries that still point at the Windows host.
return (
find("start-windows-ollama") ||
(context.isWindowsHostOllama ? find("ollama") : undefined)
);
case "start-windows-ollama":
return context.isWindowsHostOllama ? find("ollama") : undefined;
case "ollama":
return find("install-ollama");
default:
return undefined;
}
}
71 changes: 60 additions & 11 deletions test/install-preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ exit 98
expect(output).toMatch(/aliases: cloud -> build, nim -> nim-local/);
expect(output).toMatch(/NEMOCLAW_POLICY_MODE/);
expect(output).toMatch(/NEMOCLAW_NON_INTERACTIVE_SUDO_MODE=prompt/);
expect(output).toMatch(/NEMOCLAW_NO_EXPRESS=1/);
expect(output).toMatch(/NEMOCLAW_SANDBOX_NAME/);
expect(output).toMatch(/nvidia\.com\/nemoclaw\.sh/);
});
Expand Down Expand Up @@ -3081,7 +3082,11 @@ exit 0`,
});

describe("installer express install prompt (sourced)", () => {
function runExpressPromptWithTty(answer: string, stdinMode: "pipe" | "tty") {
function runExpressPromptWithTty(
answer: string,
stdinMode: "pipe" | "tty",
platform = "DGX Spark",
) {
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() ||
Expand All @@ -3097,9 +3102,10 @@ 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 "DGX Spark"; }
detect_express_platform() { printf "$EXPRESS_PLATFORM"; }
NON_INTERACTIVE=""
NEMOCLAW_PROVIDER=""
NEMOCLAW_NO_EXPRESS=""
Expand All @@ -3110,6 +3116,7 @@ printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s POLICY=%s YE
'''
env = dict(os.environ)
env["INSTALLER_UNDER_TEST"] = installer
env["EXPRESS_PLATFORM"] = platform
pid, fd = pty.fork()
if pid == 0:
if stdin_mode == "pipe":
Expand Down Expand Up @@ -3159,16 +3166,20 @@ except OSError:
sys.stdout.buffer.write(output)
sys.exit(exit_code)
`;
return spawnSync(python, ["-c", ptyRunner, INSTALLER_PAYLOAD, answer, stdinMode], {
cwd: tmp,
encoding: "utf-8",
timeout: 15_000,
killSignal: "SIGKILL",
env: {
HOME: tmp,
PATH: TEST_SYSTEM_PATH,
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,
},
},
});
);
}

it("offers express install when curl-piped stdin still has a controlling TTY", () => {
Expand All @@ -3183,6 +3194,44 @@ sys.exit(exit_code)
);
});

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(/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= POLICY=suggested YES=1/,
);
});

it("skips express install without a controlling TTY", () => {
if (process.platform === "darwin") {
return;
Expand Down
Loading
Loading