#!/usr/bin/env bash # install-bruno.sh — Install Bruno API client from the official APT repository. # Hardened: arch-aware, signed-by keyring with retries (keyserver flake), idempotent, # clean desktop entry, --dry-run. set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="${0##*/}" DRY_RUN=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 ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1 (try --help)" ;; esac shift done (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" [[ -r /etc/os-release ]] || die "/etc/os-release not found." # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}:${ID_LIKE:-}" in *ubuntu*|*debian*) : ;; *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; esac ARCH="$(dpkg --print-architecture)" [[ "$ARCH" == "amd64" ]] || die "Bruno APT repo only ships amd64 (detected: $ARCH)." log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH" export DEBIAN_FRONTEND=noninteractive KEYRING=/etc/apt/keyrings/bruno.gpg SOURCES=/etc/apt/sources.list.d/bruno.list KEY_ID="9FA6017ECABE0266" DESKTOP=/usr/share/applications/bruno.desktop log "Installing prerequisites..." run "apt-get update -qq" run "apt-get install -y curl gpg ca-certificates" log "Configuring Bruno APT repository..." run "install -d -m 0755 /etc/apt/keyrings" if [[ ! -s "$KEYRING" ]]; then # keyserver.ubuntu.com is occasionally flaky — retry a few times. ok=0 for attempt in 1 2 3 4 5; do if (( DRY_RUN )); then printf ' DRY-RUN: fetch key 0x%s (attempt %d)\n' "$KEY_ID" "$attempt" ok=1 break fi if curl -fsSL --max-time 30 "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x${KEY_ID}" \ | gpg --dearmor -o "$KEYRING" 2>/dev/null && [[ -s "$KEYRING" ]]; then ok=1 break fi warn "Key fetch failed (attempt $attempt/5); retrying in $((attempt*2))s..." sleep "$((attempt*2))" done (( ok )) || die "Could not retrieve Bruno signing key after 5 attempts." run "chmod 0644 '$KEYRING'" fi DESIRED_SRC="deb [arch=${ARCH} signed-by=${KEYRING}] http://debian.usebruno.com/ bruno stable" if [[ ! -f "$SOURCES" ]] || ! grep -qxF "$DESIRED_SRC" "$SOURCES"; then run "printf '%s\n' '$DESIRED_SRC' > '$SOURCES'" fi log "Installing Bruno..." run "apt-get update -qq" run "apt-get install -y bruno" log "Writing desktop entry..." if (( DRY_RUN )); then printf ' DRY-RUN: write %s\n' "$DESKTOP" else cat >"$DESKTOP" <<'EOF' [Desktop Entry] Name=Bruno Comment=Open-source API Client Exec=bruno %U Terminal=false Type=Application Icon=bruno Categories=Development;Utility; StartupNotify=true EOF chmod 0644 "$DESKTOP" fi log "Done. Bruno installed."