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
25 changes: 24 additions & 1 deletion src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3774,6 +3774,20 @@ function hostCommandExists(commandName: string): boolean {
});
}

function ensureOllamaLinuxExtractionDependencies(): void {
if (hostCommandExists("zstd")) return;
console.log(
" The Ollama Linux installer requires zstd for archive extraction. " +
"The next step uses sudo to install zstd; you may be prompted for your password.",
);
runShell(`if ! command -v apt-get >/dev/null 2>&1; then
echo "ERROR: Ollama requires zstd for extraction, and only apt-based Linux is supported here." >&2
echo "Install zstd manually (for example, sudo dnf install zstd or sudo pacman -S zstd), then rerun ${cliName()} onboard." >&2
exit 1
fi
sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends zstd`);
}

function captureProcessArgs(pid: number): string {
return runCapture(["ps", "-p", String(pid), "-o", "args="], {
ignoreError: true,
Expand Down Expand Up @@ -8343,7 +8357,11 @@ async function setupNim(
});
sleep(2);
} else {
console.log(" Installing Ollama via official installer...");
ensureOllamaLinuxExtractionDependencies();
console.log(
" The Ollama installer creates a system user, a systemd service, and writes to /usr/local. " +
"It uses sudo for those steps; you may be prompted for your password.",
);
runShell("set -o pipefail; curl -fsSL https://ollama.com/install.sh | sh");
// Give the just-started ollama.service a moment to bind port
// 11434 before we probe or apply the systemd drop-in override.
Expand All @@ -8368,6 +8386,11 @@ async function setupNim(
// start: manual launch with the loopback binding.
if (!isWsl() && hasOllamaSystemdUnit) {
console.log(" Configuring Ollama systemd loopback override...");
console.log(
` Applying an Ollama systemd override (OLLAMA_HOST=127.0.0.1:${OLLAMA_PORT}). ` +
"The next steps use sudo to write the drop-in, reload systemd, and restart the service; " +
"you may be prompted for your password.",
);
const dropInBody = `[Service]\nEnvironment="OLLAMA_HOST=127.0.0.1:${OLLAMA_PORT}"\n`;
const tmpDropIn = secureTempFile("nemoclaw-ollama-override", ".conf");
fs.writeFileSync(tmpDropIn, dropInBody, { mode: 0o644 });
Expand Down
67 changes: 64 additions & 3 deletions test/onboard-selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3765,6 +3765,7 @@ let promptCalls = 0;
const messages = [];
const updates = [];
const runCommands = [];
const events = [];

credentials.prompt = async (message) => {
promptCalls += 1;
Expand Down Expand Up @@ -3792,10 +3793,13 @@ runner.runCapture = (command) => {
return "";
};
runner.run = (command, opts) => {
runCommands.push(typeof command === "string" ? command : command.join(" "));
const rendered = typeof command === "string" ? command : command.join(" ");
runCommands.push(rendered);
events.push({ type: "command", value: rendered });
};
runner.runShell = (command, opts) => {
runCommands.push(command);
events.push({ type: "command", value: command });
};
registry.updateSandbox = (_name, update) => updates.push(update);

Expand All @@ -3808,10 +3812,14 @@ const { setupNim } = require(${onboardPath});
(async () => {
const originalLog = console.log;
const lines = [];
console.log = (...args) => lines.push(args.join(" "));
console.log = (...args) => {
const line = args.join(" ");
lines.push(line);
events.push({ type: "log", value: line });
};
try {
const result = await setupNim("install-test", null);
originalLog(JSON.stringify({ result, promptCalls, messages, updates, lines, runCommands }));
originalLog(JSON.stringify({ result, promptCalls, messages, updates, lines, runCommands, events }));
} finally {
console.log = originalLog;
}
Expand Down Expand Up @@ -3846,6 +3854,43 @@ const { setupNim } = require(${onboardPath});
assert.equal(payload.result.provider, "ollama-local");

// Should have run the curl installer (not brew)
const zstdPreflightIndex = payload.runCommands.findIndex((cmd: string) =>
cmd.includes("apt-get install -y -qq --no-install-recommends zstd"),
);
const ollamaInstallerIndex = payload.runCommands.findIndex((cmd: string) =>
cmd.includes("ollama.com/install.sh"),
);
assert.ok(zstdPreflightIndex >= 0, "Should preflight zstd before the Ollama installer");
assert.ok(
ollamaInstallerIndex > zstdPreflightIndex,
"Should install zstd before running the Ollama installer",
);
const zstdWarningEventIndex = payload.events.findIndex(
(event: { type: string; value: string }) =>
event.type === "log" && event.value.includes("requires zstd for archive extraction"),
);
const zstdCommandEventIndex = payload.events.findIndex(
(event: { type: string; value: string }) =>
event.type === "command" &&
event.value.includes("apt-get install -y -qq --no-install-recommends zstd"),
);
const installerWarningEventIndex = payload.events.findIndex(
(event: { type: string; value: string }) =>
event.type === "log" &&
event.value.includes("creates a system user, a systemd service, and writes to /usr/local"),
);
const installerCommandEventIndex = payload.events.findIndex(
(event: { type: string; value: string }) =>
event.type === "command" && event.value.includes("ollama.com/install.sh"),
);
assert.ok(
zstdWarningEventIndex >= 0 && zstdWarningEventIndex < zstdCommandEventIndex,
"Should explain the zstd sudo install before running apt-get",
);
assert.ok(
installerWarningEventIndex >= 0 && installerWarningEventIndex < installerCommandEventIndex,
"Should explain the Ollama installer sudo usage before running it",
);
assert.ok(
payload.runCommands.some((cmd: string) => cmd.includes("ollama.com/install.sh")),
"Should use curl installer on Linux",
Expand Down Expand Up @@ -3937,6 +3982,8 @@ const { setupNim } = require(${onboardPath});
});

assert.equal(result.status, 1);
assert.match(result.stdout, /Applying an Ollama systemd override/);
assert.match(result.stdout, /use sudo to write the drop-in, reload systemd, and restart the service/);
assert.match(result.stderr, /Failed to apply Ollama systemd loopback override/);
assert.match(result.stderr, /Refusing to continue/);
});
Expand Down Expand Up @@ -4066,6 +4113,20 @@ const { setupNim } = require(${onboardPath});

assert.equal(payload.promptCalls, 0);
assert.equal(payload.result.provider, "ollama-local");
const zstdPreflightIndex = payload.runCommands.findIndex((cmd: string) =>
cmd.includes("apt-get install -y -qq --no-install-recommends zstd"),
);
const ollamaInstallerIndex = payload.runCommands.findIndex((cmd: string) =>
cmd.includes("ollama.com/install.sh"),
);
assert.ok(
zstdPreflightIndex >= 0,
"Should preflight zstd before the non-interactive Ollama installer",
);
assert.ok(
ollamaInstallerIndex > zstdPreflightIndex,
"Should install zstd before running the non-interactive Ollama installer",
);
assert.ok(
payload.runCommands.some((cmd: string) => cmd.includes("ollama.com/install.sh")),
"Should use the Ollama installer when requested non-interactively on a fresh host",
Expand Down
Loading