#!/bin/sh # Install only the cross-platform firecrab client into a user-writable directory. set -eu RELEASE_BASE=${FIRECRAB_RELEASE_BASE:-https://github.com/SteelCrab/firecrab/releases} VERSION=${FIRECRAB_VERSION:-latest} INSTALL_DIR=${FIRECRAB_INSTALL_DIR:-} CHECK=0 PRINT_ASSET=0 PRINT_URL=0 UNINSTALL=0 PURGE=0 usage() { cat <<'EOF' Usage: install-cli.sh [options] Install the firecrab client without root, Cargo, or the Linux host services. Options: --version VER Release tag such as v0.4.1 (default: latest) --install-dir DIR Destination directory (default: ~/.local/bin) --uninstall Remove the standalone firecrab client --purge With --uninstall, also remove saved host configuration --check Print the selected asset and destination without changes --print-asset Print the selected release asset name --print-url Print the selected release asset URL -h, --help Show this help Environment: FIRECRAB_RELEASE_BASE Alternate release root for mirrors or tests FIRECRAB_TEST_ALLOW_FILE_URL=1 Permit file:// release roots in tests only FIRECRAB_VERSION Default release tag FIRECRAB_INSTALL_DIR Default destination directory FIRECRAB_CONFIG_DIR Configuration directory (default: ~/.firecrab) FIRECRAB_CLI_LIBC Linux libc override: gnu, glibc, or musl EOF } while [ "$#" -gt 0 ]; do case "$1" in --version) [ "$#" -ge 2 ] || { printf '%s\n' '--version needs a value' >&2; exit 2; } VERSION=$2 shift 2 ;; --install-dir) [ "$#" -ge 2 ] || { printf '%s\n' '--install-dir needs a value' >&2; exit 2; } INSTALL_DIR=$2 shift 2 ;; --uninstall) UNINSTALL=1; shift ;; --purge) PURGE=1; shift ;; --check) CHECK=1; shift ;; --print-asset) PRINT_ASSET=1; shift ;; --print-url) PRINT_URL=1; shift ;; -h|--help) usage; exit 0 ;; *) printf 'unknown option: %s\n' "$1" >&2; usage >&2; exit 2 ;; esac done [ "$PURGE" -eq 0 ] || [ "$UNINSTALL" -eq 1 ] \ || { printf '%s\n' '--purge requires --uninstall' >&2; exit 2; } if [ -z "$INSTALL_DIR" ]; then [ -n "${HOME:-}" ] || { printf '%s\n' 'HOME is not set; pass --install-dir or FIRECRAB_INSTALL_DIR' >&2; exit 1; } INSTALL_DIR="$HOME/.local/bin" fi if [ "$UNINSTALL" -eq 1 ]; then for name in firecrab firecrab-micromanager-macos; do binary="$INSTALL_DIR/$name" if [ -d "$binary" ]; then printf 'cannot uninstall: %s is a directory\n' "$binary" >&2 exit 1 fi if [ -e "$binary" ] || [ -L "$binary" ]; then rm -f "$binary" printf 'removed %s from %s\n' "$name" "$binary" elif [ "$name" = firecrab ]; then printf 'firecrab CLI is not installed at %s\n' "$binary" fi done if [ "$PURGE" -eq 1 ]; then if [ -n "${FIRECRAB_CONFIG_DIR:-}" ]; then config_dir=$FIRECRAB_CONFIG_DIR else [ -n "${HOME:-}" ] || { printf '%s\n' 'HOME is not set; set FIRECRAB_CONFIG_DIR to purge configuration' >&2; exit 1; } config_dir="$HOME/.firecrab" fi config="$config_dir/config.toml" if [ -e "$config" ] || [ -L "$config" ]; then rm -f "$config" printf 'removed saved hosts from %s\n' "$config" fi rmdir "$config_dir" 2>/dev/null || true fi exit 0 fi RELEASE_BASE=${RELEASE_BASE%/} case "$RELEASE_BASE" in https://?*) ;; file://?*) [ "${FIRECRAB_TEST_ALLOW_FILE_URL:-}" = 1 ] \ || { printf '%s\n' 'file:// release roots require FIRECRAB_TEST_ALLOW_FILE_URL=1' >&2; exit 1; } ;; *) printf '%s\n' 'FIRECRAB_RELEASE_BASE must use HTTPS' >&2 exit 1 ;; esac raw_os=${FIRECRAB_CLI_OS:-$(uname -s 2>/dev/null || printf unknown)} case "$raw_os" in Linux|linux) platform=linux ;; Darwin|darwin|macOS|macos) platform=macos ;; *) printf 'unsupported client OS: %s\n' "$raw_os" >&2; exit 1 ;; esac raw_arch=${FIRECRAB_CLI_ARCH:-$(uname -m 2>/dev/null || printf unknown)} case "$raw_arch" in x86_64|amd64|X64) architecture=x86_64 ;; aarch64|arm64|Arm64) architecture=aarch64 ;; *) printf 'unsupported client architecture: %s\n' "$raw_arch" >&2; exit 1 ;; esac case "$platform" in linux) raw_libc=${FIRECRAB_CLI_LIBC:-} if [ -z "$raw_libc" ]; then if [ -e /lib/ld-musl-x86_64.so.1 ] || [ -e /lib/ld-musl-aarch64.so.1 ] \ || { command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; }; then raw_libc=musl else raw_libc=gnu fi fi case "$raw_libc" in gnu|glibc) libc=gnu ;; musl) libc=musl ;; *) printf 'unsupported Linux libc: %s\n' "$raw_libc" >&2; exit 1 ;; esac asset="firecrab-cli-${architecture}-linux-${libc}.tar.gz" ;; macos) [ "$architecture" = aarch64 ] \ || { printf '%s\n' 'unsupported macOS client architecture: x86_64' >&2; exit 1; } asset="firecrab-cli-aarch64-macos.tar.gz" ;; esac case "$VERSION" in ''|latest) download_root="$RELEASE_BASE/latest/download" ;; v*) download_root="$RELEASE_BASE/download/$VERSION" ;; *) download_root="$RELEASE_BASE/download/v$VERSION" ;; esac asset_url="$download_root/$asset" [ "$PRINT_ASSET" -eq 0 ] || { printf '%s\n' "$asset"; exit 0; } [ "$PRINT_URL" -eq 0 ] || { printf '%s\n' "$asset_url"; exit 0; } if [ "$CHECK" -eq 1 ]; then printf 'would install %s to %s/firecrab\n' "$asset_url" "$INSTALL_DIR" if [ "$platform" = macos ]; then printf 'would install the signed microManager helper to %s/firecrab-micromanager-macos\n' "$INSTALL_DIR" fi exit 0 fi command -v curl >/dev/null 2>&1 || { printf '%s\n' 'curl is required' >&2; exit 1; } command -v tar >/dev/null 2>&1 || { printf '%s\n' 'tar is required' >&2; exit 1; } temporary=$(mktemp -d 2>/dev/null || mktemp -d -t firecrab-cli) temporary_binary= temporary_helper= cleanup() { rm -rf "$temporary" [ -z "$temporary_binary" ] || rm -f "$temporary_binary" [ -z "$temporary_helper" ] || rm -f "$temporary_helper" } trap cleanup EXIT HUP INT TERM curl -fsSL "$asset_url" -o "$temporary/$asset" curl -fsSL "$download_root/SHA256SUMS" -o "$temporary/SHA256SUMS" expected=$(awk -v name="$asset" ' $2 == name || $2 == "*" name || $2 ~ "/" name "$" { print $1; exit } ' "$temporary/SHA256SUMS") [ -n "$expected" ] || { printf 'checksum missing for %s\n' "$asset" >&2; exit 1; } if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$temporary/$asset" | awk '{ print $1 }') elif command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 256 "$temporary/$asset" | awk '{ print $1 }') else printf '%s\n' 'sha256sum or shasum is required' >&2 exit 1 fi [ "$actual" = "$expected" ] || { printf 'checksum mismatch for %s\n' "$asset" >&2; exit 1; } mkdir -p "$temporary/unpacked" "$INSTALL_DIR" INSTALL_DIR=$(cd "$INSTALL_DIR" && pwd -P) tar -xzf "$temporary/$asset" -C "$temporary/unpacked" [ -f "$temporary/unpacked/firecrab" ] \ || { printf '%s\n' 'release archive does not contain firecrab' >&2; exit 1; } if [ "$platform" = macos ]; then [ -f "$temporary/unpacked/firecrab-micromanager-macos" ] \ || { printf '%s\n' 'macOS release archive does not contain firecrab-micromanager-macos' >&2; exit 1; } fi [ ! -d "$INSTALL_DIR/firecrab" ] \ || { printf '%s\n' 'install destination firecrab is a directory' >&2; exit 1; } [ "$platform" != macos ] || [ ! -d "$INSTALL_DIR/firecrab-micromanager-macos" ] \ || { printf '%s\n' 'install destination firecrab-micromanager-macos is a directory' >&2; exit 1; } temporary_binary=$(mktemp "$INSTALL_DIR/.firecrab-install.XXXXXX") \ || { printf '%s\n' 'could not create a private temporary install file' >&2; exit 1; } cp "$temporary/unpacked/firecrab" "$temporary_binary" chmod 0755 "$temporary_binary" if [ "$platform" = macos ]; then temporary_helper=$(mktemp "$INSTALL_DIR/.firecrab-micromanager-install.XXXXXX") \ || { printf '%s\n' 'could not create a private temporary helper install file' >&2; exit 1; } cp "$temporary/unpacked/firecrab-micromanager-macos" "$temporary_helper" chmod 0755 "$temporary_helper" mv -f "$temporary_helper" "$INSTALL_DIR/firecrab-micromanager-macos" temporary_helper= fi mv -f "$temporary_binary" "$INSTALL_DIR/firecrab" temporary_binary= printf 'installed firecrab to %s/firecrab\n' "$INSTALL_DIR" if [ "$platform" = macos ]; then printf 'installed microManager helper to %s/firecrab-micromanager-macos\n' "$INSTALL_DIR" fi case ":${PATH:-}:" in *:"$INSTALL_DIR":*) ;; *) printf '%s\n' "$INSTALL_DIR is not on PATH. Add this line to your shell profile:" printf ' export PATH="%s:%s"\n' "$INSTALL_DIR" "\$PATH" ;; esac