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:
parent
b6205d81c5
commit
834f0504d6
3 changed files with 99 additions and 17 deletions
39
README.md
39
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
|
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
|
||||||
|
|
||||||
|
|
|
||||||
6
bin/rig
6
bin/rig
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
71
install.sh
71
install.sh
|
|
@ -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" \
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue