#!/usr/bin/env bash # install-libreoffice.sh — Install the latest stable LibreOffice from documentfoundation.org. # Hardened: arch detection, version JSON when available with HTML fallback, MD5 verification, # uninstalls bundled distro libreoffice* first to avoid conflicts, --dry-run. set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="${0##*/}" DRY_RUN=0 SKIP_REMOVE_DISTRO=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 ;; --keep-distro-libreoffice) SKIP_REMOVE_DISTRO=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} (this script installs .deb packages)." ;; esac ARCH_RAW="$(uname -m)" case "$ARCH_RAW" in x86_64) LO_ARCH=x86_64; LO_SUFFIX=Linux_x86-64 ;; aarch64) LO_ARCH=aarch64; LO_SUFFIX=Linux_aarch64 ;; *) die "Unsupported architecture: $ARCH_RAW (LibreOffice ships x86_64 and aarch64)." ;; esac log "Detected: ${PRETTY_NAME:-unknown}, arch: $ARCH_RAW" export DEBIAN_FRONTEND=noninteractive log "Installing prerequisites..." run "apt-get update -qq" run "apt-get install -y curl wget tar coreutils ca-certificates" log "Resolving latest stable LibreOffice version..." INDEX="$(curl -fsSL https://download.documentfoundation.org/libreoffice/stable/)" LATEST_VERSION="$(printf '%s\n' "$INDEX" \ | grep -oP '(?<=href=")[0-9]+\.[0-9]+\.[0-9]+(?=/")' \ | sort -V | tail -1 || true)" [[ -n "$LATEST_VERSION" ]] || die "Could not detect latest version from index page." log "Latest stable: $LATEST_VERSION" BASE="https://download.documentfoundation.org/libreoffice/stable/${LATEST_VERSION}/deb/${LO_ARCH}" ARCHIVE_NAME="LibreOffice_${LATEST_VERSION}_${LO_SUFFIX}_deb.tar.gz" URL="${BASE}/${ARCHIVE_NAME}" SUM_URL="${URL}.md5" STAGE="$(mktemp -d -t lo.XXXXXX)" trap 'rm -rf "$STAGE"' EXIT ARCHIVE="$STAGE/$ARCHIVE_NAME" log "Downloading $ARCHIVE_NAME..." run "wget -q --show-progress -O '$ARCHIVE' '$URL'" log "Verifying MD5..." if EXPECTED_MD5="$(curl -fsSL "$SUM_URL" 2>/dev/null | awk '{print $1}')" && [[ -n "$EXPECTED_MD5" ]]; then ACTUAL_MD5="$(md5sum "$ARCHIVE" | awk '{print $1}')" [[ "$EXPECTED_MD5" == "$ACTUAL_MD5" ]] || die "MD5 mismatch: expected=$EXPECTED_MD5 actual=$ACTUAL_MD5" log "MD5 ok." else warn "No MD5 published for $SUM_URL; skipping checksum." fi log "Extracting..." run "tar -xzf '$ARCHIVE' -C '$STAGE'" DEBS_DIR="$(find "$STAGE" -maxdepth 3 -type d -name DEBS | head -n1 || true)" [[ -n "$DEBS_DIR" || $DRY_RUN -eq 1 ]] || die "DEBS/ directory not found in archive." if (( ! SKIP_REMOVE_DISTRO )); then if dpkg -l 'libreoffice*' 2>/dev/null | awk '/^ii/ {print $2}' | grep -q .; then log "Removing distro-supplied libreoffice* packages to avoid conflicts..." run "apt-get remove --purge -y 'libreoffice*'" fi fi log "Installing .deb packages..." run "dpkg -i -R '$DEBS_DIR'" log "Resolving any missing dependencies..." run "apt-get install -f -y" log "Done. LibreOffice $LATEST_VERSION installed."