#!/usr/bin/env bash # Install a verified standalone SZCI release with its license and third-party notices. set -euo pipefail VERSION="" INSTALL_DIR="/opt/szci" # The platform floors of the Node.js 26 executables. MINIMUM_GLIBC=2.28 MINIMUM_MACOS=13.5 # True when version $1 is older than version $2 (dot-separated numbers). version_below() { local IFS=. index local -a version=($1) minimum=($2) for index in 0 1 2; do if (( 10#${version[index]:-0} < 10#${minimum[index]:-0} )); then return 0; fi if (( 10#${version[index]:-0} > 10#${minimum[index]:-0} )); then return 1; fi done return 1 } while [[ $# -gt 0 ]]; do case "$1" in -h|--help) echo "Usage: install.sh [--version vVERSION] [--install-dir DIR]" echo "Installs the platform standalone binary, licenses, third-party notices and source manifest." echo "Requires GNU libc $MINIMUM_GLIBC on Linux or macOS $MINIMUM_MACOS." echo "On musl Linux, install @ship.zone/szci with pnpm on Node.js 22 or newer instead." exit 0 ;; --version|--install-dir) if [[ $# -lt 2 || -z "$2" ]]; then echo "Missing value for $1" >&2; exit 1; fi if [[ "$1" == --version ]]; then VERSION="$2"; else INSTALL_DIR="$2"; fi shift 2 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac done if [[ "$EUID" -ne 0 ]]; then echo "Run this installer as root." >&2; exit 1; fi case "$(uname -s)" in Linux) PLATFORM=linux if ! GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null); then echo "The standalone Linux binary requires GNU libc. On musl, install @ship.zone/szci with pnpm on Node.js 22 or newer." >&2 exit 1 fi GLIBC_VERSION=${GLIBC_VERSION#glibc } if [[ ! "$GLIBC_VERSION" =~ ^[0-9]+(\.[0-9]+)*$ ]] || version_below "$GLIBC_VERSION" "$MINIMUM_GLIBC"; then echo "The standalone Linux binary requires GNU libc $MINIMUM_GLIBC or newer; found $GLIBC_VERSION." >&2 exit 1 fi ;; Darwin) PLATFORM=macos MACOS_VERSION=$(sw_vers -productVersion) if [[ ! "$MACOS_VERSION" =~ ^[0-9]+(\.[0-9]+)*$ ]] || version_below "$MACOS_VERSION" "$MINIMUM_MACOS"; then echo "The standalone macOS binary requires macOS $MINIMUM_MACOS or newer; found $MACOS_VERSION." >&2 exit 1 fi ;; MINGW*|MSYS*|CYGWIN*) PLATFORM=windows ;; *) echo "Unsupported operating system for standalone installation." >&2; exit 1 ;; esac case "$(uname -m)" in x86_64|amd64) ARCH=x64 ;; aarch64|arm64) ARCH=arm64 ;; *) echo "Unsupported CPU architecture." >&2; exit 1 ;; esac if [[ "$PLATFORM" == windows && "$ARCH" != x64 ]]; then echo "Windows ARM64 is not a published target." >&2; exit 1; fi BINARY="szci-$PLATFORM-$ARCH" if [[ "$PLATFORM" == windows ]]; then BINARY="$BINARY.exe"; fi BASE="https://code.foss.global/ship.zone/szci/releases/download" if [[ -z "$VERSION" ]]; then RESPONSE=$(curl --fail --silent --show-error --location 'https://code.foss.global/api/v1/repos/ship.zone/szci/releases/latest') VERSION=$(printf '%s\n' "$RESPONSE" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') fi if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Invalid release version." >&2; exit 1; fi LEGAL="szci-$VERSION-legal.tar.gz" SOURCE_MANIFEST="szci-$VERSION-source-manifest.json" STAGE=$(mktemp -d) cleanup() { for name in "$BINARY" "$LEGAL" "$SOURCE_MANIFEST" SHA256SUMS.txt; do if [[ -f "$STAGE/$name" ]]; then rm -- "$STAGE/$name"; fi done rmdir -- "$STAGE" } trap cleanup EXIT for name in "$BINARY" "$LEGAL" "$SOURCE_MANIFEST" SHA256SUMS.txt; do curl --fail --silent --show-error --location "$BASE/$VERSION/$name" --output "$STAGE/$name" done verify() { local name=$1 expected actual expected=$(awk -v name="$name" '$2 == name { print $1 }' "$STAGE/SHA256SUMS.txt") if [[ ! "$expected" =~ ^[0-9a-f]{64}$ ]]; then echo "Missing or duplicate checksum: $name" >&2; exit 1; fi if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$STAGE/$name" | awk '{ print $1 }') else actual=$(shasum -a 256 "$STAGE/$name" | awk '{ print $1 }') fi if [[ "$actual" != "$expected" ]]; then echo "Checksum mismatch: $name" >&2; exit 1; fi } verify "$BINARY" verify "$LEGAL" verify "$SOURCE_MANIFEST" mkdir -p "$INSTALL_DIR" tar -xzf "$STAGE/$LEGAL" -C "$INSTALL_DIR" --strip-components=1 cp "$STAGE/$SOURCE_MANIFEST" "$INSTALL_DIR/corresponding-source.json" install -m 755 "$STAGE/$BINARY" "$INSTALL_DIR/szci" if [[ ":$PATH:" == *":/usr/local/bin:"* ]]; then BIN_DIR=/usr/local/bin; else BIN_DIR=/usr/bin; fi ln -sf "$INSTALL_DIR/szci" "$BIN_DIR/szci" echo "Installed SZCI $VERSION at $INSTALL_DIR/szci" echo "The license, third-party notices and source manifest are retained at $INSTALL_DIR."