Skip to content

feat(install): first-party curl installer for Linux/macOS#3

Open
bschellenberger2600 wants to merge 5 commits intomainfrom
feat/curl-install-script
Open

feat(install): first-party curl installer for Linux/macOS#3
bschellenberger2600 wants to merge 5 commits intomainfrom
feat/curl-install-script

Conversation

@bschellenberger2600
Copy link
Copy Markdown
Member

@bschellenberger2600 bschellenberger2600 commented Apr 16, 2026

Summary

Adds scripts/install.sh, matching the pattern used by git-fire:

  • Resolves latest GitHub release (or VERSION env, e.g. v0.9.1)
  • Downloads git-rain_<version>_<os>_<arch>.tar.gz + checksums.txt, verifies SHA256
  • Installs to INSTALL_DIR (default $HOME/.local/bin), using sudo only if the dir is not writable
  • Linux / macOS only — README notes Windows → WinGet or .zip assets

Docs

  • README: new curl installer section + install table row
  • .goreleaser.yaml / .goreleaser.stable.yaml release footers include the one-liner

Smoke test

VERSION=v0.9.1 INSTALL_DIR="$(mktemp -d)" bash scripts/install.sh

Summary by CodeRabbit

  • New Features

    • Added a curl-based one-line installer for Linux/macOS with version pinning and custom install directory support; verifies downloads via SHA-256.
  • Documentation

    • README updated with installer usage, examples, PATH guidance, and platform notes (Windows guidance retained).
  • Chores

    • CI now runs a shellcheck job to lint the installer script before release.

Note

Medium Risk
Adds a new curl | bash install path that downloads and executes remote artifacts; while checksums are verified, mistakes could break installs or create supply-chain exposure if release assets are compromised.

Overview
Adds a first-party scripts/install.sh for Linux/macOS that resolves a release tag (latest or VERSION), downloads the matching archive plus checksums.txt, verifies SHA256, and installs git-rain into INSTALL_DIR (using sudo only when needed).

Updates docs and release messaging to promote the one-line curl installer (README + GoReleaser release footers), and extends CI with a shellcheck job gating UAT on shell script linting.

Reviewed by Cursor Bugbot for commit a1e4e29. Bugbot is set up for automated code reviews on this repo. Configure here.

Add scripts/install.sh (parity with git-fire): resolve latest or VERSION
tag, download tar.gz + checksums.txt from GitHub Releases, verify SHA256,
install to INSTALL_DIR (default ~/.local/bin). Document in README and
GoReleaser release footers. Windows remains WinGet or manual .zip.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Adds a curl-based installer: a new Bash installer downloads git-rain release artifacts from GitHub, verifies SHA-256 checksums, installs to a configurable directory, updates README and release footers, and adds a ShellCheck CI job for the installer.

Changes

Cohort / File(s) Summary
Release footers
\.goreleaser\.yaml, \.goreleaser\.stable\.yaml
Added a new "curl (Linux / macOS)" release-footer snippet that runs scripts/install.sh with VERSION={{ .Tag }}.
Documentation
README.md
Added curl-installer docs: usage examples, VERSION / INSTALL_DIR env vars, checksum verification steps, PATH guidance, and Windows compatibility note.
Installer script
scripts/install.sh
New executable Bash installer: detects OS/arch, resolves release tag (optional VERSION or latest via API), downloads .tar.gz and checksums.txt, verifies SHA-256, extracts and installs binary (uses sudo if needed), cleans up temporary files, and logs progress.
CI
.github/workflows/ci.yml
Added shellcheck job to lint scripts/install.sh and made uat depend on shellcheck in addition to existing jobs.

Sequence Diagram

