#!/bin/bash # Version: 20251218.GA02 # FileWave Booster (fwbooster) Upgrade Script (Debian 12/13) # # Safer recommended 1-liner (runs in screen): # sudo DEBIAN_FRONTEND=noninteractive bash -c 'apt-get update -y && apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" screen && screen -S fw_upgrade bash -c "wget -qO- https://kb.filewave.com/attachments/412 | bash -s -- -v 16.3.0 -r 1 -p -y"' # # Options: # -v, --version Target FileWave Booster version (e.g., 16.3.0) # -r, --revision Legacy selector (accepted for compatibility; coerced to '1') # -d, --dev Use Dev download server # -b, --beta Use Beta download server # -p, --prod Use Production download server (default) # -y, --yes Auto-yes for routine prompts (install, OS patch upgrade, reboot) # -yy, --yesyes Auto-yes for ALL prompts (includes screen warning + Debian 12->13 release upgrade); # NOTE: if root has no password set, -yy will exit with an error (no prompting). # # Notes: # - Supports Debian 12 and Debian 13 (enforced via version matrix below). # - Debian 13 is required for FileWave versions >= 16.3.0. set -u # Ensure noninteractive package operations by default (we still prompt via /dev/tty where needed) export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:-noninteractive} export DEBCONF_NONINTERACTIVE_SEEN=${DEBCONF_NONINTERACTIVE_SEEN:-true} #################### DEFAULTS #################### VERSION="16.3.0" REVISION="1" SERVER_TYPE="prod" BASE_URL="https://fwdl.filewave.com" auto_yes=false auto_risky_yes=false OS_UPGRADED=false # Minimum FileWave version that requires Debian 13 (keep aligned with other FileWave upgrade scripts) MIN_DEBIAN13_FW_VERSION="16.3.0" ############################################# ##### BEGIN SHARED PLATFORM BLOCK (KEEP IN SYNC) ##### # This block is intentionally identical across: # - FileWave IVS Upgrade Script # - FileWave Server (fwxserver) Upgrade Script # - FileWave Booster (fwbooster) Upgrade Script # Any changes to Debian upgrade, screen safety, /boot safety, # root-password enforcement, and download validation should be # made in ALL THREE scripts. ############################################# die() { echo "[ERROR] $1" >&2 exit 1 } require_amd64() { local arch arch=$(dpkg --print-architecture 2>/dev/null || echo \"unknown\") if [[ \"$arch\" != \"amd64\" ]]; then die \"[ARCH] Unsupported architecture: $arch. This script supports amd64 only.\" fi } is_running_under_multiplexer() { # Detect GNU screen via env or process ancestry (helps when sudo strips env). if [[ -n "${STY:-}" ]]; then return 0 fi local pid=$$ local depth=0 while [[ "$pid" -gt 1 && $depth -lt 6 ]]; do local comm comm=$(ps -o comm= -p "$pid" 2>/dev/null | tr -d ' ') if [[ "$comm" == "screen" || "$comm" == "SCREEN" ]]; then return 0 fi pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ') [[ -z "$pid" ]] && break depth=$((depth + 1)) done return 1 } check_screen_session_safety() { # Enforce screen usage for long-running operations. # This is intentionally NOT based on SSH_* env vars because sudo can strip them. if ! command -v screen >/dev/null 2>&1; then echo "[SESSION] Installing 'screen' (recommended)..." DEBIAN_FRONTEND=noninteractive apt-get install -y -q screen || \ die "[SESSION] Failed to install screen." fi if is_running_under_multiplexer; then return 0 fi echo "[SESSION] You are NOT running inside a 'screen' session." echo "[SESSION] This process can take a long time and an SSH disconnect can leave the system in a bad state." echo echo "[SESSION] Recommended:" echo "[SESSION] screen -S fw_upgrade" echo "[SESSION] " echo echo "[SESSION] If you are already in screen, reconnect with:" echo "[SESSION] screen -r fw_upgrade" echo if [[ "${auto_risky_yes:-false}" == "true" ]]; then echo "[SESSION] Risky auto-yes mode enabled; continuing despite not running inside 'screen'." return 0 fi local resp read -p "Continue without screen? (yes/no): " resp < /dev/tty if [[ ! "$resp" =~ ^[Yy]([Ee][Ss])?$ ]]; then die "[SESSION] Aborting. Please start a 'screen' session and re-run." fi } check_root_password_state() { # passwd -S output format: # root P ... -> password set # root L ... -> locked # root NP ... -> no password passwd -S root 2>/dev/null | awk '{print $2}' } ensure_root_password_set() { # Some package install steps invoke 'su' or call tools that enforce root auth policies. # For appliances where root is disabled by default, we require the operator to set it. while true; do local state state="$(check_root_password_state || true)" if [[ "$state" == "P" ]]; then echo "[ROOT] Root password is set." return 0 fi # If running in fully non-interactive "yesyes" mode, do not try to prompt. if [[ "${auto_risky_yes:-false}" == "true" ]]; then echo echo "[ROOT] ERROR: Root password is NOT set (state: ${state:-unknown})." echo "[ROOT] This run is in -yy/--yesyes mode and cannot prompt for passwords." echo "[ROOT] Set a root password (and write it down), then re-run WITHOUT -yy:" echo "[ROOT] sudo passwd root" echo exit 1 fi echo echo "[ROOT] Root password is NOT set (state: ${state:-unknown})." echo echo "This upgrade requires a valid root password because parts of the installation" echo "process invoke 'su'. You MUST set a root password now and write it down." echo echo "Running: passwd root" echo if passwd root < /dev/tty > /dev/tty 2>&1; then echo echo "[ROOT] Password command completed. Re-checking root password state..." else echo echo "[ROOT] passwd returned an error. Please try again." fi done } boot_usage_percent() { df -P /boot 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $5}' } has_separate_boot() { mountpoint -q /boot 2>/dev/null } boot_fs_use_pct() { df -P /boot 2>/dev/null | awk 'NR==2 {print $5}' || echo "n/a" } root_fs_use_pct() { df -P / 2>/dev/null | awk 'NR==2 {print $5}' || echo "n/a" } boot_dir_size() { du -sh /boot 2>/dev/null | awk 'NR==1 {print $1}' || echo "n/a" } check_boot_space() { if mountpoint -q /boot; then local usep usep="$(boot_usage_percent)" if [[ -n "$usep" ]]; then echo "[BOOT] /boot usage: ${usep}%" if (( usep >= 95 )); then die "[BOOT] /boot is critically full (${usep}%). Run kernel cleanup before continuing." elif (( usep >= 80 )); then echo "[BOOT] Warning: /boot is getting full (${usep}%). Kernel cleanup is recommended." fi fi fi } kernel_cleanup_keep2() { # Purge old Debian kernel image packages, keeping: # - currently running kernel # - one newest additional kernel (fallback) if ! mountpoint -q /boot; then echo "[KERNEL-CLEANUP] /boot is not a separate mountpoint; skipping kernel cleanup." return 0 fi echo "[KERNEL-CLEANUP] Checking for excess installed Debian kernels to prevent /boot filling..." local running_ver running_pkg running_ver="$(uname -r)" running_pkg="linux-image-${running_ver}" mapfile -t images < <(dpkg-query -W -f='${Package}\n' 'linux-image-[0-9]*-amd64' 2>/dev/null | sort -V || true) if ((${#images[@]} <= 2)); then echo "[KERNEL-CLEANUP] ${#images[@]} kernel image package(s) installed; nothing to clean." df -h /boot || true return 0 fi local keep=("$running_pkg") local i for (( i=${#images[@]}-1; i>=0; i-- )); do [[ "${images[$i]}" == "$running_pkg" ]] && continue keep+=("${images[$i]}") break done local remove=() local pkg k skip for pkg in "${images[@]}"; do skip=false for k in "${keep[@]}"; do [[ "$pkg" == "$k" ]] && skip=true && break done $skip || remove+=("$pkg") done echo "[KERNEL-CLEANUP] Running kernel : $running_ver" echo "[KERNEL-CLEANUP] Keeping : ${keep[*]}" echo "[KERNEL-CLEANUP] Removing : ${remove[*]:-(none)}" if ((${#remove[@]})); then DEBIAN_FRONTEND=noninteractive apt-get -y purge "${remove[@]}" || \ echo "[KERNEL-CLEANUP] Warning: kernel purge encountered issues; check logs." DEBIAN_FRONTEND=noninteractive apt-get -y autoremove --purge || true update-grub >/dev/null 2>&1 || true fi df -h /boot || true } ensure_boot_space() { local phase="${1:-unspecified}" echo "[BOOT] Preflight check before: ${phase}" # Proactively free space in /boot by purging old kernels (keeping running + one fallback). # If /boot is still critically full after cleanup, abort before attempting any upgrades that # may generate new initramfs images (which writes to /boot and can brick the upgrade). if ! mountpoint -q /boot; then return 0 fi local usep usep="$(boot_usage_percent)" # If /boot is getting full, attempt cleanup first. if [[ -n "$usep" ]] && (( usep >= 80 )); then echo "[BOOT] /boot usage is high (${usep}%). Attempting kernel cleanup..." kernel_cleanup_keep2 usep="$(boot_usage_percent)" fi # Hard stop if still critical after cleanup. if [[ -n "$usep" ]] && (( usep >= 95 )); then die "[BOOT] /boot is critically full (${usep}%) even after kernel cleanup. Manual remediation required before continuing." fi # If still high (but not critical), warn and continue. if [[ -n "$usep" ]] && (( usep >= 80 )); then echo "[BOOT] Warning: /boot is still high after cleanup (${usep}%). Upgrade may fail if additional kernels/initramfs are generated." fi } validate_download_exists() { # Validate a URL is reachable before download (clearer error than wget retries). local url="$1" echo "[DOWNLOAD] Validating download exists: $url" if command -v wget >/dev/null 2>&1; then if wget -q --spider "$url"; then return 0 fi elif command -v curl >/dev/null 2>&1; then if curl -fsSI "$url" >/dev/null; then return 0 fi else # Fall back: no validation tool available; caller will attempt download. echo "[DOWNLOAD] Warning: neither wget nor curl is available for existence check; skipping validation." return 0 fi return 1 } upgrade_to_debian13() { # Debian 12 -> 13 in-place upgrade. Requires screen session safety checks. check_screen_session_safety echo "[DEBIAN-UPGRADE] Debian 12 detected but target FileWave version requires Debian 13 (trixie)." echo "[DEBIAN-UPGRADE] This is a long-running in-place OS upgrade." local deb13_confirm if [[ "${auto_risky_yes:-false}" == "true" ]]; then deb13_confirm="yes" echo "[DEBIAN-UPGRADE] Risky auto-yes mode enabled; proceeding without interactive confirmation." else read -p "Proceed with Debian 12 -> 13 OS upgrade now? (yes/no): " deb13_confirm < /dev/tty fi if [[ ! "$deb13_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then die "[DEBIAN-UPGRADE] Aborting. Debian 13 is required for this target version." fi local REQUIRED_GIB=5 local avail_gib avail_gib=$(df -BG / | awk 'NR==2 {gsub(/G/,"",$4); print $4}') [[ -z "$avail_gib" ]] && die "[DEBIAN-UPGRADE] Unable to determine free disk space on /." (( avail_gib < REQUIRED_GIB )) && die "[DEBIAN-UPGRADE] Not enough free disk space: ${avail_gib} GiB available, need ${REQUIRED_GIB} GiB." echo "[DEBIAN-UPGRADE] Disk space OK: ${avail_gib} GiB free." # Remove legacy FileWave APT lists to avoid stale suites rm -f /etc/apt/sources.list.d/filewave-beta.list >/dev/null 2>&1 || true rm -f /etc/apt/sources.list.d/filewave-dev.list >/dev/null 2>&1 || true rm -f /etc/apt/sources.list.d/filewave-release.list >/dev/null 2>&1 || true ensure_boot_space "Debian 12 pre-upgrade kernel space check" echo "[DEBIAN-UPGRADE] Updating Debian 12 (bookworm) fully before release upgrade..." apt-get update -y || die "[DEBIAN-UPGRADE] apt-get update failed on Debian 12." DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ --autoremove || die "[DEBIAN-UPGRADE] dist-upgrade failed on Debian 12." echo "[DEBIAN-UPGRADE] Switching APT sources from bookworm -> trixie..." sed -i 's/bookworm/trixie/g' /etc/apt/sources.list || die "[DEBIAN-UPGRADE] Failed to update /etc/apt/sources.list." find /etc/apt/sources.list.d -maxdepth 1 -type f -exec sed -i 's/bookworm/trixie/g' {} \; 2>/dev/null || true # Preseed grub-pc device selection (noninteractive safety) if dpkg-query -W -f='${Status}' grub-pc 2>/dev/null | grep -q "install ok installed"; then local ROOT_OR_BOOT_DEV BOOT_DISK PARENT_DISK ROOT_OR_BOOT_DEV=$(findmnt -no SOURCE /boot 2>/dev/null || findmnt -no SOURCE / 2>/dev/null || echo "") BOOT_DISK="" if [[ -n "$ROOT_OR_BOOT_DEV" ]]; then PARENT_DISK=$(lsblk -no PKNAME "$ROOT_OR_BOOT_DEV" 2>/dev/null || echo "") if [[ -n "$PARENT_DISK" ]]; then BOOT_DISK="$PARENT_DISK" else BOOT_DISK=$(basename "$ROOT_OR_BOOT_DEV") fi fi if [[ -n "$BOOT_DISK" ]]; then echo "[DEBIAN-UPGRADE] Preseeding grub-pc install device as /dev/$BOOT_DISK..." echo "grub-pc grub-pc/install_devices multiselect /dev/$BOOT_DISK" | debconf-set-selections echo "grub-pc grub-pc/install_devices_empty boolean false" | debconf-set-selections else echo "[DEBIAN-UPGRADE] Warning: Could not determine boot disk for grub preseeding." fi fi ensure_boot_space "Debian sources switched pre-upgrade kernel space check" echo "[DEBIAN-UPGRADE] Running Debian 13 dist-upgrade (with retry)..." local max_attempts=2 local attempt=1 while (( attempt <= max_attempts )); do echo "[DEBIAN-UPGRADE] Attempt $attempt of $max_attempts..." apt-get clean rm -rf /var/lib/apt/lists/* apt-get update -y || die "[DEBIAN-UPGRADE] apt-get update failed after switching to trixie." if DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ --autoremove; then echo "[DEBIAN-UPGRADE] Release upgrade succeeded." break fi if (( attempt == max_attempts )); then die "[DEBIAN-UPGRADE] Release upgrade failed after retries. See log for remediation steps." fi echo "[DEBIAN-UPGRADE] dist-upgrade failed; retrying after cache cleanup..." attempt=$((attempt + 1)) sleep 5 done # Optional: modernize sources on Debian 13 if command -v apt >/dev/null 2>&1; then if apt modernize-sources --help >/dev/null 2>&1; then echo "[DEBIAN-UPGRADE] Modernizing APT sources to deb822..." apt modernize-sources --assume-yes || echo "[DEBIAN-UPGRADE] Warning: apt modernize-sources failed." fi fi # Refresh version info and confirm Debian 13 DEBIAN_VERSION=$(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"') DEBIAN_MAJOR=${DEBIAN_VERSION%%.*} (( DEBIAN_MAJOR != 13 )) && die "[DEBIAN-UPGRADE] Debian upgrade appears incomplete (VERSION_ID=$DEBIAN_VERSION)." echo "[DEBIAN-UPGRADE] Debian 13 confirmed. Continuing..." OS_UPGRADED=true } ############################################# # SHARED PLATFORM BLOCK END ############################################# #################### ROOT + OS CHECKS #################### ##### END SHARED PLATFORM BLOCK ##### if [[ "${EUID}" -ne 0 ]]; then die "This script must be run with sudo or as root." fi if ! grep -iq "^ID=debian" /etc/os-release; then die "Unsupported OS. This script must be run on Debian 12 or Debian 13." fi DEBIAN_VERSION=$(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"') DEBIAN_MAJOR=${DEBIAN_VERSION%%.*} if ! [[ "$DEBIAN_MAJOR" =~ ^[0-9]+$ ]]; then die "Unable to determine Debian major version." fi if (( DEBIAN_MAJOR < 12 || DEBIAN_MAJOR > 13 )); then die "Unsupported Debian version ($DEBIAN_VERSION). Only Debian 12 and Debian 13 are supported." fi require_amd64 #################### LOGGING + PREFLIGHT #################### echo "[DEBUG] Effective user: $(id -un) (uid=$(id -u))" LOG_FILE="/var/log/filewave_booster_update.log" touch "$LOG_FILE" || die "Failed to create log file $LOG_FILE. Check permissions." chmod 644 "$LOG_FILE" || die "Failed to chmod log file $LOG_FILE." [[ -w "$LOG_FILE" ]] || die "Cannot write to log file $LOG_FILE." exec > >(stdbuf -oL tee -a "$LOG_FILE") 2>&1 #################### FAILURE HANDLING (screen-friendly) #################### # When this script runs inside a short-lived `screen` session started by the 1-liner, # a hard failure can close the screen window immediately and make the last error hard to read. # To improve operator UX: # - In normal interactive runs (including -y), pause on failure and prompt "Press Enter to exit". # - In unattended/risky mode (-yy/--yesyes), do NOT pause; exit immediately. pause_on_failure() { local rc="${1:-1}" # In risky auto-yes mode, never pause (assume unattended). if [[ "${auto_risky_yes:-false}" == "true" ]]; then return 0 fi # Best-effort: only pause when we appear to have a TTY. if [[ -e /dev/tty ]]; then echo echo "================================================================================" echo "[FAILURE] Upgrade failed with exit code: ${rc}" echo "[FAILURE] Log File: ${LOG_FILE:-/var/log/filewave_update.log}" echo "[FAILURE] The screen session will close when this script exits." echo "[FAILURE] Press Enter to exit (or Ctrl+C)." echo "================================================================================" # Read directly from the controlling terminal so piping doesn't break the prompt. read -r /dev/null || echo "unknown") CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2- | xargs || echo "unknown") TOTAL_MEM=$(awk '/MemTotal/ {printf "%.1f GB", $2/1024/1024}' /proc/meminfo 2>/dev/null || echo "unknown") ROOT_FS_AVAIL=$(df -h / 2>/dev/null | awk 'NR==2 {print $4}') if has_separate_boot; then BOOT_FS_USE="$(boot_fs_use_pct)" echo "[PRE-FLIGHT] /boot Usage: ${BOOT_FS_USE:-unknown}" else echo "[PRE-FLIGHT] /boot Usage: N/A (no separate /boot filesystem; /boot is on /)" echo "[PRE-FLIGHT] Root FS Usage: $(root_fs_use_pct)" echo "[PRE-FLIGHT] /boot Directory Size: $(boot_dir_size)" fi echo "[PRE-FLIGHT] /boot Usage: ${BOOT_FS_USE:-unknown}" echo "[PRE-FLIGHT] Hypervisor: $HYPERVISOR" echo "[PRE-FLIGHT] CPU Model: ${CPU_MODEL:-unknown}" echo "[PRE-FLIGHT] Memory: ${TOTAL_MEM:-unknown}" echo "[PRE-FLIGHT] Root FS Free Space: ${ROOT_FS_AVAIL:-unknown}" echo "[PRE-FLIGHT] Running Kernel: $(uname -r)" #################### ARGUMENTS #################### if [[ "$#" -eq 0 ]]; then echo "Usage: wget -qO- | sudo bash -s -- -v -r [-d|--dev | -b|--beta | -p|--prod] [-y|--yes] [-yy|--yesyes]" echo echo "Options:" echo " -v, --version Target FileWave Booster version (e.g., 16.3.0)" echo " -r, --revision Legacy selector (accepted for compatibility; coerced to '1')" echo " -d, --dev Use Dev download server" echo " -b, --beta Use Beta download server" echo " -p, --prod Use Production download server (default)" echo " -y, --yes Auto-yes for routine prompts (install, OS patch upgrade, reboot)" echo " -yy, --yesyes Auto-yes for ALL prompts (includes screen warning + Debian 12->13 release upgrade)" exit 1 fi while [[ "$#" -gt 0 ]]; do case $1 in -v|--version) VERSION="$2"; shift 2 ;; -r|--revision) REVISION="$2"; shift 2 ;; -d|--dev) SERVER_TYPE="dev"; shift ;; -b|--beta) SERVER_TYPE="beta"; shift ;; -p|--prod) SERVER_TYPE="prod"; shift ;; -y|--yes) auto_yes=true; shift ;; -yy|--yesyes) auto_yes=true; auto_risky_yes=true; shift ;; *) die "Unknown option: $1" ;; esac done # Normalize revision for legacy compatibility if [[ "$REVISION" != "1" ]]; then echo "[INFO] Ignoring requested revision '$REVISION' and using revision '1'. (fwbooster packages are not revisioned.)" REVISION="1" fi # Root password must be set for maintainer scripts that may invoke su. ensure_root_password_set #################### SUMMARY BLOCK #################### INSTALLED_FWBOOSTER_VERSION=$(dpkg-query -W -f='${Version}' fwbooster 2>/dev/null || echo "not installed") FWXSERVER_PRESENT="NO" if [[ "$INSTALLED_FWBOOSTER_VERSION" != "not installed" && -n "$INSTALLED_FWBOOSTER_VERSION" ]]; then FWXSERVER_PRESENT="YES" fi PLANNED_DEBIAN_BEHAVIOR="Unknown" if dpkg --compare-versions "$VERSION" ge "$MIN_DEBIAN13_FW_VERSION"; then if (( DEBIAN_MAJOR == 12 )); then PLANNED_DEBIAN_BEHAVIOR="Upgrade Debian 12 -> 13 (trixie) before FileWave install" elif (( DEBIAN_MAJOR == 13 )); then PLANNED_DEBIAN_BEHAVIOR="Stay on Debian 13 (trixie)" else PLANNED_DEBIAN_BEHAVIOR="Unsupported Debian major $DEBIAN_MAJOR" fi else if (( DEBIAN_MAJOR == 12 )); then PLANNED_DEBIAN_BEHAVIOR="Stay on Debian 12 (bookworm)" elif (( DEBIAN_MAJOR == 13 )); then PLANNED_DEBIAN_BEHAVIOR="Blocked: FileWave $VERSION supported only on Debian 12 (per matrix)" else PLANNED_DEBIAN_BEHAVIOR="Unsupported Debian major $DEBIAN_MAJOR" fi fi echo "[SUMMARY] Detected Debian Version: $DEBIAN_VERSION" echo "[SUMMARY] /boot Usage: $(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || echo "n/a")" echo "[SUMMARY] Installed FileWave Booster Version (fwbooster): $INSTALLED_FWBOOSTER_VERSION" if [[ "$INSTALLED_FWBOOSTER_VERSION" == "not installed" || "$INSTALLED_FWBOOSTER_VERSION" == "unknown" || -z "$INSTALLED_FWBOOSTER_VERSION" ]]; then echo "[SUMMARY] FileWave Booster Presence Check: NO — this system does not appear to have an existing FileWave Booster installation" else echo "[SUMMARY] FileWave Booster Presence Check: YES — existing FileWave Booster installation detected" fi echo "[SUMMARY] Target FileWave Version: $VERSION-$REVISION" echo "[SUMMARY] Server Type: $SERVER_TYPE" echo "[SUMMARY] Auto-Yes Mode: $auto_yes" echo "[SUMMARY] Risky Auto-Yes Mode (yesyes): $auto_risky_yes" echo "[SUMMARY] Planned Debian behavior: $PLANNED_DEBIAN_BEHAVIOR" echo "[SUMMARY] Beginning validation and upgrade process..." #################### EARLY DOWNGRADE GUARD #################### # Block downgrades before doing ANY OS changes. Fresh installs are allowed for FileWave Booster. INSTALLED_VERSION_EARLY=$(dpkg-query -W -f='${Version}' fwbooster 2>/dev/null || true) if [[ -n "${INSTALLED_VERSION_EARLY:-}" && "${INSTALLED_VERSION_EARLY}" != "none" ]]; then if dpkg --compare-versions "$INSTALLED_VERSION_EARLY" gt "$VERSION"; then die "[CHECK] Installed version ($INSTALLED_VERSION_EARLY) is newer than requested ($VERSION). Downgrade is blocked." fi fi #################### VERSION MATRIX ENFORCEMENT #################### if dpkg --compare-versions "$VERSION" ge "$MIN_DEBIAN13_FW_VERSION"; then if (( DEBIAN_MAJOR == 12 )); then upgrade_to_debian13 elif (( DEBIAN_MAJOR == 13 )); then echo "[MATRIX] Debian 13 detected; proceeding with FileWave $VERSION." else die "[MATRIX] Unsupported Debian major $DEBIAN_MAJOR for FileWave $VERSION. Debian 13 is required." fi else if (( DEBIAN_MAJOR != 12 )); then die "[MATRIX] FileWave $VERSION is only supported on Debian 12 (per matrix). Current Debian: $DEBIAN_VERSION." fi fi #################### OPERATOR CONFIRMATION #################### echo "This script will update your OS and install/upgrade the FileWave Booster package (fwbooster)." echo "Version: $VERSION, Revision: $REVISION, Server Type: $SERVER_TYPE" if [[ "$auto_yes" == "true" ]]; then confirm="yes" else read -p "Do you wish to proceed? (yes/no): " confirm < /dev/tty fi if [[ ! "$confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then echo "Aborting installation as requested." exit 0 fi #################### DOWNLOAD SERVER #################### if [[ "$SERVER_TYPE" == "beta" ]]; then BASE_URL="https://fwbetas.filewave.com" elif [[ "$SERVER_TYPE" == "dev" ]]; then BASE_URL="https://fwdevdl.filewave.com" else BASE_URL="https://fwdl.filewave.com" fi #################### MAIN #################### rm -f /etc/apt/sources.list.d/filewave-beta.list >/dev/null 2>&1 || true rm -f /etc/apt/sources.list.d/filewave-dev.list >/dev/null 2>&1 || true rm -f /etc/apt/sources.list.d/filewave-release.list >/dev/null 2>&1 || true echo "[TOOLS] Installing essential tools..." apt-get clean || die "[TOOLS] apt-get clean failed." apt-get update -y || die "[TOOLS] apt-get update failed." apt-get --fix-broken install -y || die "[TOOLS] apt --fix-broken failed." apt-get autoremove -y || die "[TOOLS] apt-get autoremove failed." ensure_boot_space "pre-install kernel space check" for dep in "python3" "curl" "zip" "gdebi" "wget"; do if ! command -v "$dep" >/dev/null 2>&1; then echo "[TOOLS] $dep not found. Installing..." apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -q "$dep" || die "[TOOLS] Failed to install $dep." fi done if ! dpkg-query -W -f='${Status}' net-tools 2>/dev/null | grep -q "install ok installed"; then echo "[TOOLS] net-tools not found. Installing..." DEBIAN_FRONTEND=noninteractive apt-get install -y -q net-tools || die "[TOOLS] Failed to install net-tools." fi #################### INSTALLED VERSION + DOWNGRADE PROTECTION #################### INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' fwbooster 2>/dev/null || true) if [[ -z "$INSTALLED_VERSION" || "$INSTALLED_VERSION" == "none" ]]; then echo "[CHECK] No fwbooster package detected. This will be treated as a fresh install." if [[ "$auto_yes" == "true" ]]; then fresh_confirm="yes" else read -p "Proceed with FRESH install of fwbooster $VERSION? (yes/no): " fresh_confirm < /dev/tty fi [[ "$fresh_confirm" =~ ^[Yy]([Ee][Ss])?$ ]] || die "[CHECK] Aborting fresh install at user request." INSTALLED_VERSION="0.0.0" fi if dpkg --compare-versions "$INSTALLED_VERSION" gt "$VERSION"; then die "[CHECK] Installed version ($INSTALLED_VERSION) is newer than requested ($VERSION). Downgrade is blocked." fi if dpkg --compare-versions "$INSTALLED_VERSION" eq "$VERSION"; then echo "[CHECK] Requested version matches installed version ($INSTALLED_VERSION). Proceeding with reinstall." fi install_packages() { echo "[INSTALL] Downloading and installing FileWave Booster package..." local PACKAGE="fwbooster_${VERSION}_amd64.deb" DOWNLOAD_DIR="/tmp/filewave_install_$$" mkdir -p "$DOWNLOAD_DIR" || die "[INSTALL] Failed to create download directory." local PACKAGE_URL="$BASE_URL/$VERSION/$PACKAGE" echo "[DOWNLOAD] Validating download exists: $PACKAGE_URL" if ! validate_download_exists "$PACKAGE_URL" 2; then die "[DOWNLOAD] Package not found at: $PACKAGE_URL (server_type=$SERVER_TYPE). Verify the version/channel and try again." fi echo "[DOWNLOAD] Downloading $PACKAGE..." local ok=false for i in {1..3}; do if wget -q -O "$DOWNLOAD_DIR/$PACKAGE" "$PACKAGE_URL"; then ok=true break fi echo "[DOWNLOAD] Retrying download ($i/3)..." sleep 5 done $ok || die "[DOWNLOAD] Failed to download $PACKAGE after multiple attempts." echo "[INSTALL] Installing $PACKAGE..." DEBIAN_FRONTEND=noninteractive gdebi -n "$DOWNLOAD_DIR/$PACKAGE" || die "[INSTALL] Failed to install $PACKAGE." } echo "[INSTALL] Upgrading/installing from $INSTALLED_VERSION to $VERSION..." install_packages ensure_boot_space "post FileWave package install kernel space check" #################### OS PATCH UPGRADE #################### if [[ "$OS_UPGRADED" == "true" ]]; then echo "[OS-UPGRADE] Skipping generic OS upgrade step because Debian 12 -> 13 release upgrade already occurred in this run." else echo "[OS-UPGRADE] This will apply OS updates (apt upgrade). Recommended for security." if [[ "$auto_yes" == "true" ]]; then upgrade_confirm="yes" else read -p "Proceed with OS patch upgrade now? (yes/no): " upgrade_confirm < /dev/tty fi if [[ "$upgrade_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then ensure_boot_space "pre OS patch upgrade kernel space check" echo "[OS-UPGRADE] Running apt upgrade..." apt-get update -y || die "[OS-UPGRADE] apt-get update failed." DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ --autoremove || die "[OS-UPGRADE] apt-get upgrade failed." else echo "[OS-UPGRADE] Skipping OS patch upgrade at user request." fi fi #################### REBOOT #################### echo "[REBOOT] The system should reboot to complete updates." if [[ "$auto_yes" == "true" ]]; then reboot_confirm="yes" else read -p "Reboot now? (yes/no): " reboot_confirm < /dev/tty fi if [[ "$reboot_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then FINAL_FWXSERVER_VERSION=$(dpkg-query -W -f='${Version}' fwbooster 2>/dev/null || echo "unknown") echo "[SUMMARY-END] Debian Version After Upgrade: $(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '\"')" if has_separate_boot; then echo "[SUMMARY-END] /boot Usage After Upgrade: $(boot_fs_use_pct)" else echo "[SUMMARY-END] /boot Usage After Upgrade: N/A (no separate /boot filesystem; /boot is on /)" echo "[SUMMARY-END] Root FS Usage After Upgrade: $(root_fs_use_pct)" echo "[SUMMARY-END] /boot Directory Size After Upgrade: $(boot_dir_size)" fi echo "[SUMMARY-END] FileWave Booster Version Installed (fwbooster): $FINAL_FWXSERVER_VERSION" echo "[SUMMARY-END] Major OS Upgrade Performed (Debian 12 -> 13): $OS_UPGRADED" echo "[SUMMARY-END] Log File: $LOG_FILE" if [[ "${auto_risky_yes:-false}" == "true" ]]; then echo "[REBOOT] Rebooting in 0 seconds... (unattended mode)" reboot else echo "[REBOOT] Rebooting in 120 seconds... (press Enter to reboot now)" # Wait up to 120 seconds for Enter; then reboot automatically. if read -r -t 120 _ < /dev/tty; then echo "[REBOOT] Rebooting now..." else echo "[REBOOT] Timeout reached; rebooting now..." fi reboot fi else echo "[REBOOT] Skipping reboot. Please reboot manually to apply all changes." echo "[REBOOT] Log File: $LOG_FILE" fi