#!/usr/bin/env bash # install-jetbrains-toolbox.sh — Install JetBrains Toolbox into the invoking user's home. # Hardened: dependency check w/ auto-install, SHA-256 verification against JetBrains' # release metadata, idempotent (replaces atomically), .desktop entry, --dry-run. set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="${0##*/}" DRY_RUN=0 ASSUME_YES=0 usage() { cat <&2; } die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR while (( $# )); do case "$1" in --dry-run) DRY_RUN=1 ;; -y|--yes) ASSUME_YES=1 ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1 (try --help)" ;; esac shift done (( EUID != 0 )) || die "Do NOT run as root. Toolbox is a per-user install." [[ -r /etc/os-release ]] || die "/etc/os-release not found." # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}:${ID_LIKE:-}" in *ubuntu*|*debian*) PKG_MGR=apt ;; *fedora*|*rhel*) PKG_MGR=dnf ;; *arch*) PKG_MGR=pacman ;; *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; esac log "Detected: ${PRETTY_NAME:-unknown} (pkg: $PKG_MGR)" confirm() { (( ASSUME_YES )) && return 0 read -rp "$1 [y/N] " ans [[ "$ans" =~ ^[Yy] ]] } ensure_pkg() { local pkg="$1" probe="$2" if eval "$probe" >/dev/null 2>&1; then return 0; fi if ! confirm "Package '$pkg' is missing. Install via sudo $PKG_MGR?"; then die "'$pkg' is required." fi case "$PKG_MGR" in apt) run "sudo apt-get update -qq && sudo apt-get install -y '$pkg'" ;; dnf) run "sudo dnf install -y '$pkg'" ;; pacman) run "sudo pacman -Sy --noconfirm '$pkg'" ;; esac } # Map deps: command-or-package-probe ensure_pkg curl "command -v curl" ensure_pkg jq "command -v jq" ensure_pkg tar "command -v tar" case "$PKG_MGR" in apt) ensure_pkg libfuse2 "dpkg -s libfuse2" ;; dnf) ensure_pkg fuse-libs "rpm -q fuse-libs" ;; pacman) ensure_pkg fuse2 "pacman -Q fuse2" ;; esac ARCH_RAW="$(uname -m)" case "$ARCH_RAW" in x86_64) TOOLBOX_ARCH_KEY=linux ;; aarch64) TOOLBOX_ARCH_KEY=linuxARM64 ;; *) die "Unsupported architecture: $ARCH_RAW" ;; esac log "Querying JetBrains release feed..." RELEASE_JSON="$(curl -fsSL 'https://data.services.jetbrains.com/products/releases?code=TBA&latest=true&type=release' \ -H 'Origin: https://www.jetbrains.com' \ -H 'Referer: https://www.jetbrains.com/toolbox/download/')" TOOLBOX_URL="$(jq -r --arg k "$TOOLBOX_ARCH_KEY" '.TBA[0].downloads[$k].link // empty' <<<"$RELEASE_JSON")" TOOLBOX_SHA="$(jq -r --arg k "$TOOLBOX_ARCH_KEY" '.TBA[0].downloads[$k].checksumLink // empty' <<<"$RELEASE_JSON")" TOOLBOX_VER="$(jq -r '.TBA[0].version // "?"' <<<"$RELEASE_JSON")" [[ -n "$TOOLBOX_URL" ]] || die "Could not resolve Toolbox download URL from release feed." log "Latest Toolbox: $TOOLBOX_VER ($TOOLBOX_ARCH_KEY)" INSTALL_DIR="$HOME/.local/share/JetBrains/Toolbox" STAGE_DIR="$(mktemp -d -t toolbox.XXXXXX)" trap 'rm -rf "$STAGE_DIR"' EXIT TARBALL="$STAGE_DIR/toolbox.tar.gz" log "Downloading..." run "curl -fsSL -o '$TARBALL' '$TOOLBOX_URL'" if [[ -n "$TOOLBOX_SHA" ]]; then log "Verifying SHA-256..." EXPECTED_SHA="$(curl -fsSL "$TOOLBOX_SHA" | awk '{print $1}')" ACTUAL_SHA="$(sha256sum "$TARBALL" | awk '{print $1}')" if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "$ACTUAL_SHA" ]]; then die "SHA-256 mismatch! expected=$EXPECTED_SHA actual=$ACTUAL_SHA" fi log "SHA-256 ok." else warn "No checksum URL in release feed; skipping verification." fi log "Extracting to $INSTALL_DIR..." run "mkdir -p '$INSTALL_DIR'" run "tar -xzf '$TARBALL' --strip-components=1 -C '$INSTALL_DIR'" BIN_PATH="$INSTALL_DIR/bin/jetbrains-toolbox" DESKTOP_SRC="$INSTALL_DIR/bin/jetbrains-toolbox.desktop" ICON_PATH="$INSTALL_DIR/bin/toolbox-tray-color.png" [[ -x "$BIN_PATH" || $DRY_RUN -eq 1 ]] || die "Toolbox binary missing after extract." # Optional ~/bin symlink if [[ -d "$HOME/bin" ]]; then run "ln -sfn '$BIN_PATH' '$HOME/bin/jetbrains-toolbox'" fi # .desktop launcher APPS_DIR="$HOME/.local/share/applications" run "mkdir -p '$APPS_DIR'" DESKTOP_DST="$APPS_DIR/jetbrains-toolbox.desktop" if [[ -f "$DESKTOP_SRC" ]] || (( DRY_RUN )); then run "cp '$DESKTOP_SRC' '$DESKTOP_DST'" run "sed -i 's|^Exec=.*|Exec=$BIN_PATH %u|' '$DESKTOP_DST'" if [[ -f "$ICON_PATH" ]]; then run "sed -i 's|^Icon=.*|Icon=$ICON_PATH|' '$DESKTOP_DST'" fi run "chmod 0755 '$DESKTOP_DST'" log "Desktop entry installed: $DESKTOP_DST" else warn "No .desktop file in archive; skipping launcher." fi log "Done. JetBrains Toolbox $TOOLBOX_VER installed to $INSTALL_DIR"