sequenceDiagram
    participant User
    participant Script as scripts/install.sh
    participant GitHubAPI as GitHub API
    participant GitHubReleases as GitHub Releases
    participant FS as Local Filesystem

    User->>Script: Execute curl ... | bash (with optional VERSION/INSTALL_DIR)
    Script->>Script: Detect OS & architecture
    Script->>GitHubAPI: Query release tag (latest or provided)
    GitHubAPI-->>Script: Return tag
    Script->>GitHubReleases: Download archive (.tar.gz)
    GitHubReleases-->>Script: Archive bytes
    Script->>GitHubReleases: Download checksums.txt
    GitHubReleases-->>Script: checksums.txt
    Script->>Script: Extract expected SHA-256, compute actual SHA-256
    Script->>Script: Compare checksums (abort on mismatch)
    Script->>Script: Extract archive, verify binary present
    Script->>FS: Install binary to INSTALL_DIR (use sudo if needed)
    FS-->>Script: Install success
    Script-->>User: Print success / version hint
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped a curl across the net,

fetched a tarball with checksums set,
extracted, installed with a careful paw—
now git-rain runs, neat and raw! 🎩✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(install): first-party curl installer for Linux/macOS' directly and clearly summarizes the main change: introduction of a first-party curl-based installer for Linux/macOS systems.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/curl-install-script

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
scripts/install.sh (1)

80-82: Normalize VERSION input to accept both v1.2.3 and 1.2.3.

Currently the script expects a tag-like value; a bare semver leads to a 404 later. Normalizing here makes CLI behavior more forgiving.

Suggested fix
 resolve_version() {
   if [ -n "$VERSION" ]; then
-    printf '%s\n' "$VERSION"
+    case "$VERSION" in
+      v*) printf '%s\n' "$VERSION" ;;
+      *)  printf 'v%s\n' "$VERSION" ;;
+    esac
     return
   fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.sh` around lines 80 - 82, The script currently returns the
raw $VERSION in the if block (if [ -n "$VERSION" ]; then printf '%s\n'
"$VERSION" return) which breaks later calls when a bare semver (1.2.3) is
provided; change this block to normalize $VERSION to a tag by ensuring it begins
with "v" (e.g., if it does not start with "v", prepend "v") and then print the
normalized value instead of the raw input so both "v1.2.3" and "1.2.3" work
transparently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.goreleaser.stable.yaml:
- Around line 135-138: The stable release notes currently use the curl installer
URL referencing the main branch; update the installer command and any VERSION
placeholder so they are tag-pinned using the release tag variable: replace the
"main" path segment in the curl URL that fetches "scripts/install.sh" with the
release tag token ({{ .Tag }}) and ensure any exported/embedded VERSION value in
the snippet is set to {{ .Tag }} as well so the curl invocation and VERSION
become reproducible and tied to the release tag.

In @.goreleaser.yaml:
- Around line 99-102: Update the release footer curl command so it pins the
installer to the release tag and passes the VERSION environment to the
installer: replace the current URL that points to scripts/install.sh on main
with one using the release template variable {{ .Tag }} (e.g.,
.../raw.githubusercontent.com/.../{{ .Tag }}/scripts/install.sh) and ensure the
shell invocation passes VERSION={{ .Tag }} to bash so the installer run is
deterministic for that release.

In `@README.md`:
- Around line 95-99: The example binds VERSION and INSTALL_DIR to curl rather
than the shell that runs the installer, so the installer script (install.sh)
won't see them; fix by moving the env-var assignment to the bash invocation that
executes the downloaded script (e.g., ensure VERSION and INSTALL_DIR are
exported/prefixed on the bash side or passed via bash -s/ bash -c) so that when
curl | bash runs, the installer receives VERSION and INSTALL_DIR instead of
attaching them to curl.

In `@scripts/install.sh`:
- Around line 99-112: The install_binary function should explicitly guard
against INSTALL_DIR being a regular file: at the top of install_binary (before
writability checks) detect if the passed target_dir exists but is not a
directory (e.g., [ -e "$target_dir" ] && [ ! -d "$target_dir" ]) and fail fast
with a clear error message referencing $target_dir and $BINARY_NAME (or return
non-zero) so the subsequent mkdir/install logic doesn't produce confusing
errors; keep existing writability and mkdir code unchanged after this early
directory-type check.

---

Nitpick comments:
In `@scripts/install.sh`:
- Around line 80-82: The script currently returns the raw $VERSION in the if
block (if [ -n "$VERSION" ]; then printf '%s\n' "$VERSION" return) which breaks
later calls when a bare semver (1.2.3) is provided; change this block to
normalize $VERSION to a tag by ensuring it begins with "v" (e.g., if it does not
start with "v", prepend "v") and then print the normalized value instead of the
raw input so both "v1.2.3" and "1.2.3" work transparently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d07fc27-a97c-423c-bac4-9a08b1a1fe3a

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce021f and 8735502.

📒 Files selected for processing (4)
  • .goreleaser.stable.yaml
  • .goreleaser.yaml
  • README.md
  • scripts/install.sh

Comment thread .goreleaser.stable.yaml
Comment thread .goreleaser.yaml
Comment thread README.md Outdated
Comment thread scripts/install.sh
…R guard

- GoReleaser footers: fetch install.sh at {{ .Tag }} and pass VERSION={{ .Tag }}
  to bash for reproducible release notes.
- README: move VERSION/INSTALL_DIR to the bash side of the pipeline.
- install.sh: fail fast if INSTALL_DIR exists but is not a directory.

Addresses CodeRabbit review on PR #3.

Made-with: Cursor
@bschellenberger2600
Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

@bschellenberger2600 Sure! I'll kick off a new review of this PR now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Resolve VERSION against real release tags (try v-prefix and bare),
optional GITHUB_TOKEN/GH_TOKEN for the releases/latest API, defensive
checksum parsing, install/tar preflight, PATH dir normalization, and
README notes. Add a shellcheck job for scripts/install.sh.

Made-with: Cursor
@bschellenberger2600
Copy link
Copy Markdown
Member Author

bugbot review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix resolved 1 of the 2 issues found in the latest run.

  • ✅ Fixed: Wget path omits API version header
    • Added X-GitHub-Api-Version: 2022-11-28 to the authenticated wget headers in fetch_github_api.
Preview (a1e4e29df3)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,6 +10,16 @@
   contents: read
 
 jobs:
+  shellcheck:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: ShellCheck install.sh
+        run: |
+          sudo apt-get update -qq
+          sudo apt-get install -y shellcheck
+          shellcheck scripts/install.sh
+
   lint:
     runs-on: ubuntu-latest
     steps:
@@ -44,7 +54,7 @@
 
   uat:
     runs-on: ubuntu-latest
-    needs: [lint, test]
+    needs: [shellcheck, lint, test]
     steps:
       - uses: actions/checkout@v4
 

diff --git a/.goreleaser.stable.yaml b/.goreleaser.stable.yaml
--- a/.goreleaser.stable.yaml
+++ b/.goreleaser.stable.yaml
@@ -132,6 +132,11 @@
     go install github.com/git-rain/git-rain@{{ .Tag }}
     ```
 
