#!/usr/bin/env bash # # Installs the latest orbit release for this machine's OS and # architecture. Safe to re-run: it always fetches the current release # and overwrites whatever is already installed. # # Usage: # curl -fsSL /install.sh | bash # # Env overrides: # ORBIT_VERSION pin to a specific tag (e.g. v0.1.4) instead of latest # ORBIT_INSTALL_DIR where the binary goes (default: $HOME/.local/bin) # ORBIT_BASE_URL where releases are fetched from (for testing) # # Releases are laid out under ORBIT_BASE_URL the way scripts/publish.sh # writes them: # # latest one line, the newest version: v0.2.6 # install.sh this file # / one tar.gz per os and architecture # /checksums.txt the sha256 of every archive of that version set -euo pipefail ORBIT_INSTALL_DIR="${ORBIT_INSTALL_DIR:-$HOME/.local/bin}" ORBIT_BASE_URL="${ORBIT_BASE_URL:-https://dl.getorbit.sh}" # os_name maps a `uname -s` value to orbit's os name, or exits. os_name() { case "$1" in Darwin) echo "darwin" ;; Linux) echo "linux" ;; *) echo "orbit: unsupported OS '$1' — only macOS and Linux have builds today" >&2 return 1 ;; esac } # arch_name maps a `uname -m` value to orbit's arch name, or exits. arch_name() { case "$1" in x86_64|amd64) echo "amd64" ;; arm64|aarch64) echo "arm64" ;; *) echo "orbit: unsupported architecture '$1' — only amd64 and arm64 have builds today" >&2 return 1 ;; esac } # count_install tells getorbit.sh that a machine installed Orbit: a random # id it makes for itself, kept in ORBIT_HOME where the binary finds it, the # version, the system and the processor, and nothing else. It is sent once, # never waited on for more than three seconds, and never stops an install. # ORBIT_NO_TELEMETRY or DO_NOT_TRACK, set to anything, sends nothing. count_install() { local version="$1" os="$2" arch="$3" home id_file id if [ -n "${ORBIT_NO_TELEMETRY:-}" ] || [ -n "${DO_NOT_TRACK:-}" ]; then return 0 fi home="${ORBIT_HOME:-$HOME/.orbit}" id_file="$home/telemetry-id" id="" if [ -f "$id_file" ]; then id=$(tr -d ' \n' < "$id_file") fi if ! printf '%s' "$id" | grep -Eq '^[0-9a-f]{32}$'; then id=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n') mkdir -p "$home" && (umask 077 && printf '%s\n' "$id" > "$id_file") || return 0 fi echo "" echo "orbit: Orbit counts the machines it runs on: once a day it sends an anonymous id, its" echo " version and your system. Turn it off with ORBIT_NO_TELEMETRY=1." echo " What is sent: https://getorbit.sh/#docs-telemetry" curl -fsS -m 3 -o /dev/null -H 'content-type: application/json' \ -d "{\"id\":\"$id\",\"kind\":\"install\",\"version\":\"$version\",\"os\":\"$os\",\"arch\":\"$arch\"}" \ "${ORBIT_PING_URL:-https://getorbit.sh/api/ping}" >/dev/null 2>&1 || true } detect_os() { os_name "$(uname -s)"; } detect_arch() { arch_name "$(uname -m)"; } # resolve_version prints the tag to install: $ORBIT_VERSION if the # caller set one, otherwise the one line $ORBIT_BASE_URL/latest holds. # # curl is asked for the status rather than run with -f, so that each way # of not getting a version is said in its own sentence. curl -f prints # "curl: (22) The requested URL returned error: 404", which is true and is # not what somebody who just pasted an install command needs to read. resolve_version() { if [ -n "${ORBIT_VERSION:-}" ]; then echo "$ORBIT_VERSION" return fi local body code latest body=$(mktemp) code=$(curl -sSL -o "$body" -w '%{http_code}' "${ORBIT_BASE_URL}/latest" 2>/dev/null) || code="000" latest=$(tr -d '[:space:]' < "$body") rm -f "$body" case "$code" in 200) ;; 000) echo "orbit: could not reach ${ORBIT_BASE_URL} to ask for the latest version" >&2 return 1 ;; 404) echo "orbit: ${ORBIT_BASE_URL}/latest does not exist — no release has been published there" >&2 return 1 ;; *) echo "orbit: ${ORBIT_BASE_URL}/latest answered HTTP ${code} instead of a version" >&2 return 1 ;; esac # A 200 that is not a version is a different failure: a page of HTML from # a domain that is not pointed at the bucket yet, or an empty file. if ! printf '%s' "$latest" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then echo "orbit: ${ORBIT_BASE_URL}/latest does not hold a version" >&2 return 1 fi echo "$latest" } # archive_name prints the asset filename goreleaser publishes for a # given version, os and arch — must match .goreleaser.yml's # archives.name_template. archive_name() { local version="$1" os="$2" arch="$3" echo "orbit_${version#v}_${os}_${arch}.tar.gz" } # download_url prints the full URL of one file of a release. download_url() { local version="$1" file="$2" echo "${ORBIT_BASE_URL}/${version}/${file}" } # checksum_tool prints the sha256 command available on this machine. checksum_tool() { if command -v shasum >/dev/null 2>&1; then echo "shasum -a 256" elif command -v sha256sum >/dev/null 2>&1; then echo "sha256sum" else echo "orbit: neither shasum nor sha256sum is on this machine — cannot verify the download" >&2 exit 1 fi } main() { local os arch version archive url checksums_url work_dir tool want got os=$(detect_os) arch=$(detect_arch) version=$(resolve_version) archive=$(archive_name "$version" "$os" "$arch") echo "orbit: installing ${version} for ${os}/${arch}" work_dir=$(mktemp -d) trap 'rm -rf "$work_dir"' EXIT url=$(download_url "$version" "$archive") checksums_url=$(download_url "$version" "checksums.txt") if ! curl -fsSL "$url" -o "$work_dir/$archive" 2>/dev/null; then echo "orbit: could not download ${url}" >&2 exit 1 fi if ! curl -fsSL "$checksums_url" -o "$work_dir/checksums.txt" 2>/dev/null; then echo "orbit: could not download ${checksums_url} — refusing to install what cannot be checked" >&2 exit 1 fi tool=$(checksum_tool) want=$(grep " ${archive}\$" "$work_dir/checksums.txt" | awk '{print $1}') if [ -z "$want" ]; then echo "orbit: ${archive} is not listed in checksums.txt — refusing to install" >&2 exit 1 fi got=$($tool "$work_dir/$archive" | awk '{print $1}') if [ "$want" != "$got" ]; then echo "orbit: checksum mismatch for ${archive} — refusing to install" >&2 echo " expected: $want" >&2 echo " got: $got" >&2 exit 1 fi tar -xzf "$work_dir/$archive" -C "$work_dir" mkdir -p "$ORBIT_INSTALL_DIR" install -m 0755 "$work_dir/orbit" "$ORBIT_INSTALL_DIR/orbit" echo "orbit: installed ${version} to ${ORBIT_INSTALL_DIR}/orbit" count_install "$version" "$os" "$arch" case ":$PATH:" in *":$ORBIT_INSTALL_DIR:"*) ;; *) echo "" echo "orbit: ${ORBIT_INSTALL_DIR} is not on your PATH. Add this to your shell rc file:" echo " export PATH=\"${ORBIT_INSTALL_DIR}:\$PATH\"" ;; esac echo "" echo "Run 'orbit --help' to get started." # Exit explicitly (rather than falling off the end of the function) # so the EXIT trap above fires while work_dir is still in scope: a # trap set inside a function is process-wide, but the function's # `local` variables are torn down once it returns. If main() were # to just return here, the trap would fire afterward at top level, # where work_dir is unset — an unbound-variable error under `set # -u` on every successful install. exit 0 } if [ "${BASH_SOURCE[0]:-$0}" = "${0}" ]; then main "$@" fi