#!/bin/sh # Installs the Neeuh CLI. # # curl -fsSL https://neeuh.com/install.sh | sh # # Options (environment variables): # NEEUH_VERSION install a specific version, e.g. v0.1.0 (default: latest) # NEEUH_INSTALL_DIR where to put the binary (default: $HOME/.local/bin) # # Every download is checked against the release's SHA-256 checksums before # anything is installed. Windows builds: https://neeuh.com/cli # # Everything runs inside main, which is only called on the last line: a # download cut off part-way through is a syntax error, never a partial run. set -eu fail() { printf 'neeuh install: %s\n' "$*" >&2 exit 1 } fetch() { if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" -o "$2" || fail "could not download $1" elif command -v wget >/dev/null 2>&1; then wget -qO "$2" "$1" || fail "could not download $1" else fail "curl or wget is required" fi } sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}' else fail "sha256sum or shasum is required to verify the download" fi } main() { BASE="${NEEUH_DOWNLOAD_BASE:-https://dl.neeuh.com/cli}" if [ -n "${NEEUH_INSTALL_DIR:-}" ]; then INSTALL_DIR=$NEEUH_INSTALL_DIR elif [ -n "${HOME:-}" ]; then INSTALL_DIR="$HOME/.local/bin" else fail "HOME is not set; set NEEUH_INSTALL_DIR to the directory to install into" fi case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; *) fail "$(uname -s) is not supported by this script. Download a build from https://neeuh.com/cli" ;; esac case "$(uname -m)" in x86_64 | amd64) arch=amd64 ;; arm64 | aarch64) arch=arm64 ;; *) fail "$(uname -m) is not supported. Download a build from https://neeuh.com/cli" ;; esac tmp=$(mktemp -d) trap 'rm -rf "$tmp"; rm -f "${staged:-}"' EXIT trap 'exit 130' INT TERM if [ -n "${NEEUH_VERSION:-}" ]; then version=$NEEUH_VERSION where="NEEUH_VERSION" else fetch "$BASE/latest" "$tmp/latest" version=$(tr -d ' \t\r\n' <"$tmp/latest") where="$BASE/latest" fi # Accept only a plain vX.Y.Z (three numeric parts) before building URLs from # it: a CDN error page in `latest` must never turn into a download path. The # message is fixed so nothing from the response is echoed to the terminal. [ "${#version}" -le 32 ] || fail "unexpected version in $where" case "$version" in v*) ;; *) fail "unexpected version in $where" ;; esac num=${version#v} case "$num" in *[!0-9.]* | *..* | .* | *.) fail "unexpected version in $where" ;; esac dots=$(printf '%s' "$num" | tr -cd '.') [ "$dots" = ".." ] || fail "unexpected version in $where" archive="neeuh_${num}_${os}_${arch}.tar.gz" printf 'Downloading neeuh %s for %s/%s...\n' "$version" "$os" "$arch" fetch "$BASE/$version/$archive" "$tmp/$archive" fetch "$BASE/$version/checksums.txt" "$tmp/checksums.txt" expected=$(awk -v f="$archive" '$2 == f {print $1}' "$tmp/checksums.txt") [ -n "$expected" ] || fail "no checksum listed for $archive" actual=$(sha256 "$tmp/$archive") [ "$expected" = "$actual" ] || fail "checksum mismatch for $archive (expected $expected, got $actual). Nothing was installed." tar -xzf "$tmp/$archive" -C "$tmp" neeuh || fail "could not unpack $archive" mkdir -p "$INSTALL_DIR" || fail "cannot create $INSTALL_DIR (set NEEUH_INSTALL_DIR to a directory you can write to)" # Stage next to the destination, then rename: the swap is atomic, so an # interrupted install never leaves a half-written neeuh behind. staged="$INSTALL_DIR/.neeuh.$$" cp "$tmp/neeuh" "$staged" || fail "cannot write to $INSTALL_DIR (set NEEUH_INSTALL_DIR to a directory you can write to)" chmod 755 "$staged" mv -f "$staged" "$INSTALL_DIR/neeuh" || fail "cannot write to $INSTALL_DIR (set NEEUH_INSTALL_DIR to a directory you can write to)" "$INSTALL_DIR/neeuh" --version