#!/bin/sh
# The install script behind `curl -fsSL https://securechain.tuxcare.com/get/securechain | sh`
# (doc 08, "The install script" - the contract lives there, this file carries
# the reasons).
#
# It runs on a machine we know nothing about, so it assumes nothing: POSIX sh
# (a pipe into `sh` must not require bash), curl (the one-liner already used
# it, so it is there by construction), uname, and a sha256 tool - sha256sum on
# Linux, shasum on macOS, both checked. No sudo, ever: a script piped into a
# shell has no business escalating; when /usr/local/bin is not writable the
# binary goes to ~/.local/bin instead.
#
# Verification is layered the same way the release is: the checksum gates
# unconditionally - a mismatch aborts before anything is installed - and the
# cosign signature is verified when BOTH a cosign binary and a public key are
# at hand, because a customer must not need a tool to install a tool. The
# checksum protects against a corrupted or truncated download; the signature,
# when it runs, protects against the download source itself.
#
# Everything comes from one host, the public mirror the release pipeline
# fills (doc 08, "The public mirror"): <base>/latest/latest names the newest
# final release, <base>/<tag>/ holds its files. The GitLab project is
# internal and is never asked - a customer machine cannot reach it.
#
# Knobs, all optional:
#   SECURECHAIN_VERSION        vX.Y.Z to install; unset means the newest final
#                              release, read from <base>/latest/latest
#   SECURECHAIN_INSTALL_DIR    where the binary goes (default: /usr/local/bin,
#                              falling back to ~/.local/bin)
#   SECURECHAIN_DOWNLOAD_BASE  the mirror root (default:
#                              https://securechain.tuxcare.com/get) - a staging
#                              prefix or a private mirror goes here
#   SECURECHAIN_PUBKEY         path to the cosign public key; with cosign
#                              installed, turns signature verification on
#   SECURECHAIN_AUTH_HEADER    a curl header for a non-public mirror, e.g.
#                              "PRIVATE-TOKEN: ..." - internal testing only
set -eu

base="${SECURECHAIN_DOWNLOAD_BASE:-https://securechain.tuxcare.com/get}"
base="${base%/}"

say() { printf 'securechain install: %s\n' "$*"; }
die() { printf 'securechain install: ERROR: %s\n' "$*" >&2; exit 1; }

# Every download goes through here: -f so an HTTP error is an exit code and
# never a saved error page, and an explicit -o so the file lands under the
# name this script expects whatever the server calls it.
fetch() {
  if [ -n "${SECURECHAIN_AUTH_HEADER:-}" ]; then
    curl -fsSL --header "$SECURECHAIN_AUTH_HEADER" -o "$2" "$1"
  else
    curl -fsSL -o "$2" "$1"
  fi
}

os="$(uname -s)"
case "$os" in
  Linux)  os=linux ;;
  Darwin) os=darwin ;;
  *) die "unsupported OS: $os. This script installs the linux and macOS binaries; on Windows download securechain-windows-amd64.exe from https://securechain.tuxcare.com/get/, or use npm (npx @tuxcare/securechain) or PyPI (pipx install securechain)" ;;
esac
arch="$(uname -m)"
case "$arch" in
  x86_64|amd64)  arch=amd64 ;;
  aarch64|arm64) arch=arm64 ;;
  *) die "unsupported architecture: $arch. amd64 and arm64 are the tier 1 targets (doc 08); tier 3 is built on request" ;;
esac
artifact="securechain-$os-$arch"

workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT
cd "$workdir"

version="${SECURECHAIN_VERSION:-}"
if [ -z "$version" ]; then
  # The newest final release, as one line of text the release pipeline
  # writes. Only a final version ever moves it (doc 08): a customer who did
  # not pin never receives a release candidate.
  fetch "$base/latest/latest" latest \
    || die "cannot read $base/latest/latest; pin a version with SECURECHAIN_VERSION=vX.Y.Z"
  version="$(head -1 latest | tr -d '[:space:]')"
  [ -n "$version" ] || die "$base/latest/latest is empty"
fi
case "$version" in
  v[0-9]*) : ;;
  *) die "the version must look like v1.2.3, got: $version" ;;
esac

src="$base/$version"

say "downloading $artifact $version from $src"
fetch "$src/$artifact" "$artifact"
fetch "$src/checksums.txt" checksums.txt

# The checksum gate. Only this artifact's line is checked - the others name
# files that were deliberately not downloaded - and a missing line is the same
# refusal as a wrong hash: a checksums.txt that does not know this artifact
# is not the release's checksums.txt.
grep -E "[[:space:]]${artifact}\$" checksums.txt > checksum.line \
  || die "checksums.txt carries no line for $artifact - refusing to install"
if command -v sha256sum >/dev/null 2>&1; then
  sha256sum --check --strict checksum.line
elif command -v shasum >/dev/null 2>&1; then
  shasum -a 256 --check --strict checksum.line
else
  die "neither sha256sum nor shasum found - cannot verify the download, refusing to install"
fi

if [ -n "${SECURECHAIN_PUBKEY:-}" ] && command -v cosign >/dev/null 2>&1; then
  fetch "$src/$artifact.sig" "$artifact.sig"
  # --insecure-ignore-tlog skips the transparency-log LOOKUP, not the
  # signature check: releases sign with --tlog-upload=false (.goreleaser.yaml
  # has the policy note), so there is no Rekor entry to find.
  cosign verify-blob --key "$SECURECHAIN_PUBKEY" --signature "$artifact.sig" \
    --insecure-ignore-tlog "$artifact" \
    || die "cosign signature verification FAILED for $artifact"
  say "cosign signature verified"
fi

dir="${SECURECHAIN_INSTALL_DIR:-}"
if [ -z "$dir" ]; then
  if [ -d /usr/local/bin ] && [ -w /usr/local/bin ]; then
    dir=/usr/local/bin
  else
    dir="$HOME/.local/bin"
  fi
fi
mkdir -p "$dir"
cp "$artifact" "$dir/securechain"
chmod 0755 "$dir/securechain"
say "installed $dir/securechain"
case ":$PATH:" in
  *":$dir:"*) : ;;
  *) say "NOTE: $dir is not in your PATH" ;;
esac

# An install that cannot execute what it installed has failed, and must say so
# here rather than at the customer's first real use.
"$dir/securechain" version
