Skip to content
Merged
7 changes: 4 additions & 3 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ The wizard creates an OpenShell gateway, registers inference providers, builds t
Use this command for new installs and for recreating a sandbox after changes to policy or configuration.

```console
$ nemoclaw onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--gpu | --no-gpu] [--from <Dockerfile>] [--name <sandbox>] [--sandbox-gpu | --no-sandbox-gpu] [--sandbox-gpu-device <device>] [--agent <name>] [--control-ui-port <N>] [--yes | -y] [--yes-i-accept-third-party-software]
$ nemoclaw onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--gpu | --no-gpu] [--from <Dockerfile>] [--name <sandbox>] [--sandbox-gpu | --no-sandbox-gpu] [--sandbox-gpu-device <device>] [--agent <name>] [--control-ui-port <N>] [--yes | -y] [--no-ollama-autostart] [--yes-i-accept-third-party-software]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

<Warning>
Expand Down Expand Up @@ -1086,7 +1086,7 @@ The `nemoclaw setup` command is deprecated.
Use `nemoclaw onboard` instead.
</Warning>

This command remains as a compatibility alias to `nemoclaw onboard` and accepts the same flags: `--non-interactive`, `--resume`, `--fresh`, `--recreate-sandbox`, `--gpu` / `--no-gpu`, `--from`, `--name`, `--sandbox-gpu` / `--no-sandbox-gpu`, `--sandbox-gpu-device`, `--agent`, `--control-ui-port`, `--yes` / `-y`, `--yes-i-accept-third-party-software`.
This command remains as a compatibility alias to `nemoclaw onboard` and accepts the same flags: `--non-interactive`, `--resume`, `--fresh`, `--recreate-sandbox`, `--gpu` / `--no-gpu`, `--from`, `--name`, `--sandbox-gpu` / `--no-sandbox-gpu`, `--sandbox-gpu-device`, `--agent`, `--control-ui-port`, `--yes` / `-y`, `--no-ollama-autostart`, `--yes-i-accept-third-party-software`.

```console
$ nemoclaw setup
Expand All @@ -1099,7 +1099,7 @@ The `nemoclaw setup-spark` command is deprecated.
Use the standard installer and run `nemoclaw onboard` instead, because current OpenShell releases handle the older DGX Spark cgroup behavior.
</Warning>

This command remains as a compatibility alias to `nemoclaw onboard` and accepts the same flags: `--non-interactive`, `--resume`, `--fresh`, `--recreate-sandbox`, `--gpu` / `--no-gpu`, `--from`, `--name`, `--sandbox-gpu` / `--no-sandbox-gpu`, `--sandbox-gpu-device`, `--agent`, `--control-ui-port`, `--yes` / `-y`, `--yes-i-accept-third-party-software`.
This command remains as a compatibility alias to `nemoclaw onboard` and accepts the same flags: `--non-interactive`, `--resume`, `--fresh`, `--recreate-sandbox`, `--gpu` / `--no-gpu`, `--from`, `--name`, `--sandbox-gpu` / `--no-sandbox-gpu`, `--sandbox-gpu-device`, `--agent`, `--control-ui-port`, `--yes` / `-y`, `--no-ollama-autostart`, `--yes-i-accept-third-party-software`.

