#!/bin/sh
# TaskCloud CLI installer.
#
#   curl -fsSL https://task.cloud/install.sh | sh
#
# Downloads a prebuilt `taskcloud` binary from GitHub Releases (tags cli/vX.Y.Z of taskcloud-dev/taskcloud),
# verifies its SHA-256 against the release's checksums.txt and installs it.
#
# Environment variables (all optional):
#   TASKCLOUD_VERSION       version to install, e.g. 0.2.0 (default: latest stable cli/v* release)
#   TASKCLOUD_INSTALL_DIR   target directory (default: /usr/local/bin as root, otherwise ~/.local/bin)
#   TASKCLOUD_REPO          GitHub repository (default: taskcloud-dev/taskcloud)
#   TASKCLOUD_DOWNLOAD_URL  base URL with the release assets, overrides GitHub (mirrors, testing);
#                       expects <base>/checksums.txt and <base>/taskcloud_<ver>_<os>_<arch>.tar.gz
#   GITHUB_TOKEN        optional, raises the GitHub API rate limit when resolving "latest"
#
# Supported: Linux and macOS on amd64 (x86_64) and arm64 (aarch64). Needs curl or wget, tar,
# and sha256sum or shasum. Signatures: see the release notes for cosign / gh attestation commands.
set -eu

# Everything runs inside main(), so a truncated download never executes half a script.
main() {
  repo=${TASKCLOUD_REPO:-taskcloud-dev/taskcloud}
  version=${TASKCLOUD_VERSION:-}
  version=${version#v}

  need tar
  if command -v curl >/dev/null 2>&1; then dl=curl
  elif command -v wget >/dev/null 2>&1; then dl=wget
  else die "curl or wget is required"; fi

  os=$(uname -s)
  case "$os" in
    Linux) os=linux ;;
    Darwin) os=darwin ;;
    *) die "unsupported OS: $os (prebuilt binaries exist for Linux and macOS; on other systems build from source: cd cli && go build)" ;;
  esac
  arch=$(uname -m)
  case "$arch" in
    x86_64 | amd64) arch=amd64 ;;
    aarch64 | arm64) arch=arm64 ;;
    *) die "unsupported architecture: $arch (supported: amd64, arm64)" ;;
  esac
  # Rosetta: a shell running as x86_64 on Apple silicon should still get the native binary.
  if [ "$os" = darwin ] && [ "$arch" = amd64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
    arch=arm64
  fi

  if [ -z "$version" ]; then
    if [ -n "${TASKCLOUD_DOWNLOAD_URL:-}" ]; then die "set TASKCLOUD_VERSION when using TASKCLOUD_DOWNLOAD_URL"; fi
    say "resolving the latest taskcloud CLI release of $repo"
    version=$(latest_version "$repo") || true
    [ -n "$version" ] || die "no stable CLI release (tag cli/vX.Y.Z) found in $repo yet; set TASKCLOUD_VERSION or build from source"
  fi
  case "$version" in
    *[!0-9A-Za-z.+-]*) die "invalid version: $version" ;;
  esac

  if [ -n "${TASKCLOUD_DOWNLOAD_URL:-}" ]; then
    base=${TASKCLOUD_DOWNLOAD_URL%/}
  else
    # The tag contains a slash (cli/v1.2.3); GitHub expects it URL-encoded in download links.
    base="https://github.com/$repo/releases/download/cli%2Fv$version"
  fi
  archive="taskcloud_${version}_${os}_${arch}.tar.gz"

  tmp=$(mktemp -d 2>/dev/null || mktemp -d -t taskcloud-install)
  trap 'rm -rf "$tmp"' EXIT INT TERM HUP

  say "downloading taskcloud $version ($os/$arch)"
  fetch "$base/$archive" "$tmp/$archive" || die "download failed: $base/$archive"
  fetch "$base/checksums.txt" "$tmp/checksums.txt" || die "download failed: $base/checksums.txt"

  expected=$(awk -v f="$archive" '$2 == f || $2 == "*" f { print $1; exit }' "$tmp/checksums.txt")
  [ -n "$expected" ] || die "$archive is not listed in checksums.txt"
  actual=$(sha256 "$tmp/$archive")
  if [ "$expected" != "$actual" ]; then
    die "checksum mismatch for $archive (expected $expected, got $actual)"
  fi
  say "checksum OK (sha256 $actual)"

  tar -xzf "$tmp/$archive" -C "$tmp" taskcloud || die "could not extract taskcloud from $archive"
  [ -f "$tmp/taskcloud" ] || die "archive does not contain the taskcloud binary"
  chmod 0755 "$tmp/taskcloud"

  dir=$(install_dir)
  if [ ! -d "$dir" ]; then
    mkdir -p "$dir" 2>/dev/null || die "cannot create $dir (set TASKCLOUD_INSTALL_DIR to a writable directory)"
  fi
  if [ ! -w "$dir" ]; then
    die "$dir is not writable; re-run as root (curl -fsSL https://task.cloud/install.sh | sudo sh) or set TASKCLOUD_INSTALL_DIR=\$HOME/.local/bin"
  fi
  # Replace atomically so a running taskcloud is not corrupted.
  cp "$tmp/taskcloud" "$dir/.taskcloud.new.$$"
  mv -f "$dir/.taskcloud.new.$$" "$dir/taskcloud"
  if [ "$os" = darwin ]; then
    xattr -d com.apple.quarantine "$dir/taskcloud" 2>/dev/null || true
  fi

  say "installed $dir/taskcloud"
  "$dir/taskcloud" version 2>/dev/null || true
  case ":$PATH:" in
    *":$dir:"*) ;;
    *) say "note: $dir is not in your PATH; add it, e.g.: export PATH=\"$dir:\$PATH\"" ;;
  esac
  say "next: taskcloud login"
}

