Skip to content

Commit bdaba58

Browse files
barckcodeclaude
andcommitted
Add install script for easy CLI installation via curl
Provides a POSIX-compatible installer that detects OS/arch, downloads the correct binary from GitHub Releases, verifies SHA256 checksums, and installs to /usr/local/bin (with ~/.local/bin fallback). Usage: curl -fsSL https://raw.githubusercontent.com/helmcode/finops-cli/main/install.sh | sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1589590 commit bdaba58

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

install.sh

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/bin/sh
2+
# install.sh — Install the finops CLI from GitHub Releases.
3+
#
4+
# Usage:
5+
# curl -fsSL https://raw.githubusercontent.com/helmcode/finops-cli/main/install.sh | sh
6+
#
7+
# The script detects your OS and architecture, downloads the latest release,
8+
# and installs the binary to /usr/local/bin (or ~/.local/bin as fallback).
9+
10+
set -e
11+
12+
REPO="helmcode/finops-cli"
13+
BINARY_NAME="finops"
14+
15+
# --- Helpers ---
16+
17+
info() {
18+
printf '[info] %s\n' "$1"
19+
}
20+
21+
error() {
22+
printf '[error] %s\n' "$1" >&2
23+
exit 1
24+
}
25+
26+
# --- Detect OS ---
27+
28+
detect_os() {
29+
os="$(uname -s)"
30+
case "$os" in
31+
Linux*) echo "linux" ;;
32+
Darwin*) echo "darwin" ;;
33+
CYGWIN*|MINGW*|MSYS*|Windows*)
34+
printf '[error] Windows is not supported by this installer.\n' >&2
35+
printf ' Please download the binary manually from:\n' >&2
36+
printf ' https://github.com/%s/releases/latest\n' "$REPO" >&2
37+
exit 1
38+
;;
39+
*)
40+
error "Unsupported operating system: $os"
41+
;;
42+
esac
43+
}
44+
45+
# --- Detect Architecture ---
46+
47+
detect_arch() {
48+
arch="$(uname -m)"
49+
case "$arch" in
50+
x86_64|amd64) echo "amd64" ;;
51+
aarch64|arm64) echo "arm64" ;;
52+
*)
53+
error "Unsupported architecture: $arch"
54+
;;
55+
esac
56+
}
57+
58+
# --- Fetch latest version ---
59+
60+
get_latest_version() {
61+
url="https://api.github.com/repos/${REPO}/releases/latest"
62+
63+
if command -v curl >/dev/null 2>&1; then
64+
response="$(curl -fsSL "$url")" || error "Failed to fetch latest release from GitHub API."
65+
elif command -v wget >/dev/null 2>&1; then
66+
response="$(wget -qO- "$url")" || error "Failed to fetch latest release from GitHub API."
67+
else
68+
error "Neither curl nor wget found. Please install one and try again."
69+
fi
70+
71+
# Extract tag_name from JSON without requiring jq.
72+
version="$(printf '%s' "$response" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')"
73+
74+
if [ -z "$version" ]; then
75+
error "Could not determine latest version. GitHub API response may have changed or rate limit reached."
76+
fi
77+
78+
# Strip leading 'v' if present (goreleaser uses the version without it in archive names).
79+
echo "$version" | sed 's/^v//'
80+
}
81+
82+
# --- Determine install directory ---
83+
84+
get_install_dir() {
85+
if [ -d "/usr/local/bin" ] && [ -w "/usr/local/bin" ]; then
86+
echo "/usr/local/bin"
87+
elif [ -d "$HOME/.local/bin" ] && [ -w "$HOME/.local/bin" ]; then
88+
echo "$HOME/.local/bin"
89+
else
90+
mkdir -p "$HOME/.local/bin" 2>/dev/null || error "Cannot create $HOME/.local/bin. Please create it manually."
91+
echo "$HOME/.local/bin"
92+
fi
93+
}
94+
95+
# --- Main ---
96+
97+
main() {
98+
info "Detecting platform..."
99+
os="$(detect_os)"
100+
arch="$(detect_arch)"
101+
info "Platform: ${os}/${arch}"
102+
103+
info "Fetching latest release version..."
104+
version="$(get_latest_version)"
105+
info "Latest version: ${version}"
106+
107+
archive="finops-cli_${version}_${os}_${arch}.tar.gz"
108+
download_url="https://github.com/${REPO}/releases/download/v${version}/${archive}"
109+
110+
tmpdir="$(mktemp -d)" || error "Failed to create temporary directory."
111+
trap 'rm -rf "$tmpdir"' EXIT
112+
113+
checksums_file="finops-cli_${version}_checksums.txt"
114+
checksums_url="https://github.com/${REPO}/releases/download/v${version}/${checksums_file}"
115+
116+
info "Downloading ${archive}..."
117+
if command -v curl >/dev/null 2>&1; then
118+
curl -fsSL -o "${tmpdir}/${archive}" "$download_url" || error "Download failed. Check that version v${version} exists at: ${download_url}"
119+
curl -fsSL -o "${tmpdir}/${checksums_file}" "$checksums_url" || error "Failed to download checksums file."
120+
elif command -v wget >/dev/null 2>&1; then
121+
wget -qO "${tmpdir}/${archive}" "$download_url" || error "Download failed. Check that version v${version} exists at: ${download_url}"
122+
wget -qO "${tmpdir}/${checksums_file}" "$checksums_url" || error "Failed to download checksums file."
123+
fi
124+
125+
info "Verifying checksum..."
126+
expected_checksum="$(grep "${archive}" "${tmpdir}/${checksums_file}" | cut -d ' ' -f 1)"
127+
if [ -z "$expected_checksum" ]; then
128+
error "Checksum for ${archive} not found in checksums file."
129+
fi
130+
if command -v sha256sum >/dev/null 2>&1; then
131+
actual_checksum="$(sha256sum "${tmpdir}/${archive}" | cut -d ' ' -f 1)"
132+
elif command -v shasum >/dev/null 2>&1; then
133+
actual_checksum="$(shasum -a 256 "${tmpdir}/${archive}" | cut -d ' ' -f 1)"
134+
else
135+
error "No SHA256 tool found. Cannot verify download integrity."
136+
fi
137+
if [ "$expected_checksum" != "$actual_checksum" ]; then
138+
error "Checksum verification failed. Expected: ${expected_checksum}, Got: ${actual_checksum}"
139+
fi
140+
info "Checksum verified."
141+
142+
info "Extracting binary..."
143+
tar --no-same-owner -xzf "${tmpdir}/${archive}" -C "$tmpdir" || error "Failed to extract archive."
144+
145+
if [ ! -f "${tmpdir}/${BINARY_NAME}" ]; then
146+
error "Binary '${BINARY_NAME}' not found in archive. The release archive may have an unexpected structure."
147+
fi
148+
149+
install_dir="$(get_install_dir)"
150+
info "Installing to ${install_dir}/${BINARY_NAME}..."
151+
152+
mv "${tmpdir}/${BINARY_NAME}" "${install_dir}/${BINARY_NAME}" || error "Failed to install binary to ${install_dir}. Try running with sudo."
153+
chmod 755 "${install_dir}/${BINARY_NAME}"
154+
155+
info "Successfully installed finops v${version} to ${install_dir}/${BINARY_NAME}"
156+
157+
# Check if the install directory is in PATH.
158+
case ":${PATH}:" in
159+
*":${install_dir}:"*) ;;
160+
*)
161+
printf '\n[warn] %s is not in your PATH.\n' "$install_dir"
162+
printf ' Add it with: export PATH="%s:$PATH"\n' "$install_dir"
163+
;;
164+
esac
165+
}
166+
167+
main

0 commit comments

Comments
 (0)