feat(install): default to the latest release — three channels, one installer (#32)

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/<tag>.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 <noreply@anthropic.com>
This commit is contained in:
dan-claude-bot 2026-07-18 20:57:13 +00:00
parent b6205d81c5
commit 834f0504d6
3 changed files with 99 additions and 17 deletions

View file

@ -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 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/<ref>`.
> **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`): 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 **Re-running is a safe converge.** Installing a version you already have
changes nothing and says so (`RIG_REINSTALL=1` replaces that version's 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 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 "re-run any time to upgrade" stays true, and now means *upgrade to the
installed as the way back: latest release*; every version you had stays installed as the way back:
```sh ```sh
rig versions # what is installed, which is current, which is running 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 > **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 > above.** The seed preinstalls rig via its curl installer, which resolves
> `RIG_REPO`/`RIG_REF` (default `heavy-duty/rig@main`, branches only — rig > `RIG_REPO`/`RIG_REF` — and since rig#32 the installer defaults to the
> cuts no tags yet). That inverts the install edge on this page: rig installs > **latest release**, with `RIG_REF=<tag>` the pin and `RIG_REF=main` the
> box on host-class machines, and box guests now install rig — both tracking > dev channel. Until rig cuts 0.1.0 there is no release to resolve, so the
> a moving `main` until a release flow exists (rig#32). `RIG_REPO`/`RIG_REF` > seed must set `RIG_REF=main` explicitly (the default channel fails loudly
> are the pin points the day there is something to pin to, or point them at a > rather than falling back). That inverts the install edge on this page:
> frozen branch of your own fork. The seed side of this edge is box#81's to > rig installs box on host-class machines, and box guests now install rig.
> document. > `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 ### The identity model

View file

@ -95,8 +95,10 @@ commands:
install/upgrade: install/upgrade:
curl -fsSL https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh | bash 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 the latest RELEASE (RIG_REF=<tag> pins one, RIG_REF=main
installs side by side at <root>/versions/<v> and becomes the default. tracks the development tree). Re-run any time: an installed version
converges (no-op), a new one installs side by side at
<root>/versions/<v> and becomes the default.
EOF EOF
} }

View file

@ -3,6 +3,15 @@ set -euo pipefail
# rig installer — intended for: curl -fsSL .../install.sh | bash # 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=<tag> that release, pinned (a tag outranks a branch of the
# same name)
# RIG_REF=<branch> the development tree, e.g. RIG_REF=main
#
# Downloads the rig repo tarball and installs it into the VERSIONED layout # Downloads the rig repo tarball and installs it into the VERSIONED layout
# under $DEST (box#79's layout, ported — heavy-duty/rig#35): # under $DEST (box#79's layout, ported — heavy-duty/rig#35):
# #
@ -25,7 +34,7 @@ set -euo pipefail
# review. # review.
REPO="${RIG_REPO:-heavy-duty/rig}" 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}" DEST="${RIG_HOME:-$HOME/.local/share/rig}"
if [ "$(id -u)" -eq 0 ]; then if [ "$(id -u)" -eq 0 ]; then
BINDIR="${RIG_BIN:-/usr/local/bin}" 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." 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 <owner/repo> — 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/<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 <owner/repo> <ref> — 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 ----------------------------------------------------------- # --- prerequisites -----------------------------------------------------------
# curl only when something must be downloaded — a local RIG_INSTALL_SOURCE # 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. # 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 if [ -n "${RIG_INSTALL_SOURCE:-}" ]; then
SRCDESC="local source $RIG_INSTALL_SOURCE" SRCDESC="local source $RIG_INSTALL_SOURCE"
else else
SRCDESC="$REPO@$REF" SRCDESC="$REPO@${REF:-latest-release}" # refined once the tag resolves
fi fi
# Flip $DEST/current to versions/<v> atomically: build the new link beside it, # Flip $DEST/current to versions/<v> 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" die "RIG_INSTALL_SOURCE is set but is neither a directory nor a tarball: $SRC"
fi fi
else 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" INSTALLED_FROM="$REPO@$REF"
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
log "installing rig ($REPO@$REF)" log "installing rig ($REPO@$REF)"
log "downloading $URL" got=""
curl -fsSL "$URL" -o "$TMPDIR/rig.tar.gz" \ for URL in "${urls[@]}"; do
|| die "failed to download $URL" 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" log "extracting archive"
tar -xzf "$TMPDIR/rig.tar.gz" -C "$TMPDIR" \ tar -xzf "$TMPDIR/rig.tar.gz" -C "$TMPDIR" \