#!/bin/sh
# Installs the keal command-line client, from a Keal engine or from the
# published releases:
#
#   curl -fsSL https://your-engine/static/cli/install.sh | sh
#   curl -fsSL https://registry.keal.sh/downloads/install.sh | sh
#
# The engine fills in its own address as it serves this (see install_script
# in api/main.py), so it fetches from wherever it came from. A copy that did
# not come from an engine fetches the latest release, whose checksums are
# signed - and checked, where openssl is there to check them with.
#
# Settings (environment):
#   KEAL_ENGINE        the engine to fetch from, e.g. https://keal.example (default: the one serving this)
#   KEAL_CLI_BASE_URL  or the exact place the archives are, if not <engine>/static/cli
#   KEAL_VERSION       the release to fetch, with no engine (default: the latest)
#   KEAL_DOWNLOADS     where releases are (default: https://registry.keal.sh/downloads)
#   KEAL_INSTALL_DIR   where to put it (default: /usr/local/bin if writable, else ~/.local/bin)
#   KEAL_INSECURE=1    do not check the engine's TLS certificate
set -eu

SERVED_BY=""
KEAL_ENGINE="${KEAL_ENGINE:-$SERVED_BY}"

# The key the published releases are signed with (support/keal-release.pub).
PUBKEY='-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAKFe927p361EbcxfMWDmsntRN0zdslwOZUUrWapO3jGk=
-----END PUBLIC KEY-----'

BASE="${KEAL_CLI_BASE_URL:-}"
SUMS=checksums.txt
if [ -z "$BASE" ] && [ -n "${KEAL_ENGINE:-}" ]; then
    BASE="${KEAL_ENGINE%/}/static/cli"
fi
if [ -z "$BASE" ]; then
    DOWNLOADS="${KEAL_DOWNLOADS:-https://registry.keal.sh/downloads}"
    DOWNLOADS="${DOWNLOADS%/}"
    version="${KEAL_VERSION:-}"
    if [ -z "$version" ]; then
        version="$(curl -fsSL "$DOWNLOADS/latest" | tr -d '[:space:]')" || version=""
    fi
    case "$version" in
        v[0-9]*) ;;
        *) echo "Could not tell the latest release from ${DOWNLOADS}/latest" >&2; exit 1 ;;
    esac
    BASE="$DOWNLOADS/$version"
    SUMS=SHA256SUMS
fi

# The archive is checked against checksums.txt either way, but with the
# certificate unchecked that file could itself have been swapped on the
# way: only on a network you trust.
CURL_INSECURE=""
if [ "${KEAL_INSECURE:-}" = "1" ]; then
    CURL_INSECURE="-k"
    echo "WARNING: KEAL_INSECURE=1 - not checking the engine's TLS certificate." >&2
fi

os="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$os" in
    linux|darwin) ;;
    *) echo "No installer for '$os'. Fetch an archive by hand from ${BASE}" >&2; exit 1 ;;
esac

arch="$(uname -m)"
case "$arch" in
    x86_64|amd64)  arch=amd64 ;;
    aarch64|arm64) arch=arm64 ;;
    *) echo "No build for '$arch'." >&2; exit 1 ;;
esac

archive="keal_${os}_${arch}.tar.gz"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

echo "Fetching ${archive} from ${BASE}"
curl -fsSL $CURL_INSECURE -o "$tmp/$archive" "$BASE/$archive"
curl -fsSL $CURL_INSECURE -o "$tmp/sums" "$BASE/$SUMS"

# A release's checksums are signed: checked, if there is an openssl here that
# can (macOS's own, LibreSSL, has no -rawin for ed25519).
if [ "$SUMS" = SHA256SUMS ]; then
    if command -v openssl >/dev/null 2>&1 && openssl pkeyutl -help 2>&1 | grep -q -- -rawin; then
        curl -fsSL -o "$tmp/sums.sig" "$BASE/SHA256SUMS.sig"
        printf '%s\n' "$PUBKEY" > "$tmp/release.pub"
        if ! openssl pkeyutl -verify -pubin -inkey "$tmp/release.pub" -rawin \
                -in "$tmp/sums" -sigfile "$tmp/sums.sig" >/dev/null 2>&1; then
            echo "The release's signature does not check out - not installing it." >&2
            exit 1
        fi
    else
        echo "Note: no openssl here that checks ed25519 signatures, so only the checksum is checked." >&2
    fi
fi

want="$(grep " ${archive}\$" "$tmp/sums" | awk '{print $1}')"
if command -v sha256sum >/dev/null 2>&1; then
    got="$(sha256sum "$tmp/$archive" | awk '{print $1}')"
else
    got="$(shasum -a 256 "$tmp/$archive" | awk '{print $1}')"
fi
if [ -z "$want" ] || [ "$want" != "$got" ]; then
    echo "The archive does not match its checksum - not installing it." >&2
    exit 1
fi

tar -xzf "$tmp/$archive" -C "$tmp"

dir="${KEAL_INSTALL_DIR:-}"
if [ -z "$dir" ]; then
    if [ -w /usr/local/bin ]; then dir=/usr/local/bin; else dir="$HOME/.local/bin"; fi
fi
mkdir -p "$dir"
install -m 0755 "$tmp/keal" "$dir/keal"

echo "Installed $("$dir/keal" version) to $dir/keal"
case ":$PATH:" in
    *":$dir:"*) ;;
    *) echo "Note: $dir is not on your PATH." ;;
esac
# The engine this came from is the one to talk to, unless keal already
# talks to another (it says so). A build older than 'context init' answers
# with the help for 'context', and still exits 0 - so what it said is what
# tells: every answer of the real one starts "keal ".
said=""
if [ -n "${KEAL_ENGINE:-}" ]; then
    said="$("$dir/keal" context init "$KEAL_ENGINE" 2>&1)" || said=""
fi
case "$said" in
    "keal will talk to "*)
        echo "$said"
        echo "Next: keal login" ;;
    "keal already talks to "*)
        echo "$said" ;;
    *)
        echo "Next: keal login --url ${KEAL_ENGINE:-https://your-engine}" ;;
esac