```console
$ nemoclaw setup-spark
Expand Down Expand Up @@ -1295,6 +1295,7 @@ These flags toggle optional behaviors during onboarding; set them before running
| Variable | Format | Effect |
|----------|--------|--------|
| `NEMOCLAW_YES` | `1` to enable | Auto-accepts confirmation prompts (`--yes` equivalent) including in helpers like the Ollama proxy auth setup. |
| `NEMOCLAW_OLLAMA_NO_AUTOSTART` | `1` to enable | Skips the wizard's eager Ollama auto-start during inference-provider selection (equivalent to passing `--no-ollama-autostart`). When set and Ollama is not running on `localhost:11434`, the `nemoclaw onboard` Local Ollama path prints a warning and selects the default fallback model instead of spawning `ollama serve`. The flag covers only the provider-selection step; later setup steps (auth proxy, validation, model warm) still expect a reachable Ollama. On Linux hosts with a systemd Ollama unit, the loopback-override path may still restart the daemon before this gate runs. |
| `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, 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. |
Expand Down
23 changes: 11 additions & 12 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ import {
readMessagingChannelConfigFromEnv,
} from "./messaging-channel-config";
import { streamGatewayStart } from "./onboard/gateway";
import { runOllamaStartupOrGate } from "./onboard/ollama-startup";
import {
mergeRequiredHermesToolGatewayPolicyPresets,
normalizeHermesToolGatewaySelections,
Expand Down Expand Up @@ -5104,18 +5105,16 @@ async function setupNim(
);
process.exit(1);
}
if (!ollamaReady) {
console.log(" Starting Ollama...");
// Keep raw Ollama loopback-only; the auth proxy (or Docker Desktop
// on WSL via host.docker.internal) fronts container access.
runShell(`OLLAMA_HOST=127.0.0.1:${OLLAMA_PORT} ollama serve > /dev/null 2>&1 &`, {
ignoreError: true,
});
if (!waitForHttp(`http://127.0.0.1:${OLLAMA_PORT}/`, 10)) {
console.error(` Ollama did not become ready on :${OLLAMA_PORT} within timeout.`);
if (isNonInteractive()) process.exit(1);
continue selectionLoop;
}
const ollamaStartup = runOllamaStartupOrGate({
ollamaReady,
ollamaPort: OLLAMA_PORT,
getLocalProviderBaseUrl,
isNonInteractive,
});
if (ollamaStartup.kind === "continue") continue selectionLoop;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (ollamaStartup.kind === "fallback") {
({ provider, credentialEnv, endpointUrl, model, preferredInferenceApi } = ollamaStartup.result);
break;
}
if (shouldFrontOllamaWithProxy()) {
if (!startOllamaAuthProxy()) process.exit(1);
Expand Down
8 changes: 7 additions & 1 deletion src/lib/onboard/command-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { NOTICE_ACCEPT_FLAG } from "./usage-notice";
const acceptFlagName = NOTICE_ACCEPT_FLAG.replace(/^--/, "");

export const onboardUsage = [
`onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--gpu | --no-gpu] [--from <Dockerfile>] [--name <sandbox>] [--sandbox-gpu | --no-sandbox-gpu] [--sandbox-gpu-device <device>] [--agent <name>] [--control-ui-port <N>] [--yes | -y] [${NOTICE_ACCEPT_FLAG}]`,
`onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--gpu | --no-gpu] [--from <Dockerfile>] [--name <sandbox>] [--sandbox-gpu | --no-sandbox-gpu] [--sandbox-gpu-device <device>] [--agent <name>] [--control-ui-port <N>] [--yes | -y] [--no-ollama-autostart] [${NOTICE_ACCEPT_FLAG}]`,
];

export const onboardExamples = [
Expand Down Expand Up @@ -36,6 +36,7 @@ export type OnboardFlags = {
agent?: string;
"control-ui-port"?: number;
yes?: boolean;
"no-ollama-autostart"?: boolean;
[acceptFlagName]?: boolean;
};

Expand Down Expand Up @@ -80,6 +81,10 @@ export function buildOnboardFlags(): Record<string, any> {
char: "y",
description: "Auto-confirm prompts that are safe for unattended onboarding",
}),
"no-ollama-autostart": Flags.boolean({
description:
"Skip the wizard's eager Ollama auto-start during inference-provider selection so onboard surfaces the unreachable-Ollama warning and the default fallback model; later setup steps still expect a reachable Ollama, and on Linux/systemd hosts the loopback-override path may still restart the daemon",
}),
[acceptFlagName]: Flags.boolean({ description: "Accept the third-party software notice" }),
} as Record<string, any>;
}
Expand All @@ -104,6 +109,7 @@ export function toLegacyOnboardArgs(flags: OnboardFlags): string[] {
args.push("--control-ui-port", String(flags["control-ui-port"]));
}
if (flags.yes) args.push("--yes");
if (flags["no-ollama-autostart"]) args.push("--no-ollama-autostart");
if (flags[acceptFlagName]) args.push(NOTICE_ACCEPT_FLAG);
return args;
}
54 changes: 54 additions & 0 deletions src/lib/onboard/legacy-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -97,6 +98,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -126,6 +128,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -184,6 +187,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -214,6 +218,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -265,6 +270,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -405,6 +411,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -577,6 +584,7 @@ describe("onboard command", () => {
gpu: false,
noGpu: false,
autoYes: false,
noOllamaAutostart: false,
});
});

