#!/bin/sh
# Busbar installer. Downloads the latest release binary for your platform plus the
# provider catalog, into the current directory (or $BUSBAR_INSTALL_DIR).
#
#   curl -fsSL https://getbusbar.com/install.sh | sh
#
# No sudo, no global install: it drops `busbar` and `providers.yaml` where you run it,
# then prints the next steps. To install onto your PATH, set BUSBAR_INSTALL_DIR *for the shell
# that runs the script*, which is the one on the RIGHT of the pipe:
#
#   curl -fsSL https://getbusbar.com/install.sh | BUSBAR_INSTALL_DIR=/usr/local/bin sh
#
# (`BUSBAR_INSTALL_DIR=... curl ... | sh` sets the variable for curl, not for sh, so it has no
# effect at all: a silent no-op that installs into the current directory instead.)
set -eu

REPO="GetBusbar/busbar"
INSTALL_DIR="${BUSBAR_INSTALL_DIR:-$(pwd)}"

say() { printf '\033[1;36mbusbar\033[0m %s\n' "$1"; }
err() { printf '\033[1;31mbusbar: %s\033[0m\n' "$1" >&2; exit 1; }

# --- detect platform → Rust target triple --------------------------------------------
os="$(uname -s)"
arch="$(uname -m)"
case "$os" in
  Linux)  plat="unknown-linux-gnu" ;;
  Darwin) plat="apple-darwin" ;;
  *) err "unsupported OS '$os'. Windows: download the .zip from https://github.com/$REPO/releases/latest" ;;
esac
case "$arch" in
  x86_64|amd64)   cpu="x86_64" ;;
  arm64|aarch64)  cpu="aarch64" ;;
  *) err "unsupported architecture '$arch'" ;;
esac

# --- which libc? glibc and musl are NOT interchangeable -------------------------------
# Every published Linux artifact is `*-unknown-linux-gnu`: dynamically linked against glibc,
# and it CANNOT run on a musl system (Alpine, and any distroless/musl base built on it).
#
# The failure this prevents is the nastiest kind of silent one. Install the glibc build on
# Alpine and every step reports success (the download works, the file lands, the installer
# exits 0) and then `./busbar` prints:
#
#     sh: ./busbar: not found
#
# ...for a file that is plainly there and executable. The "not found" is the missing ELF
# INTERPRETER (`/lib/ld-linux-*.so.*`), not the missing binary, and it sends people hunting
# for a typo or a PATH problem that does not exist. An installer that exits 0 having placed
# something that cannot run is worse than one that refuses, so this refuses, and says why.
#
# There is NO musl artifact today. When one is published this branch becomes
# `plat="unknown-linux-musl"` and the error below goes away; nothing else here changes.
detect_libc() {
  # 1. The musl loader itself. Definitive, and present before any tool is installed.
  for f in /lib/ld-musl-*.so.1; do
    [ -e "$f" ] && { echo musl; return; }
  done
  # 2. musl's own `ldd` self-identifies (and exits non-zero, hence the pipe).
  if ldd --version 2>&1 | grep -qi 'musl'; then echo musl; return; fi
  # 3. Positively confirm glibc, rather than assuming it from the absence of musl.
  if ldd --version 2>&1 | grep -qiE 'gnu libc|glibc'; then echo gnu; return; fi
  if getconf GNU_LIBC_VERSION >/dev/null 2>&1; then echo gnu; return; fi
  for f in /lib/ld-linux-*.so.* /lib64/ld-linux-*.so.* /lib/*/ld-linux-*.so.*; do
    [ -e "$f" ] && { echo gnu; return; }
  done
  echo unknown
}

if [ "$os" = "Linux" ]; then
  libc="$(detect_libc)"
  if [ "$libc" = "musl" ]; then
    err "this is a musl system (Alpine or similar), and busbar publishes no musl build yet.

  The only Linux artifacts are ${cpu}-unknown-linux-gnu, which are linked against glibc and
  cannot run here: the loader is missing, so the binary would install cleanly and then fail
  with a misleading 'not found'. Refusing to install it.

  Your options:
    - run busbar in the official container image, which is already the right base:
        docker run --rm getbusbar/busbar:latest --version
    - use a glibc image instead of Alpine (debian:12-slim, ubuntu:24.04, or -slim variants
      of the language images);
    - on Alpine specifically, 'apk add gcompat' is NOT sufficient for this binary;
    - or build from source on this machine: https://github.com/$REPO"
  fi
  if [ "$libc" = "unknown" ]; then
    say "warning: could not identify this system's libc; assuming glibc (the only Linux build
       published). If busbar fails to start with 'not found', you are on musl: see
       https://github.com/$REPO/releases/latest"
  fi
fi

target="${cpu}-${plat}"

# --- resolve the latest release tag --------------------------------------------------
# github.com/<repo>/releases/latest redirects to .../releases/tag/vX.Y.Z, so the tag falls
# straight out of the final URL. That path is plain github.com, needs no token, and is not
# subject to the REST API's 60-requests-per-hour-per-IP anonymous limit.
#
# The REST API (api.github.com) is deliberately NOT used here, not even as a fallback: from any
# shared or NAT'd address it returns 403 rate-limited, which is exactly what broke
# `curl … | sh` installs for everyone. A resolver that fails whenever the network is busy is
# not a fallback, it is a second way to fail slowly. The three resolvers below all avoid it.
latest_url="https://github.com/$REPO/releases/latest"

# vX.Y.Z, optionally with a -prerelease / +build suffix. A silently-wrong tag is worse than a
# loud failure, so nothing that fails this check is ever used to build a download URL.
# (grep matches line-by-line, so a multi-line candidate could sneak a good line past it,
# reject anything that isn't exactly one line before pattern-matching it.)
valid_tag() {
  [ "$(printf '%s' "${1:-}" | wc -l | tr -d ' ')" -eq 0 ] || return 1
  printf '%s' "${1:-}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'
}

# 1. GET the redirect chain and read the final effective URL. GET rather than HEAD on purpose:
#    some proxies (and some edges) answer HEAD with a 404 or drop it entirely, so a HEAD-only
#    resolver is not a safe primary.
tag_from_effective_url() {
  url="$(curl -fsSL -o /dev/null -w '%{url_effective}' "$latest_url" 2>/dev/null | tr -d '\r')" || return 1
  printf '%s' "${url##*/releases/tag/}"
}

