Skip to content

fix(installer): resolve releases from the public channel, not stale GitHub assets - #964

Merged
AlexMikhalev merged 1 commit into
mainfrom
fix/installer-resolve-from-channel
Sep 27, 2026
Merged

AlexMikhalev merged 1 commit into
mainfrom
fix/installer-resolve-from-channel

Conversation

@AlexMikhalev

Copy link
Copy Markdown
Contributor

Problem

The documented install one-liner:

curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash

resolved latest from the version-less GitHub assets published under
terraphim/terraphim-ai. That release line stops at v1.21.3 and never produces
the names the current line ships, so the installer either installed a release
thirteen patches behind or failed outright. The current release, v1.21.16, is
published through downloads.terraphim.ai as version-and-target archives.

Change

Resolution is now manifest-driven. Both scripts read
<binary>/stable-v2.json from the channel, with stable.json as a
checksum-less fallback for the transitional period. GitHub Releases is no
longer consulted for resolution.

  • Asset names come from the manifest, so all seven published targets resolve,
    including the architecture-specific macOS archives ahead of the universal one
  • The requested version must match what the channel serves; anything else exits
    with a distinct code rather than silently substituting another version
  • Downloads are verified against the manifest SHA-256, then unpacked in a
    staging directory; the destination is written only after the bytes verify and
    the archive is proved to contain the expected binary
  • The previously vendored asset-name guesses and the partial GitHub scan are
    gone

Exit codes: 0 success, 1 usage, 2 manifest unreachable, 3 version
unavailable, 4 download failed, 5 checksum mismatch, 6 install failed.

scripts/SHA256SUMS pins the sibling utilities the installer fetches when it
runs under curl | bash and has no directory of its own.

Test evidence

scripts/test-installer.sh replaces the previous harness, which reported
success without asserting anything about the artefacts. It installs and runs
the real binary, and proves fail-closed behaviour by serving a manifest whose
digests are wrong while proxying the real archive bytes.

1. Static checks
  PASS  install.sh parses
  PASS  binary-resolution.sh parses
  PASS  no GitHub Releases download path
2. Manifest resolution
  PASS  resolver reports a channel version
  PASS  resolver reports a strict-manifest checksum
  PASS  unavailable version refused (exit 2)
3. Install and run
  PASS  installer exits 0
  PASS  binary is present
  PASS  installed binary reports 1.21.16
  PASS  installed binary is non-trivial
4. Fail-closed on manipulation
  PASS  live manifest fetchable
  PASS  tampered release refused (exit 5)
  PASS  nothing installed from tampered release

Results: 13 passed, 0 failed

Run it with bash scripts/test-installer.sh.

Manually exercised as well: all seven targets resolve against the live
manifests; --with-cli --with-grep, --cli-only, --version 1.21.16 and
--skip-verify each behave; a tampered manifest exits 5 with an empty install
directory; a tampered helper script is rejected before any install.

Follow-up (not in this PR)

raw.githubusercontent.com caches for several minutes, so the published
one-liner picks up this script shortly after merge. The website will set
branch=main on the raw URL to make the fix immediate, and can pin
UTILS_REVISION to a tag for reproducibility.

…itHub assets

The documented one-liner resolved "latest" from the version-less GitHub assets
published under terraphim/terraphim-ai, which top out at v1.21.3 and do not
carry the names the current release line produces. The installer therefore
either installed an older release or failed outright.

Resolution is now manifest-driven:

  * both scripts read <binary>/stable-v2.json from downloads.terraphim.ai, with
    stable.json as a checksum-less fallback for the transitional period
  * asset names come from the manifest, so the seven published targets resolve
    correctly, including the architecture-specific macOS archives ahead of the
    universal one
  * the requested version must match what the channel serves; anything else is
    a distinct exit code rather than a silent substitution
  * downloads are verified against the manifest SHA-256 and unpacked from a
    staging directory, so a mismatch or an unexpected archive leaves nothing in
    the install directory

scripts/test-installer.sh replaces the previous harness, which asserted nothing
about the artefacts: it now installs and runs the real binary and proves, by
serving a manifest with wrong digests, that a manipulated release is refused.

scripts/SHA256SUMS pins the sibling utilities the installer fetches when it is
run through curl|bash and has no directory of its own.

Verified: 13/13 checks pass against the live channel; installer exit codes
0/2/3/5 exercised; all seven targets resolve; tampered manifest and tampered
utility both refused with nothing installed.
@AlexMikhalev
AlexMikhalev merged commit 2f81d4f into main Sep 27, 2026
1 check passed
@AlexMikhalev
AlexMikhalev deleted the fix/installer-resolve-from-channel branch September 27, 2026 11:16
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