Files
netris-nestri/apps/nesdoctor/install/install.sh
Wanjohi 166c1e9c24 fix(nesdoctor): pin the release tag; releases/latest is a time bomb here
Both installers fetched from `releases/latest/download`, which is wrong in this
repository specifically: it ships product releases as well as this tool, so
`latest` is whichever release went out most recently regardless of what it
contains.

Measured before publishing anything: `releases/latest` resolved to `v0.2.0`,
from May 2024, and the asset URL 404'd. Publishing nesdoctor-v0.1.0 would have
papered over it by becoming the newest release -- and then the first product
release after it would have moved `latest` again and broken every
`curl | sh` in the announcement, silently, for everyone, with the tool itself
untouched and nothing to point at.

Now pinned to a tag that is bumped when a nesdoctor release is cut, with
`NESDOCTOR_TAG` still overriding for testing. The download failure message also
now names the tag and says outright that an unpublished tag is the likely
cause, since that is the one mistake this arrangement invites.
2026-09-02 13:23:13 +03:00

130 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env sh
# nesdoctor installer — https://doctor.nestri.io/install.sh
#
# This file is the source of what that URL serves. It lives in the public
# repository so that anyone about to pipe it into a shell can read it first:
#
# https://github.com/nestrilabs/nestri/blob/dev/apps/nesdoctor/install/install.sh
#
# What it does, in order: work out your platform, download the matching
# nesdoctor binary from GitHub Releases, verify it against the published
# SHA256SUMS, run it, and delete it. It installs nothing permanently, touches
# no system directory, and never asks for sudo.
#
# What nesdoctor itself does is printed when it starts.
set -eu
REPO="nestrilabs/nestri"
# Pinned, and NOT `releases/latest`. This repository ships product releases as
# well as this tool, so `latest` is whatever went out most recently -- it
# resolved to a 2024 release while this was being written, and the day a
# product release goes out it would move again and every install here would
# 404. Bump this line when cutting a nesdoctor release; `NESDOCTOR_TAG`
# overrides it for testing.
DEFAULT_TAG="nesdoctor-v0.1.0"
TAG="${NESDOCTOR_TAG:-$DEFAULT_TAG}"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT INT TERM
say() { printf '%s\n' "$*" >&2; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
# --- platform ---------------------------------------------------------------
case "$(uname -s)" in
Linux) os=linux ;;
Darwin) os=macos ;;
*) die "unsupported OS: $(uname -s). nesdoctor runs on Linux, macOS and Windows." ;;
esac
case "$(uname -m)" in
x86_64|amd64) arch=x86_64 ;;
arm64|aarch64) arch=aarch64 ;;
*) die "unsupported architecture: $(uname -m)" ;;
esac
case "$os/$arch" in
linux/x86_64) target=x86_64-unknown-linux-musl ;;
macos/aarch64) target=aarch64-apple-darwin ;;
macos/x86_64) target=x86_64-apple-darwin ;;
linux/aarch64)
die "no aarch64 Linux build yet. Building from source takes a minute:
git clone --depth 1 https://github.com/$REPO && cd nestri
cargo run --release -p nesdoctor" ;;
*) die "no build for $os/$arch" ;;
esac
ASSET="nesdoctor-$target"
# --- fetch ------------------------------------------------------------------
# curl or wget, whichever is present. -f so an HTML error page is never
# mistaken for a binary.
if command -v curl >/dev/null 2>&1; then
get() { curl -fsSL "$1" -o "$2"; }
elif command -v wget >/dev/null 2>&1; then
get() { wget -qO "$2" "$1"; }
else
die "need curl or wget"
fi
BASE="https://github.com/$REPO/releases/download/$TAG"
say "Downloading nesdoctor ($target)…"
get "$BASE/$ASSET" "$TMP/$ASSET" || die "download failed: $BASE/$ASSET\n If $TAG is not published yet, that is why."
# --- verify -----------------------------------------------------------------
# A checksum we fetch from the same place as the binary is not a security
# boundary, and pretending otherwise would be worse than saying so: it catches
# a truncated or corrupted download, which is the failure that actually
# happens. The signed-release version of this is a later job.
if get "$BASE/SHA256SUMS" "$TMP/SHA256SUMS" 2>/dev/null; then
if command -v sha256sum >/dev/null 2>&1; then
have="$(sha256sum "$TMP/$ASSET" | cut -d' ' -f1)"
elif command -v shasum >/dev/null 2>&1; then
have="$(shasum -a 256 "$TMP/$ASSET" | cut -d' ' -f1)"
else
have=""
fi
if [ -n "$have" ]; then
want="$(grep -F "$ASSET" "$TMP/SHA256SUMS" | cut -d' ' -f1 | head -n1)"
[ -n "$want" ] || die "no checksum for $ASSET in SHA256SUMS"
[ "$have" = "$want" ] || die "checksum mismatch — do not run this file
expected $want
got $have"
say "Checksum OK."
else
say "No sha256 tool found; skipping verification."
fi
else
say "No SHA256SUMS published; skipping verification."
fi
# --- run --------------------------------------------------------------------
chmod +x "$TMP/$ASSET"
say ""
# Reopen stdin on the terminal before handing over.
#
# This matters more than it looks. When this script is run the documented way
# -- `curl -fsSL url | sh` -- the shell's stdin *is* the pipe, and the pipe is
# at end-of-file by the time we get here. nesdoctor inherits that, sees a
# non-terminal stdin, and correctly skips every question. The result is a run
# that completes, looks fine, and answers nothing: the exact failure the whole
# install path exists to avoid, and it would have been invisible in testing
# because running the script from a file works perfectly.
#
# /dev/tty is the controlling terminal regardless of what stdin was piped to.
# Where there is no terminal at all -- CI, a cron job -- the redirect fails and
# we run without it, which is the right behaviour rather than a fallback.
# The probe runs in a subshell on purpose. A failing redirection on `exec` is
# *fatal* to a non-interactive shell rather than merely non-zero, so testing it
# inline killed this script outright on any machine without a controlling
# terminal -- measured, not theorised. The parentheses contain that.
if [ -e /dev/tty ] && (exec 3</dev/tty) 2>/dev/null; then
exec "$TMP/$ASSET" "$@" < /dev/tty
else
say "(no terminal available, so the questions will be skipped)"
exec "$TMP/$ASSET" "$@"
fi