# 2. the last Location: header of the chain, via HEAD: cheap, and covers curl builds or proxies
#    where url_effective comes back empty or the body cannot be fetched.
tag_from_location_header() {
  curl -sIL "$latest_url" 2>/dev/null \
    | tr -d '\r' \
    | awk 'tolower($1) == "location:" { print $2 }' \
    | sed -n 's#.*/releases/tag/##p' \
    | tail -1
}

# 3. the releases Atom feed: a static, unauthenticated, un-rate-limited document whose first
#    entry id ends in the newest tag. Covers the case where redirects are not followed at all.
tag_from_releases_atom() {
  curl -fsSL "https://github.com/$REPO/releases.atom" 2>/dev/null \
    | grep -m1 -o 'Repository/[0-9]*/[^<]*' \
    | sed 's#.*/##'
}

say "finding the latest release..."
tag=""
for resolver in tag_from_effective_url tag_from_location_header tag_from_releases_atom; do
  candidate="$("$resolver" 2>/dev/null | tr -d '\r' | tr -d ' ')"
  if valid_tag "$candidate"; then tag="$candidate"; break; fi
done
[ -n "$tag" ] || err "could not determine the latest release tag.
  Check your network/proxy, then download the archive for your platform directly from
  https://github.com/$REPO/releases/latest
  (you want busbar-${target}.tar.gz), unpack it, and put \`busbar\` on your PATH."
say "latest is $tag for $target"

asset="busbar-${target}.tar.gz"
url="https://github.com/$REPO/releases/download/$tag/$asset"

# --- download + extract the binary ---------------------------------------------------
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
say "downloading ${asset}..."
curl -fsSL "$url" -o "$tmp/busbar.tar.gz" || err "download failed: $url"
tar -xzf "$tmp/busbar.tar.gz" -C "$tmp"
bin="$(find "$tmp" -type f -name busbar | head -1)"
[ -n "$bin" ] || err "binary not found in archive"

mkdir -p "$INSTALL_DIR"
install -m 0755 "$bin" "$INSTALL_DIR/busbar" 2>/dev/null || { cp "$bin" "$INSTALL_DIR/busbar"; chmod 0755 "$INSTALL_DIR/busbar"; }

# --- prove it RUNS before claiming it is installed -----------------------------------
# The libc check above is the specific diagnosis; this is the general guarantee behind it.
# `--version` touches no config, no network and no state, so it is a free proof that the
# thing we just placed can actually execute on this machine. Exiting 0 having installed
# something that cannot run is the failure this whole script exists to avoid, so a binary
# that will not start is a hard error here, not a surprise the user meets ten minutes later.
if ! "$INSTALL_DIR/busbar" --version >/dev/null 2>&1; then
  smoke="$("$INSTALL_DIR/busbar" --version 2>&1 || true)"
  rm -f "$INSTALL_DIR/busbar"
  err "downloaded busbar $tag for $target, but it does not run on this machine, so it has
  been removed rather than left behind as a broken install.

  The system said: ${smoke:-(no output)}

  A 'not found' here means the ELF interpreter is missing, i.e. a libc mismatch: the
  published Linux builds are glibc-only and this looks like a musl system.
  A 'cannot execute binary file' means the architecture is wrong (detected: $arch).

  Run busbar in the official container image instead:
    docker run --rm getbusbar/busbar:latest --version
  or build from source: https://github.com/$REPO"
fi

# --- fetch the provider catalog (needed at runtime) ----------------------------------
say "downloading providers.yaml..."
curl -fsSL "https://getbusbar.com/providers.yaml" -o "$INSTALL_DIR/providers.yaml" \
  || say "warning: could not fetch providers.yaml; get it from https://getbusbar.com/providers.yaml before running"

say "installed busbar $tag → $INSTALL_DIR/busbar"
cat <<EOF

Next steps:
  1. Write a config.yaml (see https://getbusbar.com/docs/getting-started/). Minimal:

       providers:
         anthropic:
           api_key: { env: ANTHROPIC_KEY }  # the NAME of the env var holding your key
       models:
         claude-sonnet: { provider: anthropic, max_concurrent: 10 }

  2. Export your provider key and run:

       export ANTHROPIC_KEY=sk-ant-...
       BUSBAR_PROVIDERS=$INSTALL_DIR/providers.yaml BUSBAR_CONFIG=./config.yaml $INSTALL_DIR/busbar

  3. Send a request (OpenAI-style):

       curl http://localhost:8080/v1/chat/completions \\
         -H 'content-type: application/json' \\
         -d '{"model":"claude-sonnet","messages":[{"role":"user","content":"Hello!"}]}'

Docs: https://getbusbar.com  ·  Agent-readable: https://getbusbar.com/llms.txt
EOF