Expand Down Expand Up @@ -647,4 +655,50 @@ describe("onboard command", () => {
).toThrow("exit:1");
expect(errors.join("\n")).toContain("--gpu and --no-gpu are mutually exclusive");
});

it("defaults noOllamaAutostart to false when the flag is absent", () => {
const result = parseOnboardArgs(
[],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: exitWithCode,
},
);
expect(result.noOllamaAutostart).toBe(false);
});

it("parses --no-ollama-autostart as noOllamaAutostart=true without rejecting it as unknown", () => {
const errors: string[] = [];
const result = parseOnboardArgs(
["--no-ollama-autostart"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: (message = "") => errors.push(message),
exit: exitWithPrefixedCode,
},
);
expect(result.noOllamaAutostart).toBe(true);
expect(errors.join("\n")).not.toContain("Unknown onboard option(s)");
});

it("--help advertises --no-ollama-autostart in the usage output", async () => {
const lines: string[] = [];
await runOnboardCommand({
args: ["--help"],
noticeAcceptFlag: "--yes-i-accept-third-party-software",
noticeAcceptEnv: "NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
env: {},
runOnboard: vi.fn(async () => {}),
log: (message = "") => lines.push(message),
error: () => {},
exit: exitWithCode,
});
expect(lines.join("\n")).toContain("--no-ollama-autostart");
expect(lines.join("\n")).toContain("inference-provider selection");
});
});
7 changes: 6 additions & 1 deletion src/lib/onboard/legacy-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface OnboardCommandOptions {
gpu: boolean;
noGpu: boolean;
autoYes: boolean;
noOllamaAutostart: boolean;
}

export interface RunOnboardCommandDeps {
Expand Down Expand Up @@ -48,14 +49,16 @@ const ONBOARD_BASE_ARGS = [
"--no-gpu",
"--yes",
"-y",
"--no-ollama-autostart",
];

function onboardUsageLines(noticeAcceptFlag: string): string[] {
const name = CLI_NAME;
return [
` Usage: ${name} onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--gpu | --no-gpu] [--from <Dockerfile>] [--name <sandbox>] [--sandbox-gpu | --no-sandbox-gpu] [--sandbox-gpu-device <device>] [--agent <name>] [--control-ui-port <N>] [--yes | -y] [${noticeAcceptFlag}]`,
` Usage: ${name} onboard [--non-interactive] [--resume | --fresh] [--recreate-sandbox] [--gpu | --no-gpu] [--from <Dockerfile>] [--name <sandbox>] [--sandbox-gpu | --no-sandbox-gpu] [--sandbox-gpu-device <device>] [--agent <name>] [--control-ui-port <N>] [--yes | -y] [--no-ollama-autostart] [${noticeAcceptFlag}]`,
"",
" --from <Dockerfile> uses the Dockerfile's parent directory as the Docker build context.",
" --no-ollama-autostart skips the wizard's eager Ollama auto-start during inference-provider selection so onboard surfaces the unreachable-Ollama warning and the default fallback model; later setup steps still expect a reachable Ollama, and on Linux hosts with a systemd Ollama unit the loopback-override path may still restart the daemon ahead of this gate.",
" --gpu enables direct NVIDIA GPU access inside the sandbox; --no-gpu forces CPU sandbox behavior.",
" --sandbox-gpu enables direct NVIDIA GPU access inside the sandbox; --no-sandbox-gpu forces CPU sandbox behavior.",
" --sandbox-gpu-device passes a specific OpenShell GPU device selector to sandbox create; requires --sandbox-gpu.",
Expand Down Expand Up @@ -241,6 +244,7 @@ export function parseOnboardArgs(
gpu,
noGpu,
autoYes: parsedArgs.includes("--yes") || parsedArgs.includes("-y"),
noOllamaAutostart: parsedArgs.includes("--no-ollama-autostart"),
};
}

Expand All @@ -252,6 +256,7 @@ export async function runOnboardCommand(deps: RunOnboardCommandDeps): Promise<vo
}

const options = parseOnboardArgs(deps.args, deps.noticeAcceptFlag, deps.noticeAcceptEnv, deps);
if (options.noOllamaAutostart) process.env.NEMOCLAW_OLLAMA_NO_AUTOSTART = "1";
await deps.runOnboard(options);
}

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