+    **curl (Linux / macOS)**
+    ```sh
+    curl -fsSL https://raw.githubusercontent.com/git-fire/git-rain/{{ .Tag }}/scripts/install.sh | VERSION={{ .Tag }} bash
+    ```
+
     **Homebrew (macOS/Linuxbrew)**
     ```sh
     brew install git-fire/tap/git-rain

diff --git a/.goreleaser.yaml b/.goreleaser.yaml
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -96,6 +96,11 @@
     go install github.com/git-rain/git-rain@{{ .Tag }}
     ```
 
+    **curl (Linux / macOS)**
+    ```sh
+    curl -fsSL https://raw.githubusercontent.com/git-fire/git-rain/{{ .Tag }}/scripts/install.sh | VERSION={{ .Tag }} bash
+    ```
+
     **Binaries**
     Download platform archives from this release's assets.
     Package-manager channels are published for stable tags (`vX.Y.Z`).

diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@
 - [Install](#install)
   - [Homebrew (macOS/Linuxbrew)](#homebrew-macoslinuxbrew)
   - [WinGet (Windows)](#winget-windows)
+  - [curl installer (Linux / macOS)](#curl-installer-linux--macos)
   - [Linux native packages (.deb / .rpm)](#linux-native-packages-deb--rpm)
   - [Go install](#go-install)
   - [Binary archive (manual)](#binary-archive-manual)
@@ -65,6 +66,7 @@
 |---|---|---|
 | Homebrew | `brew install git-fire/tap/git-rain` | macOS, Linuxbrew |
 | WinGet | `winget install git-rain.git-rain` | Windows |
+| curl script | [curl installer](#curl-installer-linux--macos) | Linux, macOS |
 | Linux package | Download `.deb` or `.rpm` from [GitHub Releases](https://github.com/git-fire/git-rain/releases) | Linux |
 | Go | `go install github.com/git-rain/git-rain@latest` | All (Go 1.24.2+) |
 | Binary archive | [GitHub Releases](https://github.com/git-fire/git-rain/releases) | All |
@@ -82,6 +84,35 @@
 winget install git-rain.git-rain

+### curl installer (Linux / macOS)
+
+First-party install script (same idea as git-fire/scripts/install.sh): downloads the matching .tar.gz from Releases, verifies checksums.txt, and installs to $INSTALL_DIR (default ~/.local/bin).
+
+The main URL below always runs the installer script from the latest commit on that branch, while the binary itself comes from the latest GitHub release (or from VERSION if you set it). That is convenient for copy-paste installs, but it means the script can drift ahead of any given release. For a fully pinned install, use the release tag in the URL (as in each release’s notes) and set VERSION to the same tag.
+
+For repeated automation against the GitHub API (resolving latest), set GITHUB_TOKEN or GH_TOKEN so authenticated rate limits apply. VERSION may be a bare semver (0.9.1); the installer tries the v-prefixed release tag first, then the exact string you passed.
+
+bash +curl -fsSL https://raw.githubusercontent.com/git-fire/git-rain/main/scripts/install.sh | bash +
+
+Pin a version or install directory (environment variables must apply to bash, not curl):
+
+bash +curl -fsSL https://raw.githubusercontent.com/git-fire/git-rain/main/scripts/install.sh | VERSION=v0.9.1 INSTALL_DIR=/usr/local/bin bash +
+
+If your shell does not already include ~/.local/bin on PATH, add it (the installer prints a reminder). Example for bash (skips the line if .local/bin is already mentioned in ~/.bashrc):
+
+```bash
+if ! grep -qF '.local/bin' ~/.bashrc 2>/dev/null; then

  • echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
    +fi
    +source ~/.bashrc
    +```

+Windows is not supported by this script — use WinGet or download a .zip from Releases.
+

Linux native packages (.deb / .rpm)

Download from GitHub Releases, then:

diff --git a/scripts/install.sh b/scripts/install.sh
new file mode 100644
--- /dev/null
+++ b/scripts/install.sh
@@ -1,0 +1,287 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Install git-rain from GitHub release assets with checksum verification.
+# Usage:
+# curl -fsSL https://raw.githubusercontent.com/git-fire/git-rain/main/scripts/install.sh | bash
+# Optional env vars:
+# VERSION=v0.9.1 (must match a GitHub release tag; bare semver like 0.9.1 tries v0.9.1 first)
+# INSTALL_DIR=$HOME/.local/bin
+# REPO=git-fire/git-rain
+# GITHUB_TOKEN or GH_TOKEN (optional; increases api.github.com rate limits for "latest" resolution)
+
+REPO="${REPO:-git-fire/git-rain}"
+INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
+VERSION="${VERSION:-}"
+BINARY_NAME="git-rain"
+
+log() {

  • printf '[git-rain install] %s\n' "$1"
    +}

+fail() {

  • printf '[git-rain install] ERROR: %s\n' "$1" >&2
  • exit 1
    +}

+need_cmd() {

  • command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
    +}

+github_token() {

  • printf '%s' "${GITHUB_TOKEN:-${GH_TOKEN:-}}"
    +}

+download_to() {

  • local src="$1"
  • local dst="$2"
  • if command -v curl >/dev/null 2>&1; then
  • curl -fsSL "$src" -o "$dst"
  • elif command -v wget >/dev/null 2>&1; then
  • wget -qO "$dst" "$src"
  • else
  • fail "curl or wget is required"
  • fi
    +}

+fetch_github_api() {

  • local url="$1"
  • if command -v curl >/dev/null 2>&1; then
  • local token
  • token="$(github_token)"
  • if [ -n "$token" ]; then
  •  curl -fsSL \
    
  •    -H "Authorization: Bearer ${token}" \
    
  •    -H "Accept: application/vnd.github+json" \
    
  •    -H "X-GitHub-Api-Version: 2022-11-28" \
    
  •    "$url"
    
  • else
  •  curl -fsSL "$url"
    
  • fi
  • elif command -v wget >/dev/null 2>&1; then
  • local token
  • token="$(github_token)"
  • if [ -n "$token" ]; then
  •  wget -qO- \
    
  •    --header="Authorization: Bearer ${token}" \
    
  •    --header="Accept: application/vnd.github+json" \
    
  •    --header="X-GitHub-Api-Version: 2022-11-28" \
    
  •    "$url"
    
  • else
  •  wget -qO- "$url"
    
  • fi
  • else
  • fail "curl or wget is required"
  • fi
    +}

+sha256_file() {

  • local target="$1"
  • if command -v sha256sum >/dev/null 2>&1; then
  • sha256sum "$target" | awk '{print $1}'
  • elif command -v shasum >/dev/null 2>&1; then
  • shasum -a 256 "$target" | awk '{print $1}'
  • else
  • fail "sha256sum or shasum is required"
  • fi
    +}

+# First field is digest; remainder is filename (GNU text " " or binary " *").
+checksum_for_file() {

  • local sums="$1"
  • local want="$2"
  • awk -v want="$want" '
  • {
  •  hash = $1
    
  •  name = $0
    
  •  sub(/^[^[:space:]]+[[:space:]]+/, "", name)
    
  •  sub(/^\*/, "", name)
    
  •  if (name == want) {
    
  •    print hash
    
  •    exit
    
  •  }
    
  • }' "$sums"
    +}

+normalize_os() {

  • local raw_os
  • raw_os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  • case "$raw_os" in
  • linux) echo "linux" ;;
  • darwin) echo "darwin" ;;
  • *)
  •  fail "unsupported OS: $raw_os (expected linux or darwin). On Windows use WinGet or a release .zip from GitHub."
    
  •  ;;
    
  • esac
    +}

+normalize_arch() {

  • local raw_arch
  • raw_arch="$(uname -m)"
  • case "$raw_arch" in
  • x86_64 | amd64) echo "amd64" ;;
  • aarch64 | arm64) echo "arm64" ;;
  • linux/arm is published as armv6 only; armv7l is ABI-compatible.

  • armv6l | armv7l) echo "armv6" ;;
  • i386 | i686) echo "386" ;;
  • *)
  •  fail "unsupported architecture: $raw_arch"
    
  •  ;;
    
  • esac
    +}

+resolve_raw_version_tag() {

  • if [ -n "$VERSION" ]; then
  • printf '%s\n' "$VERSION"
  • return
  • fi
  • local api_url response tag
  • api_url="https://api.github.com/repos/$REPO/releases/latest"
  • response="$(fetch_github_api "$api_url")"
  • tag="$(printf '%s\n' "$response" | awk -F '"' '/"tag_name"[[:space:]]*:/ {print $4; exit}')"
  • [ -n "$tag" ] || fail "could not resolve latest release tag from GitHub API"
  • printf '%s\n' "$tag"
    +}

+release_archive_url() {

+# Return 0 if a release asset URL responds with success (follows redirects).
+release_asset_head_ok() {

  • local url="$1"
  • local code
  • if command -v curl >/dev/null 2>&1; then
  • code="$(curl -gfsSIL -L -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || printf '%s' "000")"
  • [ "$code" = "200" ]
  • elif command -v wget >/dev/null 2>&1; then
  • wget --spider -q -L "$url"
  • else
  • false
  • fi
    +}

+# Map user input or "latest" API tag to the GitHub release tag that owns the archive.
+pick_release_tag() {

  • local raw="$1"
  • local -a candidates=()
  • case "$raw" in
  • v*)
  •  candidates=("$raw" "${raw#v}")
    
  •  ;;
    
  • *)
  •  candidates=("v${raw}" "${raw}")
    
  •  ;;
    
  • esac
  • local tag archive_version archive_name url
  • for tag in "${candidates[@]}"; do
  • archive_version="${tag#v}"
  • archive_name="${BINARY_NAME}${archive_version}${os}_${arch}.tar.gz"
  • url="$(release_archive_url "$tag" "$archive_name")"
  • if release_asset_head_ok "$url"; then
  •  printf '%s\n' "$tag"
    
  •  return
    
  • fi
  • done
  • fail "no GitHub release matched VERSION=${raw} for ${os}/${arch} (check tag spelling and that this platform is published)"
    +}

+normalize_path_dir() {

  • local d="$1"
  • while [ "${#d}" -gt 1 ] && [ "${d%/}" != "$d" ]; do
  • d="${d%/}"
  • done
  • printf '%s\n' "$d"
    +}

+install_binary() {

  • local src_bin="$1"
  • local target_dir="$2"
  • local target_bin="$target_dir/$BINARY_NAME"
  • if [ -e "$target_dir" ] && [ ! -d "$target_dir" ]; then
  • fail "install path exists but is not a directory: $target_dir"
  • fi
  • if [ ! -d "$target_dir" ]; then
  • if mkdir -p "$target_dir" 2>/dev/null; then
  •  :
    
  • elif command -v sudo >/dev/null 2>&1; then
  •  sudo mkdir -p "$target_dir"
    
  • else
  •  fail "could not create install directory: $target_dir"
    
  • fi
  • fi
  • if [ -w "$target_dir" ]; then
  • install -m 0755 "$src_bin" "$target_bin"
  • return
  • fi
  • if command -v sudo >/dev/null 2>&1; then
  • sudo install -m 0755 "$src_bin" "$target_bin"
  • return
  • fi
  • fail "install directory is not writable and sudo is unavailable: $target_dir"
    +}

+path_has_dir() {

  • local dir
  • dir="$(normalize_path_dir "$1")"
  • case ":${PATH:-}:" in
  • ":${dir}:") return 0 ;;
  • *) return 1 ;;
  • esac
    +}

