#!/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'