const runner: typeof import("../runner") = require("../runner");
const wait: typeof import("../core/wait") = require("../core/wait");
const localInference: typeof import("../inference/local") = require("../inference/local");

let NO_OLLAMA_AUTOSTART = false;

export function setOllamaAutostartDisabled(value: boolean | undefined): void {
NO_OLLAMA_AUTOSTART = !!value;
}

export function isOllamaAutostartDisabled(): boolean {
return NO_OLLAMA_AUTOSTART || process.env.NEMOCLAW_OLLAMA_NO_AUTOSTART === "1";
}

export type OllamaFallbackResult = {
provider: "ollama-local";
credentialEnv: null;
endpointUrl: string;
model: string;
preferredInferenceApi: "openai-completions";
};

export type OllamaStartupOutcome =
| { kind: "ready" }
| { kind: "continue" }
| { kind: "fallback"; result: OllamaFallbackResult };

export function runOllamaStartupOrGate(args: {
ollamaReady: boolean;
ollamaPort: number;
getLocalProviderBaseUrl: (provider: "ollama-local") => string | null;
isNonInteractive: () => boolean;
}): OllamaStartupOutcome {
const { ollamaReady, ollamaPort, getLocalProviderBaseUrl, isNonInteractive } = args;
if (ollamaReady) return { kind: "ready" };
if (isOllamaAutostartDisabled()) {
console.log(
" ⚠ Ollama is not running on localhost:" +
`${ollamaPort} and --no-ollama-autostart is set; ` +
"skipping auto-start and falling back to the default model.",
);
const endpointUrl = getLocalProviderBaseUrl("ollama-local");
if (!endpointUrl) {
console.error(" Local Ollama base URL could not be determined.");
process.exit(1);
}
return {
kind: "fallback",
result: {
provider: "ollama-local",
credentialEnv: null,
endpointUrl,
model: localInference.DEFAULT_OLLAMA_MODEL,
preferredInferenceApi: "openai-completions",
},
};
}
console.log(" Starting Ollama...");
runner.runShell(`OLLAMA_HOST=127.0.0.1:${ollamaPort} ollama serve > /dev/null 2>&1 &`, {
ignoreError: true,
});
if (!wait.waitForHttp(`http://127.0.0.1:${ollamaPort}/`, 10)) {
console.error(` Ollama did not become ready on :${ollamaPort} within timeout.`);
const providerPinned = process.env.NEMOCLAW_PROVIDER === "ollama";
if (isNonInteractive() || providerPinned) {
if (providerPinned) {
console.error(
" NEMOCLAW_PROVIDER=ollama is pinned but Ollama is unreachable; refusing to loop on provider selection.",
);
}
process.exit(1);
}
return { kind: "continue" };
}
return { kind: "ready" };
}
Loading
Loading