#!/bin/sh # Installs the Tembo CLI: curl -fsSL https://cli.tembo.sh | sh # Options (TEMBO_VERSION, TEMBO_INSTALL_DIR, TEMBO_DOWNLOAD_URL) are described in apps/cli/README.md. set -eu # Everything runs inside main, so a truncated download can't run half a script. main() { base_url="${TEMBO_DOWNLOAD_URL:-https://cli.tembo.sh}" install_dir="${TEMBO_INSTALL_DIR:-$HOME/.local/bin}" need uname need tar need mktemp if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then fail "curl or wget is required" fi target="$(detect_target)" archive="tembo-${target}.tar.gz" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT trap 'exit 1' INT TERM # Resolve "latest" once, so the archive and its checksum come from the same immutable release. version="${TEMBO_VERSION:-}" if [ -z "$version" ]; then download "${base_url}/latest/VERSION" "$tmp/VERSION" version="$(tr -d ' \t\r\n' <"$tmp/VERSION")" fi version="${version#v}" case "$version" in "" | *[!0-9A-Za-z.+-]*) fail "invalid version: ${version}" ;; esac url="${base_url}/releases/v${version}/${archive}" say "Downloading ${url}" download "$url" "$tmp/$archive" download "$url.sha256" "$tmp/$archive.sha256" verify_checksum "$tmp/$archive" "$tmp/$archive.sha256" tar -xzf "$tmp/$archive" -C "$tmp" mkdir -p "$install_dir" # Copy next to the destination, then rename, so a running `tembo` is replaced atomically. cp "$tmp/tembo" "$install_dir/.tembo.tmp" chmod 755 "$install_dir/.tembo.tmp" mv -f "$install_dir/.tembo.tmp" "$install_dir/tembo" say "Installed tembo $("$install_dir/tembo" --version) to ${install_dir}/tembo" case ":${PATH}:" in *":${install_dir}:"*) ;; *) path_hint "$install_dir" ;; esac say "" say "To connect this machine to Tembo, copy the pair command from" say "Settings > Devices > Add device, then run: tembo devices start" } detect_target() { os="$(uname -s)" arch="$(uname -m)" case "$arch" in x86_64 | amd64) arch="x86_64" ;; arm64 | aarch64) arch="aarch64" ;; *) fail "unsupported CPU architecture: ${arch}" ;; esac case "$os" in Darwin) # A shell running under Rosetta reports x86_64; prefer the native build. if [ "$arch" = "x86_64" ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then arch="aarch64" fi echo "${arch}-apple-darwin" ;; Linux) # Static binaries, so any distribution and libc works. echo "${arch}-unknown-linux-musl" ;; MINGW* | MSYS* | CYGWIN* | Windows_NT) fail "on Windows, download ${base_url}/latest/tembo-x86_64-pc-windows-msvc.zip and put tembo.exe on your PATH" ;; *) fail "unsupported operating system: ${os}" ;; esac } # Startup files differ by shell; macOS's default zsh doesn't read ~/.profile. path_hint() { case "$(basename "${SHELL:-sh}")" in zsh) profile=".zshrc" ;; bash) if [ "$(uname -s)" = "Darwin" ]; then profile=".bash_profile"; else profile=".bashrc"; fi ;; fish) say "" say "$1 is not on your PATH. Add it with: fish_add_path $1" return ;; *) profile=".profile" ;; esac say "" say "$1 is not on your PATH. Add it, then open a new terminal:" say " echo 'export PATH=\"$1:\$PATH\"' >> ~/${profile}" } download() { rm -f "$2" if command -v curl >/dev/null 2>&1; then curl --fail --silent --show-error --location --retry 3 --output "$2" "$1" || fail "could not download $1" else wget --quiet --tries=3 --output-document="$2" "$1" || fail "could not download $1" fi # Some curl releases exit 0 after an HTTP error when retrying. [ -s "$2" ] || fail "could not download $1" } verify_checksum() { expected="$(cut -d ' ' -f 1 <"$2")" case "$expected" in *[!0-9a-f]* | "") fail "invalid checksum file for $(basename "$1")" ;; esac [ "${#expected}" -eq 64 ] || fail "invalid checksum file for $(basename "$1")" if command -v sha256sum >/dev/null 2>&1; then actual="$(sha256sum "$1" | cut -d ' ' -f 1)" elif command -v shasum >/dev/null 2>&1; then actual="$(shasum -a 256 "$1" | cut -d ' ' -f 1)" else fail "sha256sum or shasum is required to verify the download" fi if [ "$expected" != "$actual" ]; then fail "checksum mismatch for $(basename "$1"): expected ${expected}, got ${actual}" fi } need() { command -v "$1" >/dev/null 2>&1 || fail "$1 is required" } say() { printf '%s\n' "$1" } fail() { printf 'error: %s\n' "$1" >&2 exit 1 } main "$@"