#!/bin/sh
# Bastion's installer:  curl https://getbastion.sh | sh
#
# Puts the `bastion` binary for this machine in ~/.local/bin, then has it
# install itself as a service — a LaunchAgent on macOS, a systemd user service
# on Linux — which starts the daemon and prints the pairing QR.
#
#   BASTION_INSTALL_DIR  where the binary goes (default ~/.local/bin)
#   BASTION_RELAY        a relay to dial out to; `bastion install` records it
#
# The first install trusts TLS and the checksum published beside the binary.
# Every update after it is checked by the daemon itself against the release
# key compiled into it, so a tampered binary is refused there.
#
# All in one function, so a download cut off halfway runs nothing at all.

main() {
    set -eu
    cdn="${BASTION_CDN:-https://cdn.getbastion.sh}"

    case "$(uname -s)" in
        Darwin) os=macos ;;
        Linux) os=linux ;;
        *) fail "there is no build for $(uname -s); macOS and Linux only" ;;
    esac
    case "$(uname -m)" in
        arm64 | aarch64) arch=arm64 ;;
        x86_64 | amd64) arch=amd64 ;;
        *) fail "there is no build for $(uname -m); arm64 and amd64 only" ;;
    esac
    target="$os-$arch"
    command -v curl >/dev/null 2>&1 || fail "curl is needed"

    # One line per binary, exactly as the release workflow writes it
    # (.github/workflows/daemon.yaml, publish), so no JSON parser is needed:
    #   "macos-arm64": {"path": "releases/42/bastion-macos-arm64", "sha256": "…", "signature": "…"},
    # Change the one and the other changes with it.
    latest=$(curl -fsSL "$cdn/releases/latest.json") || fail "could not reach $cdn"
    line=$(printf '%s\n' "$latest" | grep "\"$target\":" || true)
    [ -n "$line" ] || fail "the latest release has no $target build"
    path=$(field path)
    sum=$(field sha256)
    build=$(printf '%s\n' "$latest" | sed -n 's/^ *"build": *\([0-9][0-9]*\).*/\1/p')
    [ -n "$path" ] && [ -n "$sum" ] || fail "the latest release is not in the shape this script reads"

    dir="${BASTION_INSTALL_DIR:-$HOME/.local/bin}"
    mkdir -p "$dir"
    # Beside where it lands, so the move is a rename: a binary a running
    # daemon holds is replaced, not written over.
    tmp=$(mktemp "$dir/.bastion.XXXXXX")
    trap 'rm -f "$tmp"' EXIT
    say "Downloading bastion build $build for $target"
    curl -fsSL -o "$tmp" "$cdn/$path" || fail "the download failed"
    [ "$(checksum "$tmp")" = "$sum" ] || fail "the download does not match the published checksum"
    chmod 755 "$tmp"
    mv -f "$tmp" "$dir/bastion"
    trap - EXIT
    say "Installed $dir/bastion"

    case ":$PATH:" in
        *":$dir:"*) ;;
        *)
            say "$dir is not on your PATH. Add it with:"
            # shellcheck disable=SC2016 # $PATH is for the reader's shell, not this one.
            printf '    export PATH="%s:$PATH"\n' "$dir"
            ;;
    esac

    # Not the pipe this script came in on: nothing of it is for the daemon.
    "$dir/bastion" install </dev/null
}

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

fail() {
    printf 'bastion: %s\n' "$1" >&2
    exit 1
}

field() { printf '%s\n' "$line" | sed -n "s/.*\"$1\": *\"\([^\"]*\)\".*/\1/p"; }

checksum() {
    if command -v sha256sum >/dev/null 2>&1; then
        sha256sum "$1" | cut -d' ' -f1
    else
        shasum -a 256 "$1" | cut -d' ' -f1
    fi
}

main "$@"
