#!/usr/bin/env sh set -e # --------------------------------------------------------------------------- # Ankos CLI installer # # Usage: # curl -fsSL https://get.ankos.dev | sh # # # Pin a specific version (default: latest): # curl -fsSL https://get.ankos.dev | ANKOS_VERSION=1.4.0 sh # # Detects OS and architecture, downloads the requested binary from # releases.ankos.dev, VERIFIES its SHA-256 against the published # checksums.txt, and installs it to /usr/local/bin. # # Environment variables: # ANKOS_VERSION — version to install (default: "latest"). A leading "v" # is optional and normalized away (1.4.0 == v1.4.0). # --------------------------------------------------------------------------- RELEASES_BASE="https://releases.ankos.dev/cli" INSTALL_DIR="/usr/local/bin" BINARY="ankos" # Version to install. Defaults to the floating "latest" channel; pin with # ANKOS_VERSION (e.g. ANKOS_VERSION=1.4.0). release.sh publishes both # cli/latest/ and cli/v/ with a leading "v" on the versioned path # (the version itself has any leading "v" stripped), so normalize here to # match: strip a leading "v", then build the "v" path segment. ANKOS_VERSION="${ANKOS_VERSION:-latest}" main() { os="$(detect_os)" arch="$(detect_arch)" if [ -z "$os" ] || [ -z "$arch" ]; then echo "Error: Unsupported platform: $(uname -s)/$(uname -m)" >&2 exit 1 fi # Resolve the release path segment from ANKOS_VERSION. # latest -> cli/latest # 1.4.0 / v1.4.0 -> cli/v1.4.0 (matches release.sh's versioned path) if [ "$ANKOS_VERSION" = "latest" ]; then version_segment="latest" version_label="latest" else # Strip a single leading "v" so both 1.4.0 and v1.4.0 work. normalized="${ANKOS_VERSION#v}" version_segment="v${normalized}" version_label="v${normalized}" fi releases_url="${RELEASES_BASE}/${version_segment}" filename="${BINARY}-${os}-${arch}" url="${releases_url}/${filename}" checksums_url="${releases_url}/checksums.txt" echo "Installing Ankos CLI..." echo " Version: ${version_label}" echo " Platform: ${os}/${arch}" echo " URL: ${url}" echo "" tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT # Download the binary. download "$url" "$tmpdir/$BINARY" || { echo "Error: failed to download binary from $url" >&2 exit 1 } # Download checksums.txt alongside the binary. Failing to fetch it is a # hard error — we will not install an unverified binary (fail closed). if ! download "$checksums_url" "$tmpdir/checksums.txt"; then echo "Error: failed to download checksums from $checksums_url" >&2 echo " Refusing to install an unverified binary." >&2 exit 1 fi # Verify the SHA-256 of the downloaded binary against checksums.txt. verify_checksum "$tmpdir/$BINARY" "$filename" "$tmpdir/checksums.txt" chmod +x "$tmpdir/$BINARY" # Install. if [ -w "$INSTALL_DIR" ]; then mv "$tmpdir/$BINARY" "$INSTALL_DIR/$BINARY" else echo " Installing to $INSTALL_DIR (requires sudo)..." sudo mv "$tmpdir/$BINARY" "$INSTALL_DIR/$BINARY" fi echo "" echo "Ankos CLI installed to $INSTALL_DIR/$BINARY" "$INSTALL_DIR/$BINARY" version echo "" echo "Run 'hash -r' or open a new terminal, then get started:" echo " ankos auth set-key --key " echo " ankos scan --help" } # download URL DEST — fetch URL to DEST using curl or wget, fail on HTTP error. download() { _url="$1" _dest="$2" if command -v curl >/dev/null 2>&1; then curl -fsSL "$_url" -o "$_dest" elif command -v wget >/dev/null 2>&1; then wget -qO "$_dest" "$_url" else echo "Error: curl or wget is required" >&2 exit 1 fi } # sha256_of FILE — print the lowercase hex SHA-256 of FILE. # Uses shasum -a 256 or sha256sum, whichever is present (macOS + Linux). sha256_of() { _file="$1" if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$_file" | awk '{ print $1 }' elif command -v sha256sum >/dev/null 2>&1; then sha256sum "$_file" | awk '{ print $1 }' else echo "Error: neither shasum nor sha256sum is available for checksum verification" >&2 exit 1 fi } # verify_checksum FILE NAME CHECKSUMS_FILE # # checksums.txt is the standard `shasum -a 256` format, one line per binary: # <64-hex-sha256> # (two spaces between the hash and the basename, as produced by build.sh and # published by release.sh to cli//checksums.txt). We look up the # expected hash for NAME and compare against the actual hash of FILE. verify_checksum() { _file="$1" _name="$2" _checksums="$3" echo " Verifying checksum..." # Extract the expected hash for this exact basename. Anchor the filename # at end-of-line so e.g. "ankos-linux-amd64" doesn't match a longer name. _expected="$(awk -v name="$_name" '$2 == name { print $1; exit }' "$_checksums")" if [ -z "$_expected" ]; then echo "Error: no checksum entry for $_name in checksums.txt" >&2 echo " Refusing to install an unverified binary." >&2 exit 1 fi _actual="$(sha256_of "$_file")" if [ "$_expected" != "$_actual" ]; then echo "Error: checksum mismatch for $_name" >&2 echo " expected: $_expected" >&2 echo " actual: $_actual" >&2 echo " Aborting install — the download may be corrupt or tampered." >&2 exit 1 fi echo " Checksum OK (sha256: $_actual)" } detect_os() { case "$(uname -s)" in Linux*) echo "linux" ;; Darwin*) echo "darwin" ;; *) echo "" ;; esac } detect_arch() { case "$(uname -m)" in x86_64|amd64) echo "amd64" ;; arm64|aarch64) echo "arm64" ;; *) echo "" ;; esac } main