|
| 1 | +#!/bin/sh |
| 2 | +# |
| 3 | +# install.sh — Install (or update) the standalone Diffbot `db` CLI binary. |
| 4 | +# |
| 5 | +# Detects your platform, downloads the matching binary from the latest GitHub |
| 6 | +# release, verifies its SHA256 checksum, and installs it to a bin directory on |
| 7 | +# your PATH. Re-running upgrades an existing install in place. |
| 8 | +# |
| 9 | +# Quick start: |
| 10 | +# curl -fsSL https://raw.githubusercontent.com/diffbot/diffbot-python/main/install.sh | sh |
| 11 | +# |
| 12 | +# Options (also settable via env var): |
| 13 | +# --version <tag> DB_VERSION Release tag to install (default: latest). |
| 14 | +# --bin-dir <dir> DB_INSTALL_DIR Install location (default: ~/.local/bin). |
| 15 | +# --repo <owner/repo> DB_REPO Source repo (default: diffbot/diffbot-python). |
| 16 | +# -h, --help |
| 17 | +# |
| 18 | +# Supported platforms: linux/darwin on x86_64 (x64/amd64) and aarch64 (arm64). |
| 19 | +set -eu |
| 20 | + |
| 21 | +REPO="${DB_REPO:-diffbot/diffbot-python}" |
| 22 | +VERSION="${DB_VERSION:-latest}" |
| 23 | +BIN_DIR="${DB_INSTALL_DIR:-${HOME}/.local/bin}" |
| 24 | +BIN_NAME="db" |
| 25 | + |
| 26 | +err() { printf 'error: %s\n' "$*" >&2; exit 1; } |
| 27 | +info() { printf '%s\n' "$*" >&2; } |
| 28 | + |
| 29 | +# True if $1 is a Python console-script shim (pip / uv tool / pipx / venv), |
| 30 | +# i.e. a text shebang wrapper pointing at python. Our standalone binary is an |
| 31 | +# ELF/Mach-O file and never starts with "#!", so this never matches it. |
| 32 | +is_python_console_script() { |
| 33 | + [ -f "$1" ] || return 1 |
| 34 | + [ "$(dd if="$1" bs=2 count=1 2>/dev/null)" = "#!" ] || return 1 |
| 35 | + head -n1 "$1" 2>/dev/null | grep -q 'python' |
| 36 | +} |
| 37 | + |
| 38 | +while [ $# -gt 0 ]; do |
| 39 | + case "$1" in |
| 40 | + --version) VERSION="$2"; shift 2 ;; |
| 41 | + --version=*) VERSION="${1#*=}"; shift ;; |
| 42 | + --bin-dir) BIN_DIR="$2"; shift 2 ;; |
| 43 | + --bin-dir=*) BIN_DIR="${1#*=}"; shift ;; |
| 44 | + --repo) REPO="$2"; shift 2 ;; |
| 45 | + --repo=*) REPO="${1#*=}"; shift ;; |
| 46 | + -h | --help) sed -n '2,/^[^#]/p' "$0" | sed 's/^# \{0,1\}//;$d'; exit 0 ;; |
| 47 | + *) err "unknown argument: $1 (try --help)" ;; |
| 48 | + esac |
| 49 | +done |
| 50 | + |
| 51 | +# --- Detect platform ------------------------------------------------------- |
| 52 | +os="$(uname -s)" |
| 53 | +case "$os" in |
| 54 | + Linux) os="linux" ;; |
| 55 | + Darwin) os="darwin" ;; |
| 56 | + *) err "unsupported OS: $os (this installer supports Linux and macOS)" ;; |
| 57 | +esac |
| 58 | + |
| 59 | +arch="$(uname -m)" |
| 60 | +case "$arch" in |
| 61 | + x86_64 | x64 | amd64) arch="x86_64" ;; |
| 62 | + aarch64 | arm64) arch="aarch64" ;; |
| 63 | + *) err "unsupported architecture: $arch" ;; |
| 64 | +esac |
| 65 | + |
| 66 | +asset="${BIN_NAME}-${os}-${arch}" |
| 67 | + |
| 68 | +# --- Pick a download tool -------------------------------------------------- |
| 69 | +if command -v curl >/dev/null 2>&1; then |
| 70 | + download() { curl -fsSL "$1" -o "$2"; } |
| 71 | + fetch() { curl -fsSL "$1"; } |
| 72 | +elif command -v wget >/dev/null 2>&1; then |
| 73 | + download() { wget -qO "$2" "$1"; } |
| 74 | + fetch() { wget -qO - "$1"; } |
| 75 | +else |
| 76 | + err "need curl or wget to download the binary" |
| 77 | +fi |
| 78 | + |
| 79 | +# --- Resolve the release tag ---------------------------------------------- |
| 80 | +if [ "$VERSION" = "latest" ]; then |
| 81 | + info "Resolving latest release of ${REPO}..." |
| 82 | + api="https://api.github.com/repos/${REPO}/releases/latest" |
| 83 | + VERSION="$(fetch "$api" | grep -m1 '"tag_name"' \ |
| 84 | + | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')" |
| 85 | + [ -n "$VERSION" ] || err "could not determine the latest release tag from ${api}" |
| 86 | +fi |
| 87 | + |
| 88 | +base="https://github.com/${REPO}/releases/download/${VERSION}" |
| 89 | +info "Installing ${asset} from ${REPO} ${VERSION}" |
| 90 | + |
| 91 | +# --- Download binary + checksum into a temp dir ---------------------------- |
| 92 | +tmp="$(mktemp -d)" |
| 93 | +trap 'rm -rf "$tmp"' EXIT INT TERM |
| 94 | + |
| 95 | +info "Downloading binary..." |
| 96 | +download "${base}/${asset}" "${tmp}/${asset}" \ |
| 97 | + || err "failed to download ${base}/${asset} (no build for ${os}/${arch} in ${VERSION}?)" |
| 98 | + |
| 99 | +info "Downloading checksum..." |
| 100 | +download "${base}/${asset}.sha256" "${tmp}/${asset}.sha256" \ |
| 101 | + || err "failed to download checksum ${base}/${asset}.sha256" |
| 102 | + |
| 103 | +# --- Verify checksum ------------------------------------------------------- |
| 104 | +info "Verifying SHA256 checksum..." |
| 105 | +( |
| 106 | + cd "$tmp" |
| 107 | + if command -v sha256sum >/dev/null 2>&1; then |
| 108 | + sha256sum -c "${asset}.sha256" |
| 109 | + elif command -v shasum >/dev/null 2>&1; then |
| 110 | + shasum -a 256 -c "${asset}.sha256" |
| 111 | + else |
| 112 | + err "need sha256sum or shasum to verify the download" |
| 113 | + fi |
| 114 | +) >/dev/null 2>&1 || err "checksum verification failed — refusing to install" |
| 115 | +info "Checksum OK." |
| 116 | + |
| 117 | +# --- Install (atomically), updating any existing install ------------------- |
| 118 | +mkdir -p "$BIN_DIR" |
| 119 | +target="${BIN_DIR}/${BIN_NAME}" |
| 120 | + |
| 121 | +if [ -e "$target" ]; then |
| 122 | + old="$("$target" --version 2>/dev/null || echo "unknown")" |
| 123 | + if is_python_console_script "$target"; then |
| 124 | + info "" |
| 125 | + info "Warning: ${target} looks like a pip/uv-managed 'db' entry point (${old})," |
| 126 | + info "not a binary installed by this script. Overwriting it replaces that launcher," |
| 127 | + info "but your Python package manager still treats the file as its own — a later" |
| 128 | + info "'pip install --upgrade' / 'uv tool upgrade' could clobber it again, and an" |
| 129 | + info "uninstall would delete it. To avoid the conflict, remove the managed copy first:" |
| 130 | + info " pip uninstall diffbot-python # or: uv tool uninstall diffbot-python" |
| 131 | + info "" |
| 132 | + info "Proceeding to overwrite ${target}..." |
| 133 | + else |
| 134 | + info "Updating existing install at ${target} (${old})" |
| 135 | + fi |
| 136 | +else |
| 137 | + info "Installing to ${target}" |
| 138 | +fi |
| 139 | + |
| 140 | +chmod +x "${tmp}/${asset}" |
| 141 | +# mv within the same filesystem is atomic; fall back to cp for cross-device. |
| 142 | +mv -f "${tmp}/${asset}" "$target" 2>/dev/null || cp -f "${tmp}/${asset}" "$target" |
| 143 | + |
| 144 | +new="$("$target" --version 2>/dev/null || echo "$VERSION")" |
| 145 | +info "" |
| 146 | +info "Installed ${new} -> ${target}" |
| 147 | + |
| 148 | +# --- PATH guidance --------------------------------------------------------- |
| 149 | +case ":${PATH}:" in |
| 150 | + *":${BIN_DIR}:"*) ;; |
| 151 | + *) |
| 152 | + info "" |
| 153 | + info "Note: ${BIN_DIR} is not on your PATH. Add it, e.g.:" |
| 154 | + info " export PATH=\"${BIN_DIR}:\$PATH\"" |
| 155 | + ;; |
| 156 | +esac |
| 157 | + |
| 158 | +# Warn if a different `db` shadows the one we just installed. |
| 159 | +existing="$(command -v "$BIN_NAME" 2>/dev/null || true)" |
| 160 | +if [ -n "$existing" ] && [ "$existing" != "$target" ]; then |
| 161 | + info "" |
| 162 | + info "Note: another '${BIN_NAME}' is first on your PATH and will take precedence:" |
| 163 | + info " ${existing}" |
| 164 | + if is_python_console_script "$existing"; then |
| 165 | + info " (it looks pip/uv-managed; remove it with 'pip uninstall diffbot-python'" |
| 166 | + info " or 'uv tool uninstall diffbot-python', or put ${BIN_DIR} earlier on PATH.)" |
| 167 | + fi |
| 168 | +fi |
| 169 | + |
| 170 | +info "" |
| 171 | +info "Run '${BIN_NAME} --help' to get started." |
0 commit comments