2026-07-10 20:56:05 +00:00
#!/usr/bin/env bash
set -euo pipefail
2026-07-11 08:25:48 +00:00
# rig installer — intended for: curl -fsSL .../install.sh | bash
2026-07-10 20:56:05 +00:00
#
2026-07-18 20:57:13 +00:00
# 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
#
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# Downloads the rig repo tarball and installs it into the VERSIONED layout
# under $DEST (box#79's layout, ported — heavy-duty/rig#35):
#
# $DEST/versions/<version>/ one full tree per installed version
# $DEST/current -> versions/<version> the default version
# $BINDIR/rig -> $DEST/current/bin/rig the PATH entry
#
# Versions install side by side: `rig versions` lists them, `rig use <v>`
# switches the default, `rig uninstall` removes them. Re-running with an
# already-installed version is a converging no-op (RIG_REINSTALL=1 replaces
# that version's tree); a NEW version installs beside the old one and becomes
# the default — with a WARNING when this host is bootstrapped (/etc/rig/role
# exists), because switching the rig under a converged host changes what a
# re-converge would do. rig warns where box refuses: rig has no boxes to
# protect, and the operator flipping versions on purpose is the common case.
# A pre-versioning flat tree is migrated in place, so upgrading is seamless.
#
2026-07-24 01:43:15 +00:00
# RIG_INSTALL_SOURCE=<dir-or-tarball> is the LOCAL channel, a supported input
# like RIG_REF (#106): installs from that tree instead of downloading — CI's
# install-lifecycle job and the test suites use it, so what lands is the code
# under review. A path that is neither refuses by name, never falls back to
# a download.
2026-07-10 20:56:05 +00:00
2026-07-11 08:25:48 +00:00
REPO = " ${ RIG_REPO :- heavy -duty/rig } "
2026-07-18 20:57:13 +00:00
REF = " ${ RIG_REF :- } " # empty = the latest release, resolved below
2026-07-18 22:36:05 +00:00
# cloud-init's runcmd runs with NO $HOME in the environment, and under set -u
# the expansions just below turned that into a death instead of an install —
# found live by box#88's seed, which pins HOME=/root as its own scar (rig#39).
# Derive it from the effective user instead: getent knows every user's home,
# root included. (Inline error: die() is not defined this early on purpose —
# this guard must run before any path is derived from $HOME.)
if [ -z " ${ HOME :- } " ] ; then
# '|| true': under pipefail a no-answer getent would kill the script here
# with ITS exit code, instead of falling through to the named refusal.
HOME = " $( getent passwd " $( id -u) " | cut -d: -f6 || true ) " ; export HOME
if [ -z " $HOME " ] ; then
printf "rig-install: ERROR: \$HOME is unset and getent knows no home for uid %s — set HOME and re-run\n" " $( id -u) " >& 2
exit 1
fi
fi
2026-07-11 08:25:48 +00:00
DEST = " ${ RIG_HOME :- $HOME /.local/share/rig } "
2026-07-10 20:56:05 +00:00
if [ " $( id -u) " -eq 0 ] ; then
2026-07-11 08:25:48 +00:00
BINDIR = " ${ RIG_BIN :- /usr/local/bin } "
2026-07-10 20:56:05 +00:00
else
2026-07-11 08:25:48 +00:00
BINDIR = " ${ RIG_BIN :- $HOME /.local/bin } "
2026-07-10 20:56:05 +00:00
fi
2026-07-11 08:25:48 +00:00
log( ) { printf 'rig-install: %s\n' " $* " ; }
warn( ) { printf 'rig-install: WARNING: %s\n' " $* " >& 2; }
die( ) { printf 'rig-install: ERROR: %s\n' " $* " >& 2; exit 1; }
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# A version is a DIRECTORY NAME under versions/ — nothing else. One strict
# gate for every caller that builds a path from one (the installer's new_ver,
# migration's flat_ver, and bin/rig's 'use'/single-version uninstall): only
# [A-Za-z0-9._+-], no leading '.' or '-'. That forbids '/', '..'-escapes,
# spaces and option-lookalikes by construction — a crafted version dies HERE,
# never in an rm -rf or an ln. bin/rig carries a byte-identical copy;
# test/cli.sh diffs the two so the gates cannot drift.
valid_version( ) {
case " $1 " in
'' | .*| -*) return 1 ; ;
*[ !A-Za-z0-9._+-] *) return 1 ; ;
esac
return 0
}
# The flip gate, rig's shape (#35): box refuses version flips under existing
# boxes; rig's stake is the converged HOST — /etc/rig/role marks a box that
# bootstrap has made into something. Switching the default rig under it
# changes what a re-converge would do, which is worth a warning, not a
# refusal: there is no user state a flip can strand, and flipping versions on
# a bootstrapped host is the normal upgrade. RIG_ROLE_MARKER overrides the
# path so tests point it at fixtures (repo precedent: the coolify marker
# gate). bin/rig carries a byte-identical copy; test/cli.sh diffs the two.
warn_bootstrapped( ) { # $1 = what is about to happen
local marker = " ${ RIG_ROLE_MARKER :- /etc/rig/role } "
[ -e " $marker " ] || return 0
warn " this host is bootstrapped ( $( head -n1 " $marker " 2>/dev/null || echo " role marker at $marker " ) ) "
warn " $1 changes what a re-converge (rig bootstrap, users apply) would do — proceeding. "
}
2026-07-18 20:57:13 +00:00
# --- 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 "
}
2026-07-10 20:56:05 +00:00
# --- prerequisites -----------------------------------------------------------
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# 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.
if [ -z " ${ RIG_INSTALL_SOURCE :- } " ] ; then
command -v curl >/dev/null 2>& 1 || die "curl is required but was not found."
fi
2026-07-10 20:56:05 +00:00
command -v tar >/dev/null 2>& 1 || die "tar is required but was not found."
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
if [ -n " ${ RIG_INSTALL_SOURCE :- } " ] ; then
SRCDESC = " local source $RIG_INSTALL_SOURCE "
else
2026-07-18 20:57:13 +00:00
SRCDESC = " $REPO @ ${ REF :- latest -release } " # refined once the tag resolves
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
fi
# Flip $DEST/current to versions/<v> atomically: build the new link beside it,
# rename over. Plain ln -sfn is unlink+create — a window where current names
# nothing and a concurrent 'rig' invocation dies mid-chain. bin/rig's cmd_use
# flips with the same pattern.
flip_current( ) {
ln -sfn " versions/ $1 " " $DEST /current.new. $$ "
mv -Tf " $DEST /current.new. $$ " " $DEST /current "
}
# --- migrate a pre-versioning flat install -----------------------------------
# The old installer put the tree FLAT at $DEST (bin/rig directly under it).
# Move such a tree to versions/<its-VERSION> BEFORE anything else, so the
# upgrade is seamless and the version comparison below sees the truth. The
# move is two renames inside one parent directory — no copying, no window with
# no install — and the operator's tree is preserved bit for bit. Pre-VERSION
# trees (every flat rig install, rig#32) migrate as 0.0.0-unknown.
if [ -e " $DEST /bin/rig " ] && [ ! -d " $DEST /versions " ] ; then
flat_ver = " $( cat " $DEST /VERSION " 2>/dev/null || echo 0.0.0-unknown) "
# The flat tree's VERSION is data from disk, not from this installer — the
# same trust boundary as the new_ver check, so the same gate: a corrupted
# (or hostile) VERSION must not steer the mv/ln below out of versions/.
valid_version " $flat_ver " || die " the flat install's VERSION is not a sane directory name: ' $flat_ver ' — fix $DEST /VERSION (one line, e.g. 0.1.0), then re-run "
log " found a pre-versioning flat install at $DEST (version $flat_ver ) — migrating it into the versioned layout "
staging = " $DEST .migrating. $$ "
mv " $DEST " " $staging "
mkdir -p " $DEST /versions "
mv " $staging " " $DEST /versions/ $flat_ver "
flip_current " $flat_ver "
mkdir -p " $BINDIR "
ln -sfn " $DEST /current/bin/rig " " $BINDIR /rig "
log " migrated: it now lives at $DEST /versions/ $flat_ver (still current) "
fi
2026-07-10 20:56:05 +00:00
# --- temp workspace ----------------------------------------------------------
TMPDIR = " $( mktemp -d) "
cleanup( ) { rm -rf " $TMPDIR " ; }
trap cleanup EXIT
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# --- acquire the tree --------------------------------------------------------
if [ -n " ${ RIG_INSTALL_SOURCE :- } " ] ; then
SRC = " $RIG_INSTALL_SOURCE "
INSTALLED_FROM = " local: $SRC "
if [ -d " $SRC " ] ; then
log " copying local tree $SRC "
mkdir -p " $TMPDIR /tree "
# tar, not cp -a: --exclude=.git, so a working checkout never carries its
# VCS state (or its size) into the install tree.
tar -C " $SRC " --exclude= .git -cf - . | tar -xf - -C " $TMPDIR /tree "
EXTRACTED = " $TMPDIR /tree "
elif [ -f " $SRC " ] ; then
log " extracting local tarball $SRC "
tar -xzf " $SRC " -C " $TMPDIR " || die " failed to extract $SRC "
EXTRACTED = " $( find " $TMPDIR " -mindepth 1 -maxdepth 1 -type d | head -n1) "
else
die " RIG_INSTALL_SOURCE is set but is neither a directory nor a tarball: $SRC "
fi
else
2026-07-18 20:57:13 +00:00
# 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 "
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
INSTALLED_FROM = " $REPO @ $REF "
log " installing rig ( $REPO @ $REF ) "
2026-07-18 20:57:13 +00:00
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) "
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
log "extracting archive"
tar -xzf " $TMPDIR /rig.tar.gz " -C " $TMPDIR " \
|| die "failed to extract archive"
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# GitHub names the archive's top dir <repo>-<ref> — deriving that name is
# guesswork (it broke for real at box's repo rename). The tarball has
# exactly ONE top-level directory: take the directory, whatever it is
# called, and let the bin/rig check below judge whether it is the right tree.
EXTRACTED = " $( find " $TMPDIR " -mindepth 1 -maxdepth 1 -type d | head -n1) "
fi
[ -n " ${ EXTRACTED :- } " ] || die " could not find the source tree in $SRCDESC "
[ -f " $EXTRACTED /bin/rig " ] || die " source does not contain bin/rig — is $SRCDESC correct? "
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# The tree's own VERSION file names the directory it lands in — the version IS
# the identity of what is being installed, and 'rig versions' lists these names.
new_ver = " $( cat " $EXTRACTED /VERSION " 2>/dev/null || true ) "
[ -n " $new_ver " ] || die "source has no VERSION file — cannot install it as a version"
valid_version " $new_ver " || die " the source's VERSION is not a sane directory name: ' $new_ver ' "
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
set_exec( ) { # $1 = a rig tree: the executable bits install.sh owns
chmod +x " $1 /bin/rig "
if [ -d " $1 /commands " ] ; then
find " $1 /commands " -name '*.sh' -exec chmod +x { } +
fi
}
2026-07-10 20:56:05 +00:00
2026-07-25 13:38:54 +00:00
# snapshot_templates <rig-tree> — best-effort install-time cache of the exact
# registry pin carried by that tree. The pin remains the sole source of truth;
# the directory name makes a stale snapshot invisible after an upgrade.
# Failure is deliberately a warning: rig itself is still a complete install,
# and templates_resolve preserves the live-fetch fallback.
snapshot_templates( ) {
local tree = " $1 " pin repo url got = "" unpack top snapshot
pin = " $( sed -n 's/^RIG_TEMPLATES_PIN=//p' " $tree /commands/lib/templates.sh " 2>/dev/null | head -n1 || true ) "
if [ -z " $pin " ] ; then
warn "installed tree carries no RIG_TEMPLATES_PIN; template registry snapshot skipped."
return 0
fi
repo = " ${ RIG_TEMPLATES_REPO :- heavy -duty/rig-templates } "
snapshot = " $tree /templates@ $pin "
if ! command -v curl >/dev/null 2>& 1; then
warn " curl is unavailable; template registry snapshot $repo @ $pin was not installed (converge will retry the live fetch). "
return 0
fi
unpack = " $TMPDIR /templates-unpack "
rm -rf " $unpack "
mkdir -p " $unpack "
log " downloading template registry snapshot $repo @ $pin "
for url in \
" https://github.com/ $repo /archive/refs/tags/ $pin .tar.gz " \
" https://github.com/ $repo /archive/refs/heads/ $pin .tar.gz " \
" https://github.com/ $repo /archive/ $pin .tar.gz " ; do
if curl -fsSL " $url " -o " $TMPDIR /templates.tar.gz " 2>/dev/null; then got = " $url " ; break; fi
done
if [ -z " $got " ] ; then
warn " could not fetch template registry snapshot $repo @ $pin ; rig installed without it (converge will retry the live fetch). "
return 0
fi
if ! tar -xzf " $TMPDIR /templates.tar.gz " -C " $unpack " ; then
warn " could not extract template registry snapshot from $got ; rig installed without it (converge will retry the live fetch). "
return 0
fi
set -- " $unpack " /*/
if ! { [ $# -eq 1 ] && [ -d " $1 " ] ; } ; then
warn " template registry snapshot from $got has an unexpected archive shape; rig installed without it (converge will retry the live fetch). "
return 0
fi
top = " ${ 1 %/ } "
if [ -z " $( find " $top " -mindepth 2 -maxdepth 2 -type f -name template.env -print -quit 2>/dev/null) " ] ; then
warn " template registry snapshot from $got has no definitions; rig installed without it (converge will retry the live fetch). "
return 0
fi
rm -rf " $snapshot "
mv " $top " " $snapshot "
log " template registry snapshot installed: $repo @ $pin "
}
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# --- install into $DEST/versions/<version> -----------------------------------
VDIR = " $DEST /versions/ $new_ver "
newly_installed = 0
if [ -d " $VDIR " ] ; then
if [ -n " ${ RIG_REINSTALL :- } " ] ; then
# Replace THIS version's tree, as atomically as two renames allow — never
# a partial overlay of new files onto an old tree.
log " RIG_REINSTALL=1 — replacing the installed $new_ver tree "
stage = " $VDIR .new. $$ " ; old = " $VDIR .old. $$ "
rm -rf " $stage " " $old "
2026-07-25 13:38:54 +00:00
snapshot_templates " $EXTRACTED "
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
set_exec " $EXTRACTED "
mv " $EXTRACTED " " $stage "
# Swap by renames, delete LAST: rm-then-move leaves a hole the whole
# length of the delete where current -> this version resolves to nothing.
mv " $VDIR " " $old "
mv " $stage " " $VDIR "
rm -rf " $old "
printf '%s\n' " $INSTALLED_FROM " > " $VDIR /INSTALLED_FROM "
log " reinstalled $new_ver "
else
cur_from = " $( cat " $VDIR /INSTALLED_FROM " 2>/dev/null || echo '<unknown source>' ) "
log " rig $new_ver is already installed ( $cur_from ) — nothing to do. "
log "(RIG_REINSTALL=1 replaces this version's tree; 'rig versions' lists what is installed.)"
fi
else
log " installing $new_ver into $VDIR "
mkdir -p " $DEST /versions "
2026-07-25 13:38:54 +00:00
snapshot_templates " $EXTRACTED "
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
set_exec " $EXTRACTED "
mv " $EXTRACTED " " $VDIR "
newly_installed = 1
# Record WHAT was installed, so a caller can assert it got what it asked
# for — an installer invoked with stale env vars silently falls back to the
# defaults, and INSTALLED_FROM is how that lie gets caught.
printf '%s\n' " $INSTALLED_FROM " > " $VDIR /INSTALLED_FROM "
fi
# --- which version is the default? -------------------------------------------
# 'current' is the tracked default; flipping it is the ONLY step that changes
# what an operator's `rig` runs. A fresh host (or a dangling current) is
# claimed outright; an upgrade flips — with the bootstrapped-host warning —
# because a re-run that silently left you on the old version would make
# "re-run any time to upgrade" a lie. Judged from versions/<v> itself
# (readlink -f), never from what a wedged current claims.
cur = " $( readlink -f " $DEST /current " 2>/dev/null || true ) "
want = " $( readlink -f " $VDIR " ) "
if [ -z " $cur " ] || [ ! -d " $cur " ] ; then
flip_current " $new_ver "
log " default version: $new_ver "
elif [ " $cur " = " $want " ] ; then
: # already the default — nothing to flip
elif [ " $newly_installed " -eq 0 ] ; then
# A converge/no-op (or RIG_REINSTALL) of a version that is NOT the default
# never moves the default — a re-run must change nothing; switching is
# 'rig use', a deliberate act.
log " the default stays $( basename " $cur " ) — 'rig use $new_ver ' switches. "
else
old_ver = " $( basename " $cur " ) "
warn_bootstrapped " switching the default rig version ( $old_ver -> $new_ver ) "
flip_current " $new_ver "
log " default version switched: $old_ver -> $new_ver ('rig use $old_ver ' switches back) "
fi
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# --- put rig on PATH ---------------------------------------------------------
# ln -sfn converges, and that includes HEALING: a stale or dangling
# $BINDIR/rig (say, its tree half-removed by hand) must never block or wedge
# an install — it gets repointed at the current chain, whatever it said before.
2026-07-10 20:56:05 +00:00
mkdir -p " $BINDIR "
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
ln -sfn " $DEST /current/bin/rig " " $BINDIR /rig "
log " linked $BINDIR /rig -> $DEST /current/bin/rig "
2026-07-10 20:56:05 +00:00
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
# --- PATH check --------------------------------------------------------------
2026-07-10 20:56:05 +00:00
case " : $PATH : " in
*" : $BINDIR : " *) : ; ;
*)
warn " $BINDIR is not on your PATH. "
warn " add: export PATH=\" $BINDIR :\$PATH\" "
; ;
esac
feat(install): versioned installs and a real uninstall — box#79's layout, ported
install.sh now lands every version at <root>/versions/<v> (each tree
carrying its own VERSION + INSTALLED_FROM), tracks the default through an
atomically-flipped 'current' symlink, and converges instead of clobbering:
a same-version re-run is a no-op that says so, RIG_REINSTALL=1 replaces
that version's tree by two renames (delete last), and a new version
installs side by side. A pre-versioning flat tree is migrated in place —
two renames, preserved bit for bit, VERSION-less trees as 0.0.0-unknown.
bin/rig grows the table verbs: 'rig versions' (current + running marked),
'rig use <v>' (atomic flip, asserted effective through the PATH chain),
'rig uninstall [<v>|--all]' — which ENDS with an absence assert: every
removed path re-checked, survivors exit 1 as 'uninstall INCOMPLETE' by
name. One strict valid_version gate guards every place a version string
becomes a path (byte-identical copies in bin/rig and install.sh, diffed by
the suite so they cannot drift). Plus the VERSION file and 'rig --version'
(rig#32's first item, folded in minimally — rig main had neither).
The flip gate is rig's own shape, deliberately: box refuses flips under
existing boxes; rig's stake is the converged host, so a flip (upgrade,
'rig use', full uninstall) on a host where /etc/rig/role exists WARNS and
proceeds — no user state to strand, and upgrading a bootstrapped host is
the normal case.
The suite drives REAL installer runs (RIG_INSTALL_SOURCE against throwaway
RIG_HOME/RIG_BIN roots): fresh install, converge, reinstall, side-by-side
upgrade, use/rollback, both migrations, hostile flat VERSION, wedged-
symlink healing, the marker warn gate (RIG_ROLE_MARKER fixtures), both
uninstalls and the INCOMPLETE scream — driven, not grepped.
Closes #35.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:17 +00:00
log " done ( $SRCDESC , version $new_ver ) — try: rig --help "