#!/usr/bin/env bash # shellcheck disable=SC2086 # Downloads and installs the latest ctop (container terminal UI) from # https://github.com/bcicen/ctop/releases # # Supported platforms: # macOS : Intel (x86_64), Apple Silicon via Rosetta 2 (arm64) # Linux : x86_64 (amd64), aarch64/arm64, arm, ppc64le # Windows : Not supported — use install-ctop.ps1 (PowerShell) set -euo pipefail # ──────────────────────────────────────────── # Constants # ──────────────────────────────────────────── REPO="bcicen/ctop" 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 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: "-" matching ctop's GitHub release asset naming detect_platform() { local os arch os="$(uname -s)" arch="$(uname -m)" case "$os" in # ── macOS ────────────────────────────────────────────── Darwin) case "$arch" in x86_64) echo "darwin-amd64" ;; arm64) # ctop does not provide a native darwin-arm64 binary. # Use the amd64 binary via Rosetta 2 (works on Apple Silicon). if [[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" == "1" ]] \ || command -v arch &>/dev/null; then warn "ctop has no native Apple Silicon build." warn "Using amd64 binary via Rosetta 2." echo "darwin-amd64" else error "No native Apple Silicon build available and Rosetta 2 is not installed." \ "Install Rosetta 2: softwareupdate --install-rosetta" fi ;; *) error "Unsupported macOS architecture: $arch" ;; esac ;; # ── Linux ────────────────────────────────────────────── Linux) case "$arch" in x86_64|amd64) echo "linux-amd64" ;; aarch64|arm64) echo "linux-arm64" ;; armv7l|armhf) echo "linux-arm" ;; ppc64le) echo "linux-ppc64le" ;; *) error "Unsupported Linux architecture: $arch" ;; esac ;; # ── Windows (Git Bash / MSYS2 / Cygwin) ─────────────── MINGW*|MSYS*|CYGWIN*) error "Windows is not supported by this script. Use install-ctop.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 # ──────────────────────────────────────────── # ctop release assets follow the pattern: ctop-{version}-{os}-{arch} # where {version} has the 'v' prefix stripped from the git tag. build_download_url() { local tag="$1" local platform="$2" # e.g. darwin-amd64, linux-amd64 local version="${tag#v}" # Remove 'v' prefix (v0.7.7 → 0.7.7) local filename="ctop-${version}-${platform}" echo "https://github.com/${REPO}/releases/download/${tag}/${filename}" } # ──────────────────────────────────────────── # Download & install (raw binary — no archive extraction needed) # ──────────────────────────────────────────── download_and_install() { local url="$1" 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" # ctop provides raw binaries (not archives), so skip extraction local bin_src="${tmpdir}/${filename}" # Create install directory mkdir -p "$INSTALL_DIR" local install_path="${INSTALL_DIR}/ctop" info "Installing: ${install_path}" cp "$bin_src" "$install_path" chmod +x "$install_path" success "Installation complete!" info "Version check:" "$install_path" -v || 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 tag url platform="$(detect_platform)" info "Detected platform: $platform" tag="$(get_latest_tag)" info "Latest release: $tag" url="$(build_download_url "$tag" "$platform")" download_and_install "$url" } main "$@"