#!/usr/bin/env sh set -eu repo="sophotechlabs/spinoza" releases="https://github.com/$repo/releases" main() { detect_platform dir="$(install_dir)" if [ -n "${SPINOZA_UNINSTALL:-}" ]; then uninstall return 0 fi detect_downloader version="${SPINOZA_VERSION:-}" if [ -z "$version" ]; then version="$(resolve_latest)" fi asset="spinoza_${version}_${os}_${arch}.tar.gz" make_temp trap cleanup EXIT INT TERM echo "Downloading spinoza $version ($os/$arch)" download "$releases/download/$version/$asset" "$temp/$asset" download "$releases/download/$version/checksums.txt" "$temp/checksums.txt" verify "$temp/$asset" "$asset" attest "$temp/$asset" "$asset" tar -xzf "$temp/$asset" -C "$temp" if [ ! -f "$temp/spinoza" ]; then die "the archive did not contain a spinoza binary" fi previous="" if [ -x "$dir/spinoza" ]; then previous="$("$dir/spinoza" --version 2>/dev/null || true)" fi mkdir -p "$dir" install -m 0755 "$temp/spinoza" "$dir/spinoza" install_copyright if [ -n "$previous" ]; then echo "Updated spinoza $previous -> $version in $dir" else echo "Installed spinoza $version in $dir" fi app_dir="" if [ -z "${SPINOZA_SKIP_APP:-}" ]; then if [ "$os" = "darwin" ]; then install_app fi if [ "$os" = "linux" ]; then install_linux_app fi fi report_path "$dir" report_app } uninstall() { removed="" for name in spinoza Spinoza; do if [ -e "$dir/$name" ]; then rm -f "$dir/$name" removed="$removed $name" fi done entry="$HOME/.local/share/applications/spinoza.desktop" if [ -e "$entry" ]; then rm -f "$entry" removed="$removed the desktop entry" fi icon="$HOME/.local/share/icons/hicolor/512x512/apps/spinoza.png" if [ -e "$icon" ]; then rm -f "$icon" removed="$removed the icon" fi copyright="$(doc_dir)/copyright" if [ -e "$copyright" ]; then rm -f "$copyright" rmdir "$(doc_dir)" 2>/dev/null || true removed="$removed the license" fi for candidate in /Applications "$HOME/Applications"; do if [ -d "$candidate/Spinoza.app" ]; then rm -rf "$candidate/Spinoza.app" removed="$removed $candidate/Spinoza.app" fi done if [ -z "$removed" ]; then echo "Nothing to remove: spinoza is not installed in $dir" return 0 fi echo "Removed$removed" echo "Settings and kubeconfigs were left alone" } install_app() { app_asset="spinoza_${version}_darwin_app.zip" if ! listed "$app_asset"; then return 0 fi if ! command -v unzip >/dev/null 2>&1; then echo "Skipped the desktop app: unzip is not on PATH" return 0 fi download "$releases/download/$version/$app_asset" "$temp/$app_asset" verify "$temp/$app_asset" "$app_asset" attest "$temp/$app_asset" "$app_asset" unzip -q -o "$temp/$app_asset" -d "$temp/app" if [ ! -d "$temp/app/Spinoza.app" ]; then die "the app archive did not contain Spinoza.app" fi for candidate in /Applications "$HOME/Applications"; do if try_app_dir "$candidate"; then app_dir="$candidate" echo "Installed the Spinoza app in $candidate" return 0 fi done echo "Could not write the app to /Applications or ~/Applications" } install_linux_app() { app_asset="spinoza_${version}_linux_${arch}_app.tar.gz" if ! listed "$app_asset"; then return 0 fi if ! glibc; then echo "Skipped the desktop app: it is built against glibc and this system is not" return 0 fi download "$releases/download/$version/$app_asset" "$temp/$app_asset" verify "$temp/$app_asset" "$app_asset" attest "$temp/$app_asset" "$app_asset" mkdir -p "$temp/app" tar -xzf "$temp/$app_asset" -C "$temp/app" if [ ! -f "$temp/app/Spinoza" ]; then die "the app archive did not contain a Spinoza binary" fi install -m 0755 "$temp/app/Spinoza" "$dir/Spinoza" icons="$HOME/.local/share/icons/hicolor/512x512/apps" mkdir -p "$icons" install -m 0644 "$temp/app/spinoza.png" "$icons/spinoza.png" entries="$HOME/.local/share/applications" mkdir -p "$entries" write_desktop_entry "$entries/spinoza.desktop" app_dir="$entries" echo "Installed the Spinoza app in $dir, with a desktop entry in $entries" } glibc() { if ! command -v ldd >/dev/null 2>&1; then return 1 fi if ldd --version 2>&1 | grep -qi musl; then return 1 fi return 0 } write_desktop_entry() { cat > "$1" </dev/null; then return 1 fi if ! rm -rf "$target/Spinoza.app" 2>/dev/null; then return 1 fi if ! cp -R "$temp/app/Spinoza.app" "$target/Spinoza.app" 2>/dev/null; then return 1 fi return 0 } listed() { found="$(awk -v name="$1" '$2 == name { print $1 }' "$temp/checksums.txt")" if [ -z "$found" ]; then return 1 fi return 0 } report_app() { if [ -z "$app_dir" ]; then return 0 fi if [ "$os" = "darwin" ]; then echo "Open the desktop app with 'open -a Spinoza', or find it in Spotlight" return 0 fi echo "Open the desktop app from your launcher, or run '$dir/Spinoza'" } detect_downloader() { if command -v curl >/dev/null 2>&1; then downloader=curl return 0 fi if command -v wget >/dev/null 2>&1; then downloader=wget return 0 fi die "neither curl nor wget is on PATH" } detect_platform() { case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; *) die "$(uname -s) is not supported; see $releases for the other builds" ;; esac case "$(uname -m)" in x86_64 | amd64) arch=amd64 ;; arm64 | aarch64) arch=arm64 ;; *) die "$(uname -m) is not supported; see $releases for the other builds" ;; esac } resolve_latest() { if [ "$downloader" = curl ]; then final="$(curl -fsSL -o /dev/null -w '%{url_effective}' "$releases/latest")" else final="$(wget -q -S --spider -O /dev/null "$releases/latest" 2>&1 | awk 'tolower($1) == "location:" {print $2}' | tail -n 1)" fi case "$final" in */releases/tag/*) printf '%s\n' "${final##*/}" ;; *) die "$repo has no published release yet; set SPINOZA_VERSION to pick one" ;; esac } download() { if [ "$downloader" = curl ]; then curl -fsSL "$1" -o "$2" return 0 fi wget -q "$1" -O "$2" } verify() { expected="$(awk -v name="$2" '$2 == name { print $1 }' "$temp/checksums.txt")" if [ -z "$expected" ]; then die "$2 is not listed in checksums.txt" fi actual="$(sha256_of "$1")" if [ "$expected" != "$actual" ]; then die "checksum mismatch for $2" fi } attest() { if [ -z "${SPINOZA_VERIFY_ATTESTATION:-}" ]; then return 0 fi if ! command -v gh >/dev/null 2>&1; then die "SPINOZA_VERIFY_ATTESTATION is set but gh is not on PATH" fi if ! gh attestation verify "$1" --repo "$repo" >/dev/null 2>&1; then die "$2 carries no build provenance signed for $repo" fi echo "Verified the build provenance of $2" } sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{ print $1 }' return 0 fi if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{ print $1 }' return 0 fi if command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 "$1" | awk '{ print $NF }' return 0 fi die "no sha256 tool found, refusing to install unverified" } install_copyright() { if [ ! -f "$temp/LICENSE" ]; then return 0 fi docs="$(doc_dir)" mkdir -p "$docs" install -m 0644 "$temp/LICENSE" "$docs/copyright" } doc_dir() { if [ -n "${SPINOZA_INSTALL_DIR:-}" ]; then printf '%s\n' "$SPINOZA_INSTALL_DIR/doc/spinoza" return 0 fi if [ "$(id -u)" = 0 ]; then printf '%s\n' /usr/local/share/doc/spinoza return 0 fi printf '%s\n' "$HOME/.local/share/doc/spinoza" } install_dir() { if [ -n "${SPINOZA_INSTALL_DIR:-}" ]; then printf '%s\n' "$SPINOZA_INSTALL_DIR" return 0 fi if [ "$(id -u)" = 0 ]; then printf '%s\n' /usr/local/bin return 0 fi printf '%s\n' "$HOME/.local/bin" } make_temp() { if [ -n "${TMPDIR:-}" ] && [ -d "${TMPDIR}" ]; then temp="$(mktemp -d "$TMPDIR/spinoza-XXXXXX")" return 0 fi temp="$(mktemp -d /tmp/spinoza-XXXXXX)" } cleanup() { rm -rf "$temp" } report_path() { if [ "$(command -v spinoza || true)" = "$1/spinoza" ]; then echo "Run it in your browser with 'spinoza --open'" return 0 fi hint="$1" case "$hint" in "$HOME"/*) hint="\$HOME${hint#"$HOME"}" ;; esac echo "$1 is not on your PATH. Add it with:" case "${SHELL:-}" in *zsh) echo " echo 'export PATH=$hint:\$PATH' >> ~/.zshrc" ;; *fish) echo " fish_add_path -U $1" ;; *) echo " echo 'export PATH=$hint:\$PATH' >> ~/.bashrc" ;; esac echo "Or run it now with '$1/spinoza --open'" } die() { echo "install: $1" >&2 exit 1 } main "$@"