#!/bin/sh set -e REPO="nimbus-solution/nimbus-releases" INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" BINARY="nimbus" # Anonymous install ping (see public.installs in supabase). Schema and # privacy posture documented in the matching migration. Opt out with # NIMBUS_NO_TRACKING=1, NIMBUS_TELEMETRY=false, or NIMBUS_NO_TELEMETRY=1. SUPABASE_URL="https://fmqylpjoqnfamdexitbu.supabase.co" SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZtcXlscGpvcW5mYW1kZXhpdGJ1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzUzNTc1NDQsImV4cCI6MjA5MDkzMzU0NH0.2Y2jKwvmWLsS17unE5JjOoKdbVe7LDzax0oQpD_sc_A" INSTALLER_VERSION="2026-05-09" NIMBUS_DIR="${HOME}/.nimbus" INSTALL_ID_FILE="${NIMBUS_DIR}/install.id" # Detect OS OS=$(uname -s | tr '[:upper:]' '[:lower:]') case "$OS" in linux|darwin) ;; *) echo "Unsupported OS: $OS" >&2; exit 1 ;; esac # Detect architecture ARCH=$(uname -m) case "$ARCH" in x86_64) ARCH="amd64" ;; arm64|aarch64) ARCH="arm64" ;; *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; esac # Resolve version. Precedence: # 1. NIMBUS_VERSION env var (pin for CI / reproducible installs) # 2. Follow the releases/latest 302 redirect (no auth, no rate limit) # 3. Fall back to the GitHub API, optionally using GITHUB_TOKEN # (the anonymous API limit is 60/hr/IP and shared GHA runners hit it) VERSION="${NIMBUS_VERSION:-}" VERSION="${VERSION#v}" if [ -z "$VERSION" ]; then VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \ "https://github.com/${REPO}/releases/latest" 2>/dev/null \ | sed -n 's|.*/tag/v\([^/?#]*\).*|\1|p') fi if [ -z "$VERSION" ]; then AUTH_HEADER="" if [ -n "${GITHUB_TOKEN:-}" ]; then AUTH_HEADER="Authorization: Bearer ${GITHUB_TOKEN}" fi VERSION=$(curl -fsSL ${AUTH_HEADER:+-H "$AUTH_HEADER"} \ "https://api.github.com/repos/${REPO}/releases/latest" \ | grep '"tag_name"' \ | sed 's/.*"tag_name": *"v\([^"]*\)".*/\1/') fi if [ -z "$VERSION" ]; then echo "error: could not determine latest version" >&2 echo "hint: set NIMBUS_VERSION=x.y.z to pin a release" >&2 exit 1 fi FILENAME="${BINARY}_${VERSION}_${OS}_${ARCH}.tar.gz" URL="https://github.com/${REPO}/releases/download/v${VERSION}/${FILENAME}" echo "Installing nimbus v${VERSION} (${OS}/${ARCH})..." # Download and extract to a temp dir TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT curl -fsSL "$URL" -o "$TMP/$FILENAME" tar -xzf "$TMP/$FILENAME" -C "$TMP" # Ensure install dir exists; fall back to ~/.local/bin if not writable mkdir -p "$INSTALL_DIR" 2>/dev/null || true if [ ! -w "$INSTALL_DIR" ]; then INSTALL_DIR="$HOME/.local/bin" mkdir -p "$INSTALL_DIR" fi install -m 755 "$TMP/$BINARY" "$INSTALL_DIR/$BINARY" echo "nimbus v${VERSION} installed to $INSTALL_DIR/$BINARY" # Remind user to add to PATH if using the fallback location if [ "$INSTALL_DIR" = "$HOME/.local/bin" ]; then echo "" echo "Add to your PATH if needed:" echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.zshrc && source ~/.zshrc" fi # --- anonymous install ping --- # Runs after the binary is in place so a tracking failure can never block # the install. All errors are swallowed; the user never sees them. # Honor opt-outs (must mirror internal/telemetry/firstrun.go). TRACKING_DISABLED=0 [ "${NIMBUS_NO_TRACKING:-}" = "1" ] && TRACKING_DISABLED=1 [ "${NIMBUS_NO_TELEMETRY:-}" = "1" ] && TRACKING_DISABLED=1 [ "${NIMBUS_NO_TELEMETRY:-}" = "true" ] && TRACKING_DISABLED=1 case "${NIMBUS_TELEMETRY:-}" in false|0|off|FALSE|OFF) TRACKING_DISABLED=1 ;; esac if [ "$TRACKING_DISABLED" = "0" ]; then # KIND tells us "first install on this machine" vs "re-install / upgrade". # If install.id already exists, the user has run nimbus before (or run # this script before) — so this is an upgrade event regardless of # whether they're moving versions or just re-running the installer. if [ -f "$INSTALL_ID_FILE" ]; then KIND="upgrade" ANON_ID=$(tr -d '[:space:]' < "$INSTALL_ID_FILE" 2>/dev/null || true) else KIND="install" ANON_ID="" fi # Generate a fresh anon_id if needed. Try sources in order of how # reliably they exist across darwin and major linux distros. if [ -z "$ANON_ID" ]; then if [ -r /proc/sys/kernel/random/uuid ]; then ANON_ID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || true) elif command -v uuidgen >/dev/null 2>&1; then ANON_ID=$(uuidgen 2>/dev/null | tr 'A-Z' 'a-z' || true) fi fi # Persist so the binary's first-run telemetry can correlate this install # with the activation event it'll fire later. if [ -n "$ANON_ID" ]; then mkdir -p "$NIMBUS_DIR" 2>/dev/null || true printf '%s\n' "$ANON_ID" > "$INSTALL_ID_FILE" 2>/dev/null || true chmod 600 "$INSTALL_ID_FILE" 2>/dev/null || true BODY=$(printf '{"anon_id":"%s","kind":"%s","version":"%s","os":"%s","arch":"%s","installer_version":"%s"}' \ "$ANON_ID" "$KIND" "$VERSION" "$OS" "$ARCH" "$INSTALLER_VERSION") # 3-second timeout, no retries, output discarded. The install must # finish whether or not the ping succeeds. curl -fsS \ --max-time 3 \ -X POST \ -H "apikey: $SUPABASE_ANON_KEY" \ -H "Authorization: Bearer $SUPABASE_ANON_KEY" \ -H "Content-Type: application/json" \ -H "Prefer: return=minimal" \ -d "$BODY" \ "$SUPABASE_URL/rest/v1/installs" >/dev/null 2>&1 || true fi fi