#!/usr/bin/env sh # install.sh — install the okx-outcomes Rust CLI on macOS / Linux. # # Pulls a prebuilt tarball from the first reachable source — GitHub Releases # first (SHA-256 verified against the aggregate checksums.txt), OKX CDN domains # as the fallback for networks that block github.com — and installs the binary # into the prefix directory (default: $HOME/.okx/bin). # Re-runs skip the install when the installed version already matches the # target. # # Usage: # curl -fsSL https://raw.githubusercontent.com/okx/outcomes-cli/main/install.sh | sh # sh install.sh [--version vX.Y.Z] [--prefix DIR] # # Exit codes: # 1 unsupported platform / arch # 2 download failed # 3 checksum mismatch / no matching checksum entry / extraction failed set -eu REPO="${OKX_OUTCOMES_REPO:-okx/outcomes-cli}" PREFIX="" VERSION="latest" # Last-resort pinned version, used only when `latest` can't be resolved from # github.com AND no CDN checksums.txt is readable to derive it. Normally the # CDN checksums supply the current version, so this rarely fires. # BUMP THIS EACH RELEASE to match the Cargo.toml version. FALLBACK_VERSION="v1.0.1" # Release archives are pulled from the first reachable source. GitHub Releases # is tried first, using its // layout. The OKX CDN domains # are the fallback for networks that block github.com, with a FLAT layout — the # tarball sits directly under the prefix as / (the version lives # in the filename, there is no per-tag subdirectory). # # SHA-256 verification: both CDN and GitHub downloads are verified against the # aggregate checksums.txt published next to the archive. A CDN that does not # (yet) publish checksums.txt falls back to trusted-over-HTTPS, unverified. # # Override without editing this script: # OKX_OUTCOMES_CDN_BASES space-separated CDN base URLs (replaces the list) # OKX_OUTCOMES_CDN_PREFIX path prefix on the built-in CDN domains CDN_PREFIX="${OKX_OUTCOMES_CDN_PREFIX:-/upgradeapp/cli/outcomes}" CDN_BASES="${OKX_OUTCOMES_CDN_BASES:-https://static.okx.com${CDN_PREFIX} https://apk.daksjs.com${CDN_PREFIX}}" GH_BASE="https://github.com/${REPO}/releases/download" while [ $# -gt 0 ]; do case "$1" in --version) VERSION="${2:?--version requires a value}"; shift 2 ;; --version=*) VERSION="${1#*=}"; : "${VERSION:?--version requires a value}"; shift ;; --prefix) PREFIX="${2:?--prefix requires a value}"; shift 2 ;; --prefix=*) PREFIX="${1#*=}"; : "${PREFIX:?--prefix requires a value}"; shift ;; -h|--help) cat <<'EOF' install.sh — install the okx-outcomes Rust CLI on macOS / Linux. Usage: curl -fsSL https://raw.githubusercontent.com/okx/outcomes-cli/main/install.sh | sh curl ... | sh -s -- --version v0.1.0 --prefix /usr/local sh install.sh [--version vX.Y.Z] [--prefix DIR] Flags: --version vX.Y.Z Pin a specific release (default: latest). --prefix DIR Autoconf-style; binary lands at DIR/bin/okx-outcomes. Default install location: $HOME/.okx/bin/okx-outcomes. -h, --help Show this help. Env: OKX_OUTCOMES_REPO Override the default 'okx/outcomes-cli' GitHub target (e.g. for fork / internal-mirror tests). OKX_OUTCOMES_CDN_BASES Space-separated CDN base URLs used as the fallback when GitHub is unreachable (replaces the built-in CDN list). Each is fetched flat as /, verified against /checksums.txt when present. OKX_OUTCOMES_CDN_PREFIX Path prefix on the built-in CDN domains (default: /upgradeapp/cli/outcomes). Re-runs detect the installed version and skip when it equals the target. Exit codes: 1 unsupported platform / arch 2 download failed 3 checksum mismatch / no matching checksum entry / extraction failed EOF exit 0 ;; *) printf 'Unknown option: %s\n' "$1" >&2 exit 1 ;; esac done if [ -n "$PREFIX" ]; then INSTALL_DIR="$PREFIX/bin" else INSTALL_DIR="${INSTALL_DIR:-$HOME/.okx/bin}" fi OS_NAME="$(uname -s)" ARCH_NAME="$(uname -m)" case "$OS_NAME" in Darwin) case "$ARCH_NAME" in arm64|aarch64) TARGET="aarch64-apple-darwin" ;; x86_64) TARGET="x86_64-apple-darwin" ;; *) printf 'Unsupported macOS arch: %s\n' "$ARCH_NAME" >&2 exit 1 ;; esac ;; Linux) # Detect musl libc (Alpine, distroless, OpenWrt, minimal Docker images). # `ldd --version` is the portable probe; the loader-path fallbacks cover # busybox-ldd / stripped systems where the version string is missing. if ldd --version 2>&1 | grep -qi musl \ || [ -e /lib/ld-musl-x86_64.so.1 ] \ || [ -e /lib/ld-musl-aarch64.so.1 ]; then LIBC="musl" else LIBC="gnu" fi case "$ARCH_NAME" in x86_64) TARGET="x86_64-unknown-linux-${LIBC}" ;; aarch64|arm64) TARGET="aarch64-unknown-linux-${LIBC}" ;; *) printf 'Unsupported Linux arch: %s\n' "$ARCH_NAME" >&2 exit 1 ;; esac ;; *) printf 'install.sh supports macOS / Linux only (got uname -s = %s).\n' "$OS_NAME" >&2 printf 'On Windows, download the .zip artifact from the Releases page directly.\n' >&2 exit 1 ;; esac if [ "$VERSION" = "latest" ]; then LATEST_URL="$(curl -fsSLI --connect-timeout 5 -o /dev/null -w '%{url_effective}' \ "https://github.com/${REPO}/releases/latest" 2>/dev/null)" || LATEST_URL="" TAG="${LATEST_URL##*/}" if [ -z "$TAG" ] || [ "$TAG" = "$LATEST_URL" ]; then # github.com unreachable (e.g. a blocked network): derive the version # from a CDN checksums.txt instead. The CDN path is flat and version- # less, so checksums.txt is reachable without knowing the tag, and its # entries name a single version (-vX.Y.Z-...). TAG="" for BASE in $CDN_BASES; do TAG="$(curl -fsSL --connect-timeout 5 "$BASE/checksums.txt" 2>/dev/null \ | sed -n "s/.*${REPO##*/}-\(v[0-9][0-9.]*\)-.*/\1/p" | head -1)" if [ -n "$TAG" ]; then printf '==> Could not resolve latest from github; using %s from CDN checksums (%s)\n' "$TAG" "$BASE" >&2 break fi done fi if [ -z "$TAG" ]; then # Last resort: neither github nor a CDN checksums.txt was readable. TAG="$FALLBACK_VERSION" printf '==> Could not resolve version from github or CDN; falling back to %s\n' "$TAG" >&2 fi else TAG="$VERSION" fi TARGET_VER="${TAG#v}" # Skip download when the installed version already matches the target. if [ -x "$INSTALL_DIR/okx-outcomes" ]; then INSTALLED_VER="$("$INSTALL_DIR/okx-outcomes" --version 2>/dev/null \ | head -1 \ | awk '{for (i = 1; i <= NF; i++) if ($i ~ /^[0-9]/) { print $i; exit }}' \ || true)" if [ -n "$INSTALLED_VER" ] && [ "$INSTALLED_VER" = "$TARGET_VER" ]; then printf '==> okx-outcomes %s already installed, nothing to do.\n' "$INSTALLED_VER" exit 0 fi if [ -n "$INSTALLED_VER" ]; then printf '==> Updating okx-outcomes from %s to %s\n' "$INSTALLED_VER" "$TARGET_VER" fi fi REPO_NAME="${REPO##*/}" ARCHIVE="${REPO_NAME}-${TAG}-${TARGET}.tar.gz" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT INT TERM printf '==> Resolved %s for %s (%s)\n' "$TAG" "$TARGET" "$OS_NAME" # Pull the archive: GitHub Releases first (tag layout, always paired with a # checksums.txt so this path is SHA-256 verified), CDN domains as the fallback # for networks that block github.com (flat layout). VERIFY records whether the # winning source published a checksums.txt we must check below. WINNER="" VERIFY="no" if curl -fsSL --connect-timeout 5 "$GH_BASE/$TAG/$ARCHIVE" -o "$TMP/$ARCHIVE" 2>/dev/null \ && curl -fsSL --connect-timeout 5 "$GH_BASE/$TAG/checksums.txt" -o "$TMP/checksums.txt" 2>/dev/null; then WINNER="$GH_BASE" VERIFY="yes" printf '==> Source: GitHub Releases %s/%s\n' "$GH_BASE" "$TAG" fi if [ -z "$WINNER" ]; then for BASE in $CDN_BASES; do if curl -fsSL --connect-timeout 5 "$BASE/$ARCHIVE" -o "$TMP/$ARCHIVE" 2>/dev/null; then WINNER="$BASE" printf '==> Source: CDN %s\n' "$BASE" # checksums.txt sits flat next to the archive (same path, different # filename). Verify ONLY when the CDN publishes it AND it lists this # archive; otherwise install unverified (older CDN / partial sums). if ! curl -fsSL --connect-timeout 5 "$BASE/checksums.txt" -o "$TMP/checksums.txt" 2>/dev/null; then printf '==> No checksums.txt on this CDN — installing WITHOUT SHA-256 verification\n' elif ! awk -v a="$ARCHIVE" '$NF == a { f=1 } END { exit f ? 0 : 1 }' \ "$TMP/checksums.txt"; then printf '==> checksums.txt has no entry for %s — installing WITHOUT SHA-256 verification\n' "$ARCHIVE" else VERIFY="yes" fi break fi done fi if [ -z "$WINNER" ]; then printf 'Error: could not download %s (%s) from any source. Tried:\n' "$ARCHIVE" "$TAG" >&2 for BASE in $CDN_BASES; do printf ' - %s/%s\n' "$BASE" "$ARCHIVE" >&2 done printf ' - %s/%s/%s\n' "$GH_BASE" "$TAG" "$ARCHIVE" >&2 printf 'Hint: if you are behind a firewall, curl needs HTTPS_PROXY set explicitly:\n' >&2 printf ' export HTTPS_PROXY=http://: # then re-run this script\n' >&2 exit 2 fi if [ "$VERIFY" = "yes" ]; then awk -v archive="${ARCHIVE}" '$NF == archive { print; found=1 } END { exit found ? 0 : 1 }' \ "$TMP/checksums.txt" > "$TMP/expected.sha256" || { printf 'Error: no checksum entry for %s in checksums.txt\n' "$ARCHIVE" >&2 cat "$TMP/checksums.txt" >&2 exit 3 } printf '==> Verifying SHA-256\n' if command -v sha256sum >/dev/null 2>&1; then ( cd "$TMP" && sha256sum -c expected.sha256 >&2 ) || { printf 'Error: SHA-256 checksum mismatch for %s\n' "$ARCHIVE" >&2 exit 3 } elif command -v shasum >/dev/null 2>&1; then ( cd "$TMP" && shasum -a 256 -c expected.sha256 >&2 ) || { printf 'Error: SHA-256 checksum mismatch for %s\n' "$ARCHIVE" >&2 exit 3 } else printf 'Error: neither sha256sum nor shasum found; cannot verify\n' >&2 exit 3 fi fi printf '==> Extracting and installing to %s\n' "$INSTALL_DIR" mkdir -p "$INSTALL_DIR" tar -xzf "$TMP/$ARCHIVE" -C "$TMP" BIN_SRC="$(find "$TMP" -type f -name 'okx-outcomes' 2>/dev/null | head -1)" if [ -z "$BIN_SRC" ]; then printf 'Error: okx-outcomes binary not found in archive\n' >&2 exit 3 fi install -m 0755 "$BIN_SRC" "$INSTALL_DIR/okx-outcomes" printf ' installed: %s/okx-outcomes\n' "$INSTALL_DIR" case ":$PATH:" in *":$INSTALL_DIR:"*) printf '==> %s is already on PATH\n' "$INSTALL_DIR" ;; *) RC="" case "${SHELL:-}" in */zsh) RC="$HOME/.zshrc" ;; */bash) if [ -f "$HOME/.bash_profile" ]; then RC="$HOME/.bash_profile" else RC="$HOME/.bashrc" fi ;; esac LINE="export PATH=\"$INSTALL_DIR:\$PATH\"" if [ -n "$RC" ]; then if ! grep -Fq "$LINE" "$RC" 2>/dev/null; then { printf '\n' printf '# Added by okx-outcomes install.sh\n' printf '%s\n' "$LINE" } >> "$RC" printf '==> Updated %s; restart shell or run: source %s\n' "$RC" "$RC" else printf '==> PATH already set in %s\n' "$RC" fi else printf 'Note: %s is not on PATH. Add this to your shell rc:\n' "$INSTALL_DIR" printf ' %s\n' "$LINE" fi ;; esac printf '\nDone. Try: okx-outcomes --version\n'