#!/bin/sh
# Weave CLI installer for macOS and Linux.
#
#   curl -fsSL https://weave-cli.com/install.sh | sh
#
# Installs `weave` and `weave-watcher` into $WEAVE_INSTALL_DIR (default
# ~/.local/bin). Never uses sudo.
#
# Environment:
#   WEAVE_VERSION        install this version instead of the latest (e.g. 0.1.0)
#   WEAVE_INSTALL_DIR    install directory (default: $HOME/.local/bin)
#   WEAVE_DOWNLOADS_URL  downloads base (default: https://weave-cli.com/downloads)
#
# The Weave CLI is source-available and free for non-commercial use under the
# PolyForm Noncommercial License 1.0.0: https://weave-cli.com/license

set -eu

DOWNLOADS_URL="${WEAVE_DOWNLOADS_URL:-https://weave-cli.com/downloads}"
DOWNLOADS_URL="${DOWNLOADS_URL%/}"

say() {
  printf '%s\n' "$*"
}

err() {
  printf 'weave-install: error: %s\n' "$*" >&2
  exit 1
}

have() {
  command -v "$1" >/dev/null 2>&1
}

# fetch URL DEST: download URL to DEST with curl or wget, following redirects.
fetch() {
  if have curl; then
    curl -fsSL --retry 3 -o "$2" "$1" || return 1
  elif have wget; then
    wget -q -O "$2" "$1" || return 1
  else
    err "need curl or wget to download Weave"
  fi
}

sha256_of() {
  if have sha256sum; then
    sha256sum "$1" | awk '{print $1}'
  elif have shasum; then
    shasum -a 256 "$1" | awk '{print $1}'
  else
    err "need sha256sum or shasum to verify the download"
  fi
}

detect_platform() {
  os="$(uname -s)"
  arch="$(uname -m)"
  case "$os" in
    Darwin)
      platform="macos-universal"
      ;;
    Linux)
      case "$arch" in
        x86_64 | amd64) platform="linux-x86_64" ;;
        aarch64 | arm64) platform="linux-arm64" ;;
        *) err "unsupported Linux architecture: $arch (prebuilt binaries exist for x86_64 and arm64)" ;;
      esac
      ;;
    MINGW* | MSYS* | CYGWIN* | Windows_NT)
      err "on Windows, run this in PowerShell instead: irm https://weave-cli.com/install.ps1 | iex"
      ;;
    *)
      err "unsupported operating system: $os (prebuilt binaries exist for macOS, Linux and Windows)"
      ;;
  esac
}

# json_flat FILE: the file's contents on one line.
json_flat() {
  tr -d '\n\r' <"$1"
}

main() {
  for tool in uname tar mkdir mktemp awk sed tr grep chmod mv cp rm; do
    have "$tool" || err "required command not found: $tool"
  done

  detect_platform

  tmp="$(mktemp -d 2>/dev/null || mktemp -d -t weave-install)"
  [ -n "$tmp" ] && [ -d "$tmp" ] || err "could not create a temporary directory"
  trap 'rm -rf "$tmp"' EXIT
  trap 'rm -rf "$tmp"; exit 130' INT
  trap 'rm -rf "$tmp"; exit 143' TERM

  expected=""
  if [ -n "${WEAVE_VERSION:-}" ]; then
    version="${WEAVE_VERSION#v}"
  else
    say "Finding the latest Weave release..."
    fetch "$DOWNLOADS_URL/latest.json" "$tmp/latest.json" ||
      err "could not download $DOWNLOADS_URL/latest.json"
    version="$(json_flat "$tmp/latest.json" |
      sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
    # The checksum for this platform, when latest.json lists it.
    expected="$(json_flat "$tmp/latest.json" |
      sed -n "s/.*\"$platform\"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p" |
      sed -n 's/.*"sha256"[[:space:]]*:[[:space:]]*"\([0-9a-fA-F]*\)".*/\1/p')"
  fi

  case "$version" in
    '' ) err "could not determine the version to install" ;;
    *[!0-9A-Za-z.+-]*) err "invalid version: $version" ;;
  esac

  name="weave-$version-$platform"
  archive="$name.tar.gz"

  if [ -z "$expected" ]; then
    fetch "$DOWNLOADS_URL/$version/SHA256SUMS" "$tmp/SHA256SUMS" ||
      err "could not download $DOWNLOADS_URL/$version/SHA256SUMS (is $version a released version?)"
    expected="$(awk -v f="$archive" '{n=$2; sub(/^\*/, "", n); if (n == f) {print $1; exit}}' "$tmp/SHA256SUMS")"
  fi
  [ -n "$expected" ] || err "no checksum published for $archive"

  say "Downloading Weave $version for $platform..."
  fetch "$DOWNLOADS_URL/$version/$archive" "$tmp/$archive" ||
    err "could not download $DOWNLOADS_URL/$version/$archive"

  actual="$(sha256_of "$tmp/$archive")"
  expected_lc="$(printf '%s' "$expected" | tr 'A-F' 'a-f')"
  if [ "$actual" != "$expected_lc" ]; then
    err "checksum mismatch for $archive (expected $expected_lc, got $actual)"
  fi
  say "Checksum verified."

  mkdir "$tmp/x"
  tar -xzf "$tmp/$archive" -C "$tmp/x" || err "could not extract $archive"
  src="$tmp/x/$name"
  for bin in weave weave-watcher; do
    [ -f "$src/$bin" ] || err "$archive does not contain $name/$bin"
  done

  install_dir="${WEAVE_INSTALL_DIR:-}"
  if [ -z "$install_dir" ]; then
    [ -n "${HOME:-}" ] || err "HOME is not set; set WEAVE_INSTALL_DIR"
    install_dir="$HOME/.local/bin"
  fi
  mkdir -p "$install_dir" || err "could not create $install_dir (set WEAVE_INSTALL_DIR to a directory you can write to)"
  [ -w "$install_dir" ] || err "$install_dir is not writable (set WEAVE_INSTALL_DIR to a directory you can write to)"

  for bin in weave weave-watcher; do
    # Copy then rename, so a running binary is replaced atomically.
    cp "$src/$bin" "$install_dir/.$bin.tmp.$$"
    chmod 755 "$install_dir/.$bin.tmp.$$"
    if [ "$platform" = "macos-universal" ] && have xattr; then
      xattr -d com.apple.quarantine "$install_dir/.$bin.tmp.$$" 2>/dev/null || true
    fi
    mv -f "$install_dir/.$bin.tmp.$$" "$install_dir/$bin"
  done

  say ""
  say "Installed Weave $version to $install_dir:"
  say "  $install_dir/weave"
  say "  $install_dir/weave-watcher"

  case ":${PATH:-}:" in
    *":$install_dir:"*) ;;
    *)
      say ""
      say "$install_dir is not on your PATH. Add it, for example:"
      say "  echo 'export PATH=\"$install_dir:\$PATH\"' >> ~/.profile"
      say "then open a new terminal (use ~/.zshrc or ~/.bashrc if your shell reads those)."
      ;;
  esac

  say ""
  say "Next steps:"
  say "  weave login    # paste a Personal Access Token from https://www.weave.directory"
  say "  weave init     # in your project: connect it to Weave (or --local to work offline)"
  say ""
  say "Docs: https://weave-cli.com/docs"
}

main "$@"
