mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat: add curl | sh install script
- install.sh: POSIX sh installer that detects OS/arch (incl. Rosetta), resolves the newest vX.Y.Z release via the GitHub API (skipping ownership-snapshot-current), verifies SHA256SUMS, sanity-runs the binary, and installs atomically into ~/.local/bin without sudo. Supports --version/--dir and IDX_* env overrides; prints a PATH hint, a NixOS flake suggestion, and helper-tool install hints instead of calling package managers. - scripts/install-sh-test.sh: fixture tests against a local fake release server (latest, pinned, piped, checksum mismatch, missing release, unsupported OS, PATH hint, reinstall). - ci.yml: install-script job (ShellCheck, sh + dash on Linux, sh on macOS). - release.yml: attach install.sh to each release. - README + docs/INSTALL.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
This commit is contained in:
parent
52cf73beb9
commit
a142a3718f
6 changed files with 551 additions and 1 deletions
246
install.sh
Executable file
246
install.sh
Executable file
|
|
@ -0,0 +1,246 @@
|
|||
#!/bin/sh
|
||||
# idx-cli installer: downloads a prebuilt `idx` binary from GitHub Releases,
|
||||
# verifies it against the release SHA256SUMS, and installs it without sudo.
|
||||
#
|
||||
# curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh
|
||||
# curl -fsSL https://raw.githubusercontent.com/0xrsydn/idx-cli/main/install.sh | sh -s -- --version v0.2.3
|
||||
#
|
||||
# Environment (flags take precedence):
|
||||
# IDX_VERSION release tag to install (default: latest vX.Y.Z release)
|
||||
# IDX_INSTALL_DIR install directory (default: $HOME/.local/bin)
|
||||
# IDX_GITHUB_URL GitHub base URL, for mirrors (default: https://github.com)
|
||||
# IDX_GITHUB_API GitHub API base URL, for mirrors (default: https://api.github.com)
|
||||
|
||||
set -eu
|
||||
|
||||
REPO="0xrsydn/idx-cli"
|
||||
BIN_NAME="idx"
|
||||
|
||||
say() {
|
||||
printf 'idx-install: %s\n' "$*"
|
||||
}
|
||||
|
||||
err() {
|
||||
printf 'idx-install: error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Install the idx CLI from GitHub Releases.
|
||||
|
||||
Usage: install.sh [--version <tag>] [--dir <path>]
|
||||
|
||||
Options:
|
||||
--version <tag> Release tag to install, for example v0.2.3 (default: latest)
|
||||
--dir <path> Install directory (default: $HOME/.local/bin)
|
||||
-h, --help Show this help
|
||||
|
||||
Environment: IDX_VERSION, IDX_INSTALL_DIR, IDX_GITHUB_URL, IDX_GITHUB_API
|
||||
EOF
|
||||
}
|
||||
|
||||
has() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# fetch <url> <output-file>
|
||||
fetch() {
|
||||
if has curl; then
|
||||
curl --fail --silent --show-error --location --retry 3 --output "$2" "$1"
|
||||
elif has wget; then
|
||||
wget --quiet --tries=3 --output-document="$2" "$1"
|
||||
else
|
||||
err "need curl or wget to download files"
|
||||
fi
|
||||
}
|
||||
|
||||
sha256_of() {
|
||||
if has sha256sum; then
|
||||
sha256sum "$1" | cut -d ' ' -f 1
|
||||
elif has shasum; then
|
||||
shasum -a 256 "$1" | cut -d ' ' -f 1
|
||||
else
|
||||
err "need sha256sum or shasum to verify the download"
|
||||
fi
|
||||
}
|
||||
|
||||
detect_asset() {
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
|
||||
case "$os" in
|
||||
Linux) os=linux ;;
|
||||
Darwin) os=darwin ;;
|
||||
*) err "unsupported OS: $os (supported: Linux, macOS; on Windows use WSL)" ;;
|
||||
esac
|
||||
|
||||
case "$arch" in
|
||||
x86_64 | amd64) arch=x64 ;;
|
||||
aarch64 | arm64) arch=arm64 ;;
|
||||
*) err "unsupported CPU architecture: $arch (supported: x86_64, arm64)" ;;
|
||||
esac
|
||||
|
||||
# An x64 shell under Rosetta on Apple Silicon should still get the native build.
|
||||
if [ "$os" = darwin ] && [ "$arch" = x64 ] &&
|
||||
[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
|
||||
arch=arm64
|
||||
fi
|
||||
|
||||
echo "idx-${os}-${arch}"
|
||||
}
|
||||
|
||||
# Pick the newest vX.Y.Z tag. The repo also carries non-app releases such as
|
||||
# ownership-snapshot-current, so /releases/latest is not reliable here.
|
||||
resolve_latest_version() {
|
||||
releases="$tmp_dir/releases.json"
|
||||
fetch "$api_url/repos/$REPO/releases?per_page=30" "$releases" ||
|
||||
err "could not list releases; pass --version <tag> to skip the lookup"
|
||||
|
||||
tag="$(
|
||||
sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\(v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)".*/\1/p' "$releases" |
|
||||
head -n 1
|
||||
)"
|
||||
[ -n "$tag" ] || err "no vX.Y.Z release found for $REPO"
|
||||
echo "$tag"
|
||||
}
|
||||
|
||||
os_release_id() {
|
||||
if [ -r /etc/os-release ]; then
|
||||
# shellcheck source=/dev/null
|
||||
(. /etc/os-release && echo "${ID:-}")
|
||||
fi
|
||||
}
|
||||
|
||||
mutool_hint() {
|
||||
if [ "$(uname -s)" = Darwin ]; then
|
||||
echo "brew install mupdf"
|
||||
return
|
||||
fi
|
||||
case "$(os_release_id)" in
|
||||
arch | manjaro | endeavouros) echo "sudo pacman -S mupdf-tools" ;;
|
||||
debian | ubuntu | linuxmint | pop) echo "sudo apt install mupdf-tools" ;;
|
||||
fedora) echo "sudo dnf install mupdf" ;;
|
||||
alpine) echo "sudo apk add mupdf-tools" ;;
|
||||
opensuse* | sles) echo "sudo zypper install mupdf" ;;
|
||||
*) echo "install MuPDF's mutool with your package manager" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
print_notes() {
|
||||
if [ -e /etc/NIXOS ] || [ "$(os_release_id)" = nixos ]; then
|
||||
say "NixOS detected: the flake wraps idx with its helper tools and may suit you better:"
|
||||
say " nix profile install github:$REPO#default"
|
||||
fi
|
||||
|
||||
if ! has mutool; then
|
||||
say "optional: 'idx ownership import' needs mutool from MuPDF: $(mutool_hint)"
|
||||
fi
|
||||
|
||||
found_curl_impersonate=
|
||||
for candidate in curl_chrome142 curl_chrome136 curl_chrome133a curl_chrome131 curl_chrome124 curl_chrome120 curl_chrome116; do
|
||||
if has "$candidate"; then
|
||||
found_curl_impersonate=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$found_curl_impersonate" ] && [ -z "${IDX_CURL_IMPERSONATE_BIN:-}" ]; then
|
||||
say "optional: Yahoo-backed commands need a curl_chrome* binary from curl-impersonate"
|
||||
say " (https://github.com/lexiforest/curl-impersonate) or IDX_CURL_IMPERSONATE_BIN"
|
||||
fi
|
||||
}
|
||||
|
||||
print_path_hint() {
|
||||
case ":$PATH:" in
|
||||
*":$install_dir:"*) return ;;
|
||||
esac
|
||||
|
||||
case "${SHELL:-}" in
|
||||
*/fish) line="fish_add_path $install_dir" ;;
|
||||
*/zsh) line="echo 'export PATH=\"$install_dir:\$PATH\"' >> ~/.zshrc" ;;
|
||||
*) line="echo 'export PATH=\"$install_dir:\$PATH\"' >> ~/.bashrc" ;;
|
||||
esac
|
||||
say "$install_dir is not on your PATH. Add it with:"
|
||||
say " $line"
|
||||
}
|
||||
|
||||
main() {
|
||||
version="${IDX_VERSION:-}"
|
||||
install_dir="${IDX_INSTALL_DIR:-${HOME:?HOME is not set}/.local/bin}"
|
||||
github_url="${IDX_GITHUB_URL:-https://github.com}"
|
||||
api_url="${IDX_GITHUB_API:-https://api.github.com}"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--version)
|
||||
[ $# -ge 2 ] || err "--version needs a value"
|
||||
version="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dir)
|
||||
[ $# -ge 2 ] || err "--dir needs a value"
|
||||
install_dir="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
return 0
|
||||
;;
|
||||
*) err "unknown argument: $1 (see --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
asset="$(detect_asset)"
|
||||
|
||||
tmp_dir="$(mktemp -d 2>/dev/null || mktemp -d -t idx-install)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
version="$(resolve_latest_version)"
|
||||
fi
|
||||
case "$version" in
|
||||
v*) ;;
|
||||
*) version="v$version" ;;
|
||||
esac
|
||||
|
||||
download_base="$github_url/$REPO/releases/download/$version"
|
||||
say "installing idx $version ($asset) into $install_dir"
|
||||
|
||||
fetch "$download_base/$asset" "$tmp_dir/$asset" ||
|
||||
err "download failed: $download_base/$asset (does release $version have binaries?)"
|
||||
fetch "$download_base/SHA256SUMS" "$tmp_dir/SHA256SUMS" ||
|
||||
err "download failed: $download_base/SHA256SUMS"
|
||||
|
||||
expected="$(awk -v name="$asset" '$2 == name || $2 == "*" name { print $1; exit }' "$tmp_dir/SHA256SUMS")"
|
||||
[ -n "$expected" ] || err "SHA256SUMS for $version has no entry for $asset"
|
||||
actual="$(sha256_of "$tmp_dir/$asset")"
|
||||
[ "$actual" = "$expected" ] ||
|
||||
err "checksum mismatch for $asset: expected $expected, got $actual"
|
||||
|
||||
chmod 0755 "$tmp_dir/$asset"
|
||||
"$tmp_dir/$asset" version >/dev/null 2>&1 ||
|
||||
err "downloaded binary does not run on this system"
|
||||
|
||||
mkdir -p "$install_dir" || err "cannot create $install_dir"
|
||||
staged="$install_dir/.$BIN_NAME.tmp.$$"
|
||||
if ! { cp "$tmp_dir/$asset" "$staged" && chmod 0755 "$staged" && mv -f "$staged" "$install_dir/$BIN_NAME"; }; then
|
||||
rm -f "$staged"
|
||||
err "cannot write $install_dir/$BIN_NAME"
|
||||
fi
|
||||
|
||||
say "installed $install_dir/$BIN_NAME"
|
||||
|
||||
existing="$(command -v "$BIN_NAME" 2>/dev/null || true)"
|
||||
if [ -n "$existing" ] && [ "$existing" != "$install_dir/$BIN_NAME" ]; then
|
||||
say "warning: another idx at $existing comes first on your PATH"
|
||||
fi
|
||||
|
||||
print_path_hint
|
||||
print_notes
|
||||
say "done; run 'idx --help' to get started"
|
||||
}
|
||||
|
||||
# Everything runs from main so a truncated download of this script does nothing.
|
||||
main "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue