feat(installer): implement install.sh per 07-spec-installer - #645
Conversation
📝 WalkthroughWalkthroughAdds a POSIX ChangesCurl|sh Installer and Verification
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
|
Failing CI / cargo-deny (pull_request) will be fixed by #646 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
.github/workflows/release.yml (1)
520-530: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider staging
install.shviascripts/stage-release.shinstead of a standalonegcloud storage cp.The step immediately above (Line 516-519) already stages release artifacts through
scripts/stage-release.sh, which presumably centralizes the immutable cache-control header and bucket-path conventions. This new step duplicates that header inline instead of extending the shared script, so the two staging paths can drift (e.g. if the cache-control policy changes later, only one call site gets updated).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 520 - 530, The installer staging step duplicates release-upload logic and cache-control settings instead of using the shared staging flow. Update the release workflow to stage install.sh through scripts/stage-release.sh, reusing the same bucket/path conventions and immutable header handling already used for the other release artifacts. Keep the change aligned with the existing release artifact staging steps so the logic stays centralized and consistent.scripts/install_test.sh (2)
241-250: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winTraversal test uses
../evil, which is rejected only because of the/, not because..is blocked.This test doesn't cover a bare
".."target (no slash), which — per the companion finding onscripts/install.shLines 124-127 — currently passes the charset validation unmodified since every character in..is individually allowed. Once that validation gap is fixed, please add a case assertingrun traversal2 "$H3" ".."also exits non-zero, to lock in the fix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/install_test.sh` around lines 241 - 250, The traversal coverage in install_test.sh only exercises "../evil", which can fail for the slash rather than for dot-dot traversal. Update the Unit 2 validation block to also test the bare ".." target using the existing run traversal2 helper, and assert it exits non-zero like the other invalid target cases. Keep the new assertion alongside run traversal and run emptytarget so the install.sh charset validation fix is locked in by a direct check on the traversal2 path.
102-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFallback downloader/hasher branches (wget, shasum, openssl) are never exercised.
The stub
wgetalways exits 1 (Line 128-132), soinstall.sh'sDL_TOOL=wgetbranch and thewgetcase infetch()(install.sh Line 58) are dead code as far as this suite goes — curl is always found first. Likewise, whicheverSHA_TOOLthe CI host happens to have wins, so theshasum/opensslbranches ofinstall.sh'ssha256()are only covered opportunistically, not deliberately, depending on the runner.Consider adding a scenario that hides the curl stub from
PATH(functional wget stub) to force and assert the wget path, and one that forcesSHA_TOOLselection order via a similar PATH trick, so all of Unit 1's fallback branches get direct coverage rather than relying on host happenstance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/install_test.sh` around lines 102 - 134, The test setup currently never exercises the fallback downloader/hasher paths because the stubbed curl is always preferred and the stubbed wget always fails, so the install.sh fetch() wget branch and sha256() shasum/openssl branches are not directly covered. Update the install_test.sh scenario around the curl/wget stubs to add a case that removes curl from PATH and provides a working wget stub so install.sh is forced through the DL_TOOL=wget path, and add a separate PATH-controlled scenario that forces SHA_TOOL selection to verify both shasum and openssl branches deterministically. Use the existing install.sh fetch() and sha256() logic, plus the stubbin curl/wget setup, to locate and adjust the test coverage.scripts/install.sh (1)
167-169: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winInstall record write is not same-filesystem atomic, unlike per-component installs.
Per-component installs are deliberately made atomic by writing the temp file as a sibling of
target_file(Lines 226-228 comment) beforemv -f. The install record, however, is built at$tmpdir/installed(under/tmptypically) and thenmv -f'd to$prev_recordunder$state_dir(typically$HOME/.local/state/...) — usually a different filesystem, somvfalls back to copy+unlink and is not atomic. An interruption mid-write could leave a truncated/corrupt record.Impact is bounded (a corrupted line just fails the hash match on the next run, causing an extra download/re-sign — not silent corruption of installed binaries), but this undermines the "reruns are cheap" goal (Goal 2) referenced by the macOS signing tests.
♻️ Proposed fix: build the record in-place under `state_dir`
+mkdir -p "$state_dir" -records="$tmpdir/installed" +records="$state_dir/installed.tmp.$$" : >"$records"And drop the later
mkdir -p "$state_dir"before the finalmv -f "$records" "$prev_record", which now becomes a same-directory atomic rename.Also applies to: 263-269
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/install.sh` around lines 167 - 169, The install record is being written to a temp file under $tmpdir and later moved into $state_dir, which makes the final rename non-atomic across filesystems. Update the install record flow in scripts/install.sh so the record is created in-place under state_dir as a sibling of the final $prev_record target, then keep the final mv -f as a same-directory atomic rename. Use the existing records/$prev_record handling in the install logic and remove the extra mkdir -p step that was only needed for the cross-filesystem move.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/install.sh`:
- Around line 124-127: The target/version validation in install.sh still allows
literal dot segments, so update the checks around target="${1-stable}" and the
version/pointer handling to explicitly reject "." and ".." in addition to the
existing character whitelist. Keep the current allowlist in place, but add a
direct guard before any URL/path construction so scripts like install and the
version resolution logic cannot pass dot-segment values into curl-based
downloads or bucket prefix assembly.
- Around line 43-60: The wget fallback in fetch is not enforcing redirect-scheme
restrictions, so it can still follow a 3xx to http:// despite --https-only.
Update the fetch() branch for wget to explicitly reject or validate redirect
targets so it matches the curl path’s HTTPS-only behavior, using the DL_TOOL and
fetch symbols as the place to make the change; if that’s not feasible, remove
the wget fallback from the downloader selection block.
---
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 520-530: The installer staging step duplicates release-upload
logic and cache-control settings instead of using the shared staging flow.
Update the release workflow to stage install.sh through
scripts/stage-release.sh, reusing the same bucket/path conventions and immutable
header handling already used for the other release artifacts. Keep the change
aligned with the existing release artifact staging steps so the logic stays
centralized and consistent.
In `@scripts/install_test.sh`:
- Around line 241-250: The traversal coverage in install_test.sh only exercises
"../evil", which can fail for the slash rather than for dot-dot traversal.
Update the Unit 2 validation block to also test the bare ".." target using the
existing run traversal2 helper, and assert it exits non-zero like the other
invalid target cases. Keep the new assertion alongside run traversal and run
emptytarget so the install.sh charset validation fix is locked in by a direct
check on the traversal2 path.
- Around line 102-134: The test setup currently never exercises the fallback
downloader/hasher paths because the stubbed curl is always preferred and the
stubbed wget always fails, so the install.sh fetch() wget branch and sha256()
shasum/openssl branches are not directly covered. Update the install_test.sh
scenario around the curl/wget stubs to add a case that removes curl from PATH
and provides a working wget stub so install.sh is forced through the
DL_TOOL=wget path, and add a separate PATH-controlled scenario that forces
SHA_TOOL selection to verify both shasum and openssl branches deterministically.
Use the existing install.sh fetch() and sha256() logic, plus the stubbin
curl/wget setup, to locate and adjust the test coverage.
In `@scripts/install.sh`:
- Around line 167-169: The install record is being written to a temp file under
$tmpdir and later moved into $state_dir, which makes the final rename non-atomic
across filesystems. Update the install record flow in scripts/install.sh so the
record is created in-place under state_dir as a sibling of the final
$prev_record target, then keep the final mv -f as a same-directory atomic
rename. Use the existing records/$prev_record handling in the install logic and
remove the extra mkdir -p step that was only needed for the cross-filesystem
move.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e2c49943-26cb-4610-b1d9-a2b190628ee5
📒 Files selected for processing (5)
.github/workflows/ci-shell-installer.yml.github/workflows/release.ymljustfilescripts/install.shscripts/install_test.sh
455dd97 to
e4c5d90
Compare
e4c5d90 to
e1a2cde
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/install_test.sh (1)
312-320: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated invocation logic instead of extending
run().This block re-implements
run()'senv -i ... "$SH" "$installer"invocation just to add$H1/bintoPATH, and unlikerun()it doesn't capturerc. Consider adding an optional PATH-prefix parameter torun()to cover this case and keep a single source of truth for the invocation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/install_test.sh` around lines 312 - 320, The PATH advisory test block duplicates the shell invocation logic already centralized in run(), instead of extending that helper. Update run() to accept an optional PATH-prefix argument so it can prepend $H1/bin when needed, keep the env -i and "$SH" "$installer" invocation in one place, and preserve rc capture there for callers like this PATH advisory check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@scripts/install_test.sh`:
- Around line 312-320: The PATH advisory test block duplicates the shell
invocation logic already centralized in run(), instead of extending that helper.
Update run() to accept an optional PATH-prefix argument so it can prepend
$H1/bin when needed, keep the env -i and "$SH" "$installer" invocation in one
place, and preserve rc capture there for callers like this PATH advisory check.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1d0bb65d-2103-4124-b0ed-07e77e38f07d
📒 Files selected for processing (5)
.github/workflows/ci-shell-installer.yml.github/workflows/release.ymljustfilescripts/install.shscripts/install_test.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- justfile
- scripts/install.sh
There was a problem hiding this comment.
🧹 Nitpick comments (2)
.github/workflows/release.yml (1)
520-530: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueCorrectly ordered before the channel update; consider
env:indirection for the interpolated output.zizmor flags the direct
${{ steps.release_info.outputs.short_sha }}expansion inside therun:block as a template-injection pattern. The value here is a deterministic hexgit rev-parse --shortoutput, so it isn't attacker-controllable — but routing GitHub Action outputs through anenv:var before use in shell avoids the general anti-pattern (and matches the same opportunity at line 518, already pre-existing).♻️ Optional hardening
- name: Stage the installer script into the versioned folder run: | + SHORT_SHA="$SHORT_SHA" gcloud storage cp \ --cache-control="public, max-age=31536000, immutable" \ scripts/install.sh \ - "gs://minimal-one/versions/${{ steps.release_info.outputs.short_sha }}/install.sh" + "gs://minimal-one/versions/${SHORT_SHA}/install.sh" + env: + SHORT_SHA: ${{ steps.release_info.outputs.short_sha }}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 520 - 530, The installer staging step uses a direct GitHub Actions expression inside the shell command, which zizmor flags as a template-injection pattern. Update the release workflow job that stages the installer script by moving the `steps.release_info.outputs.short_sha` value into an `env:` variable and reference that variable inside the `run:` block, keeping the `gcloud storage cp` step and its ordering unchanged. Use the existing `release_info` output and the installer staging step as the anchors when making the change.Source: Linters/SAST tools
scripts/install_test.sh (1)
179-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated
run()body for the PATH-suppression case.The
advise_onblock re-implementsrun()'senv -iinvocation just to prepend$H1/bintoPATH. Consider adding an optional PATH-prefix parameter torun()to avoid the duplication.Also applies to: 312-320
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/install_test.sh` around lines 179 - 196, The PATH-suppression case duplicates the same env -i launcher logic inside run(), so update run() to accept an optional PATH-prefix argument and build PATH from it instead of re-implementing the invocation in the advise_on path. Keep the existing setup in run() (env -i, HOME, MINIMAL_* variables, STUB_UNAME_* and "$SH" "$installer") and only parameterize the PATH value so both callers use the same function body.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 520-530: The installer staging step uses a direct GitHub Actions
expression inside the shell command, which zizmor flags as a template-injection
pattern. Update the release workflow job that stages the installer script by
moving the `steps.release_info.outputs.short_sha` value into an `env:` variable
and reference that variable inside the `run:` block, keeping the `gcloud storage
cp` step and its ordering unchanged. Use the existing `release_info` output and
the installer staging step as the anchors when making the change.
In `@scripts/install_test.sh`:
- Around line 179-196: The PATH-suppression case duplicates the same env -i
launcher logic inside run(), so update run() to accept an optional PATH-prefix
argument and build PATH from it instead of re-implementing the invocation in the
advise_on path. Keep the existing setup in run() (env -i, HOME, MINIMAL_*
variables, STUB_UNAME_* and "$SH" "$installer") and only parameterize the PATH
value so both callers use the same function body.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ecce8a61-6c61-4d09-9846-a81219eff59d
📒 Files selected for processing (5)
.github/workflows/ci-shell-installer.yml.github/workflows/release.ymljustfilescripts/install.shscripts/install_test.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- justfile
- scripts/install.sh
docs/specs/07-spec-installer.exe.devfresh VMshellcheckfor posix compat + a medium-rare shit test suite.install.shinto theminimal-one/<versionbucket.Summary by CodeRabbit
justtarget to run installer conformance tests.