#!/usr/bin/env bash # shellcheck disable=SC2086 # Downloads and installs the latest lazydocker from # https://github.com/jesseduffield/lazydocker/releases # # Supported platforms: # macOS : Apple Silicon (arm64), Intel (x86_64) # Linux : x86_64, aarch64/arm64, armv6, armv7, x86 (32-bit) # Windows : Not supported — use install-lazydocker.ps1 (PowerShell) set -euo pipefail # ──────────────────────────────────────────── # Constants # ──────────────────────────────────────────── REPO="jesseduffield/lazydocker" 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; } # ──────────────────────────────────────────── # Ensure Homebrew is available on macOS # ──────────────────────────────────────────── ensure_brew() { if command -v brew &>/dev/null; then return 0 fi info "Homebrew is not installed. Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" if [ -d /opt/homebrew/bin ]; then export PATH="/opt/homebrew/bin:$PATH" fi if [ -d /usr/local/bin ]; then export PATH="/usr/local/bin:$PATH" fi command -v brew &>/dev/null || error "Homebrew installation failed. Install it manually: https://brew.sh/" success "Homebrew installed" } # ──────────────────────────────────────────── # 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: ":" # platform matches lazydocker's GitHub release asset naming 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 ────────────────────────────────────────────── Linux) case "$arch" in x86_64 | amd64) echo "Linux_x86_64:tar.gz" ;; aarch64 | arm64) echo "Linux_arm64:tar.gz" ;; armv7l) echo "Linux_armv7:tar.gz" ;; armv6l) echo "Linux_armv6:tar.gz" ;; i686 | i386) echo "Linux_x86:tar.gz" ;; *) error "Unsupported Linux architecture: $arch" ;; esac ;; # ── Windows (Git Bash / MSYS2 / Cygwin) ─────────────── MINGW* | MSYS* | CYGWIN*) error "Windows is not supported by this script. Use install-lazydocker.ps1 instead." ;; *) 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="lazydocker_${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 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' is required to extract .zip files." fi ;; *) error "Unknown archive format: $ext" ;; esac # Binary name local bin_name="lazydocker" 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_macos tag url platform_info="$(detect_platform)" platform="${platform_info%%:*}" ext="${platform_info##*:}" info "Detected platform: $platform (format: $ext)" is_macos="false" [[ "$platform" == "Darwin"* ]] && is_macos="true" # macOS: try Homebrew first if [[ "$is_macos" == "true" ]]; then if command -v lazydocker &>/dev/null; then local current_ver current_ver="$(lazydocker --version 2>/dev/null || echo 'unknown')" info "lazydocker is already installed ($current_ver). Skipping installation." else ensure_brew brew install lazydocker success "lazydocker installed via Homebrew" fi else if command -v lazydocker &>/dev/null; then local current_ver current_ver="$(lazydocker --version 2>/dev/null || echo 'unknown')" info "lazydocker is already installed ($current_ver). Skipping installation." else tag="$(get_latest_tag)" info "Latest release: $tag" url="$(build_download_url "$tag" "$platform" "$ext")" download_and_install "$url" "$ext" fi fi # Verify lazydocker is available if command -v lazydocker &>/dev/null; then success "lazydocker is available: $(command -v lazydocker)" else warn "lazydocker was not found in PATH after installation. You may need to open a new terminal." fi } main "$@"