525 lines · 16.1 KB · Text
RawDownload
#!/usr/bin/env bashset -euo pipefailAPP=openscience MUTED='\033[0;2m'RED='\033[0;31m'ORANGE='\033[38;5;214m'NC='\033[0m' # No Color usage() {    cat <<EOFOpenScience Installer Usage: install [options] Options:    -h, --help              Display this help message    -v, --version <version> Install a specific version (e.g., 1.0.180)    -b, --binary <path>     Install from a local binary instead of downloading        --no-modify-path    Don't modify shell config files (.zshrc, .bashrc, etc.)        --no-atlas          Don't offer to install the Atlas CLI companion Examples:    curl -fsSL https://openscience.sh/install | bash    curl -fsSL https://openscience.sh/install | bash -s -- --version 1.0.180    ./install --binary /path/to/openscienceEOF} requested_version=${VERSION:-}no_modify_path=falseno_atlas=falsebinary_path="" while [[ $# -gt 0 ]]; do    case "$1" in        -h|--help)            usage            exit 0            ;;        -v|--version)            if [[ -n "${2:-}" ]]; then                requested_version="$2"                shift 2            else                echo -e "${RED}Error: --version requires a version argument${NC}"                exit 1            fi            ;;        -b|--binary)            if [[ -n "${2:-}" ]]; then                binary_path="$2"                shift 2            else                echo -e "${RED}Error: --binary requires a path argument${NC}"                exit 1            fi            ;;        --no-modify-path)            no_modify_path=true            shift            ;;        --no-atlas)            no_atlas=true            shift            ;;        *)            echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2            shift            ;;    esacdone INSTALL_DIR=$HOME/.openscience/binmkdir -p "$INSTALL_DIR" # If --binary is provided, skip all download/detection logicif [ -n "$binary_path" ]; then    if [ ! -f "$binary_path" ]; then        echo -e "${RED}Error: Binary not found at ${binary_path}${NC}"        exit 1    fi    specific_version="local"else    raw_os=$(uname -s)    os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')    case "$raw_os" in      Darwin*) os="darwin" ;;      Linux*) os="linux" ;;      MINGW*|MSYS*|CYGWIN*) os="windows" ;;    esac     arch=$(uname -m)    if [[ "$arch" == "aarch64" ]]; then      arch="arm64"    fi    if [[ "$arch" == "x86_64" ]]; then      arch="x64"    fi     if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then      rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)      if [ "$rosetta_flag" = "1" ]; then        arch="arm64"      fi    fi     combo="$os-$arch"    case "$combo" in      linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)        ;;      *)        echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"        exit 1        ;;    esac     archive_ext=".zip"    if [ "$os" = "linux" ]; then      archive_ext=".tar.gz"    fi     is_musl=false    if [ "$os" = "linux" ]; then      if [ -f /etc/alpine-release ]; then        is_musl=true      fi       if command -v ldd >/dev/null 2>&1; then        if ldd --version 2>&1 | grep -qi musl; then          is_musl=true        fi      fi    fi     needs_baseline=false    if [ "$arch" = "x64" ]; then      if [ "$os" = "linux" ]; then        if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then          needs_baseline=true        fi      fi       if [ "$os" = "darwin" ]; then        avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)        if [ "$avx2" != "1" ]; then          needs_baseline=true        fi      fi    fi     target="$os-$arch"    if [ "$needs_baseline" = "true" ]; then      target="$target-baseline"    fi    if [ "$is_musl" = "true" ]; then      target="$target-musl"    fi     filename="$APP-$target$archive_ext"      if [ "$os" = "linux" ]; then        if ! command -v tar >/dev/null 2>&1; then             echo -e "${RED}Error: 'tar' is required but not installed.${NC}"             exit 1        fi    else        if ! command -v unzip >/dev/null 2>&1; then            echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"            exit 1        fi    fi     if [ -z "$requested_version" ]; then        url="https://github.com/synthetic-sciences/OpenScience/releases/latest/download/$filename"        # The || fallback keeps set -e from killing the script inside the        # assignment when curl fails, so the message below can print.        specific_version=$(curl -fsS https://api.github.com/repos/synthetic-sciences/OpenScience/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') || specific_version=""         if [[ -z "$specific_version" ]]; then            echo -e "${RED}Failed to fetch version information${NC}"            exit 1        fi    else        # Strip leading 'v' if present        requested_version="${requested_version#v}"        url="https://github.com/synthetic-sciences/OpenScience/releases/download/v${requested_version}/$filename"        specific_version=$requested_version         # Verify the release exists before downloading        http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/synthetic-sciences/OpenScience/releases/tag/v${requested_version}")        if [ "$http_status" = "404" ]; then            echo -e "${RED}Error: Release v${requested_version} not found${NC}"            echo -e "${MUTED}Available releases: https://github.com/synthetic-sciences/OpenScience/releases${NC}"            exit 1        fi    fifi print_message() {    local level=$1    local message=$2    local color=""     case $level in        info) color="${NC}" ;;        warning) color="${NC}" ;;        error) color="${RED}" ;;    esac     echo -e "${color}${message}${NC}"} check_version() {    if command -v openscience >/dev/null 2>&1; then        openscience_path=$(which openscience)         ## Check the installed version        installed_version=$(openscience --version 2>/dev/null || echo "")         if [[ "$installed_version" != "$specific_version" ]]; then            print_message info "${MUTED}Installed version: ${NC}$installed_version."        else            print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"            exit 0        fi    fi} unbuffered_sed() {    if echo | sed -u -e "" >/dev/null 2>&1; then        sed -nu "$@"    elif echo | sed -l -e "" >/dev/null 2>&1; then        sed -nl "$@"    else        local pad="$(printf "\n%512s" "")"        sed -ne "s/$/\\${pad}/" "$@"    fi} print_progress() {    local bytes="$1"    local length="$2"    [ "$length" -gt 0 ] || return 0     local width=50    local percent=$(( bytes * 100 / length ))    [ "$percent" -gt 100 ] && percent=100    local on=$(( percent * width / 100 ))    local off=$(( width - on ))     local filled=$(printf "%*s" "$on" "")    filled=${filled// /■}    local empty=$(printf "%*s" "$off" "")    empty=${empty// /・}     printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4} download_with_progress() {    local url="$1"    local output="$2"     if [ -t 2 ]; then        exec 4>&2    else        exec 4>/dev/null    fi     local tmp_dir=${TMPDIR:-/tmp}    local basename="${tmp_dir}/openscience_install_$$"    local tracefile="${basename}.trace"     rm -f "$tracefile"    mkfifo "$tracefile"     # Hide cursor    printf "\033[?25l" >&4     trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN     (        curl --trace-ascii "$tracefile" -f -s -L -o "$output" "$url"    ) &    local curl_pid=$!     unbuffered_sed \        -e 'y/ACDEGHLNORTV/acdeghlnortv/' \        -e '/^0000: content-length:/p' \        -e '/^<= recv data/p' \        "$tracefile" | \    {        local length=0        local bytes=0         while IFS=" " read -r -a line; do            [ "${#line[@]}" -lt 2 ] && continue            local tag="${line[0]} ${line[1]}"             if [ "$tag" = "0000: content-length:" ]; then                length="${line[2]}"                length=$(echo "$length" | tr -d '\r')                bytes=0            elif [ "$tag" = "<= recv" ]; then                local size="${line[3]}"                bytes=$(( bytes + size ))                if [ "$length" -gt 0 ]; then                    print_progress "$bytes" "$length"                fi            fi        done    }     wait $curl_pid    local ret=$?    echo "" >&4    return $ret} download_and_install() {    print_message info "\n${MUTED}Installing ${NC}openscience ${MUTED}version: ${NC}$specific_version"    local tmp_dir="${TMPDIR:-/tmp}/openscience_install_$$"    mkdir -p "$tmp_dir"     if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then        # Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails        curl -f -# -L -o "$tmp_dir/$filename" "$url"    fi     # Without -f curl saves the error body ("Not Found") and exits 0, and the    # user gets a cryptic unzip/tar failure. Catch a missing/empty download    # here with a real message instead.    if [ ! -s "$tmp_dir/$filename" ]; then        echo -e "${RED}Download failed: $url${NC}"        rm -rf "$tmp_dir"        exit 1    fi     # Verify against the release's checksums.txt when a sha256 tool exists.    # Releases published before checksums.txt simply skip verification.    local shatool=""    if command -v sha256sum >/dev/null 2>&1; then        shatool="sha256sum"    elif command -v shasum >/dev/null 2>&1; then        shatool="shasum -a 256"    fi    if [ -n "$shatool" ]; then        local checksums_url="${url%/*}/checksums.txt"        local expected        expected=$(curl -fsSL "$checksums_url" 2>/dev/null | awk -v f="$filename" '$2 == f {print $1}') || expected=""        if [ -n "$expected" ]; then            local actual            actual=$($shatool "$tmp_dir/$filename" | awk '{print $1}')            if [ "$actual" != "$expected" ]; then                echo -e "${RED}Checksum mismatch for ${filename}${NC}"                echo -e "${MUTED}expected: ${expected}${NC}"                echo -e "${MUTED}actual:   ${actual}${NC}"                rm -rf "$tmp_dir"                exit 1            fi            print_message info "${MUTED}Checksum verified${NC}"        fi    fi     if [ "$os" = "linux" ]; then        tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"    else        unzip -q "$tmp_dir/$filename" -d "$tmp_dir"    fi     # Bun --compile appends .exe for win32 targets, so the archive entry is    # openscience.exe there. Git Bash resolves `openscience` to the .exe.    local bin_name="openscience"    if [ "$os" = "windows" ]; then        bin_name="openscience.exe"    fi    mv "$tmp_dir/$bin_name" "$INSTALL_DIR"    chmod 755 "${INSTALL_DIR}/${bin_name}"    rm -rf "$tmp_dir"} install_from_binary() {    print_message info "\n${MUTED}Installing ${NC}openscience ${MUTED}from: ${NC}$binary_path"    cp "$binary_path" "${INSTALL_DIR}/openscience"    chmod 755 "${INSTALL_DIR}/openscience"} if [ -n "$binary_path" ]; then    install_from_binaryelse    check_version    download_and_installfi  add_to_path() {    local config_file=$1    local command=$2     if grep -Fxq "$command" "$config_file"; then        print_message info "Command already exists in $config_file, skipping write."    elif [[ -w $config_file ]]; then        echo -e "\n# openscience" >> "$config_file"        echo "$command" >> "$config_file"        print_message info "${MUTED}Successfully added ${NC}openscience ${MUTED}to \$PATH in ${NC}$config_file"    else        print_message warning "Manually add the directory to $config_file (or similar):"        print_message info "  $command"    fi} XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} current_shell=$(basename "$SHELL")case $current_shell in    fish)        config_files="$HOME/.config/fish/config.fish"    ;;    zsh)        config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"    ;;    bash)        config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"    ;;    ash)        config_files="$HOME/.ashrc $HOME/.profile /etc/profile"    ;;    sh)        config_files="$HOME/.ashrc $HOME/.profile /etc/profile"    ;;    *)        # Default case if none of the above matches        config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"    ;;esac if [[ "$no_modify_path" != "true" ]]; then    config_file=""    for file in $config_files; do        if [[ -f $file ]]; then            config_file=$file            break        fi    done     if [[ -z $config_file ]]; then        print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"        print_message info "  export PATH=$INSTALL_DIR:\$PATH"    elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then        case $current_shell in            fish)                add_to_path "$config_file" "fish_add_path $INSTALL_DIR"            ;;            zsh)                add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"            ;;            bash)                add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"            ;;            ash)                add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"            ;;            sh)                add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"            ;;            *)                export PATH=$INSTALL_DIR:$PATH                print_message warning "Manually add the directory to $config_file (or similar):"                print_message info "  export PATH=$INSTALL_DIR:\$PATH"            ;;        esac    fifi if [ "${GITHUB_ACTIONS-}" == "true" ] && [ -n "${GITHUB_PATH-}" ]; then    echo "$INSTALL_DIR" >> "$GITHUB_PATH"    print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"fi # An `openscience` from another package manager can sit earlier on PATH and# shadow this install — most commonly the deprecated @synsci/cli npm package,# whose stale bin link also blocks `npm i -g @synsci/openscience` with EEXIST.shadow=$(command -v openscience 2>/dev/null || true)if [[ -n "$shadow" && "$shadow" != "$INSTALL_DIR/openscience" ]]; then    print_message warning "Another openscience binary at ${shadow} may shadow this install."    print_message info "${MUTED}If it was installed by npm (the deprecated ${NC}@synsci/cli${MUTED} package), remove it with:${NC}"    print_message info "  npm rm -g @synsci/cli"fi # Offer the Atlas CLI companion (research graph — maps, runs, shared library).# Piping via curl|bash makes stdin the script itself, so prompt on /dev/tty.# Skips when non-interactive, in CI, already installed, or opted out.if [ "$no_atlas" != true ] && [ -z "${OPENSCIENCE_NO_ATLAS:-}" ] && [ -z "${CI:-}" ] \    && [ -t 1 ] && [ -r /dev/tty ] && ! command -v atlas >/dev/null 2>&1; then    echo -e ""    echo -e "${MUTED}Atlas is Synthetic Sciences' research graph — maps, runs, and a shared library.${NC}"    printf "Install the Atlas CLI companion too? [y/N] "    read -r atlas_reply </dev/tty || atlas_reply=""    case "$atlas_reply" in

Lines 1–500 of 525.