#!/usr/bin/env bash # shellcheck disable=SC2086 # Downloads and installs the latest stable lazygit from # https://github.com/jesseduffield/lazygit/releases # # Supported platforms: # macOS : Apple Silicon (arm64), Intel (x86_64) # Linux : x86_64, aarch64/arm64, armv6 # FreeBSD : x86_64 # Windows : Git Bash / MSYS2 / Cygwin (.zip install) set -euo pipefail # ──────────────────────────────────────────── # Constants # ──────────────────────────────────────────── REPO="jesseduffield/lazygit" API_URL="https://api.github.com/repos/${REPO}/releases" INSTALL_DIR="${HOME}/.local/bin" # ──────────────────────────────────────────── # Utility functions (all stderr output) # ──────────────────────────────────────────── info() { echo "[INFO] $*" >&2; } warn() { echo "[WARN] $*" >&2; } error() { echo "[ERROR] $*" >&2 exit 1 } success() { echo "[OK] $*" >&2; } # ──────────────────────────────────────────── # Check for git (required by lazygit) # ──────────────────────────────────────────── if ! command -v git &>/dev/null; then info "git is not installed. Installing git..." case "$(uname -s)" in Darwin) if command -v brew &>/dev/null; then brew install -y git success "git installed via Homebrew" else error "Homebrew (brew) is required to install git on macOS. Install it from https://brew.sh/" fi ;; Linux) if command -v apt-get &>/dev/null; then sudo apt-get update -y && sudo apt-get install -y git success "git installed via apt-get" elif command -v dnf &>/dev/null; then sudo dnf install -y git success "git installed via dnf" elif command -v yum &>/dev/null; then sudo yum install -y git success "git installed via yum" elif command -v zypper &>/dev/null; then sudo zypper -n install git success "git installed via zypper" elif command -v pacman &>/dev/null; then sudo pacman -S --noconfirm git success "git installed via pacman" else error "No supported package manager found. Install git manually." fi ;; *) error "Cannot auto-install git on this OS. Please install git manually." ;; esac # Verify git is now available if ! command -v git &>/dev/null; then error "git installation failed. Please install git manually and re-run this script." fi fi info "git is available: $(command -v git)" # ──────────────────────────────────────────── # Check required commands # ──────────────────────────────────────────── for cmd in curl uname; do command -v "$cmd" &>/dev/null || error "'$cmd' command is required." done # Fallback: use python3 if jq is not available if command -v jq &>/dev/null; then _json_latest_tag() { jq -r '[.[] | select(.prerelease==false)] | .[0].tag_name' } else command -v python3 &>/dev/null || error "'jq' or 'python3' is required." _json_latest_tag() { python3 -c " import sys, json data = json.load(sys.stdin) for r in data: if not r.get('prerelease'): print(r['tag_name']) break " } fi # ──────────────────────────────────────────── # OS / Architecture detection # ──────────────────────────────────────────── # Returns: ":" # asset_name = lazygit_X.XX.X_PLATFORM # archive_format = tar.gz | zip detect_platform() { local os arch os="$(uname -s)" arch="$(uname -m)" case "$os" in # ── macOS ────────────────────────────────────────────── Darwin) case "$arch" in arm64) echo "darwin_arm64:tar.gz" ;; x86_64) echo "darwin_x86_64:tar.gz" ;; *) error "Unsupported macOS architecture: $arch" ;; esac ;; # ── Linux (including Raspberry Pi) ────────────────── Linux) case "$arch" in x86_64 | amd64) echo "linux_x86_64:tar.gz" ;; aarch64 | arm64) # RPi 4/5, 64bit OS echo "linux_arm64:tar.gz" ;; armv7l) # RPi 2/3, 32bit OS - use armv6 as fallback echo "linux_armv6:tar.gz" ;; armv6l) # RPi Zero / 1 echo "linux_armv6:tar.gz" ;; i686 | i386) echo "linux_32-bit:tar.gz" ;; *) error "Unsupported Linux architecture: $arch" ;; esac ;; # ── FreeBSD ──────────────────────────────────────────── FreeBSD) case "$arch" in amd64 | x86_64) echo "freebsd_x86_64:tar.gz" ;; *) error "FreeBSD x86_64 is only supported (current: $arch)" ;; esac ;; # ── Windows (Git Bash / MSYS2 / Cygwin) ─────────────── MINGW* | MSYS* | CYGWIN*) warn "Windows environment (Git Bash/MSYS2/Cygwin) detected." warn "Native .exe will be installed." case "$arch" in x86_64) echo "windows_x86_64:zip" ;; aarch64) echo "windows_arm64:zip" ;; i686) echo "windows_32-bit:zip" ;; *) error "Unsupported Windows architecture: $arch" ;; esac ;; *) error "Unsupported OS: $os (uname -a: $(uname -a))" ;; esac } # ──────────────────────────────────────────── # Get latest stable release tag # ──────────────────────────────────────────── get_latest_tag() { info "Fetching latest release from GitHub API..." local releases releases="$(curl -fsSL "${API_URL}")" || error "Failed to call GitHub API." local tag tag="$(echo "$releases" | _json_latest_tag)" [[ -z "$tag" || "$tag" == "null" ]] && error "Could not find latest release tag." echo "$tag" } # ──────────────────────────────────────────── # Build download URL # ──────────────────────────────────────────── build_download_url() { local tag="$1" local platform="$2" # e.g. darwin_arm64, linux_x86_64 local ext="$3" # tar.gz | zip local version="${tag#v}" # Remove 'v' prefix local filename="lazygit_${version}_${platform}.${ext}" echo "https://github.com/${REPO}/releases/download/${tag}/${filename}" } # ──────────────────────────────────────────── # Download, extract & install # ──────────────────────────────────────────── download_and_install() { local url="$1" local ext="$2" local is_windows="${3:-false}" local tmpdir tmpdir="$(mktemp -d)" trap '[[ -d "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT local filename filename="${url##*/}" info "Downloading: $url" curl -fsSL --progress-bar -o "${tmpdir}/${filename}" "$url" || error "Download failed: $url" info "Extracting..." case "$ext" in tar.gz) command -v tar &>/dev/null || error "'tar' command is required." tar -xzf "${tmpdir}/${filename}" -C "$tmpdir" ;; zip) if command -v unzip &>/dev/null; then unzip -q "${tmpdir}/${filename}" -d "$tmpdir" elif command -v tar &>/dev/null; then tar -xf "${tmpdir}/${filename}" -C "$tmpdir" else error "'unzip' or 'tar' (Windows 10+) is required." fi ;; *) error "Unknown archive format: $ext" ;; esac # Binary name local bin_name="lazygit" [[ "$is_windows" == "true" ]] && bin_name="lazygit.exe" local bin_src bin_src="$(find "$tmpdir" -maxdepth 3 -type f -name "$bin_name" | head -1)" [[ -z "$bin_src" ]] && error "Cannot find '$bin_name' in the archive." # Create install directory mkdir -p "$INSTALL_DIR" local install_path="${INSTALL_DIR}/${bin_name}" info "Installing: ${install_path}" cp "$bin_src" "$install_path" chmod +x "$install_path" success "Installation complete!" info "Version check:" "$install_path" --version || true # PATH instructions if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then warn "'${INSTALL_DIR}' is not in your PATH." warn "Add this line to your ~/.bashrc or ~/.zshrc:" warn " export PATH=\"\$HOME/.local/bin:\$PATH\"" fi } # ──────────────────────────────────────────── # Main # ──────────────────────────────────────────── main() { local platform_info platform ext is_windows tag url platform_info="$(detect_platform)" platform="${platform_info%%:*}" ext="${platform_info##*:}" info "Detected platform: $platform (format: $ext)" is_windows="false" [[ "$platform" == "windows"* ]] && is_windows="true" tag="$(get_latest_tag)" info "Latest release: $tag" url="$(build_download_url "$tag" "$platform" "$ext")" download_and_install "$url" "$ext" "$is_windows" } main "$@"