+# Preflight: fail fast before downloads (curl/wget checked in download_to).
+need_cmd tar
+need_cmd install
+os="$(normalize_os)"
+arch="$(normalize_arch)"
+version_tag="$(pick_release_tag "$(resolve_raw_version_tag)")"
+version="${version_tag#v}"
+
+archive_name="${BINARY_NAME}${version}${os}_${arch}.tar.gz"
+archive_url="$(release_archive_url "$version_tag" "$archive_name")"
+checksums_url="$(release_archive_url "$version_tag" "checksums.txt")"
+
+log "installing ${BINARY_NAME} ${version_tag} for ${os}/${arch}"
+
+tmp_dir="$(mktemp -d)"
+trap 'rm -rf "$tmp_dir"' EXIT
+
+archive_path="$tmp_dir/$archive_name"
+checksums_path="$tmp_dir/checksums.txt"
+
+log "downloading release archive"
+download_to "$archive_url" "$archive_path"
+
+log "downloading checksum file"
+download_to "$checksums_url" "$checksums_path"
+
+expected_sum="$(checksum_for_file "$checksums_path" "$archive_name")"
+[ -n "$expected_sum" ] || fail "could not find checksum entry for $archive_name (no asset for this OS/arch on this release?)"
+
+actual_sum="$(sha256_file "$archive_path")"
+if [ "$expected_sum" != "$actual_sum" ]; then

  • fail "checksum mismatch for $archive_name"
    +fi

