#!/bin/sh # Install the latest native TensorFold release under ~/.tensorfold. set -eu tf_tmp_dir='' tf_fail() { printf 'TensorFold: %s\n' "$*" >&2; exit 1; } tf_cleanup() { [ -z "$tf_tmp_dir" ] || rm -rf -- "$tf_tmp_dir"; } tf_profile_add() { tf_file=$1 tf_line='. "$HOME/.tensorfold/env"' if [ -f "$tf_file" ] && grep -Fqx "$tf_line" "$tf_file"; then return; fi printf '\n# TensorFold native CLI\n%s\n' "$tf_line" >> "$tf_file" } tf_openssl='' tf_no_openssl='Signature checks need OpenSSL 3, which can verify Ed25519; macOS ships LibreSSL, which cannot. Install OpenSSL 3, or on a Mac install with Homebrew: brew install ashhart/tensorfold/tensorfold' # macOS's LibreSSL pkeyutl has no -rawin, so it cannot check Ed25519; take the first openssl that can. tf_find_openssl() { for tf_candidate in openssl /opt/homebrew/opt/openssl@3/bin/openssl /usr/local/opt/openssl@3/bin/openssl; do command -v "$tf_candidate" >/dev/null 2>&1 || continue if "$tf_candidate" pkeyutl -help 2>&1 | grep -q -- '-rawin'; then tf_openssl=$tf_candidate; return 0; fi done return 1 } # Trust root also embedded by TensorFold Studio; rotate only in a reviewed release. # The signed bytes bind the archive filename, version, platform and digest. tf_verify_release() { tf_checksum=$1 tf_signature=$2 tf_expected_archive=$3 tf_verify_dir=$4 cat > "$tf_verify_dir/release-key.pem" <<'KEY' -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEA80FFo6a9/NXHNX9zBV3/2tM9AbC//mShbseG74mkkGw= -----END PUBLIC KEY----- KEY { printf 'tensorfold-native-checksum-v1\n'; cat "$tf_checksum"; } > "$tf_verify_dir/signed-checksum" [ -n "$tf_openssl" ] || tf_find_openssl || tf_fail "$tf_no_openssl" "$tf_openssl" pkeyutl -verify -rawin -pubin -inkey "$tf_verify_dir/release-key.pem" \ -in "$tf_verify_dir/signed-checksum" -sigfile "$tf_signature" >/dev/null 2>&1 || \ tf_fail 'Release signature verification failed; a signed checksum and OpenSSL with Ed25519 support are required.' tf_hash=$(cut -d ' ' -f 1 "$tf_checksum") case "$tf_hash" in ''|*[!0-9a-f]*) tf_fail 'Invalid signed release digest.';; esac [ "${#tf_hash}" -eq 64 ] || tf_fail 'Invalid signed release digest length.' [ "$(cat "$tf_checksum")" = "$tf_hash $tf_expected_archive" ] || \ tf_fail 'Signed checksum names an unexpected archive.' } tf_main() { case "${1-}" in -h|--help) printf '%s\n' \ 'Install: curl -fsSL https://tensorfold.dev/install.sh | sh' \ 'Apple Silicon macOS and NVIDIA Linux x86_64/aarch64 are supported.' \ 'Installs under ~/.tensorfold after Ed25519 signature and SHA-256 verification.' \ 'Requires OpenSSL 3 with Ed25519 support (Homebrew openssl@3 on a Mac); unsigned releases are refused.' \ 'TENSORFOLD_VERSION=1.0.8 pins a release; otherwise the latest release is used.' \ 'TENSORFOLD_INSTALL_DIR sets an absolute installation directory.' \ 'TENSORFOLD_NO_MODIFY_PATH=1 skips shell profile changes.' return 0 ;; '') ;; *) tf_fail "Unknown option: $1" ;; esac [ "$#" -eq 0 ] || tf_fail 'Use --help to see installation options.' [ -n "${HOME-}" ] || tf_fail 'HOME must be set.' case "$HOME" in /*) ;; *) tf_fail 'HOME must be an absolute path.';; esac command -v curl >/dev/null 2>&1 || tf_fail 'curl is required.' command -v tar >/dev/null 2>&1 || tf_fail 'tar is required.' tf_find_openssl || tf_fail "$tf_no_openssl" command -v diff >/dev/null 2>&1 || tf_fail 'diff is required for installed-release verification.' case "$(uname -s):$(uname -m)" in Darwin:arm64) tf_platform=macos-arm64 ;; Linux:x86_64) tf_platform=linux-x86_64 ;; Linux:aarch64|Linux:arm64) tf_platform=linux-aarch64 ;; Darwin:*) tf_fail 'TensorFold native requires Apple Silicon on macOS.' ;; *) tf_fail 'Supported hosts are Apple Silicon Macs and NVIDIA Linux x86_64/aarch64.' ;; esac tf_version=${TENSORFOLD_VERSION-} if [ -z "$tf_version" ]; then tf_latest=$(curl --proto '=https' --proto-redir '=https' -fsSIL --retry 3 \ -o /dev/null -w '%{url_effective}' \ 'https://github.com/ashhart/TensorFold/releases/latest') || \ tf_fail 'Could not find the latest GitHub release.' case "$tf_latest" in https://github.com/ashhart/TensorFold/releases/tag/v*) tf_version=${tf_latest##*/v} ;; *) tf_fail 'GitHub returned an unexpected release URL.' ;; esac fi case "$tf_version" in ''|*[!0-9.]*|.*|*..*|*.) tf_fail 'Invalid release version.' ;; *.*.*) ;; *) tf_fail 'Release version must be MAJOR.MINOR.PATCH.' ;; esac tf_root=${TENSORFOLD_INSTALL_DIR:-"$HOME/.tensorfold"} case "$tf_root" in /*) ;; *) tf_fail 'TENSORFOLD_INSTALL_DIR must be absolute.';; esac if [ "$tf_root" != "$HOME/.tensorfold" ] && [ "${TENSORFOLD_NO_MODIFY_PATH:-0}" != 1 ]; then tf_fail 'A custom installation directory requires TENSORFOLD_NO_MODIFY_PATH=1.' fi tf_release="$tf_root/releases/v$tf_version/$tf_platform" tf_exe="$tf_release/bin/tensorfold-native" tf_link="$tf_root/bin/tensorfold" # Verify release identity on every run, including an already installed version. [ ! -L "$tf_release" ] || tf_fail 'The release directory must not be a symlink.' { tf_tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/tensorfold-install.XXXXXX") tf_archive="tensorfold-$tf_version-$tf_platform.tar.gz" tf_base="https://github.com/ashhart/TensorFold/releases/download/v$tf_version/$tf_archive" printf 'Downloading TensorFold %s for %s...\n' "$tf_version" "$tf_platform" curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 "$tf_base" -o "$tf_tmp_dir/$tf_archive" curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 --max-filesize 1024 "$tf_base.sha256" -o "$tf_tmp_dir/$tf_archive.sha256" curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 --max-filesize 64 "$tf_base.sha256.sig" -o "$tf_tmp_dir/$tf_archive.sha256.sig" || \ tf_fail 'This release has no checksum signature, so this installer stops. Install with Homebrew (brew install ashhart/tensorfold/tensorfold) or follow the README archive steps: https://github.com/ashhart/TensorFold#readme' tf_verify_release "$tf_tmp_dir/$tf_archive.sha256" "$tf_tmp_dir/$tf_archive.sha256.sig" "$tf_archive" "$tf_tmp_dir" ( cd "$tf_tmp_dir" if command -v sha256sum >/dev/null 2>&1; then sha256sum -c "$tf_archive.sha256" elif command -v shasum >/dev/null 2>&1; then shasum -a 256 -c "$tf_archive.sha256" else tf_fail 'SHA-256 verification needs sha256sum or shasum.' fi tar -xzf "$tf_archive" ) [ -x "$tf_tmp_dir/tensorfold-$tf_version-$tf_platform/bin/tensorfold-native" ] || \ tf_fail 'The release archive did not contain the native binary.' mkdir -p "$tf_root/releases/v$tf_version" if [ -e "$tf_release" ]; then diff -qr "$tf_tmp_dir/tensorfold-$tf_version-$tf_platform" "$tf_release" >/dev/null || \ tf_fail 'Existing release differs from its authenticated archive; use a clean installation directory.' else mv "$tf_tmp_dir/tensorfold-$tf_version-$tf_platform" "$tf_release" fi } mkdir -p "$tf_root/bin" if [ -e "$tf_link" ] && [ ! -L "$tf_link" ]; then tf_fail "The launcher path is not a symlink: $tf_link" fi ln -sfn "$tf_exe" "$tf_link" "$tf_link" --version if [ "$tf_root" = "$HOME/.tensorfold" ]; then cat > "$tf_root/env" <<'ENV' case ":${PATH-}:" in *:"$HOME/.tensorfold/bin":*) ;; *) PATH="$HOME/.tensorfold/bin:${PATH-}"; export PATH ;; esac ENV else case "$tf_root" in *[\"\`\\\$]*) tf_fail 'Custom directory contains an unsupported shell character.';; esac printf 'PATH="%s/bin:${PATH-}"; export PATH\n' "$tf_root" > "$tf_root/env" fi if [ "${TENSORFOLD_NO_MODIFY_PATH:-0}" != 1 ]; then case "${SHELL:-/bin/sh}" in */zsh) tf_profile_add "${ZDOTDIR:-$HOME}/.zshrc" ;; */bash) tf_profile_add "$HOME/.bashrc" ;; */fish) tf_fish="$HOME/.config/fish/conf.d" mkdir -p "$tf_fish" printf '%s\n' 'fish_add_path $HOME/.tensorfold/bin' > "$tf_fish/tensorfold.fish" ;; *) tf_profile_add "$HOME/.profile" ;; esac fi printf '\nInstalled in %s\n' "$tf_release" printf 'Open a new terminal, or run: . "%s/env"\n' "$tf_root" printf '%s\n' 'Then run: tensorfold pull TensorFold/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-MLX-4bit' } trap tf_cleanup 0 trap 'exit 130' 2 trap 'exit 143' 15 # Keep this call last so a truncated download cannot run a partial installer. tf_main "$@"