#!/bin/sh # Installer for the `neo` CLI. # # curl -LsSf https://get.neosource.dev | sh # # Published verbatim at https://get.neosource.dev (and /install.sh) — this file IS the # artifact, so keep it POSIX sh and dependency-free beyond curl/tar. # # Shape follows uv's and rustup's installers, deliberately: # * resolve the build from a JSON manifest we publish, so no version is # hardcoded here and the script never has to be regenerated; # * verify sha256, but WARN-and-continue if the host has no hash tool rather # than refusing to install; # * never regex-edit shell rc files — write one `env` script and append a # single idempotent line that sources it; # * default to ~/.local/bin, overridable, and never sudo. set -eu BASE="${NEO_INSTALL_BASE:-https://get.neosource.dev/dl}" MANIFEST="$BASE/latest.json" say() { printf '%s\n' "$*"; } err() { printf 'error: %s\n' "$*" >&2; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || err "need $1 on PATH"; } need uname need tar # HTTPS is pinned for the real download host. The only way to get plain HTTP is # to point NEO_INSTALL_BASE at an http:// URL yourself, which is what the # release script's own smoke test does — and it says so out loud, so a # downgrade can never happen quietly. proto_opt="--proto =https --tlsv1.2" case "$BASE" in https://*) ;; http://*) printf 'warning: NEO_INSTALL_BASE is plain HTTP (%s) — downloads are unauthenticated\n' "$BASE" >&2 proto_opt="" ;; *) err "NEO_INSTALL_BASE must be an http(s) URL, got: $BASE" ;; esac if command -v curl >/dev/null 2>&1; then # shellcheck disable=SC2086 # word-splitting of proto_opt is intended fetch() { curl $proto_opt -LsSf "$1" -o "$2"; } # shellcheck disable=SC2086 fetch_stdout() { curl $proto_opt -LsSf "$1"; } elif command -v wget >/dev/null 2>&1; then fetch() { wget -qO "$2" "$1"; } fetch_stdout() { wget -qO - "$1"; } else err "need curl or wget on PATH" fi # ── which build ────────────────────────────────────────────────────────────── # Linux always resolves to musl. Our musl builds are fully static, so this # sidesteps glibc-version skew entirely rather than probing for it — the reason # there is no glibc gate here is that there is no glibc build to fall back to. os="$(uname -s)" arch="$(uname -m)" case "$os" in Linux) case "$arch" in x86_64|amd64) target=x86_64-unknown-linux-musl ;; aarch64|arm64) target=aarch64-unknown-linux-musl ;; *) err "unsupported Linux architecture: $arch" ;; esac ;; Darwin) case "$arch" in arm64) target=aarch64-apple-darwin ;; x86_64) target=x86_64-apple-darwin ;; *) err "unsupported macOS architecture: $arch" ;; esac ;; MINGW*|MSYS*|CYGWIN*) err "on Windows use PowerShell instead: powershell -c \"irm https://get.neosource.dev/install.ps1 | iex\"" ;; *) err "unsupported OS: $os (open an issue naming this exact triple: $os/$arch)" ;; esac say "neo installer — target $target" manifest="$(fetch_stdout "$MANIFEST")" || err "cannot reach $MANIFEST" # Do NOT trust a 200. The download host has no catch-all, but this script is # also runnable against any NEO_INSTALL_BASE — and the apex DOES serve the SPA # as a catch-all, so a misconfigured base comes back as HTTP 200 with HTML that # `curl -f` cannot detect and that would otherwise be parsed as a manifest. case "$manifest" in '<'*|*'/dev/null 2>&1; then got="$(sha256sum "$archive" | cut -d' ' -f1)" elif command -v shasum >/dev/null 2>&1; then got="$(shasum -a 256 "$archive" | cut -d' ' -f1)" elif command -v openssl >/dev/null 2>&1; then got="$(openssl dgst -sha256 "$archive" | awk '{print $NF}')" elif command -v python3 >/dev/null 2>&1; then got="$(python3 -c 'import hashlib,sys;print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "$archive")" fi if [ -z "$got" ]; then if [ "${NEO_INSECURE_SKIP_CHECKSUM:-}" = "1" ]; then say "warning: no sha256 tool found and NEO_INSECURE_SKIP_CHECKSUM=1 is set" say "warning: installing UNVERIFIED bytes at your own risk" else err "no sha256 tool found (tried sha256sum, shasum, openssl, python3). Refusing to install unverified bytes. Install one of those, or set NEO_INSECURE_SKIP_CHECKSUM=1 if you accept the risk." fi elif [ "$got" != "$sha" ]; then err "checksum mismatch for $url expected $sha got $got" fi tar -xzf "$archive" -C "$tmp" extracted="$(find "$tmp" -maxdepth 2 -name neo -type f | head -n 1)" [ -n "$extracted" ] || err "archive did not contain a neo binary" # ── install ────────────────────────────────────────────────────────────────── if [ -n "${NEO_INSTALL_DIR:-}" ]; then bindir="$NEO_INSTALL_DIR" elif [ -n "${XDG_BIN_HOME:-}" ]; then bindir="$XDG_BIN_HOME" else bindir="$HOME/.local/bin" fi mkdir -p "$bindir" install -m 755 "$extracted" "$bindir/neo" 2>/dev/null || { cp "$extracted" "$bindir/neo" && chmod 755 "$bindir/neo" } say "installed $bindir/neo" # Completions: only into directories that already exist. Creating a shell's # completion tree on its behalf is the installer overstepping, and `neo # completion ` works regardless. comp_src="$(dirname "$extracted")/completions" if [ -d "$comp_src" ]; then for pair in \ "$HOME/.local/share/bash-completion/completions:neo.bash:neo" \ "$HOME/.local/share/zsh/site-functions:neo.zsh:_neo" \ "$HOME/.config/fish/completions:neo.fish:neo.fish" do dir="${pair%%:*}"; rest="${pair#*:}"; src="${rest%%:*}"; dst="${rest#*:}" if [ -d "$dir" ] && [ -f "$comp_src/$src" ]; then cp "$comp_src/$src" "$dir/$dst" && say "installed completions → $dir/$dst" fi done fi # Receipt: what a future `neo upgrade`/uninstall reads. Written even though # neither exists yet — retrofitting it onto already-installed copies is not # possible, so the cost of writing it now is one file and the cost of not # writing it is every existing install being unmanageable later. receipt_dir="${XDG_DATA_HOME:-$HOME/.local/share}/neosource" mkdir -p "$receipt_dir" cat > "$receipt_dir/neo-receipt.json" < "$env_file" </dev/null; then printf '\n%s\n' "$line" >> "$rc" added="$added $rc" fi done say "" if [ -n "$added" ]; then say "$bindir was not on PATH; added a source line to:$added" say "Open a new shell, or run: $line" else say "warning: $bindir is not on your PATH. Add it with:" say " $line" fi ;; esac # A stale copy earlier on PATH silently wins over the one just installed — # worth naming, since the symptom is "I installed it and nothing changed". if command -v neo >/dev/null 2>&1; then found="$(command -v neo)" if [ "$found" != "$bindir/neo" ]; then say "" say "warning: another neo is earlier on your PATH and will win: $found" fi fi say "" say "Done. Try: neo auth login"