+log "checksum verified"
+tar -xzf "$archive_path" -C "$tmp_dir"
+[ -f "$tmp_dir/$BINARY_NAME" ] || fail "archive did not contain $BINARY_NAME"
+
+install_binary "$tmp_dir/$BINARY_NAME" "$INSTALL_DIR"
+
+log "installed to $INSTALL_DIR/$BINARY_NAME"
+if ! path_has_dir "$INSTALL_DIR"; then

  • log "warning: $INSTALL_DIR is not on PATH; add it to your shell profile, e.g. export PATH="$INSTALL_DIR:$PATH""
    +fi
    +log "verify with: $BINARY_NAME --version"

</details>


<sub>You can send follow-ups to the cloud agent <a href="https://cursor.com/agents/bc-9b515c5c-d029-48ed-bbba-f18b7df2b198">here</a>.</sub>
<!-- BUGBOT_AUTOFIX_REVIEW_FOOTNOTE_END -->

<sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7dea654c8c8e26653e2078ab27d527d03287f4b3. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>

Comment thread .goreleaser.yaml

**curl (Linux / macOS)**
```sh
curl -fsSL https://raw.githubusercontent.com/git-fire/git-rain/{{ .Tag }}/scripts/install.sh | VERSION={{ .Tag }} bash
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prerelease footer installs wrong version silently

Medium Severity

The prerelease .goreleaser.yaml footer pipes the install script from main into bare bash without setting VERSION, while the stable .goreleaser.stable.yaml correctly uses {{ .Tag }} in both the URL and VERSION={{ .Tag }}. Without VERSION, the installer calls GitHub's /releases/latest endpoint, which only returns non-prerelease releases. Users copying the curl snippet from a prerelease's release notes would silently install the latest stable release instead of the prerelease they intended.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7dea654. Configure here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugbot Autofix determined this is a false positive.

.goreleaser.yaml already uses {{ .Tag }} for the script URL and VERSION={{ .Tag }} on the curl line, matching stable behavior.

You can send follow-ups to the cloud agent here.

Comment thread scripts/install.sh
Match authenticated curl behavior so wget uses the same REST API version.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

13-21: Remove redundant shellcheck installation — it's preinstalled on ubuntu-latest.

The apt-get update and install steps add ~10–20s per run unnecessarily. GitHub's ubuntu-latest runners ship with shellcheck available on PATH. Drop the install step or use a dedicated action like ludeeus/action-shellcheck for PR annotations.

♻️ Simplification
   shellcheck:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
       - name: ShellCheck install.sh
-        run: |
-          sudo apt-get update -qq
-          sudo apt-get install -y shellcheck
-          shellcheck scripts/install.sh
+        run: shellcheck scripts/install.sh
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 13 - 21, The workflow job "shellcheck"
currently runs "apt-get update" and "apt-get install -y shellcheck" before
invoking "shellcheck scripts/install.sh"; remove the redundant install steps
(the "sudo apt-get update -qq" and "sudo apt-get install -y shellcheck" lines)
since shellcheck is preinstalled on ubuntu-latest, or alternatively replace the
install block with a dedicated action like "ludeeus/action-shellcheck" for PR
annotations; keep the existing "uses: actions/checkout@v4" and the final
"shellcheck scripts/install.sh" step unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 13-21: The workflow job "shellcheck" currently runs "apt-get
update" and "apt-get install -y shellcheck" before invoking "shellcheck
scripts/install.sh"; remove the redundant install steps (the "sudo apt-get
update -qq" and "sudo apt-get install -y shellcheck" lines) since shellcheck is
preinstalled on ubuntu-latest, or alternatively replace the install block with a
dedicated action like "ludeeus/action-shellcheck" for PR annotations; keep the
existing "uses: actions/checkout@v4" and the final "shellcheck
scripts/install.sh" step unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a3d6c22-4e7d-42b2-a2e4-d05e62211b75

📥 Commits

Reviewing files that changed from the base of the PR and between 8735502 and a1e4e29.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .goreleaser.stable.yaml
  • .goreleaser.yaml
  • README.md
  • scripts/install.sh
✅ Files skipped from review due to trivial changes (3)
  • .goreleaser.stable.yaml
  • .goreleaser.yaml
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/install.sh

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.

2 participants