diff --git a/README.md b/README.md index 4a3fa86..5a87dd7 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,10 @@ please [open an issue][issues]. To install zoxide, run this command in your terminal: ```sh -curl -sS https://webinstall.dev/zoxide | bash +curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash ``` -Alternatively, you can use a package manager: +Or, you can use a package manager: | Distribution | Repository | Instructions | | ------------------- | ----------------------- | ---------------------------------------------------------------------------------------------- | @@ -102,6 +102,12 @@ To install zoxide, use a package manager: | [Homebrew] | `brew install zoxide` | | [MacPorts] | `port install zoxide` | +Or, run this command in your terminal: + +```sh +curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash +``` +
@@ -113,7 +119,7 @@ To install zoxide, run this command in your command prompt: curl.exe -A "MS" https://webinstall.dev/zoxide | powershell ``` -Alternatively, you can use a package manager: +Or, you can use a package manager: | Repository | Instructions | | --------------- | ------------------------------------- | diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..f971b57 --- /dev/null +++ b/install.sh @@ -0,0 +1,385 @@ +#!/bin/sh +# shellcheck shell=dash + +# The official zoxide installer. +# +# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local` +# extension. Note: Most shells limit `local` to 1 var per line, contra bash. + +main() { + if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then + # The version of ksh93 that ships with many illumos systems does not + # support the "local" extension. Print a message rather than fail in + # subtle ways later on: + err 'the installer does not work with this ksh93 version; please try bash' + fi + + set -u + + # Detect and print host target triple. + ensure get_architecture + local _arch="$RETVAL" + assert_nz "$_arch" "arch" + echo "Detected architecture: $_arch" + + # Create and enter a temporary directory. + local _tmp_dir + _tmp_dir="$(mktemp -d)" || err "mktemp: could not create temporary directory" + cd "$_tmp_dir" || err "cd: failed to enter directory: $_tmp_dir" + + # Download and extract zoxide. + ensure download_zoxide "$_arch" + local _package="$RETVAL" + assert_nz "$_package" "package" + echo "Downloaded package: $_package" + case "$_package" in + *.tar.gz) + need_cmd tar + ensure tar -xf "$_package" + ;; + *.zip) + need_cmd unzip + ensure unzip -oq "$_package" + ;; + *) + err "unsupported package format: $_package" + ;; + esac + + # Install binary. + local _bin_dir="$HOME/.local/bin" + local _bin_name + case "$_arch" in + *windows*) _bin_name="zoxide.exe" ;; + *) _bin_name="zoxide" ;; + esac + ensure mkdir -p "$_bin_dir" + ensure cp "$_bin_name" "$_bin_dir" + echo "Installed zoxide to $_bin_dir" + + # Install manpages. + local _man_dir="$HOME/.local/share/man" + ensure mkdir -p "$_man_dir/man1" + ensure cp "man/man1/"* "$_man_dir/man1/" + echo "Installed manpages to $_man_dir" + + # Print success message and check $PATH. + echo "" + echo "zoxide is installed!" + if ! echo ":$PATH:" | grep -Fq ":$_bin_dir:"; then + echo "NOTE: $_bin_dir is not on your \$PATH. zoxide will not work unless it is added to \$PATH." + fi +} + +download_zoxide() { + local _arch="$1" + + if check_cmd curl; then + _dld=curl + elif check_cmd wget; then + _dld=wget + else + need_cmd 'curl or wget' + fi + need_cmd grep + + local _releases_url="https://api.github.com/repos/ajeetdsouza/zoxide/releases/latest" + local _releases + case "$_dld" in + curl) _releases="$(curl -sL "$_releases_url")" || + err "curl: failed to download $_releases_url" ;; + wget) _releases="$(wget -qO- "$_releases_url")" || + err "wget: failed to download $_releases_url" ;; + esac + + local _package_url + _package_url="$(echo "$_releases" | grep "browser_download_url" | cut -d '"' -f 4 | grep "$_arch")" || + err "zoxide has not yet been packaged for your architecture ($_arch), please file an issue at https://github.com/ajeetdsouza/zoxide/issues" + + local _ext + case "$_package_url" in + *.tar.gz) _ext="tar.gz" ;; + *.zip) _ext="zip" ;; + *) err "unsupported package format: $_package_url" ;; + esac + + local _package="zoxide.$_ext" + case "$_dld" in + curl) _releases="$(curl -sLo "$_package" "$_package_url")" || err "curl: failed to download $_package_url" ;; + wget) _releases="$(wget -qO "$_package" "$_package_url")" || err "wget: failed to download $_package_url" ;; + esac + + RETVAL="$_package" +} + +# The below functions have been extracted with minor modifications from the +# Rustup install script: +# +# https://github.com/rust-lang/rustup/blob/4c1289b2c3f3702783900934a38d7c5f912af787/rustup-init.sh + +get_architecture() { + local _ostype _cputype _bitness _arch _clibtype + _ostype="$(uname -s)" + _cputype="$(uname -m)" + _clibtype="musl" + + if [ "$_ostype" = Linux ]; then + if [ "$(uname -o)" = Android ]; then + _ostype=Android + fi + fi + + if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then + # Darwin `uname -m` lies + if sysctl hw.optional.x86_64 | grep -q ': 1'; then + _cputype=x86_64 + fi + fi + + if [ "$_ostype" = SunOS ]; then + # Both Solaris and illumos presently announce as "SunOS" in "uname -s" + # so use "uname -o" to disambiguate. We use the full path to the + # system uname in case the user has coreutils uname first in PATH, + # which has historically sometimes printed the wrong value here. + if [ "$(/usr/bin/uname -o)" = illumos ]; then + _ostype=illumos + fi + + # illumos systems have multi-arch userlands, and "uname -m" reports the + # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 + # systems. Check for the native (widest) instruction set on the + # running kernel: + if [ "$_cputype" = i86pc ]; then + _cputype="$(isainfo -n)" + fi + fi + + case "$_ostype" in + Android) + _ostype=linux-android + ;; + Linux) + check_proc + _ostype=unknown-linux-$_clibtype + _bitness=$(get_bitness) + ;; + FreeBSD) + _ostype=unknown-freebsd + ;; + NetBSD) + _ostype=unknown-netbsd + ;; + DragonFly) + _ostype=unknown-dragonfly + ;; + Darwin) + _ostype=apple-darwin + ;; + illumos) + _ostype=unknown-illumos + ;; + MINGW* | MSYS* | CYGWIN* | Windows_NT) + _ostype=pc-windows-msvc + ;; + *) + err "unrecognized OS type: $_ostype" + ;; + esac + + case "$_cputype" in + i386 | i486 | i686 | i786 | x86) + _cputype=i686 + ;; + xscale | arm) + _cputype=arm + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + fi + ;; + armv6l) + _cputype=arm + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + armv7l | armv8l) + _cputype=armv7 + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + aarch64 | arm64) + _cputype=aarch64 + ;; + x86_64 | x86-64 | x64 | amd64) + _cputype=x86_64 + ;; + mips) + _cputype=$(get_endianness mips '' el) + ;; + mips64) + if [ "$_bitness" -eq 64 ]; then + # only n64 ABI is supported for now + _ostype="${_ostype}abi64" + _cputype=$(get_endianness mips64 '' el) + fi + ;; + ppc) + _cputype=powerpc + ;; + ppc64) + _cputype=powerpc64 + ;; + ppc64le) + _cputype=powerpc64le + ;; + s390x) + _cputype=s390x + ;; + riscv64) + _cputype=riscv64gc + ;; + *) + err "unknown CPU type: $_cputype" + ;; + esac + + # Detect 64-bit linux with 32-bit userland + if [ "${_ostype}" = unknown-linux-musl ] && [ "${_bitness}" -eq 32 ]; then + case $_cputype in + x86_64) + # 32-bit executable for amd64 = x32 + if is_host_amd64_elf; then { + echo "x32 userland is unsupported" 1>&2 + exit 1 + }; else + _cputype=i686 + fi + ;; + mips64) + _cputype=$(get_endianness mips '' el) + ;; + powerpc64) + _cputype=powerpc + ;; + aarch64) + _cputype=armv7 + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + riscv64gc) + err "riscv64 with 32-bit userland unsupported" + ;; + esac + fi + + # Detect armv7 but without the CPU features Rust needs in that build, + # and fall back to arm. + # See https://github.com/rust-lang/rustup.rs/issues/587. + if [ "$_ostype" = "unknown-linux-musleabihf" ] && [ "$_cputype" = armv7 ]; then + if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then + # At least one processor does not have NEON. + _cputype=arm + fi + fi + + _arch="${_cputype}-${_ostype}" + RETVAL="$_arch" +} + +get_bitness() { + need_cmd head + # Architecture detection without dependencies beyond coreutils. + # ELF files start out "\x7fELF", and the following byte is + # 0x01 for 32-bit and + # 0x02 for 64-bit. + # The printf builtin on some shells like dash only supports octal + # escape sequences, so we use those. + local _current_exe_head + _current_exe_head=$(head -c 5 /proc/self/exe) + if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then + echo 32 + elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then + echo 64 + else + err "unknown platform bitness" + fi +} + +get_endianness() { + local cputype=$1 + local suffix_eb=$2 + local suffix_el=$3 + + # detect endianness without od/hexdump, like get_bitness() does. + need_cmd head + need_cmd tail + + local _current_exe_endianness + _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)" + if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then + echo "${cputype}${suffix_el}" + elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then + echo "${cputype}${suffix_eb}" + else + err "unknown platform endianness" + fi +} + +is_host_amd64_elf() { + need_cmd head + need_cmd tail + # ELF e_machine detection without dependencies beyond coreutils. + # Two-byte field at offset 0x12 indicates the CPU, + # but we're interested in it being 0x3E to indicate amd64, or not that. + local _current_exe_machine + _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1) + [ "$_current_exe_machine" = "$(printf '\076')" ] +} + +check_proc() { + # Check for /proc by looking for the /proc/self/exe link. + # This is only run on Linux. + if ! test -L /proc/self/exe; then + err "unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc." + fi +} + +need_cmd() { + if ! check_cmd "$1"; then + err "need '$1' (command not found)" + fi +} + +check_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +# Run a command that should never fail. If the command fails execution +# will immediately terminate with an error showing the failing +# command. +ensure() { + if ! "$@"; then err "command failed: $*"; fi +} + +assert_nz() { + if [ -z "$1" ]; then err "found empty string: $2"; fi +} + +err() { + echo "Error: $1" >&2 + exit 1 +} + +# This is put in braces to ensure that the script does not run until it is +# downloaded completely. +{ + main "$@" || exit 1 +}