feat(install): curl|sh installer script (ADR-0055)
scripts/install.sh — POSIX sh, shellcheck-clean: detects uname OS/arch -> target triple (Linux uses the static musl build; Windows rejected with a Scoop/winget pointer), resolves the latest release (or RDBMS_VERSION), downloads the asset + its .sha256 and verifies it, installs to ~/.local/bin with a PATH hint. RDBMS_OS/RDBMS_ARCH + --print-target are testing seams. Verified end-to-end against the live public v0.1.0 (all mappings, pinned + latest, checksum incl. tamper-rejection, install+run). ADR-0055 + README index; plan-doc step 2 done + decisions recorded (crates.io=yes, releases public, tracking via doc+ADR).
This commit is contained in:
Executable
+166
@@ -0,0 +1,166 @@
|
||||
#!/bin/sh
|
||||
# install.sh — download and install a prebuilt rdbms-playground binary.
|
||||
#
|
||||
# Quick start (Linux / macOS):
|
||||
# curl -fsSL https://git.lazyeval.net/oli/rdbms-playground/raw/branch/main/scripts/install.sh | sh
|
||||
#
|
||||
# What it does: detects your OS + CPU, downloads the matching release binary
|
||||
# from the Gitea releases (Linux uses the fully-static musl build), verifies
|
||||
# its SHA-256 checksum, and installs it to ~/.local/bin.
|
||||
#
|
||||
# Environment overrides:
|
||||
# RDBMS_VERSION install a specific tag (e.g. v0.2.0) instead of the latest
|
||||
# RDBMS_INSTALL_DIR install directory (default: $HOME/.local/bin)
|
||||
# RDBMS_OS force the OS (testing): Linux | Darwin
|
||||
# RDBMS_ARCH force the CPU (testing): x86_64 | aarch64
|
||||
#
|
||||
# Flags:
|
||||
# --print-target print the resolved target triple and exit (no download)
|
||||
# -h, --help print this help and exit
|
||||
#
|
||||
# Notes:
|
||||
# * Windows is not installable via this script (the binary is a .exe) —
|
||||
# use Scoop/winget (planned) or download the .exe from the releases page.
|
||||
# * macOS: a curl download is not quarantined by Gatekeeper, so the binary
|
||||
# runs without extra steps. (Developer-ID signing + notarization is a
|
||||
# separate, planned improvement for browser downloads.)
|
||||
#
|
||||
# POSIX sh — no bashisms, so it runs under the `sh` of `curl | sh`.
|
||||
|
||||
set -eu
|
||||
|
||||
REPO_BASE="https://git.lazyeval.net/oli/rdbms-playground"
|
||||
API_BASE="https://git.lazyeval.net/api/v1/repos/oli/rdbms-playground"
|
||||
BIN_NAME="rdbms-playground"
|
||||
PRINT_TARGET=0
|
||||
|
||||
err() {
|
||||
printf 'install: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
info() { printf '%s\n' "$1" >&2; }
|
||||
|
||||
usage() {
|
||||
# Lines 2..(first blank) of this file are the human-readable header.
|
||||
sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
# Resolve the Rust target triple for the current (or forced) platform.
|
||||
# Linux -> <arch>-unknown-linux-musl (the fully-static build)
|
||||
# macOS -> <arch>-apple-darwin
|
||||
detect_target() {
|
||||
os="${RDBMS_OS:-$(uname -s)}"
|
||||
arch="${RDBMS_ARCH:-$(uname -m)}"
|
||||
case "$os" in
|
||||
Linux | linux) os_part="unknown-linux-musl" ;;
|
||||
Darwin | darwin | macos | macOS) os_part="apple-darwin" ;;
|
||||
MINGW* | MSYS* | CYGWIN* | *Windows* | *windows*)
|
||||
err "Windows is not supported by this installer — use Scoop/winget (planned) or download the .exe from $REPO_BASE/releases" ;;
|
||||
*) err "unsupported operating system: $os" ;;
|
||||
esac
|
||||
case "$arch" in
|
||||
x86_64 | amd64) arch_part="x86_64" ;;
|
||||
aarch64 | arm64) arch_part="aarch64" ;;
|
||||
*) err "unsupported CPU architecture: $arch" ;;
|
||||
esac
|
||||
printf '%s-%s' "$arch_part" "$os_part"
|
||||
}
|
||||
|
||||
# Download $1 to file $2 (curl or wget).
|
||||
download() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$1" -o "$2"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO "$2" "$1"
|
||||
else
|
||||
err "need either curl or wget on PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# Fetch $1 to stdout (curl or wget).
|
||||
fetch() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$1"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO- "$1"
|
||||
else
|
||||
err "need either curl or wget on PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# The release tag to install: $RDBMS_VERSION if set, else the latest release.
|
||||
resolve_version() {
|
||||
if [ -n "${RDBMS_VERSION:-}" ]; then
|
||||
printf '%s' "$RDBMS_VERSION"
|
||||
return
|
||||
fi
|
||||
json=$(fetch "$API_BASE/releases/latest") ||
|
||||
err "could not query the latest release from $API_BASE"
|
||||
# Portable JSON scrape (no jq): the latest-release object carries exactly
|
||||
# one "tag_name": "<tag>" field.
|
||||
tag=$(printf '%s' "$json" |
|
||||
grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' |
|
||||
head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||
[ -n "$tag" ] || err "could not parse the latest release tag"
|
||||
printf '%s' "$tag"
|
||||
}
|
||||
|
||||
# Verify file $1 against sha256 sidecar $2 (format: "<hash> <name>").
|
||||
verify_checksum() {
|
||||
expected=$(awk '{print $1; exit}' "$2")
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
actual=$(sha256sum "$1" | awk '{print $1}')
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
actual=$(shasum -a 256 "$1" | awk '{print $1}')
|
||||
else
|
||||
info "warning: no sha256 tool found — skipping checksum verification"
|
||||
return 0
|
||||
fi
|
||||
[ "$expected" = "$actual" ] ||
|
||||
err "checksum mismatch (expected $expected, got $actual) — refusing to install"
|
||||
}
|
||||
|
||||
main() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--print-target) PRINT_TARGET=1 ;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*) err "unknown argument: $1 (try --help)" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
target=$(detect_target)
|
||||
if [ "$PRINT_TARGET" = "1" ]; then
|
||||
printf '%s\n' "$target"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
version=$(resolve_version)
|
||||
asset="$BIN_NAME-$version-$target"
|
||||
url="$REPO_BASE/releases/download/$version/$asset"
|
||||
dir="${RDBMS_INSTALL_DIR:-$HOME/.local/bin}"
|
||||
|
||||
tmp=$(mktemp -d 2>/dev/null) || err "could not create a temporary directory"
|
||||
trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||
|
||||
info "downloading $asset ..."
|
||||
download "$url" "$tmp/$BIN_NAME" || err "download failed: $url"
|
||||
download "$url.sha256" "$tmp/$BIN_NAME.sha256" || err "checksum download failed: $url.sha256"
|
||||
verify_checksum "$tmp/$BIN_NAME" "$tmp/$BIN_NAME.sha256"
|
||||
|
||||
mkdir -p "$dir" || err "could not create install directory: $dir"
|
||||
chmod +x "$tmp/$BIN_NAME"
|
||||
mv "$tmp/$BIN_NAME" "$dir/$BIN_NAME" || err "could not install to $dir"
|
||||
info "installed $BIN_NAME $version -> $dir/$BIN_NAME"
|
||||
|
||||
case ":${PATH:-}:" in
|
||||
*":$dir:"*) ;;
|
||||
*) info "note: $dir is not on your PATH. Add it, e.g.: export PATH=\"$dir:\$PATH\"" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user