diff --git a/install.sh b/install.sh index f6393eb8eba..0b3acd97ec1 100755 --- a/install.sh +++ b/install.sh @@ -2,170 +2,173 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# Thin bootstrap for the NemoClaw installer. -# Public curl|bash installs should select a ref once, clone that ref, then -# execute installer logic from that same clone. Historical tags that predate -# the extracted payload fall back to their own root install.sh. +# NemoClaw installer — installs Node.js, Ollama (if GPU present), and NemoClaw. set -euo pipefail -if [[ -n "${BASH_SOURCE[0]:-}" ]]; then - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -else - SCRIPT_DIR="" -fi -LOCAL_PAYLOAD="${SCRIPT_DIR:+${SCRIPT_DIR}/scripts/install.sh}" -BOOTSTRAP_TMPDIR="" -PAYLOAD_MARKER="NEMOCLAW_VERSIONED_INSTALLER_PAYLOAD=1" -DEFAULT_INSTALL_REF="lkg" - -resolve_release_tag() { - if [[ -n "${NEMOCLAW_INSTALL_REF:-}" ]]; then - printf "%s" "${NEMOCLAW_INSTALL_REF}" +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } +error() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*"; exit 1; } + +command_exists() { command -v "$1" &>/dev/null; } + +# Compare two semver strings (major.minor.patch). Returns 0 if $1 >= $2. +version_gte() { + local IFS=. + local -a a=($1) b=($2) + for i in 0 1 2; do + local ai=${a[$i]:-0} bi=${b[$i]:-0} + if (( ai > bi )); then return 0; fi + if (( ai < bi )); then return 1; fi + done + return 0 +} + +# --------------------------------------------------------------------------- +# 1. Node.js +# --------------------------------------------------------------------------- +install_nodejs() { + if command_exists node; then + info "Node.js found: $(node --version)" return fi - printf "%s" "${NEMOCLAW_INSTALL_TAG:-$DEFAULT_INSTALL_REF}" + + info "Node.js not found — installing via nvm…" + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash + \. "$HOME/.nvm/nvm.sh" + nvm install 24 + info "Node.js installed: $(node --version)" } -verify_downloaded_script() { - local file="$1" label="${2:-installer}" expected_hash="${3:-}" - if [[ ! -s "$file" ]]; then - printf "[ERROR] %s download is empty or missing\n" "$label" >&2 - exit 1 +# --------------------------------------------------------------------------- +# 2. Ollama +# --------------------------------------------------------------------------- +OLLAMA_MIN_VERSION="0.18.0" + +get_ollama_version() { + # `ollama --version` outputs something like "ollama version 0.18.0" + ollama --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 +} + +detect_gpu() { + # Returns 0 if a GPU is detected + if command_exists nvidia-smi; then + nvidia-smi &>/dev/null && return 0 fi - if ! head -1 "$file" | grep -qE '^#!.*(sh|bash)'; then - printf "[ERROR] %s does not start with a shell shebang\n" "$label" >&2 - exit 1 + return 1 +} + +get_vram_mb() { + # Returns total VRAM in MiB (NVIDIA only). Falls back to 0. + if command_exists nvidia-smi; then + nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null \ + | awk '{s += $1} END {print s+0}' + return fi - if [[ -n "$expected_hash" ]]; then - local actual_hash="" - if command -v sha256sum >/dev/null 2>&1; then - actual_hash="$(sha256sum "$file" | awk '{print $1}')" - elif command -v shasum >/dev/null 2>&1; then - actual_hash="$(shasum -a 256 "$file" | awk '{print $1}')" - fi - if [[ -z "$actual_hash" ]]; then - printf "[ERROR] No SHA-256 tool available — cannot verify %s integrity\n" "$label" >&2 - exit 1 - fi - if [[ "$actual_hash" != "$expected_hash" ]]; then - rm -f "$file" - printf "[ERROR] %s integrity check failed\n Expected: %s\n Actual: %s\n" "$label" "$expected_hash" "$actual_hash" >&2 - exit 1 - fi + # macOS — report unified memory as VRAM + if [[ "$(uname -s)" == "Darwin" ]] && command_exists sysctl; then + local bytes + bytes=$(sysctl -n hw.memsize 2>/dev/null || echo 0) + echo $(( bytes / 1024 / 1024 )) + return fi + echo 0 } -has_payload_marker() { - local file="$1" - [[ -f "$file" ]] && grep -q "$PAYLOAD_MARKER" "$file" -} +install_or_upgrade_ollama() { + if detect_gpu && command_exists ollama; then + local current + current=$(get_ollama_version) + if [[ -n "$current" ]] && version_gte "$current" "$OLLAMA_MIN_VERSION"; then + info "Ollama v${current} meets minimum requirement (>= v${OLLAMA_MIN_VERSION})" + else + info "Ollama v${current:-unknown} is below v${OLLAMA_MIN_VERSION} — upgrading…" + curl -fsSL https://ollama.com/install.sh | sh + info "Ollama upgraded to $(get_ollama_version)" + fi + else + # No ollama — only install if a GPU is present + if detect_gpu; then + info "GPU detected — installing Ollama…" + curl -fsSL https://ollama.com/install.sh | sh + info "Ollama installed: v$(get_ollama_version)" + else + warn "No GPU detected — skipping Ollama installation." + return + fi + fi -clone_nemoclaw_ref() { - local ref="$1" dest="$2" + # Pull the appropriate model based on VRAM + local vram_mb + vram_mb=$(get_vram_mb) + local vram_gb=$(( vram_mb / 1024 )) + info "Detected ${vram_gb} GB VRAM" - git init --quiet "$dest" - git -C "$dest" remote add origin https://github.com/NVIDIA/NemoClaw.git - if ! git -C "$dest" fetch --quiet --depth 1 origin "$ref"; then - printf "[ERROR] Requested install ref '%s' is not available from https://github.com/NVIDIA/NemoClaw.git.\n" "$ref" >&2 - printf " Check NEMOCLAW_INSTALL_TAG/NEMOCLAW_INSTALL_REF and try again.\n" >&2 - exit 1 + if (( vram_gb >= 120 )); then + info "Pulling nemotron-3-super:120b…" + ollama pull nemotron-3-super:120b + else + info "Pulling nemotron-3-nano:30b…" + ollama pull nemotron-3-nano:30b fi - git -C "$dest" -c advice.detachedHead=false checkout --quiet --detach FETCH_HEAD } -exec_installer_from_ref() { - local ref="$1" - shift - - local tmpdir source_root payload_script legacy_script - tmpdir="$(mktemp -d)" - BOOTSTRAP_TMPDIR="$tmpdir" - trap 'rm -rf "${BOOTSTRAP_TMPDIR:-}"' EXIT - source_root="${tmpdir}/source" - - clone_nemoclaw_ref "$ref" "$source_root" +# --------------------------------------------------------------------------- +# 3. NemoClaw +# --------------------------------------------------------------------------- +install_nemoclaw() { + # Ensure npm global bin is on PATH (nvm installs to a non-standard location) + local npm_bin + npm_bin="$(npm config get prefix)/bin" + if ! echo "$PATH" | tr ':' '\n' | grep -qx "$npm_bin"; then + export PATH="$npm_bin:$PATH" + fi - payload_script="${source_root}/scripts/install.sh" - legacy_script="${source_root}/install.sh" + if [[ -f "./package.json" ]] && grep -q '"name": "nemoclaw"' ./package.json 2>/dev/null; then + info "NemoClaw package.json found in current directory — installing from source…" + npm install && npm link + else + info "Installing NemoClaw from npm…" + npm install -g nemoclaw + fi - if has_payload_marker "$payload_script"; then - verify_downloaded_script "$payload_script" "versioned installer" - NEMOCLAW_INSTALL_REF="$ref" NEMOCLAW_INSTALL_TAG="$ref" NEMOCLAW_BOOTSTRAP_PAYLOAD=1 \ - bash "$payload_script" "$@" - return + # Persist npm global bin for future shells + local profile="$HOME/.bashrc" + [ -f "$HOME/.zshrc" ] && profile="$HOME/.zshrc" + if ! grep -q 'npm config get prefix' "$profile" 2>/dev/null; then + echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> "$profile" + info "Added npm global bin to $profile" fi - verify_downloaded_script "$legacy_script" "legacy installer" - NEMOCLAW_INSTALL_TAG="$ref" bash "$legacy_script" "$@" + if ! command_exists nemoclaw; then + error "nemoclaw not found in PATH after install. Try: source ~/.bashrc && nemoclaw --help" + fi + info "nemoclaw is ready: $(nemoclaw --version 2>/dev/null || echo 'installed')" } -bootstrap_version() { - printf "nemoclaw-installer\n" +# --------------------------------------------------------------------------- +# 4. Onboard +# --------------------------------------------------------------------------- +run_onboard() { + info "Running nemoclaw onboard…" + npx nemoclaw onboard } -bootstrap_usage() { - printf "\n" - printf " NemoClaw Installer\n\n" - printf " Usage:\n" - printf " curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash\n" - printf " curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- [options]\n\n" - printf " Options:\n" - printf " --non-interactive Skip prompts (uses env vars / defaults)\n" - printf " --yes-i-accept-third-party-software Accept the third-party software notice without prompting\n" - printf " --fresh Discard any failed/interrupted onboarding session and start over\n" - printf " --version, -v Print installer version and exit\n" - printf " --help, -h Show this help message and exit\n\n" - printf " Environment:\n" - printf " NEMOCLAW_INSTALL_REF Exact Git ref/SHA to install\n" - printf " NEMOCLAW_INSTALL_TAG Git ref to install (default: lkg)\n" - printf " In curl pipes, set this on bash or export it first.\n" - printf " Example: curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_INSTALL_TAG=v0.0.56 bash\n" - 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" - printf " NEMOCLAW_OPENSHELL_UPGRADE_PREPARED=1\n" - printf " Continue after manually backing up and retiring old gateway\n" - printf " NEMOCLAW_PROVIDER build | openai | anthropic | anthropicCompatible\n" - printf " | gemini | ollama | custom | nim-local | vllm | routed\n" - printf " | hermes-provider\n" - printf " (aliases: cloud -> build, nim -> nim-local)\n" - printf " NEMOCLAW_POLICY_MODE suggested | custom | skip\n" - printf "\n" -} +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +main() { + info "=== NemoClaw Installer ===" -bootstrap_main() { - for arg in "$@"; do - case "$arg" in - --help | -h) - bootstrap_usage - return 0 - ;; - --version | -v) - bootstrap_version - return 0 - ;; - esac - done + install_nodejs + # install_or_upgrade_ollama + install_nemoclaw + run_onboard - local ref - ref="$(resolve_release_tag)" - exec_installer_from_ref "$ref" "$@" + info "=== Installation complete ===" } -if has_payload_marker "$LOCAL_PAYLOAD"; then - # shellcheck source=/dev/null - . "$LOCAL_PAYLOAD" -fi - -if [[ "${BASH_SOURCE[0]:-}" == "$0" ]] || { [[ -z "${BASH_SOURCE[0]:-}" ]] && { [[ "$0" == "bash" ]] || [[ "$0" == "-bash" ]]; }; }; then - if has_payload_marker "$LOCAL_PAYLOAD"; then - main "$@" - else - bootstrap_main "$@" - fi -fi +main "$@"