#!/bin/sh # rampart curl-pipe installer # ========================================================================= # curl -fsSL https://rampart.dev/install.sh | sh # # Detects platform and either downloads the matching rampart-install # bundle and runs it, or refuses with a specific reason. # # Detection rules (matched against the official build names found in # /usr/local/rampart-build on each build machine): # # Linux x86_64 + glibc >= 2.36 -> debian12-x86_64 # Linux aarch64 + glibc >= 2.31 -> raspberry_pi_os-bullseye-aarch64 # Darwin arm64 + macOS >= 11.0 -> macos_big_sur-arm64 # FreeBSD amd64 + version >= 14.0 -> FreeBSD-14 # # Anything else exits with a clear message ("system too old", "musl # libc unsupported", etc.). This script is intentionally POSIX sh, # not bash; it has to run on every Alpine, FreeBSD and macOS shell. # # Environment overrides: # RAMPART_VERSION pin a specific version instead of fetching latest # RAMPART_BASE_URL for staging/mirror testing (default rampart.dev) # RAMPART_NO_RUN download + verify only; print the path and exit # ========================================================================= set -eu # ----- configuration -------------------------------------------------- # Production install script. DEFAULT_BASE points at downloads/latest, # which is a server-side symlink to the current rampart-/ release # directory -- mkrp's production publish updates the symlink. install.sh # itself stays static and only changes when its own logic does. # Testing channel users get a separate install-testing.sh whose # DEFAULT_BASE is rampart.dev/downloads/testing. # RAMPART_BASE_URL overrides per-invocation for mirror/staging tests. DEFAULT_BASE="https://rampart.dev/downloads/latest" BASE=${RAMPART_BASE_URL:-$DEFAULT_BASE} DEFAULT_VERSION_FALLBACK="0.7.0" # ----- colours -------------------------------------------------------- if [ -t 1 ]; then bold=$(printf '\033[1m'); red=$(printf '\033[31m') grn=$(printf '\033[32m'); ylw=$(printf '\033[33m'); rst=$(printf '\033[0m') else bold=; red=; grn=; ylw=; rst= fi info() { printf "%s%s%s\n" "$grn" "[install]" "$rst $*"; } warn() { printf "%s%s%s\n" "$ylw" "[install]" "$rst $*" >&2; } die() { printf "%s%s%s\n" "$red" "[error]" "$rst $*" >&2; exit 1; } # ----- platform detection -------------------------------------------- uname_s=$(uname -s 2>/dev/null || echo unknown) uname_m=$(uname -m 2>/dev/null || echo unknown) # Helper: version compare; succeeds iff $1 >= $2 (dotted version strings). version_ge() { [ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | tail -1)" = "$1" ] } detect_linux_libc() { # We use three probes in descending order of reliability. The first # one to return a glibc version wins. All three also reject # musl / gcompat / Alpine-without-sgerrand setups, since none of # them produce a "glibc ..." or "GNU C Library" string. libc_ver="" # 1) getconf GNU_LIBC_VERSION -- present on every glibc system going # back many years. Single-line output, no loader invocation, # works on aarch64 loaders too old to honour `--version`. if command -v getconf >/dev/null 2>&1; then out=$(LC_ALL=C getconf GNU_LIBC_VERSION 2>/dev/null || true) case "$out" in *glibc*|*GLIBC*|*"GNU libc"*|*"GNU C Library"*) libc_ver=$(echo "$out" | grep -oE '[0-9]+\.[0-9]+' | head -1) [ -n "$libc_ver" ] && return 0 ;; esac fi # 2) `ldd --version` -- works almost everywhere; output formatting # varies a bit between distros. out=$(LC_ALL=C ldd --version 2>/dev/null | head -1 || true) case "$out" in *glibc*|*GLIBC*|*"GNU libc"*|*"GNU C Library"*) libc_ver=$(echo "$out" | grep -oE '[0-9]+\.[0-9]+' | head -1) [ -n "$libc_ver" ] && return 0 ;; esac # 3) Loader probe -- newer glibc only. Older aarch64 loaders # (e.g. Pi OS bullseye) treat `--version` as a filename and # fail noisily; that case is already covered by 1) or 2) above. for ld in /lib64/ld-linux-x86-64.so.2 \ /lib/ld-linux-aarch64.so.1; do [ -e "$ld" ] || continue out=$(LC_ALL=C "$ld" --version 2>/dev/null | head -1 || true) case "$out" in *glibc*|*GLIBC*|*"GNU libc"*|*"GNU C Library"*) libc_ver=$(echo "$out" | grep -oE '[0-9]+\.[0-9]+' | head -1) [ -n "$libc_ver" ] && return 0 ;; esac done [ -n "$libc_ver" ] } detect_platform() { case "$uname_s" in Linux) if ! detect_linux_libc; then cat >&2 <= $min." fi platform=debian12-x86_64 ;; aarch64|arm64) min=2.31 if ! version_ge "$libc_ver" "$min"; then die "System glibc $libc_ver is older than $min." \ "Linux aarch64 builds require glibc >= $min." fi platform=raspberry_pi_os-bullseye-aarch64 ;; *) die "Unsupported Linux architecture: $uname_m" ;; esac ;; Darwin) case "$uname_m" in arm64) macver=$(sw_vers -productVersion 2>/dev/null || echo 0) macmajor=${macver%%.*} case "$macmajor" in ''|*[!0-9]*) die "Could not parse macOS version: $macver" ;; esac if [ "$macmajor" -lt 11 ]; then die "macOS $macver is older than 11.0 (Big Sur)." \ "Newer rampart builds require macOS >= 11." fi platform=macos_big_sur-arm64 ;; x86_64) die "macOS Intel (x86_64) is not currently a supported build target." ;; *) die "Unsupported macOS architecture: $uname_m" ;; esac ;; FreeBSD) case "$uname_m" in amd64|x86_64) rel=$(uname -r 2>/dev/null || echo 0) major=${rel%%.*} case "$major" in ''|*[!0-9]*) die "Could not parse FreeBSD version: $rel" ;; esac if [ "$major" -lt 14 ]; then die "FreeBSD $rel is older than 14.0." \ "Current builds target FreeBSD 14." fi platform=FreeBSD-14 ;; *) die "Unsupported FreeBSD architecture: $uname_m" ;; esac ;; *) die "Unsupported OS: $uname_s" ;; esac } # ----- pick a download helper ---------------------------------------- pick_fetcher() { if command -v curl >/dev/null 2>&1; then fetch="curl -fsSL --proto =https" fetch_to() { curl -fSL --proto =https "$1" -o "$2"; } elif command -v wget >/dev/null 2>&1; then fetch="wget -q -O -" fetch_to() { wget -q -O "$2" "$1"; } else die "Neither curl nor wget is available; cannot download." fi } # ----- main flow ------------------------------------------------------ info "Detecting platform..." detect_platform info " OS / arch : $uname_s / $uname_m" case "$uname_s" in Linux) info " glibc : $libc_ver" ;; Darwin) info " macOS : $macver" ;; FreeBSD) info " release : $rel" ;; esac info " matched build : $platform" pick_fetcher # Determine version by scraping BASE's autoindex for any # rampart-install-- filename and pulling the # version out of it. Both production and testing channels are flat # directories (downloads/rampart-/ and downloads/testing/ # respectively), so the same scrape works for both. if [ "${RAMPART_VERSION:-}" = "" ]; then info "Discovering version from $BASE/..." index=$($fetch "$BASE/" 2>/dev/null || true) RAMPART_VERSION=$(printf "%s" "$index" | grep -oE "rampart-install-[0-9]+\.[0-9]+\.[0-9]+-${platform}" | head -1 | sed -e 's/^rampart-install-//' -e "s/-${platform}\$//") if [ -z "$RAMPART_VERSION" ]; then warn "Could not find a bundle in $BASE/, falling back to $DEFAULT_VERSION_FALLBACK" RAMPART_VERSION="$DEFAULT_VERSION_FALLBACK" fi fi info " version : $RAMPART_VERSION" bundle="rampart-install-${RAMPART_VERSION}-${platform}" bundle_url="$BASE/${bundle}" sha_url="${bundle_url}.sha1" target="./$bundle" # Always start with a fresh inode. On macOS arm64, AMFI caches its # verdict per-inode; if a previous attempt to exec this binary was # killed (signature mismatch, stripped xattrs, half-baked state), the # next exec on the SAME inode also gets killed -- even with identical # bytes -- because the kill is cached. curl overwrites file contents # in place without changing the inode, so we have to rm explicitly. # Same reason after the install: leave nothing behind that a re-run # might inherit. rm -f "$target" "${target}.sha1" info "Downloading bundle..." info " url : $bundle_url" fetch_to "$bundle_url" "$target" \ || die "Download failed: $bundle_url" info "Downloading checksum..." fetch_to "$sha_url" "${target}.sha1" \ || die "Download failed: $sha_url" info "Verifying sha1..." expected=$(awk '{print $1}' < "${target}.sha1") if command -v sha1sum >/dev/null 2>&1; then actual=$(sha1sum "$target" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 1 "$target" | awk '{print $1}') else warn " no sha1sum / shasum found; skipping verification" actual="$expected" fi if [ "$expected" != "$actual" ]; then die "sha1 mismatch! expected $expected got $actual" fi info " sha1 OK ($(echo "$actual" | cut -c1-12)...)" chmod 0755 "$target" # Platform-specific post-download dance. case "$uname_s" in Darwin) # The bare rampart binary already carries an ad-hoc signature from # the linker at build time (required on arm64). That signature # survives the SFX-bundle cat -- AMFI validates it against the # original Mach-O bytes and ignores the appended zip. No re-sign # needed on the target. We only need to strip xattrs so Gatekeeper # doesn't quarantine the unnotarized ad-hoc-signed binary. xattr -cr "$target" 2>/dev/null || true ;; esac if [ "${RAMPART_NO_RUN:-}" != "" ]; then info "Downloaded and verified: $target" info "Run when ready: $target" exit 0 fi info "Running installer..." # Run as a child so we get control back to print the kept-bundle # notice regardless of whether the install succeeded or failed. # # Reconnect stdin to /dev/tty if we have one -- when the user invokes # us via "curl | sh", our stdin is the (already-consumed) pipe from # curl, which would feed the bundle an immediate EOF and skip every # prompt. /dev/tty exists on Linux, macOS and FreeBSD; the [ -e ] # guard means non-interactive runs (CI, etc.) just keep the pipe # stdin and rely on entry_script.js's --auto handling. # # RAMPART_PIPE_INSTALL=1 tells entry_script.js to defer its release- # notes and PATH-hint output to /tmp/rampart-post-install.txt, which # we cat after the kept-bundle line below. Without this env var the # bundle would print those blocks before our "still at" message and # the visual order would be wrong. RAMPART_PIPE_INSTALL=1 export RAMPART_PIPE_INSTALL if [ -e /dev/tty ]; then "$target" "$@" < /dev/tty else "$target" "$@" fi rc=$? # Clean up the bundle and its sha1 sidecar. See the rm-before-fetch # block above for why we don't leave the bundle around: a stale inode # with an AMFI cache entry will get the next run killed. rm -f "$target" "${target}.sha1" # Trailing release-notes + PATH-hint, deposited by entry_script.js. if [ -f /tmp/rampart-post-install.txt ]; then cat /tmp/rampart-post-install.txt rm -f /tmp/rampart-post-install.txt fi exit "$rc"