mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
curl | bash on macOS runs the script under /bin/bash, which is
still 3.2 (Apple has stayed there for licensing reasons since
GPLv3). In 3.2 a bare ${CURL_AUTH[@]} on an empty array under
set -u throws "CURL_AUTH[@]: unbound variable" and aborts \u2014 so
every public-repo install on macOS died on line 129 before the
tarball could even be fetched.
Bash 4+ handles empty-array expansion fine, so this only ever
bit macOS users. The standard 3.2-compatible workaround is
${CURL_AUTH[@]+"${CURL_AUTH[@]}"}, which expands to nothing
when the array is empty and to its contents otherwise. Applied
at every call site that expanded the array unconditionally.
Added a comment at the declaration explaining why the guard is
there so the next person doesn't take it back out.
Verified against /bin/bash 3.2 on macOS: the unbound-variable
error is gone; with and without GITHUB_TOKEN the script now
proceeds to the download step.
180 lines
5.9 KiB
Bash
Executable file
180 lines
5.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# zot installer.
|
|
#
|
|
# Usage:
|
|
# curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash
|
|
# curl -fsSL https://raw.githubusercontent.com/patriceckhart/zot/main/install.sh | bash -s -- v0.0.1 ~/bin
|
|
#
|
|
# Positional arguments:
|
|
# $1 version — release tag (e.g. v0.0.1). Defaults to "latest".
|
|
# $2 prefix — install directory. Defaults to the first writable
|
|
# directory in: /usr/local/bin, $HOME/.local/bin,
|
|
# $HOME/bin. Created if missing. Add it to your PATH
|
|
# if it isn't already.
|
|
#
|
|
# Environment overrides:
|
|
# ZOT_VERSION same as $1
|
|
# ZOT_PREFIX same as $2
|
|
# GITHUB_TOKEN personal access token — required while the repo is
|
|
# private, ignored once it goes public. Must have at
|
|
# least `contents:read` scope on the zot repository.
|
|
#
|
|
# The script detects your OS and architecture, downloads the matching
|
|
# archive from the GitHub release, verifies the sha256 against the
|
|
# release's checksums.txt, extracts the binary, and moves it into the
|
|
# prefix directory. No sudo unless you explicitly pick a prefix that
|
|
# needs it.
|
|
|
|
set -euo pipefail
|
|
|
|
OWNER="patriceckhart"
|
|
REPO="zot"
|
|
BINARY="zot"
|
|
|
|
VERSION="${1:-${ZOT_VERSION:-latest}}"
|
|
PREFIX="${2:-${ZOT_PREFIX:-}}"
|
|
|
|
msg() { printf "\033[1m==>\033[0m %s\n" "$*"; }
|
|
warn() { printf "\033[33mwarn:\033[0m %s\n" "$*" >&2; }
|
|
die() { printf "\033[31merror:\033[0m %s\n" "$*" >&2; exit 1; }
|
|
|
|
command -v curl >/dev/null 2>&1 || die "curl is required"
|
|
command -v tar >/dev/null 2>&1 || die "tar is required"
|
|
|
|
# CURL_AUTH is prepended to every curl invocation so private-repo
|
|
# downloads work while $GITHUB_TOKEN is set. Empty array when the repo
|
|
# is public.
|
|
#
|
|
# Note the "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" pattern at every call
|
|
# site: bash 3.2 (the default /bin/bash on macOS) treats an
|
|
# unquoted empty-array expansion as an unbound variable under
|
|
# `set -u` and aborts with "CURL_AUTH[@]: unbound variable". The
|
|
# guard expands to nothing when the array is empty and to the
|
|
# array's contents when it isn't. Bash 4+ doesn't need this, but
|
|
# the installer's primary audience is `curl | bash` on macOS.
|
|
CURL_AUTH=()
|
|
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
|
CURL_AUTH=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
|
fi
|
|
|
|
# ---- detect OS + arch ----
|
|
|
|
uname_s=$(uname -s)
|
|
uname_m=$(uname -m)
|
|
|
|
case "$uname_s" in
|
|
Linux) OS=linux ;;
|
|
Darwin) OS=darwin ;;
|
|
MINGW*|MSYS*|CYGWIN*)
|
|
die "windows detected — use install.ps1 from powershell instead"
|
|
;;
|
|
*) die "unsupported os: $uname_s" ;;
|
|
esac
|
|
|
|
case "$uname_m" in
|
|
x86_64|amd64) ARCH=amd64 ;;
|
|
arm64|aarch64) ARCH=arm64 ;;
|
|
*) die "unsupported arch: $uname_m" ;;
|
|
esac
|
|
|
|
# ---- resolve version ----
|
|
|
|
if [ "$VERSION" = "latest" ]; then
|
|
# Private-repo friendly: hit the api, grab tag_name. Falls back to
|
|
# following the /releases/latest redirect on public repos.
|
|
if [ ${#CURL_AUTH[@]} -gt 0 ]; then
|
|
VERSION=$(curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" \
|
|
"https://api.github.com/repos/${OWNER}/${REPO}/releases/latest" \
|
|
| sed -nE 's/.*"tag_name": *"([^"]+)".*/\1/p' | head -n1)
|
|
else
|
|
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
|
|
"https://github.com/${OWNER}/${REPO}/releases/latest" \
|
|
| sed -E 's|.*/tag/([^/]+).*|\1|')
|
|
fi
|
|
[ -n "$VERSION" ] || die "could not resolve latest version (set GITHUB_TOKEN if the repo is private)"
|
|
fi
|
|
|
|
case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac
|
|
VER_NUM="${VERSION#v}"
|
|
|
|
# ---- pick an install prefix ----
|
|
|
|
pick_prefix() {
|
|
local candidates=()
|
|
[ -n "$PREFIX" ] && { echo "$PREFIX"; return; }
|
|
candidates+=("/usr/local/bin")
|
|
[ -n "${HOME:-}" ] && candidates+=("$HOME/.local/bin" "$HOME/bin")
|
|
for d in "${candidates[@]}"; do
|
|
if [ -d "$d" ] && [ -w "$d" ]; then
|
|
echo "$d"
|
|
return
|
|
fi
|
|
done
|
|
# Nothing writable yet — create ~/.local/bin and use that.
|
|
if [ -n "${HOME:-}" ]; then
|
|
mkdir -p "$HOME/.local/bin"
|
|
echo "$HOME/.local/bin"
|
|
return
|
|
fi
|
|
die "no writable install prefix found; pass one as the second argument"
|
|
}
|
|
|
|
PREFIX=$(pick_prefix)
|
|
mkdir -p "$PREFIX"
|
|
|
|
# ---- download + verify + extract ----
|
|
|
|
ARCHIVE="${BINARY}_${VER_NUM}_${OS}_${ARCH}.tar.gz"
|
|
BASE_URL="https://github.com/${OWNER}/${REPO}/releases/download/${VERSION}"
|
|
ARCHIVE_URL="${BASE_URL}/${ARCHIVE}"
|
|
CHECKSUMS_URL="${BASE_URL}/checksums.txt"
|
|
|
|
TMP=$(mktemp -d)
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
msg "downloading ${ARCHIVE}"
|
|
curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" -o "$TMP/$ARCHIVE" "$ARCHIVE_URL" \
|
|
|| die "download failed: $ARCHIVE_URL (set GITHUB_TOKEN if the repo is private)"
|
|
|
|
msg "verifying checksum"
|
|
curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" -o "$TMP/checksums.txt" "$CHECKSUMS_URL" \
|
|
|| die "download failed: $CHECKSUMS_URL"
|
|
|
|
expected=$(grep " ${ARCHIVE}\$" "$TMP/checksums.txt" | awk '{print $1}' || true)
|
|
[ -n "$expected" ] || die "no checksum for $ARCHIVE in checksums.txt"
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
actual=$(sha256sum "$TMP/$ARCHIVE" | awk '{print $1}')
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
actual=$(shasum -a 256 "$TMP/$ARCHIVE" | awk '{print $1}')
|
|
else
|
|
die "no sha256 tool found (sha256sum or shasum)"
|
|
fi
|
|
|
|
[ "$expected" = "$actual" ] \
|
|
|| die "checksum mismatch: expected $expected, got $actual"
|
|
|
|
msg "extracting"
|
|
tar -xzf "$TMP/$ARCHIVE" -C "$TMP"
|
|
|
|
[ -f "$TMP/$BINARY" ] || die "archive did not contain a '$BINARY' binary"
|
|
|
|
msg "installing to $PREFIX/$BINARY"
|
|
install -m 0755 "$TMP/$BINARY" "$PREFIX/$BINARY" 2>/dev/null \
|
|
|| { cp "$TMP/$BINARY" "$PREFIX/$BINARY" && chmod 0755 "$PREFIX/$BINARY"; }
|
|
|
|
# ---- PATH hint ----
|
|
|
|
case ":$PATH:" in
|
|
*":$PREFIX:"*) ;;
|
|
*)
|
|
warn "$PREFIX is not on your PATH"
|
|
warn "add this to your shell rc file:"
|
|
warn " export PATH=\"$PREFIX:\$PATH\""
|
|
;;
|
|
esac
|
|
|
|
msg "installed $("$PREFIX/$BINARY" --version 2>/dev/null || echo zot)"
|
|
msg "run: zot (interactive tui)"
|
|
msg "run: zot --help (all flags and subcommands)"
|