Skip to content

feat(cli): auto-update with versioned layout, verify gate + automatic rollback - #98

Merged
distroinfinity merged 16 commits into
mainfrom
feature-cli-auto-update
May 27, 2026
Merged

distroinfinity merged 16 commits into
mainfrom
feature-cli-auto-update

Conversation

@distroinfinity

Copy link
Copy Markdown
Owner

Existing users only got fixes via a manual nudge/curl. This makes the CLI auto-update to the latest release, safely — a bad release can never leave a user stuck. Designed + spec'd + planned, then built TDD across 16 commits with independent review of the safety-critical core and a final whole-feature review.

How it works

  • Versioned layout: ~/.distrotv/versions/<v>/ per version + a current symlink to the active one. Shims and Claude hooks resolve through the stable ~/.distrotv/current/dist/index.js, so a version swap is invisible to them. install.sh lays this out for fresh installs; the CLI self-migrates legacy flat installs on first boot.
  • Pipeline (daemon, on the ~15-min update check): download → extract → npm install native deps into a staging dir → verify (node <staged> --version matches AND daemon self-check exits 0) → atomically rename into versions/<v> + repoint current → restart through current.
  • Never-stuck guarantees:
    • Verify gate runs real node on the staged build before it can become current.
    • Activation writes rollback state (previousVersion) before the symlink swap.
    • New daemon runs on probation; healthy ~60s → stable + prune old (semver-aware, keep last 1).
    • If the new build crash-loops (no heartbeat past ~90s), an fs-only pre-flight in the hook respawn path auto-reverts current to the previous version and marks the bad version (1h backoff). Hooks always resolve through current and always exit 0.
  • Opt-out: on by default; DISTRO_NO_AUTOUPDATE=1 or cli.autoUpdate:false. No server gate (yank/replace the release to halt a bad one).
  • Manual path: distro upgrade now applies the update (download→verify→activate), and is marked stable immediately (not probation).

Verification (on this machine)

  • e2e test (auto-update-e2e.test.ts) runs the real pipeline with real node: happy-path activates, a broken build is rejected (no activation), rollback reverts current, opt-out disables. 151/151 CLI tests pass.
  • Independent review of auto-update.ts caught + fixed: previousVersion-loss window, lexicographic prune bug, staging cleanup. Final whole-feature review: no Critical; two Important (manual-upgrade probation + staging leak) fixed.
  • typecheck (all packages), lint, build, sh -n install.sh all green.

Rollout note

Auto-update lives in the CLI, so existing installs only gain it once on the first release that contains it (cli-v0.2.10+). The first hop still rides the manual nudge/curl/distro upgrade; every update after is automatic. No release is cut by this PR — merging only updates the live install.sh (versioned layout for fresh installs). Design/plan: docs/superpowers/{specs,plans}/2026-05-27-cli-auto-update* (local).

Test plan

  • 151 cli tests incl. real-node e2e (happy/reject/rollback/opt-out); typecheck/lint/build; sh -n
  • post-merge: confirm live get.distrotv.xyz/install.sh produces a runnable current/dist/index.js
  • after cli-v0.2.10 is cut (separate, on your go): one machine updates 0.2.9→0.2.10 automatically; verify rollback by publishing a deliberately-broken test build to a scratch repo

adds verifyStaged() — runs the staged binary with --version and daemon self-check;
both must pass before any swap. adds tar + @types/tar deps.
downloadAndStage fetches tarball + runs npm install for native deps in isolation.
activate captures previous version before swap so rollback always has a target.
rollback is a no-op when previousVersion is absent.
pruneOldVersions protects active + previousVersion; removes oldest beyond keep limit.
bad-version backoff persists to bad-versions.json with a 1-hour window.
…gration

Wires the auto-update pipeline into runDaemon:
- migrateFlatInstall on boot (after singleton lock, before sync start)
- probation → stable promotion after 60s clean uptime via unref'd timer
- runUpdateCheck now downloads, verifies, activates and restarts when outdated + opted-in + version not bad + no update in flight
- scheduleDaemonRestart spawns fresh process via currentEntryPath() (resolves through `current` symlink), unrefs, exits so new daemon acquires the lock
@vercel

vercel Bot commented May 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devdrip Ready Ready Preview, Comment May 27, 2026 1:11pm

@distroinfinity
distroinfinity merged commit 01bfb7f into main May 27, 2026
5 checks passed
@distroinfinity
distroinfinity deleted the feature-cli-auto-update branch May 28, 2026 07:36

This branch was successfully deployed

1 active deployment
Preview — 9d534a66 Deployed May 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant