anodizer-stage-install-script 0.22.1

curl | sh installer-script generator stage for the anodizer release tool
Documentation
#!/bin/sh
set -eu

# @NAME@ — @DESCRIPTION@
# @HOMEPAGE@
#
# Installer for @REPO@ — generated by anodizer.
#
# Usage:
#   curl -fsSL @BASE_URL@/@REPO@/releases/download/<tag>/@FILENAME@ | sh
#   VERSION=@TAG_PREFIX@1.2.3 sh @FILENAME@   # pin a specific release
#   INSTALL_DIR=~/bin sh @FILENAME@           # override the install directory
#
# The script detects the host OS + architecture, downloads the matching
# release archive, verifies its sha256 checksum, and installs the binary.

REPO="@REPO@"
BASE_URL="@BASE_URL@"
BINARIES="@BINARIES@"
INSTALL_DIR="${INSTALL_DIR:-@INSTALL_DIR@}"
VERIFY_CHECKSUM="@VERIFY_CHECKSUM@"
VERSION="${VERSION:-}"

info() { printf '%s\n' "@NAME@-install: $*" >&2; }
die() { printf '%s\n' "@NAME@-install: error: $*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

fetch() {
	# fetch <url> <dest>
	if have curl; then
		curl -fsSL "$1" -o "$2"
	elif have wget; then
		wget -qO "$2" "$1"
	else
		die "need curl or wget to download files"
	fi
}

fetch_stdout() {
	# fetch_stdout <url>
	if have curl; then
		curl -fsSL "$1"
	elif have wget; then
		wget -qO- "$1"
	else
		die "need curl or wget to download files"
	fi
}

detect_os() {
	case "$(uname -s)" in
@DETECT_OS_CASES@
		*) echo "unsupported" ;;
	esac
}

detect_arch() {
	case "$(uname -m)" in
@DETECT_ARCH_CASES@
		*) echo "unsupported" ;;
	esac
}

install_binary() {
	# install_binary <src-path> <binary-name>
	_src="$1"
	_name="$2"
	dir="$INSTALL_DIR"
	if [ -w "$dir" ]; then
		install -m 0755 "$_src" "$dir/$_name"
	elif have sudo; then
		info "installing to $dir (requires sudo)"
		sudo install -m 0755 "$_src" "$dir/$_name"
	else
		dir="$HOME/.local/bin"
		info "no write access to $INSTALL_DIR and no sudo; installing to $dir"
		mkdir -p "$dir"
		install -m 0755 "$_src" "$dir/$_name"
		case ":$PATH:" in
			*":$dir:"*) ;;
			*) info "note: $dir is not in your PATH; add it to run $_name" ;;
		esac
	fi
	info "installed $_name $version to $dir/$_name"
}

# The GitHub REST API base: github.com is served from api.github.com; a
# GitHub Enterprise base is served from <base>/api/v3.
if [ "$BASE_URL" = "https://github.com" ]; then
	API_BASE="https://api.github.com"
else
	API_BASE="$BASE_URL/api/v3"
fi

if [ -z "$VERSION" ]; then
	info "resolving latest release of $REPO"
	body=$(fetch_stdout "$API_BASE/repos/$REPO/releases/latest") \
		|| die "failed to query the latest release of $REPO"
	VERSION=$(printf '%s\n' "$body" |
		grep -m1 '"tag_name"' |
		sed -e 's/.*"tag_name"[[:space:]]*:[[:space:]]*"//' -e 's/".*//')
	[ -n "$VERSION" ] || die "could not determine the latest release of $REPO"
fi

# The bare version (tag prefix stripped) names the asset; the tag names the
# release. Stripping the configured prefix accepts both `@TAG_PREFIX@1.2.3`
# and a bare `1.2.3` in VERSION.
version="${VERSION#@TAG_PREFIX@}"
tag="@TAG_PREFIX@${version}"

# Assigned here (not in the prologue) because the checksums filename embeds
# ${version}, which is only known after the version is resolved above.
CHECKSUMS="@CHECKSUMS@"

OS="$(detect_os)"
ARCH="$(detect_arch)"

ARCHIVE=""
case "${OS}-${ARCH}" in
@ASSET_CASES@
	*) die "no prebuilt binary for ${OS}-${ARCH} (supported: @SUPPORTED_PLATFORMS@)" ;;
esac

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT INT TERM

base="$BASE_URL/$REPO/releases/download/$tag"

info "downloading $ARCHIVE"
fetch "$base/$ARCHIVE" "$tmp/$ARCHIVE" || die "failed to download $ARCHIVE"

if [ "$VERIFY_CHECKSUM" = "true" ]; then
	# Prefer the combined checksums file; fall back to a per-asset .sha256 sidecar.
	expected=""
	if fetch "$base/$CHECKSUMS" "$tmp/$CHECKSUMS" 2>/dev/null; then
		expected=$(awk -v f="$ARCHIVE" '$2 == f { print $1 }' "$tmp/$CHECKSUMS" | head -n1)
	fi
	if [ -z "$expected" ] && fetch "$base/$ARCHIVE.sha256" "$tmp/$ARCHIVE.sha256" 2>/dev/null; then
		expected=$(awk '{ print $1 }' "$tmp/$ARCHIVE.sha256" | head -n1)
	fi
	[ -n "$expected" ] || die "no checksum published for $ARCHIVE"

	if have sha256sum; then
		actual=$(sha256sum "$tmp/$ARCHIVE" | awk '{ print $1 }')
	elif have shasum; then
		actual=$(shasum -a 256 "$tmp/$ARCHIVE" | awk '{ print $1 }')
	else
		die "need sha256sum or shasum to verify the download"
	fi

	[ "$expected" = "$actual" ] ||
		die "checksum mismatch for $ARCHIVE (expected $expected, got $actual)"
	info "checksum verified"
fi

info "extracting $ARCHIVE"
case "$ARCHIVE" in
	*.tar.gz | *.tgz) tar -xzf "$tmp/$ARCHIVE" -C "$tmp" ;;
	*.zip)
		have unzip || die "need unzip to extract $ARCHIVE"
		unzip -q "$tmp/$ARCHIVE" -d "$tmp"
		;;
	*) die "unknown archive format: $ARCHIVE" ;;
esac

for BINARY in $BINARIES; do
	bin=$(find "$tmp" -type f -name "$BINARY" | head -n1)
	[ -n "$bin" ] || die "binary $BINARY not found inside $ARCHIVE"
	chmod +x "$bin"
	install_binary "$bin" "$BINARY"
done