From 834f0504d65a979fce9bb743bb8ee6223e498d41 Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sat, 18 Jul 2026 20:57:13 +0000 Subject: [PATCH] =?UTF-8?q?feat(install):=20default=20to=20the=20latest=20?= =?UTF-8?q?release=20=E2=80=94=20three=20channels,=20one=20installer=20(#3?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RIG_REF unset now means the latest RELEASE: the tag is resolved by following the releases/latest redirect and reading the Location header (no API, no token), and the download is archive/refs/tags/.tar.gz. An explicit RIG_REF tries refs/tags first — a pin must outrank a branch that shares its name — and falls back to refs/heads, which keeps RIG_REF=main the dev channel. "Re-run any time to upgrade" now means upgrade to the latest release. Transitional, and loud about it: rig has no release until 0.1.0 is cut, so the default channel dies naming RIG_REF=main as the way to install today — never a silent fallback to main, because "I installed the latest release" must not quietly mean "I installed whatever main was that second". test/release.sh drives all three channels through the real install.sh with a stubbed curl (no network in CI), and the resolve/ candidate functions are extracted and exercised the valid_version way. Co-Authored-By: Claude Fable 5 --- README.md | 39 +++++++++++++++++++++++------- bin/rig | 6 +++-- install.sh | 71 +++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e1c2cdf..ea16bdd 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,26 @@ takes arguments, does its work, and stores no credential, ever. curl -fsSL https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh | bash ``` +That installs the **latest release**: the installer resolves the newest tag +by following GitHub's `releases/latest` redirect (no API, no token) and +downloads that tag's source tarball — which, for a pure-bash tree, *is* the +package. Three channels from the same script; `RIG_REF` picks: + +```sh +curl -fsSL .../install.sh | bash # the latest release +curl -fsSL .../install.sh | RIG_REF=0.1.0 bash # pinned to a release +curl -fsSL .../install.sh | RIG_REF=main bash # the development tree +``` + +A tag outranks a branch of the same name (the pin must win); anything that +is not a tag falls back to `refs/heads/`. + +> **Transitional, until 0.1.0 is cut** (right after rig#32 lands): rig has +> no GitHub release yet, so the default channel has nothing to resolve — +> it **fails loudly** naming `RIG_REF=main` as the way to install today, +> and never silently falls back to main. Once 0.1.0 exists, the plain +> `curl | bash` above is the normal path. + The layout, under the install root (`~/.local/share/rig`): ``` @@ -30,8 +50,8 @@ $BINDIR/rig -> current/bin/rig the PATH entry, riding the chain **Re-running is a safe converge.** Installing a version you already have changes nothing and says so (`RIG_REINSTALL=1` replaces that version's tree); a **new** version installs side by side and becomes the default — so -"re-run any time to upgrade" stays true, and every version you had stays -installed as the way back: +"re-run any time to upgrade" stays true, and now means *upgrade to the +latest release*; every version you had stays installed as the way back: ```sh rig versions # what is installed, which is current, which is running @@ -337,13 +357,14 @@ command is exactly who that refusal catches (it names the new spelling). > **The rig install in the seed is unpinned — same honesty as the box note > above.** The seed preinstalls rig via its curl installer, which resolves -> `RIG_REPO`/`RIG_REF` (default `heavy-duty/rig@main`, branches only — rig -> cuts no tags yet). That inverts the install edge on this page: rig installs -> box on host-class machines, and box guests now install rig — both tracking -> a moving `main` until a release flow exists (rig#32). `RIG_REPO`/`RIG_REF` -> are the pin points the day there is something to pin to, or point them at a -> frozen branch of your own fork. The seed side of this edge is box#81's to -> document. +> `RIG_REPO`/`RIG_REF` — and since rig#32 the installer defaults to the +> **latest release**, with `RIG_REF=` the pin and `RIG_REF=main` the +> dev channel. Until rig cuts 0.1.0 there is no release to resolve, so the +> seed must set `RIG_REF=main` explicitly (the default channel fails loudly +> rather than falling back). That inverts the install edge on this page: +> rig installs box on host-class machines, and box guests now install rig. +> `RIG_REPO`/`RIG_REF` are the pin points, or point them at a frozen branch +> of your own fork. The seed side of this edge is box#81's to document. ### The identity model diff --git a/bin/rig b/bin/rig index 011b7f1..98a2190 100755 --- a/bin/rig +++ b/bin/rig @@ -95,8 +95,10 @@ commands: install/upgrade: curl -fsSL https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh | bash - Re-run any time: an installed version converges (no-op), a new one - installs side by side at /versions/ and becomes the default. + Installs the latest RELEASE (RIG_REF= pins one, RIG_REF=main + tracks the development tree). Re-run any time: an installed version + converges (no-op), a new one installs side by side at + /versions/ and becomes the default. EOF } diff --git a/install.sh b/install.sh index ad00104..6e97110 100644 --- a/install.sh +++ b/install.sh @@ -3,6 +3,15 @@ set -euo pipefail # rig installer — intended for: curl -fsSL .../install.sh | bash # +# Three channels from this one script (heavy-duty/rig#32; box#83's design): +# +# RIG_REF unset the latest RELEASE — the tag is resolved from the +# releases/latest redirect, the download is that tag's +# source tarball (which IS the package) +# RIG_REF= that release, pinned (a tag outranks a branch of the +# same name) +# RIG_REF= the development tree, e.g. RIG_REF=main +# # Downloads the rig repo tarball and installs it into the VERSIONED layout # under $DEST (box#79's layout, ported — heavy-duty/rig#35): # @@ -25,7 +34,7 @@ set -euo pipefail # review. REPO="${RIG_REPO:-heavy-duty/rig}" -REF="${RIG_REF:-main}" +REF="${RIG_REF:-}" # empty = the latest release, resolved below DEST="${RIG_HOME:-$HOME/.local/share/rig}" if [ "$(id -u)" -eq 0 ]; then BINDIR="${RIG_BIN:-/usr/local/bin}" @@ -67,6 +76,32 @@ warn_bootstrapped() { # $1 = what is about to happen warn "$1 changes what a re-converge (rig bootstrap, users apply) would do — proceeding." } +# --- the release channels (#32; box#83's design, near-verbatim) -------------- +# resolve_latest_tag — print the latest RELEASE tag, resolved by +# following the releases/latest redirect and reading the Location header +# (curl's %{redirect_url} is that header, parsed): no API, no token, no +# rate-limit pain. A repo with no releases redirects to /releases — not to +# /releases/tag/ — so this returns 1 there instead of inventing a ref, +# and the CALLER owns the loud story. test/release.sh extracts this function +# (awk, the valid_version idiom) and drives it against a stubbed curl. +resolve_latest_tag() { + local loc + loc="$(curl -fsSI -o /dev/null -w '%{redirect_url}' "https://github.com/$1/releases/latest")" || return 1 + case "$loc" in + */releases/tag/?*) printf '%s\n' "${loc##*/releases/tag/}" ;; + *) return 1 ;; + esac +} + +# ref_candidate_urls — the download candidates for an +# explicit RIG_REF, in order: refs/tags first, so a tag always outranks a +# branch that happens to share its name (the pin must win), refs/heads as +# the fallback that keeps RIG_REF=main the dev channel. +ref_candidate_urls() { + printf 'https://github.com/%s/archive/refs/tags/%s.tar.gz\n' "$1" "$2" + printf 'https://github.com/%s/archive/refs/heads/%s.tar.gz\n' "$1" "$2" +} + # --- prerequisites ----------------------------------------------------------- # curl only when something must be downloaded — a local RIG_INSTALL_SOURCE # needs none, which is what lets test/cli.sh drive REAL installs offline. @@ -78,7 +113,7 @@ command -v tar >/dev/null 2>&1 || die "tar is required but was not found." if [ -n "${RIG_INSTALL_SOURCE:-}" ]; then SRCDESC="local source $RIG_INSTALL_SOURCE" else - SRCDESC="$REPO@$REF" + SRCDESC="$REPO@${REF:-latest-release}" # refined once the tag resolves fi # Flip $DEST/current to versions/ atomically: build the new link beside it, @@ -138,12 +173,36 @@ if [ -n "${RIG_INSTALL_SOURCE:-}" ]; then die "RIG_INSTALL_SOURCE is set but is neither a directory nor a tarball: $SRC" fi else + # Which ref? RIG_REF unset means the latest release — and while no release + # exists (rig cuts its first, 0.1.0, right after #32 lands), that channel + # must FAIL, loudly and with the way out, never silently fall back to + # main: "I installed the latest release" must not quietly mean "I + # installed whatever main was that second". + if [ -z "$REF" ]; then + log "resolving the latest release of $REPO" + if ! REF="$(resolve_latest_tag "$REPO")"; then + warn "could not resolve the latest release of $REPO — either no release exists yet, or GitHub was unreachable." + warn "(rig has no release until 0.1.0 is cut — rig#32. Until then, install the development tree explicitly.)" + die "set RIG_REF: e.g. curl -fsSL https://raw.githubusercontent.com/$REPO/main/install.sh | RIG_REF=main bash" + fi + log "latest release: $REF" + urls=("https://github.com/$REPO/archive/refs/tags/$REF.tar.gz") + else + mapfile -t urls < <(ref_candidate_urls "$REPO" "$REF") + fi + SRCDESC="$REPO@$REF" INSTALLED_FROM="$REPO@$REF" - URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz" log "installing rig ($REPO@$REF)" - log "downloading $URL" - curl -fsSL "$URL" -o "$TMPDIR/rig.tar.gz" \ - || die "failed to download $URL" + got="" + for URL in "${urls[@]}"; do + log "downloading $URL" + if curl -fsSL "$URL" -o "$TMPDIR/rig.tar.gz"; then + got="$URL" + break + fi + done + [ -n "$got" ] \ + || die "failed to download $REPO@$REF — not a tag and not a branch (tried refs/tags then refs/heads)" log "extracting archive" tar -xzf "$TMPDIR/rig.tar.gz" -C "$TMPDIR" \