#!/bin/sh set -eu REPOSITORY="${RIVO_REPOSITORY:-abcred-labs/rivo}" VERSION="${RIVO_INSTALL_VERSION:-latest}" INSTALL_ROOT="${RIVO_INSTALL_ROOT:-/usr/local/lib/rivo}" BIN_DIR="${RIVO_INSTALL_BIN_DIR:-}" BASE_URL="${RIVO_INSTALL_BASE_URL:-}" usage() { cat <<'EOF' Install the latest stable Rivo release. Options: --version VERSION Install VERSION instead of latest --install-root PATH Override the system install root --base-url URL Use a release mirror -h, --help Show this help EOF } while [ "$#" -gt 0 ]; do case "$1" in --version) [ "$#" -ge 2 ] || { echo "--version requires a value" >&2; exit 2; } VERSION="$2" shift 2 ;; --install-root) [ "$#" -ge 2 ] || { echo "--install-root requires a value" >&2; exit 2; } INSTALL_ROOT="$2" shift 2 ;; --base-url) [ "$#" -ge 2 ] || { echo "--base-url requires a value" >&2; exit 2; } BASE_URL="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) echo "unknown option: $1" >&2 usage >&2 exit 2 ;; esac done case "$(uname -s)" in Linux) PLATFORM=linux ;; Darwin) PLATFORM=macos ;; *) echo "unsupported operating system: $(uname -s)" >&2; exit 1 ;; esac case "$(uname -m)" in x86_64|amd64) ARCH=amd64 ;; arm64|aarch64) ARCH=arm64 ;; *) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;; esac if [ -z "$BASE_URL" ]; then if [ "$VERSION" = latest ]; then BASE_URL="https://github.com/$REPOSITORY/releases/latest/download" else BASE_URL="https://github.com/$REPOSITORY/releases/download/v$VERSION" fi MANIFEST_URL="$BASE_URL/release-manifest-$PLATFORM-$ARCH.json" MIRROR=0 else BASE_URL="${BASE_URL%/}" MANIFEST_URL="$BASE_URL/$PLATFORM-$ARCH/release-manifest.json" MIRROR=1 fi command -v curl >/dev/null 2>&1 || { echo "curl is required" >&2; exit 1; } command -v tar >/dev/null 2>&1 || { echo "tar is required" >&2; exit 1; } TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rivo-install.XXXXXX") trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM fetch() { curl --fail --silent --show-error --location --retry 3 "$1" -o "$2" } fetch "$MANIFEST_URL" "$TMP_DIR/manifest.json" field() { sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$TMP_DIR/manifest.json" | head -n 1 } MANIFEST_SCHEMA=$(sed -n 's/.*"schema"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$TMP_DIR/manifest.json" | head -n 1) MANIFEST_PRODUCT=$(field product) MANIFEST_VERSION=$(field version) MANIFEST_PLATFORM=$(field platform) MANIFEST_ARCH=$(field arch) MANIFEST_CHANNEL=$(field channel) ARTIFACT=$(field file) EXPECTED_SHA=$(field sha256) [ "$MANIFEST_SCHEMA" = 2 ] || { echo "unsupported release manifest schema: $MANIFEST_SCHEMA" >&2; exit 1; } [ "$MANIFEST_PRODUCT" = rivo ] || { echo "release manifest product is not rivo" >&2; exit 1; } [ "$MANIFEST_CHANNEL" = stable ] || { echo "release manifest is not a stable release" >&2; exit 1; } [ "$MANIFEST_PLATFORM" = "$PLATFORM" ] || { echo "release manifest platform mismatch" >&2; exit 1; } [ "$MANIFEST_ARCH" = "$ARCH" ] || { echo "release manifest architecture mismatch" >&2; exit 1; } printf '%s\n' "$MANIFEST_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || { echo "invalid release version: $MANIFEST_VERSION" >&2 exit 1 } [ "$VERSION" = latest ] || [ "$VERSION" = "$MANIFEST_VERSION" ] || { echo "requested version $VERSION does not match manifest $MANIFEST_VERSION" >&2 exit 1 } case "$ARTIFACT" in "rivo-$MANIFEST_VERSION-$PLATFORM-$ARCH.tar.gz") ;; *) echo "release artifact does not match the target: $ARTIFACT" >&2; exit 1 ;; esac if [ "$MIRROR" = 1 ]; then ARTIFACT_BASE="$BASE_URL/$PLATFORM-$ARCH" else ARTIFACT_BASE="$BASE_URL" fi ARCHIVE="$TMP_DIR/$ARTIFACT" CHECKSUM="$TMP_DIR/$ARTIFACT.sha256" fetch "$ARTIFACT_BASE/$ARTIFACT" "$ARCHIVE" fetch "$ARTIFACT_BASE/$ARTIFACT.sha256" "$CHECKSUM" if command -v sha256sum >/dev/null 2>&1; then ACTUAL_SHA=$(sha256sum "$ARCHIVE" | awk '{print $1}') else command -v shasum >/dev/null 2>&1 || { echo "sha256sum or shasum is required" >&2; exit 1; } ACTUAL_SHA=$(shasum -a 256 "$ARCHIVE" | awk '{print $1}') fi SIDECAR_SHA=$(awk 'NF {print $1; exit}' "$CHECKSUM") [ "${#EXPECTED_SHA}" -eq 64 ] || { echo "invalid manifest checksum" >&2; exit 1; } printf '%s\n' "$EXPECTED_SHA" | grep -Eq '^[0-9a-fA-F]{64}$' || { echo "invalid manifest checksum" >&2; exit 1; } [ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || { echo "manifest checksum mismatch" >&2; exit 1; } [ "$ACTUAL_SHA" = "$SIDECAR_SHA" ] || { echo "checksum sidecar mismatch" >&2; exit 1; } tar -xzf "$ARCHIVE" -C "$TMP_DIR" PAYLOAD="$TMP_DIR/rivo-$MANIFEST_VERSION-$PLATFORM-$ARCH" [ -x "$PAYLOAD/rivo" ] || { echo "archive does not contain an executable rivo" >&2; exit 1; } [ -f "$PAYLOAD/share/rivo/languages/en-US.ftl" ] || { echo "archive is missing language resources" >&2; exit 1; } case "$INSTALL_ROOT" in ""|/) echo "refusing unsafe install root: $INSTALL_ROOT" >&2; exit 1 ;; esac if [ -z "$BIN_DIR" ]; then if [ "$INSTALL_ROOT" = "/usr/local/lib/rivo" ]; then BIN_DIR=/usr/local/bin else BIN_DIR="$INSTALL_ROOT/bin" fi fi mkdir -p "$INSTALL_ROOT/versions" "$(dirname "$INSTALL_ROOT")" "$BIN_DIR" VERSION_DIR="$INSTALL_ROOT/versions/$MANIFEST_VERSION" if [ -e "$VERSION_DIR" ]; then rm -rf "$VERSION_DIR" fi mv "$PAYLOAD" "$VERSION_DIR" if [ -L "$INSTALL_ROOT/current" ]; then OLD_TARGET=$(readlink "$INSTALL_ROOT/current") ln -sfn "$OLD_TARGET" "$INSTALL_ROOT/previous.new" mv -f "$INSTALL_ROOT/previous.new" "$INSTALL_ROOT/previous" fi ln -sfn "versions/$MANIFEST_VERSION" "$INSTALL_ROOT/current.new" mv -f "$INSTALL_ROOT/current.new" "$INSTALL_ROOT/current" ln -sfn "$INSTALL_ROOT/current/rivo" "$BIN_DIR/rivo.new" mv -f "$BIN_DIR/rivo.new" "$BIN_DIR/rivo" echo "installed Rivo $MANIFEST_VERSION ($PLATFORM/$ARCH)" echo "run: rivo --version"