Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
33 changes: 33 additions & 0 deletions .github/workflows/installer-hash-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Verifies pinned installer SHA-256 hashes still match upstream scripts.
# Checked: Ollama installer.
# Runs on every PR and push to main, plus a weekly scheduled check.

name: installer-hash-check

on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [main]
schedule:
# Weekly fallback in case upstream changes between PRs
- cron: "30 9 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
check-hash:
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Verify installer hashes are current
run: bash scripts/check-installer-hash.sh
19 changes: 18 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ resolve_release_tag() {
}

verify_downloaded_script() {
local file="$1" label="${2:-installer}"
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
Expand All @@ -28,6 +28,23 @@ verify_downloaded_script() {
printf "[ERROR] %s does not start with a shell shebang\n" "$label" >&2
exit 1
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
fi
}

has_payload_marker() {
Expand Down
131 changes: 131 additions & 0 deletions scripts/check-installer-hash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Verifies that pinned SHA-256 hashes for downloaded installers still match
# the current upstream scripts.
#
# Checked installers:
# 1. Ollama installer — scripts/install.sh (OLLAMA_INSTALL_SHA256)
#
# Usage:
# scripts/check-installer-hash.sh # exit 0 if current, 1 if stale
# scripts/check-installer-hash.sh --update # rewrite stale hashes in-place

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"

case "${1:-}" in
"" | --update) ;;
*)
echo "Usage: scripts/check-installer-hash.sh [--update]" >&2
exit 2
;;
esac

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
fetch_hash() {
local url="$1" tmpfile
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' RETURN

curl --proto '=https' --tlsv1.2 -fsSL \
--connect-timeout 10 --max-time 30 \
--retry 3 --retry-delay 1 --retry-all-errors \
-o "$tmpfile" "$url"

if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$tmpfile" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$tmpfile" | awk '{print $1}'
else
echo "ERROR: No SHA-256 tool available (sha256sum/shasum)." >&2
return 1
fi
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

extract_pinned() {
local file="$1" var_name="$2"
sed -n "s/.*${var_name}=\"\\([a-f0-9]\\{64\\}\\)\".*/\\1/p" "$file" | head -1
}

update_pinned() {
local file="$1" old_hash="$2" new_hash="$3"
sed -i.bak "s/${old_hash}/${new_hash}/" "$file"
rm -f "${file}.bak"
}

# ---------------------------------------------------------------------------
# Registry of pinned hashes: (label, file, variable, upstream URL)
# ---------------------------------------------------------------------------
LABELS=()
FILES=()
VARS=()
URLS=()

register() {
LABELS+=("$1")
FILES+=("$2")
VARS+=("$3")
URLS+=("$4")
}

register "Ollama installer" \
"${REPO_ROOT}/scripts/install.sh" \
"OLLAMA_INSTALL_SHA256" \
"https://ollama.com/install.sh"

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
failures=0

for i in "${!LABELS[@]}"; do
label="${LABELS[$i]}"
file="${FILES[$i]}"
var="${VARS[$i]}"
url="${URLS[$i]}"

pinned=$(extract_pinned "$file" "$var")

if [[ -z "$pinned" ]]; then
echo " SKIP: ${var} not found in ${file} (not yet merged?)"
continue
fi

echo "Checking ${label} (${var})..."
echo " Fetching ${url}..."
upstream=$(fetch_hash "$url")

if [[ "$pinned" == "$upstream" ]]; then
echo " OK: hash is up-to-date (${pinned})"
continue
fi

if [[ "${1:-}" == "--update" ]]; then
update_pinned "$file" "$pinned" "$upstream"
echo " UPDATED ${file}: ${var}"
echo " old: ${pinned}"
echo " new: ${upstream}"
else
echo " STALE: pinned hash does not match upstream."
echo " pinned: ${pinned}"
echo " upstream: ${upstream}"
failures=$((failures + 1))
fi
done

if ((failures > 0)); then
echo ""
echo "${failures} hash(es) are stale. To update, run:"
echo ""
echo " scripts/check-installer-hash.sh --update"
echo ""
exit 1
fi

echo ""
echo "All installer hashes are current."
32 changes: 22 additions & 10 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,30 @@ error() {
ok() { printf " ${C_GREEN}✓${C_RESET} %s\n" "$*"; }

verify_downloaded_script() {
local file="$1" label="${2:-script}"
local file="$1" label="${2:-script}" expected_hash="${3:-}"
if [ ! -s "$file" ]; then
error "$label installer download is empty or missing"
error "$label download is empty or missing"
fi
if ! head -1 "$file" | grep -qE '^#!.*(sh|bash)'; then
error "$label installer does not start with a shell shebang — possible download corruption"
error "$label does not start with a shell shebang — possible download corruption"
fi
local hash
local actual_hash=""
if command -v sha256sum >/dev/null 2>&1; then
hash="$(sha256sum "$file" | awk '{print $1}')"
actual_hash="$(sha256sum "$file" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
hash="$(shasum -a 256 "$file" | awk '{print $1}')"
actual_hash="$(shasum -a 256 "$file" | awk '{print $1}')"
fi
if [ -n "${hash:-}" ]; then
info "$label installer SHA-256: $hash"
if [ -n "$expected_hash" ]; then
if [ -z "$actual_hash" ]; then
error "No SHA-256 tool available — cannot verify $label integrity"
fi
if [ "$actual_hash" != "$expected_hash" ]; then
rm -f "$file"
error "$label integrity check failed\n Expected: $expected_hash\n Actual: $actual_hash"
fi
info "$label integrity verified (SHA-256: ${actual_hash:0:16}…)"
elif [ -n "$actual_hash" ]; then
info "$label SHA-256: $actual_hash"
fi
}

Expand Down Expand Up @@ -689,6 +698,9 @@ install_nodejs() {
# 2. Ollama
# ---------------------------------------------------------------------------
OLLAMA_MIN_VERSION="0.18.0"
# IMPORTANT: update OLLAMA_INSTALL_SHA256 when changing OLLAMA_MIN_VERSION
# Pattern: pin hash and verify, same as NVM_SHA256 above (line ~656).
OLLAMA_INSTALL_SHA256="25f64b810b947145095956533e1bdf56eacea2673c55a7e586be4515fc882c9f"

get_ollama_version() {
# `ollama --version` outputs something like "ollama version 0.18.0"
Expand Down Expand Up @@ -732,7 +744,7 @@ install_or_upgrade_ollama() {
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL https://ollama.com/install.sh -o "$tmpdir/install_ollama.sh"
verify_downloaded_script "$tmpdir/install_ollama.sh" "Ollama"
verify_downloaded_script "$tmpdir/install_ollama.sh" "Ollama" "$OLLAMA_INSTALL_SHA256"
sh "$tmpdir/install_ollama.sh"
)
info "Ollama upgraded to $(get_ollama_version)"
Expand All @@ -745,7 +757,7 @@ install_or_upgrade_ollama() {
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL https://ollama.com/install.sh -o "$tmpdir/install_ollama.sh"
verify_downloaded_script "$tmpdir/install_ollama.sh" "Ollama"
verify_downloaded_script "$tmpdir/install_ollama.sh" "Ollama" "$OLLAMA_INSTALL_SHA256"
sh "$tmpdir/install_ollama.sh"
)
info "Ollama installed: v$(get_ollama_version)"
Expand Down
Loading