#!/usr/bin/env bash # install-firefox.sh — Install Firefox from Mozilla's official APT repository. # Removes the Snap transition package and pins Mozilla as the source of truth. # Hardened: distro-detect, idempotent, signed-by keyring, ERR trap, --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; } on_err() { local rc=$? line=$1; printf '\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n' "${SCRIPT_NAME%.sh}" "$line" "$rc" >&2; } trap 'on_err $LINENO' 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 "Must run as root. Try: sudo $SCRIPT_NAME" [[ -r /etc/os-release ]] || die "/etc/os-release not found; cannot detect distro." # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}:${ID_LIKE:-}" in *ubuntu*|*debian*) : ;; *) die "Unsupported distro: ${PRETTY_NAME:-unknown}. Requires Debian/Ubuntu." ;; esac log "Detected: ${PRETTY_NAME:-unknown} (codename: ${VERSION_CODENAME:-?})" APT_YES=() (( ASSUME_YES )) && APT_YES=(-y) || APT_YES=(-y) # always -y for safety in scripted use export DEBIAN_FRONTEND=noninteractive KEYRING=/etc/apt/keyrings/packages.mozilla.org.asc SOURCES=/etc/apt/sources.list.d/mozilla.list PIN=/etc/apt/preferences.d/mozilla log "Removing Firefox Snap and Ubuntu's transitional wrapper (if present)..." if command -v snap >/dev/null 2>&1; then run "snap disable firefox >/dev/null 2>&1 || true" run "snap remove --purge firefox >/dev/null 2>&1 || true" fi run "apt-get remove --purge ${APT_YES[*]} firefox >/dev/null 2>&1 || true" run "rm -f /usr/bin/firefox" log "Installing prerequisites (wget, gpg, ca-certificates)..." run "apt-get update -qq" run "apt-get install ${APT_YES[*]} wget gpg ca-certificates" log "Configuring Mozilla APT repository..." run "install -d -m 0755 /etc/apt/keyrings" if [[ ! -s "$KEYRING" ]]; then run "wget -qO '$KEYRING' https://packages.mozilla.org/apt/repo-signing-key.gpg" run "chmod 0644 '$KEYRING'" else log "Keyring already present at $KEYRING (skipping download)." fi # Verify key fingerprint matches Mozilla's published fingerprint EXPECTED_FPR="35BAA0B33E9EB396F59CA838C0BA5CE6DC6315A3" ACTUAL_FPR="$(gpg --show-keys --with-colons "$KEYRING" 2>/dev/null | awk -F: '/^fpr/ {print $10; exit}')" if [[ "$ACTUAL_FPR" != "$EXPECTED_FPR" ]]; then warn "Mozilla key fingerprint mismatch (expected $EXPECTED_FPR, got ${ACTUAL_FPR:-none}). Continuing, but verify manually." else log "Mozilla key fingerprint verified." fi DESIRED_SRC='deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main' if [[ ! -f "$SOURCES" ]] || ! grep -qxF "$DESIRED_SRC" "$SOURCES"; then run "printf '%s\n' '$DESIRED_SRC' > '$SOURCES'" fi log "Pinning Mozilla repo to priority 1000..." if [[ ! -f "$PIN" ]] || ! grep -q 'origin packages.mozilla.org' "$PIN"; then if (( DRY_RUN )); then printf ' DRY-RUN: write %s\n' "$PIN" else cat >"$PIN" <<'EOF' Package: * Pin: origin packages.mozilla.org Pin-Priority: 1000 EOF fi fi log "Installing Firefox from Mozilla repo..." run "apt-get update -qq" run "apt-get install ${APT_YES[*]} firefox" if (( ! DRY_RUN )) && command -v firefox >/dev/null 2>&1; then log "Installed: $(firefox --version 2>/dev/null || echo 'firefox')" fi log "Done. Firefox installed from Mozilla APT and will auto-update."