#!/usr/bin/env bash # # Jam CLI installer — https://jam.dev # Usage: curl -fsSL https://native.jam.dev/install | bash # set -euo pipefail # ── Configuration ────────────────────────────────────────────── BASE_URL="https://native.jam.dev" GRAPHQL_URL="https://graphql.jam.dev/graphql" INSTALL_DIR="$HOME/.local/bin" STATE_DIR="$HOME/.local/state/jam" BIN="jam" PATH_MARKER="# Added by Jam CLI installer (jam)" # ── Cleanup state (global for EXIT trap) ────────────────────── TMP_BIN="" _HDR="" _ERR_LOG="" trap 'rm -f "$TMP_BIN" "$_HDR" "$_ERR_LOG"' EXIT # ── Helpers ──────────────────────────────────────────────────── info() { printf '%s\n' "$*"; } die() { printf 'Error: %s\n' "$*" >&2; exit 1; } # ── Platform detection ───────────────────────────────────────── detect_platform() { OS="$(uname -s | tr '[:upper:]' '[:lower:]')" ARCH="$(uname -m)" case "$ARCH" in x86_64|amd64) ARCH="x64" ;; aarch64|arm64) ARCH="arm64" ;; esac case "$OS" in darwin|linux) ;; *) die "Unsupported OS: $OS" ;; esac case "$ARCH" in arm64|x64) ;; *) die "Unsupported architecture: $ARCH" ;; esac } # ── Download & verify ────────────────────────────────────────── fetch_binary() { local url="$BASE_URL/download/$OS/$ARCH" local code actual_sha _HDR="$(mktemp)" _ERR_LOG="$(mktemp)" TMP_BIN="$INSTALL_DIR/$BIN.tmp.$$" info "Downloading $BIN for $OS/$ARCH..." code="$(curl -sSL -D "$_HDR" -w '%{http_code}' -o "$TMP_BIN" "$url" 2>"$_ERR_LOG" || true)" [ -s "$TMP_BIN" ] \ || { cat "$_ERR_LOG" >&2 2>/dev/null; die "Download failed."; } if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then cat "$_ERR_LOG" >&2 2>/dev/null die "Server returned HTTP $code for $url" fi chmod +x "$TMP_BIN" # Verify SHA-256 from server header EXPECTED_SHA="$(awk -F': ' 'tolower($1)=="x-jam-sha256" { gsub(/\r/,"",$2); print $2 }' "$_HDR" | tail -1)" [ -n "$EXPECTED_SHA" ] || die "Server response missing X-Jam-Sha256 header." if command -v sha256sum >/dev/null 2>&1; then actual_sha="$(sha256sum "$TMP_BIN" | awk '{ print $1 }')" else actual_sha="$(shasum -a 256 "$TMP_BIN" | awk '{ print $1 }')" fi [ "$actual_sha" = "$EXPECTED_SHA" ] \ || die "Checksum mismatch (expected $EXPECTED_SHA, got $actual_sha)." } # ── Install ──────────────────────────────────────────────────── install_binary() { if [ "$OS" = "darwin" ]; then xattr -d com.apple.quarantine "$TMP_BIN" 2>/dev/null || true fi "$TMP_BIN" --version >/dev/null 2>&1 \ || die "Downloaded binary failed smoke test." VERSION="$("$TMP_BIN" --version | tr -d '\r\n')" mv -f "$TMP_BIN" "$INSTALL_DIR/$BIN" } # ── PATH configuration ───────────────────────────────────────── ensure_path() { echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR" && return 0 PATH_CHANGED=1 local line="export PATH=\"$INSTALL_DIR:\$PATH\" $PATH_MARKER" local found_rc=0 for rc in "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do [ -f "$rc" ] || continue found_rc=1 grep -Fq "$PATH_MARKER" "$rc" && continue printf '\n%s\n' "$line" >> "$rc" done # No shell rc files found — seed one [ "$found_rc" -ne 0 ] || printf '\n%s\n' "$line" >> "$HOME/.zshrc" } # ── Telemetry (opt out: JAM_NO_TELEMETRY=1) ──────────────────── # # Anonymous "install" lifecycle event → logCliLifecycle GraphQL mutation. # Built with printf so the version/os/arch values slot in without hand-escaping # JSON quotes; the query is single-quoted so bash leaves the GraphQL variable # names ($event, …) untouched. report_install() { [ "${JAM_NO_TELEMETRY:-0}" = "1" ] && return 0 local query='mutation($event:String!,$version:String!,$os:String,$arch:String){logCliLifecycle(event:$event,version:$version,os:$os,arch:$arch)}' local payload payload=$(printf '{"query":"%s","variables":{"event":"install","version":"%s","os":"%s","arch":"%s"}}' "$query" "$VERSION" "$OS" "$ARCH") curl -fsSL -X POST "$GRAPHQL_URL" \ -H "Content-Type: application/json" \ -d "$payload" \ >/dev/null 2>&1 || true } # ── Main ─────────────────────────────────────────────────────── PATH_CHANGED=0 mkdir -p "$INSTALL_DIR" "$STATE_DIR" detect_platform fetch_binary install_binary ensure_path report_install info "Installed $BIN $VERSION to $INSTALL_DIR/$BIN." if [ "$PATH_CHANGED" = "1" ]; then info "Open a new terminal (or reload your shell) before running $BIN." fi info "Run \"$BIN doctor\" to verify."