Files
rdbms-playground/scripts/install.sh
T
claude@clouddev1 ef99e6c676
ci / gate (push) Successful in 3m19s
website / deploy (push) Successful in 1m58s
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).
2026-06-17 19:41:34 +00:00

167 lines
5.2 KiB
Bash
Executable File

#!/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 "$@"