#!/usr/bin/env sh set -eu (set -o pipefail 2>/dev/null) && set -o pipefail help() { cat <<'EOF' Install spaces binaries (spaced and space-cli) from GitHub releases. USAGE: install.sh [options] FLAGS: -h, --help Display this message -f, --force Force overwriting existing binaries -v, --verbose Show detailed output OPTIONS: --tag VERSION Specific version to install (e.g., 0.0.6a), defaults to latest release --to LOCATION Where to install the binaries (default: auto-detected based on OS) For macOS: ~/.local/bin if exists, else ~/bin For Linux: ~/.local/bin NOTES: The script will automatically choose an appropriate user-writable location. The chosen directory will be added to your PATH if needed. EOF } say() { echo "install: $*" >&2 } err() { if [ -n "${td-}" ]; then rm -rf "$td" fi say "error: $*" say "run './install.sh --help' to see available options" exit 1 } need() { if ! command -v "$1" > /dev/null 2>&1; then err "need $1 (command not found)" fi } add_to_path() { local install_dir="$1" local shell_rc local reload_needed=false # Detect shell configuration file based on $SHELL environment variable if [[ "$SHELL" == */zsh ]]; then shell_rc="$HOME/.zshrc" shell_type="zsh" else shell_type="bash" shell_rc="$HOME/.bashrc" [ "$(uname -s)" = "Darwin" ] && shell_rc="$HOME/.bash_profile" fi if ! echo "$PATH" | tr ':' '\n' | grep -Fq "$install_dir"; then say "Adding $install_dir to PATH in $shell_rc" echo "export PATH=\"$install_dir:\$PATH\"" >> "$shell_rc" reload_needed=true fi # Return whether reload is needed [ "$reload_needed" = true ] && echo "reload" || echo "noreload" } determine_install_dir() { local os="$1" local specified_dir="$2" local install_dir if [ -n "$specified_dir" ]; then install_dir="$specified_dir" else case "$os" in darwin) if [ -d "$HOME/.local/bin" ]; then install_dir="$HOME/.local/bin" else install_dir="$HOME/bin" fi ;; linux) install_dir="$HOME/.local/bin" ;; *) err "unsupported operating system: $os" ;; esac fi # Create directory if it doesn't exist if [ ! -d "$install_dir" ]; then mkdir -p "$install_dir" || err "failed to create $install_dir" fi echo "$install_dir" } download() { url="$1" output="$2" if command -v curl > /dev/null; then if [ "$output" = "-" ]; then curl --proto =https --tlsv1.2 -sSfL "$url" else curl --proto =https --tlsv1.2 -sSfL "$url" -o "$output" fi else wget --https-only --secure-protocol=TLSv1_2 --quiet "$url" -O "$output" fi } # Initialize default values force=true verbose=false dest="" tag="" td="" # Process command line arguments while test $# -gt 0; do case $1 in --force | -f) force=true ;; --help | -h) help exit 0 ;; --verbose | -v) verbose=true ;; --tag) tag="$2" shift ;; --to) dest="$2" shift ;; *) say "error: unrecognized argument '$1'" help exit 1 ;; esac shift done # Check for required commands command -v curl > /dev/null 2>&1 || command -v wget > /dev/null 2>&1 || err "need wget or curl" need mktemp need tar # Detect OS and architecture os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m) # Map architecture to match release files case "$arch" in x86_64) arch="x86_64" ;; aarch64|arm64) arch="arm64" ;; *) err "unsupported architecture: $arch" ;; esac # Determine installation directory dest=$(determine_install_dir "$os" "$dest") [ "$verbose" = true ] && say "installing to: $dest" # Get latest version if not specified if [ -z "$tag" ]; then [ "$verbose" = true ] && say "fetching latest release version..." api_response=$(download "https://api.github.com/repos/spacesprotocol/spaces/releases/latest" - || echo "failed") if [ "$api_response" = "failed" ]; then err "could not fetch release information from GitHub" fi tag=$(echo "$api_response" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [ -z "$tag" ]; then err "no releases found in the repository" fi fi # Normalize version and release tag. Releases are tagged 'spaces_client-vX.Y.Z'; # accept '0.4.0', 'v0.4.0', or the full 'spaces_client-v0.4.0' as --tag input. case "$tag" in *-v*) version="${tag##*-v}" ;; # spaces_client-v0.4.0 -> 0.4.0 v*) version="${tag#v}" ;; # v0.4.0 -> 0.4.0 *) version="$tag" ;; # 0.4.0 -> 0.4.0 esac [ -z "$version" ] && err "could not determine version to install" release_tag="spaces_client-v$version" # Verify the release exists [ "$verbose" = true ] && say "verifying release $release_tag exists..." api_response=$(download "https://api.github.com/repos/spacesprotocol/spaces/releases/tags/$release_tag" - || echo "failed") if [ "$api_response" = "failed" ] || echo "$api_response" | grep -q "Not Found"; then err "release '$release_tag' not found" fi [ "$verbose" = true ] && say "installing spaces v$version" # Create temporary directory td=$(mktemp -d || mktemp -d -t tmp) trap 'rm -rf "$td"' EXIT # Construct download URL. The release is tagged 'spaces_client-vX.Y.Z' but the # archive asset is named 'spaces-vX.Y.Z--.tar.gz'. asset="spaces-v${version}-${os}-${arch}.tar.gz" download_url="https://github.com/spacesprotocol/spaces/releases/download/$release_tag/$asset" [ "$verbose" = true ] && say "downloading from: $download_url" # Download and extract download "$download_url" "$td/spaces.tar.gz" || err "download failed" tar xzf "$td/spaces.tar.gz" -C "$td" || err "failed to extract archive" # Install binaries for binary in spaced space-cli; do if [ -f "$dest/$binary" ] && [ "$force" = false ]; then err "Found existing spaces executables at $dest. Use --force flag to overwrite" fi binary_path="$dest/$binary" if ! find "$td" -type f -name "$binary" -exec cp {} "$binary_path" \; ; then err "failed to copy $binary to $dest (permission denied)" fi if ! chmod 755 "$binary_path"; then err "failed to set permissions on $binary (permission denied)" fi [ "$verbose" = true ] && say "installed $binary to $binary_path" done # Verify installation for binary in spaced space-cli; do if ! [ -x "$dest/$binary" ]; then err "installation verification failed for $binary" fi done # Add to PATH if needed and get reload status reload_status=$(add_to_path "$dest") # Final instructions say "Successfully installed spaces v$version to $dest" # Reload shell if needed if [ "$reload_status" = "reload" ]; then if [[ "$SHELL" == */zsh ]]; then say "Please run 'source $HOME/.zshrc' to update your PATH" else if [ "$(uname -s)" = "Darwin" ]; then say "Please run 'source $HOME/.bash_profile' to update your PATH" else say "Please run 'source $HOME/.bashrc' to update your PATH" fi fi say "Or start a new terminal session to apply changes" fi # Show versions if verbose if [ "$verbose" = true ]; then say "installed version:" "$dest/spaced" --version fi