say() { printf 'taskcloud-install: %s\n' "$*" >&2; }
die() { say "error: $*"; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required"; }

fetch() { # url dest
  case "$1" in
    https://*) proto="=https" ;;
    *) proto="all" ;; # only reachable through an explicit TASKCLOUD_DOWNLOAD_URL (mirror, local test)
  esac
  if [ "$dl" = curl ]; then
    curl -fsSL --proto "$proto" --retry 3 -o "$2" "$1"
  else
    wget -q -O "$2" "$1"
  fi
}

fetch_api() { # url -> stdout
  if [ "$dl" = curl ]; then
    if [ -n "${GITHUB_TOKEN:-}" ]; then
      curl -fsSL --proto '=https' -H "Authorization: Bearer $GITHUB_TOKEN" -H 'Accept: application/vnd.github+json' "$1"
    else
      curl -fsSL --proto '=https' -H 'Accept: application/vnd.github+json' "$1"
    fi
  else
    if [ -n "${GITHUB_TOKEN:-}" ]; then
      wget -q -O - --header="Authorization: Bearer $GITHUB_TOKEN" "$1"
    else
      wget -q -O - "$1"
    fi
  fi
}

latest_version() { # repo -> X.Y.Z of the newest stable cli/v* release
  # The monorepo also tags other components, so /releases/latest is not enough. Releases are
  # listed newest first; SemVer pre-releases contain "-" and are skipped.
  fetch_api "https://api.github.com/repos/$1/releases?per_page=100" |
    grep -o '"tag_name": *"cli/v[0-9][0-9.]*"' |
    head -n 1 |
    sed 's/.*"cli\/v\([0-9.]*\)"/\1/'
}

sha256() {
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
  elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 "$1" | awk '{print $NF}'
  else die "sha256sum, shasum or openssl is required to verify the download"; fi
}

install_dir() {
  if [ -n "${TASKCLOUD_INSTALL_DIR:-}" ]; then echo "$TASKCLOUD_INSTALL_DIR"
  elif [ "$(id -u)" = 0 ]; then echo /usr/local/bin
  else echo "${HOME:?HOME is not set}/.local/bin"; fi
}

main "$@"
