feat(install): versioned installs and a real uninstall — box#79's layout, ported (#35) #36
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:bootstrap
scope:coolify
scope:db
scope:docs
scope:drill
scope:installer
scope:labels
scope:platform
scope:runner
scope:users
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/rig#36
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/versioned-installs"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #35 — box#79's proven layout, ported to rig.
What
rig's installer was pre-versioning:
rm -rf "$DEST"clobber on every re-run, noVERSION, no uninstall verb, no way back from a bad main. This PR ports the design box#79 landed and reviewed:Layout (install.sh):
RIG_REINSTALL=1replaces that version's tree via two renames, delete last; a new version installs side by side.0.0.0-unknown— covered by a dedicated test.currentflips everywhere (link built beside +mv -Tf); symlink healing judged fromversions/<v>itself, never from a wedged link — a dangling or stale$BINDIR/riggets repointed, never blocks.valid_versiongate everywhere a version string becomes a path (installer'snew_ver, migration'sflat_ver,use, single-version uninstall) — box#79's review found the traversal hole; the gate here is a byte-identical copy inbin/rigandinstall.sh, and the suite diffs the two so they cannot drift.RIG_INSTALL_SOURCE=<dir-or-tarball>drives real installer runs offline (CI and the suite).Verbs (bin/rig):
rig versions(current + running marked),rig use <v>(flip asserted effective:currentmust resolve to the asked-for version AND the chain's ownrig --versionmust answer it),rig uninstall [<v>|--all].currentdangles (heal before delete).uninstall INCOMPLETEnaming it.--force,RIG_YES=1, or a TTY prompt; none of the three → refuse (exit 2), never assume.The flip-gate decision (deliberate, not inherited)
box refuses version flips while boxes exist — its stake is user state a flip can strand. rig's analog stake is the converged host:
/etc/rig/rolemarks a box bootstrap has made into something, and a different rig under it changes what a re-converge (rig bootstrap,rig users apply) would do. So rig WARNS and proceeds — on the installer's upgrade flip, onrig use, and onrig uninstall --all— rather than refusing: there is no strandable state, and upgrading a bootstrapped host is the normal case, not the hazard case.RIG_ROLE_MARKERoverrides the marker path (existing repo convention), which is how the suite drives both the warning and its silence. The warn helper is a byte-identical copy in both files, drift-diffed likevalid_version.VERSION sequencing (rig#32)
The issue wants rig#32's
VERSIONfile first; rig main has neither aVERSIONfile nor a--versionverb, and #32 is still open. This PR folds in the minimal piece the layout cannot exist without:VERSION(stamped0.1.0-dev— #32's release PR cuts the real0.1.0) andrig --version/-Vreading the tree's own file. CHANGELOG, release.yml, and the tag-resolving installer stay #32's.Verification
bash test/cli.sh— 273 passed, 0 failed (was 199 on main; +74 new checks, all driven through real installer runs against throwawayRIG_HOME/RIG_BINroots, no network, no root).shellcheck -xoverbin/* **/*.shclean (CI's exact invocation).test/db-integration.sh— 12 passed, 0 failed.New coverage: fresh-install layout + chain, converge no-op (canary),
RIG_REINSTALLreplace, side-by-side second version + flip,versionsmarkings,use(usage error / unknown / traversal / effective flip / rollback), installed-but-not-current no-op keeps the default, bootstrapped-host warn (fires on upgrade flip,use,uninstall --all; silent with no marker and on fresh installs), flat migration (with VERSION, VERSION-less →0.0.0-unknown, hostile../pwnVERSION refused with tree untouched), dangling-currentheal-before-delete, wedged-symlink healing (dangling + stale), uninstall matrix (current refused, unknown, traversal,<v> --allambiguous, non-current removed,--allzero residue, no-TTY-no-force refusal,RIG_YESconsent, chmod-555 survivor →INCOMPLETEexit 1), working-tree refusals for all three verbs, and the two byte-identical-copy drift diffs.End-to-end (real tarballs shaped like GitHub archives, on a host that genuinely has
/etc/rig/role— the warn gate fired live):Docs: README Install section rewritten around the layout (+ an Uninstall section, + the Testing section notes the driven installer runs);
rig --helpdocuments the three verbs and--version.🤖 Generated with Claude Code
✅ Reviewed — I agree with all of this, no concerns.
First review, at
f1ada54. Readinstall.sh,bin/rig, and the +230 lines oftest/cli.shend to end. This is a faithful, well-reasoned port of box#79's layout and I couldn't find a correctness hole. What I checked and where it holds up:valid_version(''|.*|-*and the[!A-Za-z0-9._+-]class) forbids/,..-escapes, leading./-, and option-lookalikes by construction, and it fires at every site a version becomes a path — installernew_ver, migrationflat_ver,use, single-version uninstall. The byte-identical copy in both files plus the suite'sdiffof the twoawk-extracted bodies is the right way to keep them from drifting;warn_bootstrappedgets the same treatment.ln -sfn …current.new.$$ && mv -Tfeverywhere (installerflip_current,cmd_use) closes the unlink+create window;currentis judged fromreadlink -f versions/<v>, never from a wedged link, so a dangling/stalecurrentheals rather than blocks.RIG_REINSTALLswaps by rename with the old tree deleted last.cmd_useasserts the effective result —currentresolves to the asked-for version AND the chain's ownrig --versionanswers it — which is the flakiness this verb exists to kill.--allboth: rm's exit code is explicitly not the verdict, the re-check is, and a survivor screamsINCOMPLETEby name (exit 1).--allonly removes PATH symlinks that resolve into (or dangle at) this root, gathered before deletion. Refuses the current version, refuses whilecurrentdangles (heal-before-delete), and consent is--force/RIG_YES/TTY or refuse — never assume.VERSIONrefused before anything moves, VERSION-less trees as0.0.0-unknown.The flip-gate divergence from box (warn-and-proceed vs refuse) is well-justified: rig holds no strandable per-box state, upgrading a converged host is the normal case, and the marker path is overridable for tests. And the tests earn their keep — every one of the above is driven through real
install.shruns against throwaway roots, not grepped. Nice work; nothing for me to add.— automated review by `claude-bot-andresmgsl` · heavy-duty-review-bot
Verdict: I agree with everything and have no additional feedback.
Reviewed at
f1ada54. The version/path gates, atomic current flips, safe flat-tree migration, effective-result assertions, and uninstall absence checks are coherent and covered by driven installer tests. Both current checks pass.Verdict: Approve — I agree with this as-is.
First review at
f1ada54. Faithful port of box#79's versioned layout with rig-shaped flip gate. CI green (check + db-integration). What I checked:valid_versionis real — forbids empty, leading./-, and anything outside[A-Za-z0-9._+-]by construction; fires at every path-builder site (installernew_ver, migrationflat_ver,use, single-version uninstall). Byte-identical copy inbin/rigandinstall.sh; suite diffs the two so they cannot drift (warn_bootstrappedsame treatment).currentflips —ln -sfn …current.new.$$ && mv -Tfeverywhere; default judged fromreadlink -f versions/<v>, never a wedged link. Same-version re-run converges;RIG_REINSTALL=1swaps by rename, delete last; non-current re-run never steals the default.cmd_useasserts the effective result —currentresolves to the asked version AND the chain's ownrig --versionanswers it.--all; rm exit is not the verdict; survivors screamINCOMPLETEby name. Consent is--force/RIG_YES/TTY or refuse. Current refused; dangling current → heal-before-delete.--allonly removes PATH symlinks that resolve into this root.VERSIONrefused before move; VERSION-less →0.0.0-unknown./etc/rig/role(no strandable per-box state; upgrade is the normal case);RIG_ROLE_MARKERfor fixtures.Tests earn their keep: driven real
install.shruns against throwaway roots (fresh/converge/reinstall/side-by-side/use/migration/hostile VERSION/dangling current/uninstall matrix/consent/INCOMPLETE). Nothing to add.Review sweep, round 1 (codex ✅ approved / grok ✅ approved / claude ✅-agree, comment-only by config): unanimous, no changes requested by anyone — nothing to push. Requesting @danmt for final review.