-
Notifications
You must be signed in to change notification settings - Fork 0
feat(install): first-party curl installer for Linux/macOS #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8735502
0f601a3
cdb6a18
7dea654
a1e4e29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -96,6 +96,11 @@ release: | |
| 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prerelease footer installs wrong version silentlyMedium Severity The prerelease Reviewed by Cursor Bugbot for commit 7dea654. Configure here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bugbot Autofix determined this is a false positive.
You can send follow-ups to the cloud agent here. |
||
| ``` | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| **Binaries** | ||
| Download platform archives from this release's assets. | ||
| Package-manager channels are published for stable tags (`vX.Y.Z`). | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,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" | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| 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() { | ||
| local tag="$1" | ||
| local archive_name="$2" | ||
| printf 'https://github.com/%s/releases/download/%s/%s\n' "$REPO" "$tag" "$archive_name" | ||
| } | ||
|
|
||
| # 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 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| 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" | ||


Uh oh!
There was an error while loading. Please reload this page.