diff --git a/README.md b/README.md index 919da9a..47ca7cf 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,16 @@ every command into `~/.local/bin`. `CLI_TOOLS_HOME` and `CLI_TOOLS_PREFIX` override both. If a checkout already owns these command names, the installer updates *that* one rather than cloning a second copy beside it. +The installer also puts the official [Stripe CLI](https://docs.stripe.com/cli) +on PATH, because the payment work needs it on every box and installing it by +hand is the setup step that never happens. It is vendored under +`~/.local/share/cli-tools/vendor/stripe` and linked into `~/.local/bin`, so the +name exists once; a `stripe` already on PATH from somewhere else is left alone. +`STRIPE_CLI_VERSION` pins a version, `CLI_TOOLS_SKIP_STRIPE=1` skips it. It +downloads the release tarball and checks the published sha256, and if any of +that fails it warns and moves on rather than failing an install that otherwise +worked. Authenticate it once with `stripe login`. + With moshcode on the box, the same thing: ```sh diff --git a/install.sh b/install.sh index abd1dd3..7aa65a5 100755 --- a/install.sh +++ b/install.sh @@ -13,6 +13,8 @@ # CLI_TOOLS_REPO clone URL # CLI_TOOLS_BRANCH branch to track (default master) # CLI_TOOLS_FORCE set to 1 to take over links owned by another checkout +# CLI_TOOLS_SKIP_STRIPE set to 1 to skip the Stripe CLI +# STRIPE_CLI_VERSION pin the Stripe CLI (default: latest release) set -eu @@ -101,9 +103,143 @@ LINK_ARGS="" # shellcheck disable=SC2086 CLI_TOOLS_PREFIX="$PREFIX" node "$HOME_DIR/scripts/install-links.mjs" $LINK_ARGS +# ── Stripe CLI ─────────────────────────────────────────────────────────────── +# +# Not one of this repo's commands: it is the official binary from +# stripe/stripe-cli. It lives here because the payment work needs it on every +# box, and "install the Stripe CLI first" is exactly the setup step that +# quietly never happens. +# +# Vendored under $HOME_DIR/vendor/stripe for the same reason codeburn is: the +# name should exist once. If some other stripe is already on PATH, that one is +# left alone and nothing is linked over it. +# +# This runs AFTER the commands are linked, and warns rather than dying. A +# GitHub outage or an unknown architecture should not fail an install that has +# otherwise already succeeded. + +# Last release verified against this installer. Used when the version cannot be +# resolved from the API, which is mostly rate limiting on a shared IP. +STRIPE_FALLBACK_VERSION="1.50.4" + +stripe_platform() { + # Asset names look like stripe_1.50.4_linux_x86_64.tar.gz — note that the + # macOS ones say mac-os, and that arm64 is arm64 on both. + os="$(uname -s)" + arch="$(uname -m)" + case "$os" in + Linux) os="linux" ;; + Darwin) os="mac-os" ;; + *) return 1 ;; + esac + case "$arch" in + x86_64 | amd64) arch="x86_64" ;; + aarch64 | arm64) arch="arm64" ;; + *) return 1 ;; + esac + printf '%s_%s\n' "$os" "$arch" +} + +stripe_sha256() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | cut -d' ' -f1 + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | cut -d' ' -f1 + else + return 1 + fi +} + +install_stripe() { + [ "${CLI_TOOLS_SKIP_STRIPE:-0}" = "1" ] && return 0 + + vendor="$HOME_DIR/vendor/stripe" + + # Someone else's stripe on PATH wins. Ours would only shadow it depending on + # the order of two directories, which is not a thing to leave to chance. + existing="$(command -v stripe 2>/dev/null || true)" + if [ -n "$existing" ] && [ "$existing" != "$PREFIX/stripe" ]; then + say " stripe already on PATH at $existing — left alone." + return 0 + fi + + command -v curl >/dev/null 2>&1 || { say " skipped: curl is required."; return 0; } + command -v tar >/dev/null 2>&1 || { say " skipped: tar is required."; return 0; } + + platform="$(stripe_platform)" || { + say " skipped: no Stripe CLI build for $(uname -s)/$(uname -m)." + return 0 + } + + version="${STRIPE_CLI_VERSION:-}" + if [ -z "$version" ]; then + # Plain grep/sed rather than jq, which is not a dependency anywhere else + # in this installer. + version="$(curl -fsSL https://api.github.com/repos/stripe/stripe-cli/releases/latest 2>/dev/null \ + | sed -n 's/.*"tag_name": *"v\{0,1\}\([^"]*\)".*/\1/p' | head -1)" + [ -n "$version" ] || version="$STRIPE_FALLBACK_VERSION" + fi + version="${version#v}" + + # Already at the wanted version? Then there is nothing to download. + if [ -x "$vendor/stripe" ] && [ "$($vendor/stripe --version 2>/dev/null | sed -n 's/.*version \([0-9.]*\).*/\1/p')" = "$version" ]; then + say " stripe $version already installed." + return 0 + fi + + case "$platform" in + mac-os_*) sums="stripe-mac-checksums.txt" ;; + *) sums="stripe-linux-checksums.txt" ;; + esac + tarball="stripe_${version}_${platform}.tar.gz" + base="https://github.com/stripe/stripe-cli/releases/download/v${version}" + + tmp="$(mktemp -d)" || { say " skipped: could not create a temp dir."; return 0; } + + if ! curl -fsSL "$base/$tarball" -o "$tmp/$tarball"; then + say " skipped: could not download $tarball." + rm -rf "$tmp" + return 0 + fi + + # The checksum comes from the same host as the tarball, so this is not a + # supply-chain guarantee — it catches a truncated or corrupted download, + # which is the failure this actually sees. + if curl -fsSL "$base/$sums" -o "$tmp/sums.txt" 2>/dev/null; then + want="$(grep " $tarball\$" "$tmp/sums.txt" 2>/dev/null | cut -d' ' -f1)" + got="$(stripe_sha256 "$tmp/$tarball" 2>/dev/null || true)" + if [ -n "$want" ] && [ -n "$got" ] && [ "$want" != "$got" ]; then + say " skipped: checksum mismatch on $tarball." + rm -rf "$tmp" + return 0 + fi + fi + + if ! tar -xzf "$tmp/$tarball" -C "$tmp" stripe 2>/dev/null; then + say " skipped: could not extract stripe from $tarball." + rm -rf "$tmp" + return 0 + fi + + mkdir -p "$vendor" + # mv onto the old binary rather than writing in place: a running stripe + # keeps its inode, and the replacement is atomic. + mv "$tmp/stripe" "$vendor/stripe" + chmod +x "$vendor/stripe" + rm -rf "$tmp" + + mkdir -p "$PREFIX" + ln -sf "$vendor/stripe" "$PREFIX/stripe" + say " stripe $version -> $PREFIX/stripe" +} + +say "Installing the Stripe CLI" +install_stripe + # install-links.mjs already warns when $PREFIX is not on PATH, so there is # deliberately no second warning here. say "" say "Installed. Try:" say " cli-tools list # what landed, and what is on PATH" say " cli-tools aliases --install # the moshcode pit aliases" +say " stripe login # authenticate the Stripe CLI"