Skip to main content

FileWave Automation Scripts

This article contains the scripts used by the Downloads page. The scripts are here for documentation purposes.

Scripts

FileWave Server Upgrade

fwxserver_upgrade.sh - This script is used by the Download page fro FileWave Server upgrades on Debian. Some details;

# To run this script, use the following 1-liner:
#

wget -qO- https://kb.filewave.com/attachments/411 | sudo bash -s -- -v <version> -r <revision> [-b for beta] -y
#

Example for version 15.5.0 with revision 1 in production:
#

wget -qO-  https://kb.filewave.com/attachments/411 | sudo bash -s -- -v 15.5.0 -r 1 -p -y

Optional: safer usage inside a 'screen' session to protect against SSH disconnects (single-line example):

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/411 | bash -s -- -v 16.3.0 -r 1 -p -y"'
fwxserver_upgrade.sh
#!/bin/bash
# DocumentationVersion: 20251217.GA01

# ToFileWave runServer this(fwxserver) script,Upgrade useScript the(Debian following 1-liner:12/13)
#
curl# Safer recommended 1-liner (runs in screen):
#   sudo DEBIAN_FRONTEND=noninteractive bash -fsSLc '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/411 | sudo bash -s -- -v <version> -r <revision> [-b for beta] -y
# Example for version 15.5.0 with revision 1 in production:
# curl -fsSL https://kb.filewave.com/attachments/411 | sudo bash -s -- -v 15.5.16.3.0 -r 1 -p -yy"'
#
# Options:
#   -v, --version    Target FileWave 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}

###############################################################################
# SUPPORT CASE CHECKLIST
#
# If this upgrade fails and you open a FileWave support case, please include:
#
#  1. The full output of this script (copy/paste or screenshot)
#  2. The log file:
#       /var/log/filewave_server_update.log
#  3. The script name and GA version (shown at the top of the log)
#  4. The FileWave target version you attempted to install
#  5. The OS version shown in the startup banner
#
###############################################################################

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 (confirmed correct for fwxserver)
MIN_DEBIAN13_FW_VERSION="16.3.0"

#############################################
##### BEGIN SHARED PLATFORM BLOCK (KEEP IN SYNC) #####
# This block is runintentionally withidentical sudo/rootacross:
privileges#   - 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 "$EUID"{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]   <re-run the upgrade 1-liner inside screen>"
    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}'
}

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



# Ensure script is running on a Debian-based system
if ! grep -iq "^ID=debian" /etc/os-release; then
    die "Unsupported OS. This script must be run on aDebian Debian-based12 system.or Debian 13."
fi

#require_amd64

SetDEBIAN_VERSION=$(grep default-E values'^VERSION_ID=' for/etc/os-release version,| revision,cut and-d= server-f2 type| VERSION=tr -d '"15.5.0"')
REVISION="1"
SERVER_TYPE="prod"
BASE_URL="https://fwdl.filewave.com"
auto_yes=falseDEBIAN_MAJOR=${DEBIAN_VERSION%%.*}
if ! [[ "$#"DEBIAN_MAJOR" -eq=~ 0^[0-9]+$ ]]; then
    echodie "Usage:Unable curlto -fsSLdetermine https://kb.filewave.com/attachments/409Debian major version."
fi
if (( DEBIAN_MAJOR < 12 || sudoDEBIAN_MAJOR bash -s -- -v <version> -r13 <revision>)); [-bthen
    for beta | -p for prod] [-y for auto-yes]"
    echodie "Unsupported Options:"
    echo "  -v, --version    Specify theDebian version of($DEBIAN_VERSION). FileWaveOnly ServerDebian to12 installand (e.g.,Debian 15.5.0)13 are supported."
    echo "  -r, --revision   Specify the revision number (e.g., 1)"
    echo "  -d, --dev		 Use Dev server for downloads (default is production)"
    echo "  -b, --beta       Use beta server for downloads (default is production)"
    echo "  -p, --prod       Use production server for downloads"
    echo "  -y, --yes        Automatically answer 'yes' to all prompts"
    exit 1
fi

#require_amd64

Function#################### toLOGGING handle+ errorsPREFLIGHT and display messages
die() {####################

echo "[ERROR]DEBUG] Effective user: $1"(id >&2-un) exit(uid=$(id 1
}

# Log all actions to syslog for audit purposes-u))"


LOG_FILE="/var/log/filewave_server_update.log"
touch "$LOG_FILE" || die "Failed to create log file $LOG_FILE. Check permissions."
chmod 644 "$LOG_FILE" || die "Failed to set permissions onchmod log file $LOG_FILE."
if [[ ! -w "$LOG_FILE" ]]; then|| die "Cannot write to log file $LOG_FILE. Please check permissions."
fi

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/tty || true
    fi
}

# Trap EXIT so we can pause on failures (rc != 0).
trap 'rc=$?; if (( rc != 0 )); then pause_on_failure "$rc"; fi' EXIT
echo "Logging to $LOG_FILE"
echo "================================================================================"
echo "FileWave Upgrade Script"
echo "Component : FileWave Server"
echo "Script    : $(basename "$0" 2>/dev/null || echo "fwxserver_upgrade.sh")"
echo "Version   : GA01"
echo "Started   : $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Hostname  : $(hostname -f 2>/dev/null || hostname)"
# ParseOS inputinformation arguments(best effort)
if command -v lsb_release >/dev/null 2>&1; then
    echo "OS        : $(lsb_release -ds 2>/dev/null)"
else
    echo "OS        : $(grep -E '^PRETTY_NAME=' /etc/os-release | cut -d= -f2- | tr -d '"')"
fi
echo "================================================================================"
HYPERVISOR=$(systemd-detect-virt 2>/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}')
BOOT_FS_USE=$(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || true)

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: (recommended, runs in screen)"
    echo "  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/411 | bash -s -- -v 16.3.0 -r 1 -p -y"'"
    echo
    echo "This script will install or upgrade FileWave Server on Debian 12/13."
    echo
    echo "Options:"
    echo "  -v, --version    Target FileWave 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 (unattended/risky; no failure pause; no success reboot delay)"
    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=truetrue; shift ;;
        -yy|--yesyes)   auto_yes=true; auto_risky_yes=true; shift ;;
        *)              echodie "Unknown option: $1"
            exit 1 ;;
    esac
done

# ConfirmNormalize actionsrevision for legacy compatibility
if [[ "$REVISION" != "1" ]]; then
    echo "[INFO] Ignoring requested revision '$REVISION' and using revision '1'. (fwxserver 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_FWXSERVER_VERSION=$(dpkg-query -W -f='${Version}' fwxserver 2>/dev/null || echo "not installed")
FWXSERVER_PRESENT="NO"
if [[ "$INSTALLED_FWXSERVER_VERSION" != "not installed" && -n "$INSTALLED_FWXSERVER_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 Server Version (fwxserver): $INSTALLED_FWXSERVER_VERSION"
if [[ "$INSTALLED_FWXSERVER_VERSION" == "not installed" || "$INSTALLED_FWXSERVER_VERSION" == "unknown" || -z "$INSTALLED_FWXSERVER_VERSION" ]]; then
    echo "[SUMMARY] FileWave Server Presence Check: NO — this system does not appear to have an existing FileWave Server installation"
else
    echo "[SUMMARY] FileWave Server Presence Check: YES — existing FileWave Server 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 Server.
INSTALLED_VERSION_EARLY=$(dpkg-query -W -f='${Version}' fwxserver 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 theFileWave user$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 Server packages.package (fwxserver)."
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

##################### SetDOWNLOAD theSERVER appropriate URL based on server type####################
if [[ "$SERVER_TYPE" == "beta" ]]; then
    BASE_URL="https://fwbetas.filewave.com"
elif [[ "$SERVER_TYPE" == "dev" ]]; then
    BASE_URL="https://fwdevdl.filewave.com"
else
    # Default to production download server
    BASE_URL="https://fwdl.filewave.com"
fi

##################### 1.MAIN Install####################
Essentialrm Tools-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 "Failed[TOOLS] toapt-get clean apt cache.failed."
apt-get update -y || die "Failed[TOOLS] toapt-get update package list.failed."
apt-get --fix-broken install -y || die "Failed[TOOLS] toapt fix --fix-broken installs from apt.failed."
apt-get autoremove -y || die "Failed[TOOLS] toapt-get autoremove fromfailed."
apt.ensure_boot_space "pre-install kernel space check"
for dep in "python3" "curl" "zip" "gdebi" "wget"; do
    if ! command -v "$depdep" &> /dev/null;null 2>&1; then
        echo "[TOOLS] $dep not found. Installing $dep.Installing..."
        apt-get update -qq
        DEBIAN_FRONTEND=noninteractive apt-get install -y -q "$depdep" || 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 net-tools.Installing..."
    DEBIAN_FRONTEND=noninteractive apt-get install -y -q net-tools || die "[TOOLS] Failed to install net-tools."
fi

##################### CheckINSTALLED theVERSION current+ versionDOWNGRADE ofPROTECTION FileWave Server####################
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' fwxserver 2>/dev/null || true)
# If no FileWave Server package is installed, set the version to 1.0.0 for comparison purposes

if [[ -z "$INSTALLED_VERSION" || "$INSTALLED_VERSION" == "none" ]]; then
    echo "[CHECK] No FileWavefwxserver Serverpackage packagesdetected. areThis installed.will be treated as a fresh install."
    if [[ "$auto_yes" == "true" ]]; then
        fresh_confirm="yes"
    else
        read -p "Proceed with FRESH install of fwxserver $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 initial installation of version $VERSION...reinstall."
    INSTALLED_VERSION="1.0.0"
fi

install_packages() {
    echo "[INSTALL] Downloading and installing FileWave Server package..."

    local PACKAGE="fwxserver_${VERSION}_amd64.deb"
    DOWNLOAD_DIR="/tmp/filewave_install_$$"
    mkdir -p "$DOWNLOAD_DIR" || die "[INSTALL] Failed to create download directory."

    traplocal 'rm -rf PACKAGE_URL="$DOWNLOAD_DIR"' EXITBASE_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
        curlif wget -fSL "$BASE_URL/$VERSION/$PACKAGE"q -oO "$DOWNLOAD_DIR/$PACKAGE" &&"$PACKAGE_URL"; then
            ok=true
            break
        fi
        echo "[DOWNLOAD] Retrying download ($i/3)..."
        sleep 5
    done
    if$ok [[ ! -f "$DOWNLOAD_DIR/$PACKAGE" ]]; then|| die "[DOWNLOAD] Failed to download $PACKAGE after multiple attempts."

    fi
    echo "[INSTALL] Installing $PACKAGE..."
    DEBIAN_FRONTEND=noninteractive gdebi -n "$DOWNLOAD_DIR/$PACKAGE" || die "[INSTALL] Failed to install $PACKAGE"PACKAGE."
# Clean up the download directory
    if [[ -d "$DOWNLOAD_DIR" ]]; then
        rm -rf "$DOWNLOAD_DIR" ||}

echo "Warning:[INSTALL] FailedUpgrading/installing from $INSTALLED_VERSION to remove download directory $DOWNLOAD_DIR"
    fi
}

# This is the meat of the script.
# The actions to take based on version.
if dpkg --compare-versions "$INSTALLED_VERSION" lt "$VERSION"; then
    echo "Upgrading to FileWave Server version $VERSION..."
install_packages
elifensure_boot_space dpkg"post --compare-versionsFileWave package install kernel space check"
#################### OS PATCH UPGRADE ####################
if [[ "$INSTALLED_VERSION"OS_UPGRADED" eq== "$VERSION"true" ]]; then
    echo "FileWave[OS-UPGRADE] ServerSkipping isgeneric OS upgrade step because Debian 12 -> 13 release upgrade already atoccurred versionin $VERSION,this no further installation required.run."
else
    echo "FileWave[OS-UPGRADE] ServerThis iswill newer than $VERSION and downgrade is not possible with this script."
fi

# Clean up the download directory
if [[ -d "$DOWNLOAD_DIR" ]]; then
    rm -rf "$DOWNLOAD_DIR" || echo "Warning: Failed to remove download directory $DOWNLOAD_DIR"
fi

# 4.apply OS Upgradeupdates echo(apt "Warning:upgrade). This script will upgrade the entire OS. This is requiredRecommended for security."
    if [[ "$auto_yes" == "true" ]]; then
        upgrade_confirm="yes"
    else
        read -p "Do you wish to proceedProceed with the OS upgrade?patch upgrade now? (yes/no): " upgrade_confirm < /dev/tty
    fi

    if [[ ! "$upgrade_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
echoensure_boot_space "Skippingpre OS upgrade."patch elseupgrade kernel space check"
        echo "Upgrading[OS-UPGRADE] theRunning system.apt upgrade..."
        apt-get update -y || die "Failed[OS-UPGRADE] toapt-get update package list.failed."
        DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
            -o Dpkg::Options::="--force-confdef" \
            -o Dpkg::Options::="--force-confold" \
            --autoremove || die "System[OS-UPGRADE] apt-get upgrade failed."
    else
        echo "[OS-UPGRADE] Skipping OS patch upgrade at user request."
    fi
#fi

5.#################### RebootREBOOT the####################
echo "[REBOOT] The system should reboot to complete the installation
echo "The system will reboot in 15 seconds to complete the installation.updates."
if [[ "$auto_yes" == "true" ]]; then
    reboot_confirm="yes"
else
    read -p "Do you want to rebootReboot now? (yes/no): " reboot_confirm < /dev/tty
fi

if [[ "$reboot_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
    FINAL_FWXSERVER_VERSION=$(dpkg-query -W -f='${Version}' fwxserver 2>/dev/null || echo "unknown")
    echo "[SUMMARY-END] Debian Version After Upgrade: $(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '\"')"
    echo "[SUMMARY-END] /boot Usage After Upgrade: $(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || echo "n/a")"
    echo "[SUMMARY-END] FileWave Server Version Installed (fwxserver): $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 system in 150 seconds... Please(unattended remember that you can check the log file at $LOG_FILE after the reboot to see the full details of the update.mode)"
    sleep 15
    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 remember to reboot manually to apply all changes."
    echo "[REBOOT] Log File: $LOG_FILE"
fi

# The end

 


FileWave Booster Upgrade

fwbooster_upgrade.sh - This script is used by the Download page fro FileWave Booster upgrades on Debian. Some details;

# To run this script, use the following 1-liner:
#

wget -qO- https://kb.filewave.com/attachments/412 | sudo bash -s -- -v <version> -r <revision> [-b for beta] -y
#

Example for version 15.5.0 with revision 1 in production:
#

wget -qO-  https://kb.filewave.com/attachments/412 | sudo bash -s -- -v 15.5.0 -r 1 -p -y

Optional: safer usage inside a 'screen' session to protect against SSH disconnects (single-line example):

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"'
fwbooster_upgrade.sh
#!/bin/bash
# DocumentationVersion: 20251217.GA01

# ToFileWave runBooster this(fwbooster) script,Upgrade useScript the(Debian following 1-liner:12/13)
#
curl# Safer recommended 1-liner (runs in screen):
#   sudo DEBIAN_FRONTEND=noninteractive bash -fsSLc '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 | sudo bash -s -- -v <version> -r <revision> [-b for beta] -y
# Example for version 15.5.0 with revision 1 in production:
# curl -fsSL https://kb.filewave.com/attachments/412 | sudo bash -s -- -v 15.5.16.3.0 -r 1 -p -yy"'
#
# 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}

###############################################################################
# SUPPORT CASE CHECKLIST
#
# If this upgrade fails and you open a FileWave support case, please include:
#
#  1. The full output of this script (copy/paste or screenshot)
#  2. The log file:
#       /var/log/filewave_booster_update.log
#  3. The script name and GA version (shown at the top of the log)
#  4. The FileWave target version you attempted to install
#  5. The OS version shown in the startup banner
#
###############################################################################

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 runintentionally withidentical sudo/rootacross:
privileges#   - 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 "$EUID"{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]   <re-run the upgrade 1-liner inside screen>"
    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}'
}

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



# Ensure script is running on a Debian-based system
if ! grep -iq "^ID=debian" /etc/os-release; then
    die "Unsupported OS. This script must be run on aDebian Debian-based12 system.or Debian 13."
fi

#require_amd64

SetDEBIAN_VERSION=$(grep default-E values'^VERSION_ID=' for/etc/os-release version,| revision,cut and-d= server-f2 type| VERSION=tr -d '"15.5.0"')
REVISION="1"
SERVER_TYPE="prod"
BASE_URL="https://fwdl.filewave.com"
auto_yes=falseDEBIAN_MAJOR=${DEBIAN_VERSION%%.*}
if ! [[ "$#"DEBIAN_MAJOR" -eq=~ 0^[0-9]+$ ]]; then
    echodie "Usage:Unable curlto -fsSLdetermine https://kb.filewave.com/attachments/409Debian major version."
fi
if (( DEBIAN_MAJOR < 12 || sudoDEBIAN_MAJOR bash -s -- -v <version> -r13 <revision>)); [-bthen
    for beta | -p for prod] [-y for auto-yes]"
    echodie "\nOptions:"Unsupported echo "  -v, --version    Specify theDebian version of($DEBIAN_VERSION). FileWaveOnly BoosterDebian to12 installand (e.g.,Debian 15.5.0)13 are supported."
    echo "  -r, --revision   Specify the revision number (e.g., 1)"
    echo "  -d, --dev		 Use Dev server for downloads (default is production)"
    echo "  -b, --beta       Use beta server for downloads (default is production)"
    echo "  -p, --prod       Use production server for downloads"
    echo "  -y, --yes        Automatically answer 'yes' to all prompts"
    exit 1
fi

#require_amd64

Function#################### toLOGGING handle+ errorsPREFLIGHT and display messages
die() {####################

echo "[ERROR]DEBUG] Effective user: $1"(id >&2-un) exit(uid=$(id 1
}

# Log all actions to syslog for audit purposes-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 set permissions onchmod log file $LOG_FILE."
if [[ ! -w "$LOG_FILE" ]]; then|| die "Cannot write to log file $LOG_FILE. Please check permissions."
fi

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/tty || true
    fi
}

# Trap EXIT so we can pause on failures (rc != 0).
trap 'rc=$?; if (( rc != 0 )); then pause_on_failure "$rc"; fi' EXIT
echo "Logging to $LOG_FILE"
echo "================================================================================"
echo "FileWave Upgrade Script"
echo "Component : FileWave Booster"
echo "Script    : $(basename "$0" 2>/dev/null || echo "fwbooster_upgrade.sh")"
echo "Version   : GA01"
echo "Started   : $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Hostname  : $(hostname -f 2>/dev/null || hostname)"
# ParseOS inputinformation arguments(best effort)
if command -v lsb_release >/dev/null 2>&1; then
    echo "OS        : $(lsb_release -ds 2>/dev/null)"
else
    echo "OS        : $(grep -E '^PRETTY_NAME=' /etc/os-release | cut -d= -f2- | tr -d '"')"
fi
echo "================================================================================"
HYPERVISOR=$(systemd-detect-virt 2>/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}')
BOOT_FS_USE=$(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || true)

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: (recommended, runs in screen)"
    echo "  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"'"
    echo
    echo "This script will install or upgrade FileWave Booster on Debian 12/13."
    echo
    echo "Options:"
    echo "  -v, --version    Target FileWave 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 (unattended/risky; no failure pause; no success reboot delay)"
    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=truetrue; shift ;;
        -yy|--yesyes)   auto_yes=true; auto_risky_yes=true; shift ;;
        *)              echodie "Unknown option: $1"
            exit 1 ;;
    esac
done

# ConfirmNormalize actionsrevision 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 theFileWave user$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 packages.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

##################### SetDOWNLOAD theSERVER appropriate URL based on server type####################
if [[ "$SERVER_TYPE" == "beta" ]]; then
    BASE_URL="https://fwbetas.filewave.com"
elif [[ "$SERVER_TYPE" == "dev" ]]; then
    BASE_URL="https://fwdevdl.filewave.com"
else
    # Default to production download server
    BASE_URL="https://fwdl.filewave.com"
fi

##################### 1.MAIN Install####################
Essentialrm Tools-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 "Failed[TOOLS] toapt-get clean apt cache.failed."
apt-get update -y || die "Failed[TOOLS] toapt-get update package list.failed."
apt-get --fix-broken install -y || die "Failed[TOOLS] toapt fix --fix-broken installs from apt.failed."
apt-get autoremove -y || die "Failed[TOOLS] toapt-get autoremove fromfailed."
apt.ensure_boot_space "pre-install kernel space check"
for dep in "python3" "curl" "zip" "gdebi" "wget"; do
    if ! command -v "$depdep" &> /dev/null;null 2>&1; then
        echo "[TOOLS] $dep not found. Installing $dep.Installing..."
        apt-get update -qq
        DEBIAN_FRONTEND=noninteractive apt-get install -y -q "$depdep" || 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 net-tools.Installing..."
    DEBIAN_FRONTEND=noninteractive apt-get install -y -q net-tools || die "[TOOLS] Failed to install net-tools."
fi

##################### CheckINSTALLED theVERSION current+ versionDOWNGRADE ofPROTECTION FileWave Booster####################
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' fwbooster 2>/dev/null || true)
# If no FileWave Booster package is installed, set the version to 1.0.0 for comparison purposes

if [[ -z "$INSTALLED_VERSION" || "$INSTALLED_VERSION" == "none" ]]; then
    echo "[CHECK] No FileWavefwbooster Boosterpackage packagesdetected. areThis installed.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 initial installation of version $VERSION...reinstall."
    INSTALLED_VERSION="1.0.0"
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."

    traplocal 'rm -rf PACKAGE_URL="$DOWNLOAD_DIR"' EXITBASE_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
        curlif wget -fSL "$BASE_URL/$VERSION/$PACKAGE"q -oO "$DOWNLOAD_DIR/$PACKAGE" &&"$PACKAGE_URL"; then
            ok=true
            break
        fi
        echo "[DOWNLOAD] Retrying download ($i/3)..."
        sleep 5
    done
    if$ok [[ ! -f "$DOWNLOAD_DIR/$PACKAGE" ]]; then|| die "[DOWNLOAD] Failed to download $PACKAGE after multiple attempts."

    fi
    echo "[INSTALL] Installing $PACKAGE..."
    DEBIAN_FRONTEND=noninteractive gdebi -n "$DOWNLOAD_DIR/$PACKAGE" || die "[INSTALL] Failed to install $PACKAGE"PACKAGE."
# Clean up the download directory
    if [[ -d "$DOWNLOAD_DIR" ]]; then
        rm -rf "$DOWNLOAD_DIR" ||}

echo "Warning:[INSTALL] FailedUpgrading/installing from $INSTALLED_VERSION to remove download directory $DOWNLOAD_DIR"
    fi
}

# This is the meat of the script.
# The actions to take based on version.
if dpkg --compare-versions "$INSTALLED_VERSION" lt "$VERSION"; then
    echo "Upgrading to FileWave Booster version $VERSION..."
install_packages
elifensure_boot_space dpkg"post --compare-versionsFileWave package install kernel space check"
#################### OS PATCH UPGRADE ####################
if [[ "$INSTALLED_VERSION"OS_UPGRADED" eq== "$VERSION"true" ]]; then
    echo "FileWave[OS-UPGRADE] BoosterSkipping isgeneric OS upgrade step because Debian 12 -> 13 release upgrade already atoccurred versionin $VERSION,this no further installation required.run."
else
    echo "FileWave[OS-UPGRADE] BoosterThis iswill newer than $VERSION and downgrade is not possible with this script."
fi

# Clean up the download directory
if [[ -d "$DOWNLOAD_DIR" ]]; then
    rm -rf "$DOWNLOAD_DIR" || echo "Warning: Failed to remove download directory $DOWNLOAD_DIR"
fi

# 4.apply OS Upgradeupdates echo(apt "Warning:upgrade). This script will upgrade the entire OS. This is requiredRecommended for security."
    if [[ "$auto_yes" == "true" ]]; then
        upgrade_confirm="yes"
    else
        read -p "Do you wish to proceedProceed with the OS upgrade?patch upgrade now? (yes/no): " upgrade_confirm < /dev/tty
    fi

    if [[ ! "$upgrade_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
echoensure_boot_space "Skippingpre OS upgrade."patch elseupgrade kernel space check"
        echo "Upgrading[OS-UPGRADE] theRunning system.apt upgrade..."
        apt-get update -y || die "Failed[OS-UPGRADE] toapt-get update package list.failed."
        DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
            -o Dpkg::Options::="--force-confdef" \
            -o Dpkg::Options::="--force-confold" \
            --autoremove || die "System[OS-UPGRADE] apt-get upgrade failed."
    else
        echo "[OS-UPGRADE] Skipping OS patch upgrade at user request."
    fi
#fi

5.#################### RebootREBOOT the####################
echo "[REBOOT] The system should reboot to complete the installation
echo "The system will reboot in 15 seconds to complete the installation.updates."
if [[ "$auto_yes" == "true" ]]; then
    reboot_confirm="yes"
else
    read -p "Do you want to rebootReboot 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 '\"')"
    echo "[SUMMARY-END] /boot Usage After Upgrade: $(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || echo "n/a")"
    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 system in 150 seconds... Please(unattended remember that you can check the log file at $LOG_FILE after the reboot to see the full details of the update.mode)"
    sleep 15
    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 remember to reboot manually to apply all changes."
    echo "[REBOOT] Log File: $LOG_FILE"
fi

# The end

 


FileWave IVS Upgrade

ivs_upgrade.sh - This script is used by the Download page for IVS upgrades on Debian. Some details;

To run this script, use the following 1-liner:

wget -qO- https://kb.filewave.com/attachments/408 | sudo bash -s -- -v <version> -r <revision> [-b for beta] -y

Example for version 15.5.0 with revision 1 in production:

wget -qO-  https://kb.filewave.com/attachments/408 | sudo bash -s -- -v 15.5.0 -r 1 -p -y

Optional: safer usage inside a 'screen' session to protect against SSH disconnects (single-line example):

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/408 | bash -s -- -v 16.3.0 -r 1 -p -y"'
ivs_upgrade.sh
#!/bin/bash
# Version: 20251215.0120251217.GA01

# FileWave IVS Upgrade Script #(Debian Usage (example 1-liner):12/13)
#
wget -qO- https://kb.filewave.com/attachments/408 | sudo bash -s -- -v <version> -r <revision> [-d|--dev | -b|--beta | -p|--prod] [-y|--yes] [-yy|--yesyes]
# Note:Safer currentrecommended FileWave IVS builds only use revision 1; the -r/--revision flag is kept for
# legacy compatibility and any value other than '1' will be coerced back to '1'.
# Example:
#   wget -qO- https://kb.filewave.com/attachments/408 | sudo bash -s -- -v 16.3.0 -r 1 -p -y
# Supports Debian 12 and 13. Newer FileWave versions may require Debian 13.
# Supported combinations1-liner (highruns level):in #   - Debian 12 (bookworm): FileWave IVS versions < 16.3.0 only
#   - Debian 13 (trixie):   FileWave IVS versions >= 16.3.0 only
#   - This script will:
#       * Refuse to run FileWave < 16.3.0 on Debian 13
#       * Offer to upgrade Debian 12 -> 13 automatically when targeting FileWave >= 16.3.0
#       * Refuse to upgrade Debian 13 down to Debian 12 under any circumstances
# Optional: safer usage inside a 'screen' session to protect against SSH disconnects (single-line example)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/408 | bash -s -- -v 16.3.0 -r 1 -p -y"'
#
# Options:
#   -v, --version    Target FileWave 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}

###############################################################################
# SUPPORT CASE CHECKLIST
#
# If this upgrade fails and you open a FileWave support case, please include:
#
#  1. The full output of this script (copy/paste or screenshot)
#  2. The log file:
#       /var/log/filewave_ivs_update.log
#  3. The script name and GA version (shown at the top of the log)
#  4. The FileWave target version you attempted to install
#  5. The OS version shown in the startup banner
#
###############################################################################

export DEBCONF_NONINTERACTIVE_SEEN=${DEBCONF_NONINTERACTIVE_SEEN:-true}

#################### VARIABLES / LOGS ##################

# Set default values for target FileWave version, legacy revision flag, and download server type
# NOTE: FileWave IVS currently ships only revision 1 builds. The REVISION variable is kept
# for backwards compatibility with existing 1-liners, but any value other than '1' will be
# ignored and coerced back to '1' at runtime.
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

# Temp download directory tracked globally for safe cleanup
DOWNLOAD_DIR=""

cleanup_download_dir() {
    if [[ -n "${DOWNLOAD_DIR:-}" && -d "${DOWNLOAD_DIR:-}" ]]; then
        rm -rf "${DOWNLOAD_DIR}"
    fi
}
trap cleanup_download_dir EXIT

# Function to handle errors and display messages
#############################################
##### 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>/bootdev/null safety|| checksecho and\"unknown\")
    kernelif cleanup[[ -----------------------------------\"$arch\" #!= Many\"amd64\" IVS]]; appliancesthen
        usedie a\"[ARCH] smallUnsupported dedicatedarchitecture: /boot$arch. partition.This Kernelscript updatessupports canamd64 #only.\"
    accumulatefi
multiple}


kernel images and initramfs files, filling /boot and causing
# upgrades to fail. The helpers below provide deterministic cleanup behavior.

boot_usage_percent(is_running_under_multiplexer() {
    # PrintsDetect integerGNU 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 percentfor 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]   <re-run the upgrade 1-liner inside screen>"
    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 /bootpasswords."
            echo "[ROOT] Set a root password (noand %write sign)it down), orthen emptyre-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 unavailable.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}'
}

check_boot_space() {
    # Guardrail: warn or stop if /boot is close to full. This prevents mid-upgrade failures.
    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:
    #  - the currently running kernel
    #  - one newest additional kernel as a rollback option
    #
    # This uses apt/dpkg package removal (safe) rather than deleting files from /boot.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}"

    # Collect installed Debian kernel image packages (amd64 flavor).
    # Note: this intentionally targets Debian's linux-image-* packages, not FileWave's ivs-kernel.

    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

    # Keep running kernel + newest other kernel as fallback.
    local keep=("$running_pkg")
    local i
    for (( i=${#images[@]}-1; i>=0; i-- )); do
        [[ "${images[$i]}" == "$running_pkg" ]] && continue
        keep+=("${images[$i]}")
        break
    done

    # Build remove list.

    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
        # EnsureFall scriptback: no validation tool available; caller will attempt download.
        echo "[DOWNLOAD] Warning: neither wget nor curl is runavailable 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
#############################################

# sudo/root privileges
##### END SHARED PLATFORM BLOCK #####

if [ "$EUID" -ne 0 ]; then
    die "This script must be run with sudo or as root."
fi

# Log all actions to a dedicated log file for audit and troubleshooting
LOG_FILE="/var/log/filewave_ivs_update.log"
touch "$LOG_FILE" || die "Failed to create log file $LOG_FILE. Check permissions."
chmod 644 "$LOG_FILE" || die "Failed to set permissions on log file $LOG_FILE."
if [[ ! -w "$LOG_FILE" ]]; then
    die "Cannot write to log file $LOG_FILE. Please check permissions."
fi
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/tty || true
    fi
}

# Trap EXIT so we can pause on failures (rc != 0).
trap 'rc=$?; if (( rc != 0 )); then pause_on_failure "$rc"; fi' EXIT
echo "Logging to $LOG_FILE"
echo "================================================================================"
echo "FileWave Upgrade Script"
echo "Component : FileWave IVS"
echo "Script    : $(basename "$0" 2>/dev/null || echo "ivs_upgrade.sh")"
echo "Version   : GA01"
echo "Started   : $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Hostname  : $(hostname -f 2>/dev/null || hostname)"
# OS information (best effort)
if command -v lsb_release >/dev/null 2>&1; then
    echo "OS        : $(lsb_release -ds 2>/dev/null)"
else
    echo "OS        : $(grep -E '^PRETTY_NAME=' /etc/os-release | cut -d= -f2- | tr -d '"')"
fi
echo "================================================================================"
# Pre-flight environment snapshot for support diagnostics
HYPERVISOR=$(systemd-detect-virt 2>/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)
ROOT_FS_AVAIL=$(df -h / 2>/dev/null | awk 'NR==2 {print $4}')

BOOT_FS_USE=$(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || true)

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}"



################# OS Check ###############

# Validate Debian version (must be 12 or 13)
if ! grep -iq "^ID=debian" /etc/os-release; then
    die "Unsupported operating system. This script must be run on Debian 12 or Debian 13."
fi

require_amd64

# Extract major version number (e.g., 12 from 12.5)
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 version. Aborting."
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


is_running_under_multiplexer() {
    # Detect whether this shell is running under a GNU screen session.
    # First, trust STY if present.
    if [[ -n "$STY" ]]; then
        return 0
    fi

    # Fallback: walk up the process tree looking for a 'screen' ancestor.
    # This helps when sudo or other wrappers strip environment variables.
    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_ssh_tty_safety(check_screen_session_safety() {
    # Safety check: encourage running under GNU screen for long-running upgrades,
    # especially when a major OS upgrade (Debian 12 → 13) is about to be performed.

    # Ensure screen is installed before any messaging
    if ! command -v screen >/dev/null 2>&1; then
        echo "[SESSION] 'screen' is not installed; installing it now to support detachable upgrade sessions..."
        DEBIAN_FRONTEND=noninteractive apt-get install -y -q screen || \
            echo "[SESSION] Warning: Failed to install 'screen'. Continuing without screen support."
    fi

    # --- CASE 1: User IS running inside screen ---
    if is_running_under_multiplexer; then
        echo "[SESSION] Running inside a 'screen' session; this is recommended for long-running upgrades."
        return 0
    fi

    # --- CASE 2: User is NOT running inside screen ---
    echo "[SESSION] This upgrade is not running inside 'screen'."
    echo "[SESSION] Because we are about to perform a major OS upgrade (Debian 12 → 13),"
    echo "[SESSION] it is strongly recommended to run this process inside a 'screen' session."
    echo "[SESSION] This protects you from SSH disconnects during the long upgrade, which"
    echo "[SESSION] could otherwise leave the system in a partially upgraded or unstable state."
    echo ""
    if command -v screen >/dev/null 2>&1; then
        echo "[SESSION] To create a protected session, run the following command in your terminal:"
        echo "[SESSION]   screen -S fw_upgrade"
        echo "[SESSION] After the screen session opens, paste the FileWave IVS upgrade 1-liner"
        echo "[SESSION] again so the entire upgrade runs inside that screen session."
        echo ""
    fi

    if [[ "$auto_risky_yes" == "true" ]]; then
        echo "[SESSION] Risky auto-yes mode enabled; continuing despite not running inside 'screen'."
        return 0
    fi

    read -p "You are NOT running inside 'screen'. Do you still want to continue with this upgrade? (yes/no): " ssh_continue < /dev/tty
    if [[ ! "$ssh_continue" =~ ^[Yy]([Ee][Ss])?$ ]]; then
        die "[SESSION] Aborting at user request because the upgrade is not running inside 'screen'. Please start a 'screen' session and re-run the upgrade."
    fi
}


################ Debian 13 Upgrade Helper ################
# Functions and constants to manage Debian 12 -> 13 release upgrades when required by FileWave version

# Minimum FileWave version that requires Debian 13
MIN_DEBIAN13_FW_VERSION="16.3.0"

# Placeholder for Debian 12 -> Debian 13 in-place upgrade
upgrade_to_debian13() {
    check_ssh_tty_safetycheck_screen_session_safety
    
    echo "[DEBIAN-UPGRADE] Detected Debian 12 but target FileWave version $VERSION requires Debian 13."
    echo "[DEBIAN-UPGRADE] Newer FileWave versions ($MIN_DEBIAN13_FW_VERSION and above) are only supported on Debian 13 (trixie)."
    echo "[DEBIAN-UPGRADE] This is a long-running, in-place OS upgrade and may take considerable time."

    if is_running_under_multiplexer; then
        echo "[DEBIAN-UPGRADE] You are already running this upgrade under a 'screen' session."
        if command -v screen >/dev/null 2>&1; then
            echo "[DEBIAN-UPGRADE] Current screen sessions:"
            screen -list || true
        fi
        echo "[DEBIAN-UPGRADE] If you disconnect, you can typically reconnect with:"
        echo "[DEBIAN-UPGRADE]   screen -ls"
        echo "[DEBIAN-UPGRADE]   screen -r <session_id>"
    else
        echo "[DEBIAN-UPGRADE] It is strongly recommended that you run this under 'screen' to protect against disconnects."
        echo "[DEBIAN-UPGRADE] If you disconnect from an SSH session, you can later run: screen -ls"
        echo "[DEBIAN-UPGRADE] This will show something like: 'There is a screen on: 1234.pts-0.servername'"
        echo "[DEBIAN-UPGRADE] You can reconnect with: screen -r 1234"
    fi

    # Confirm with the admin before performing the Debian 12 -> 13 upgrade
    if [[ "$auto_risky_yes" == "true" ]]; then
        deb13_confirm="yes"
        echo "[DEBIAN-UPGRADE] Risky auto-yes mode enabled; proceeding with Debian 12 -> 13 upgrade without interactive confirmation."
    else
        read -p "Do you wish to proceed with the 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 12 -> 13 upgrade at user request. FileWave version $VERSION requires Debian 13. Please re-run and allow the OS upgrade, or choose an older FileWave version supported on Debian 12."
    fi

    # Ensure screen is available (do not forcibly attach to a screen session here)
    if ! command -v screen >/dev/null 2>&1; then
        echo "[DEBIAN-UPGRADE] Installing 'screen' to support detachable sessions..."
        DEBIAN_FRONTEND=noninteractive apt-get install -y -q screen || echo "[DEBIAN-UPGRADE] Warning: Failed to install 'screen'. Continuing without screen."
    fi

    # Check for at least 5 GiB free on the root filesystem
    REQUIRED_GIB=5
    avail_gib=$(df -BG / | awk 'NR==2 {gsub(/G/,"",$4); print $4}')
    if [[ -z "$avail_gib" ]]; then
        die "[DEBIAN-UPGRADE] Unable to determine free disk space on root filesystem. Aborting Debian upgrade."
    fi
    if (( avail_gib < REQUIRED_GIB )); then
        die "[DEBIAN-UPGRADE] Not enough free disk space for Debian 12 -> 13 upgrade. ${avail_gib} GiB available, but at least ${REQUIRED_GIB} GiB is required."
    fi

    echo "[DEBIAN-UPGRADE] Disk space check passed: ${avail_gib} GiB available. Proceeding with Debian upgrade..."

    # Clean up 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 current Debian 12 system is fully upgraded before changing suites
check_boot_spaceensure_boot_space kernel_cleanup_keep2"pre-install kernel space check"
echo "[DEBIAN-UPGRADE] Bringing Debian 12 (bookworm) fully up to date before release upgrade..."
    apt-get update -y || die "[DEBIAN-UPGRADE] Failed to update package list 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] Failed to fully upgrade Debian 12 prior to release upgrade."

    # Switch APT sources from bookworm to trixie
    echo "[DEBIAN-UPGRADE] Updating APT sources from bookworm to trixie..."
    sed -i 's/bookworm/trixie/g' /etc/apt/sources.list || die "[DEBIAN-UPGRADE] Failed to update /etc/apt/sources.list to trixie."
    find /etc/apt/sources.list.d -maxdepth 1 -type f -exec sed -i 's/bookworm/trixie/g' {} \; 2>/dev/null || true

    # Preseed grub-pc install devices to avoid noninteractive failures during the Debian 13 upgrade.
    # On IVS appliances we assume a single-OS, single-disk layout; GRUB should be installed to the
    # disk that backs /boot (or / if /boot is not separate).
    if dpkg-query -W -f='${Status}' grub-pc 2>/dev/null | grep -q "install ok installed"; then
        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
            # Resolve the underlying disk name (e.g. sda, vda, nvme0n1) from the mount source.
            # This handles LVM and partitioned layouts by walking back to the parent disk.
            PARENT_DISK=$(lsblk -no PKNAME "$ROOT_OR_BOOT_DEV" 2>/dev/null || echo "")
            if [[ -n "$PARENT_DISK" ]]; then
                BOOT_DISK="$PARENT_DISK"
            else
                # Fallback: if PKNAME is empty, assume the source itself is the disk device.
                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 a boot disk for grub-pc preseeding; grub-pc may fail noninteractively during the upgrade."
        fi
    fi

    # Upgrade to Debian 13 (trixie) with basic retry-on-mirror-error logic
check_boot_spaceensure_boot_space kernel_cleanup_keep2"post FileWave package install kernel space check"
echo "[DEBIAN-UPGRADE] Running dist-upgrade to Debian 13 (trixie)..."
    max_attempts=2
    attempt=1
    while (( attempt <= max_attempts )); do
        echo "[DEBIAN-UPGRADE] dist-upgrade attempt $attempt of $max_attempts..."

        # Clean APT cache and lists to mitigate hash sum mismatch or corrupted metadata
        apt-get clean
        rm -rf /var/lib/apt/lists/*
        apt-get update -y || die "[DEBIAN-UPGRADE] Failed to update package list 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] Debian 12 -> 13 dist-upgrade completed successfully on attempt $attempt."
            break
        fi

        if (( attempt == max_attempts )); then
            echo "[DEBIAN-UPGRADE] Debian 12 -> 13 dist-upgrade failed after $max_attempts attempts."
            echo "[DEBIAN-UPGRADE] Common causes include transient mirror issues, HTTP proxies, or APT cache corruption."
            echo "[DEBIAN-UPGRADE] You can manually try the following commands, then re-run this script:"
            echo "[DEBIAN-UPGRADE]   apt-get clean"
            echo "[DEBIAN-UPGRADE]   rm -rf /var/lib/apt/lists/*"
            echo "[DEBIAN-UPGRADE]   apt-get update"
            echo "[DEBIAN-UPGRADE]   apt-get dist-upgrade"
            echo "[DEBIAN-UPGRADE] If the problem persists, consider switching to a different Debian mirror (e.g., deb.debian.org) and repeating the above steps."
            die "[DEBIAN-UPGRADE] Debian 12 -> 13 release upgrade failed after retry. See log output above for remediation steps."
        fi

        echo "[DEBIAN-UPGRADE] dist-upgrade failed (likely network or mirror issue, e.g., hash sum mismatch). Retrying after cleaning APT cache and lists..."
        attempt=$((attempt + 1))
        sleep 5
    done

    # Optional: modernize APT sources to deb822 format 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 format on Debian 13..."
            apt modernize-sources --assume-yes || echo "[DEBIAN-UPGRADE] Warning: 'apt modernize-sources' failed. You may want to run it manually later."
        else
            echo "[DEBIAN-UPGRADE] This version of apt does not support 'apt modernize-sources'; skipping sources modernization."
        fi
    fi

    # Refresh Debian version info after upgrade
    DEBIAN_VERSION=$(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
    DEBIAN_MAJOR=${DEBIAN_VERSION%%.*}
    if (( DEBIAN_MAJOR != 13 )); then
        die "[DEBIAN-UPGRADE] Debian upgrade appears incomplete. Current VERSION_ID=$DEBIAN_VERSION (expected major version 13)."
    fi

    echo "[DEBIAN-UPGRADE] Debian upgrade to 13 (trixie) completed successfully. Continuing with FileWave $VERSION installation..."
    OS_UPGRADED=true
}

#################### SWITCHES ##################

if [ "$#" -eq 0 ]; then
    echo "Usage: wget -qO- https://kb.filewave.com/attachments/408 | sudo bash -s -- -v <version> -r <revision> [-d|--dev | -b|--beta | -p|--prod] [-y|--yes] [-yy|--yesyes]"
    echo "
Options:"
    echo "  -v, --version    Specify the version of FileWave IVS to install (e.g., 16.3.0)"
    echo "  -r, --revision   Legacy revision selector (currently always '1'; other values are accepted but ignored)"
    echo "  -d, --dev		 Use Dev server for downloads (default is production)"
    echo "  -b, --beta       Use beta server for downloads (default is production)"
    echo "  -p, --prod       Use production server for downloads"
    echo "  -y, --yes        Automatically answer yes to routine prompts (installation, OS patch upgrade, reboot). Does NOT auto-approve SSH-without-screen or Debian 12 -> 13 release upgrades."
    echo "  -yy, --yesyes    Automatically answer yes to all prompts, including SSH-without-screen warnings and Debian 12 -> 13 OS release upgrades."
    exit 1
fi

# Parse input arguments (-v/-r/-d/-b/-p/-y/-yy) and override defaults
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
            ;;
        *)
            echo "Unknown option: $1"
            exit 1
            ;;
    esac
done

# Normalize revision: current FileWave IVS builds use revision 1 only.
# The -r/--revision switch is kept for legacy compatibility, but any value
# other than '1' is ignored and coerced back to '1'.
if [[ "$REVISION" != "1" ]]; then
    echo "[INFO] Ignoring requested revision '$REVISION' and using revision '1'. Current FileWave IVS builds ship with revision 1 only."
    REVISION="1"
fi

# Root password must be set for installer components that may invoke su.
ensure_root_password_set


# ===================== SUMMARY TAG BLOCK =====================
DETECTED_DEBIAN_VERSION=$(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
DETECTED_DEBIAN_MAJOR=${DETECTED_DEBIAN_VERSION%%.*}

# Use filewave-ivs as the authoritative source for the installed FileWave version
INSTALLED_FW_IVS_VERSION=$(dpkg-query -W -f='${Version}' filewave-ivs 2>/dev/null || echo "unknown")
# Still record the ivs-kernel package version separately for diagnostics, since it may differ
INSTALLED_IVS_KERNEL_VERSION=$(dpkg-query -W -f='${Version}' ivs-kernel 2>/dev/null || echo "unknown")

PLANNED_DEBIAN_BEHAVIOR="Unknown"

# IVS requires an existing installation (upgrade-only). Abort BEFORE any OS release upgrade prompts.
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' filewave-ivs 2>/dev/null || true)
if [[ -z "${INSTALLED_VERSION:-}" || "${INSTALLED_VERSION:-}" == "none" ]]; then
    die "No FileWave IVS packages are installed. This script upgrades an existing IVS installation only. Install the IVS appliance image first, then re-run this script for upgrades."
fi

if dpkg --compare-versions "$VERSION" ge "$MIN_DEBIAN13_FW_VERSION"; then
    if (( DETECTED_DEBIAN_MAJOR == 12 )); then
        PLANNED_DEBIAN_BEHAVIOR="Upgrade Debian 12 -> 13 (trixie) before FileWave upgrade"
    elif (( DETECTED_DEBIAN_MAJOR == 13 )); then
        PLANNED_DEBIAN_BEHAVIOR="Stay on Debian 13 (trixie) for this upgrade"
    else
        PLANNED_DEBIAN_BEHAVIOR="Unsupported Debian major $DETECTED_DEBIAN_MAJOR for this FileWave version"
    fi
else
    if (( DETECTED_DEBIAN_MAJOR == 12 )); then
        PLANNED_DEBIAN_BEHAVIOR="Stay on Debian 12 (bookworm); no release upgrade"
    elif (( DETECTED_DEBIAN_MAJOR == 13 )); then
        PLANNED_DEBIAN_BEHAVIOR="Blocked combination: FileWave $VERSION is supported only on Debian 12"
    else
        PLANNED_DEBIAN_BEHAVIOR="Unsupported Debian major $DETECTED_DEBIAN_MAJOR for this FileWave version"
    fi
fi

echo "[SUMMARY] Detected Debian Version: $DETECTED_DEBIAN_VERSION"
BOOT_SUMMARY=$(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || echo "n/a")
echo "[SUMMARY] /boot Usage: $BOOT_SUMMARY"
echo "[SUMMARY] Installed FileWave IVS Version (filewave-ivs): $INSTALLED_FW_IVS_VERSION"
echo "[SUMMARY] IVS Kernel Package Version (ivs-kernel): $INSTALLED_IVS_KERNEL_VERSION"
if [[ "$INSTALLED_FW_IVS_VERSION" == "unknown" || -z "$INSTALLED_FW_IVS_VERSION" ]]; then
    echo "[SUMMARY] IVS Presence Check: NO — this system does not appear to have an existing FileWave IVS installation"
else
    echo "[SUMMARY] IVS Presence Check: YES — existing FileWave IVS 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"

# Warn if the IVS kernel package version does not appear to match the FileWave IVS version
KERNEL_VERSION_MAIN="$INSTALLED_IVS_KERNEL_VERSION"
if [[ "$KERNEL_VERSION_MAIN" != "unknown" && -n "$KERNEL_VERSION_MAIN" ]]; then
    KERNEL_VERSION_MAIN="${KERNEL_VERSION_MAIN%%-*}"
fi
if [[ "$INSTALLED_FW_IVS_VERSION" != "unknown" && -n "$INSTALLED_FW_IVS_VERSION" && "$KERNEL_VERSION_MAIN" != "unknown" && -n "$KERNEL_VERSION_MAIN" && "$INSTALLED_FW_IVS_VERSION" != "$KERNEL_VERSION_MAIN" ]]; then
    echo "[SUMMARY] WARNING: Detected mismatch between FileWave IVS version (filewave-ivs=$INSTALLED_FW_IVS_VERSION) and IVS kernel package version (ivs-kernel=$INSTALLED_IVS_KERNEL_VERSION). This may indicate a troubleshooting kernel or an inconsistent installation state."
fi

echo "[SUMMARY] Beginning validation and upgrade process..."
# =============================================================




#################### INSTALLED VERSION + DOWNGRADE PROTECTION (EARLY) ####################
# IVS upgrades are only supported on existing IVS appliances. We enforce that early so we
# do not perform OS upgrades (including Debian 12 -> 13) or install tools on non-IVS hosts.
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' filewave-ivs 2>/dev/null || true)
if [[ -z "$INSTALLED_VERSION" || "$INSTALLED_VERSION" == "none" ]]; then
    die "No FileWave IVS packages are installed. This script only supports upgrading an existing FileWave IVS appliance and cannot perform a fresh installation. Please deploy a supported FileWave IVS appliance image first, then re-run this script for upgrades."
fi

# filewave-ivs uses a semantic version (e.g., 16.3.0) without a '-<revision>' suffix.
# For downgrade logic, treat that semantic version as the 'main' version and assume revision 1.
INSTALLED_MAIN_VERSION="$INSTALLED_VERSION"
INSTALLED_REVISION=1

# Prevent installing FileWave IVS 15.5.0 (known unsupported release); require 15.5.1 or newer
if dpkg --compare-versions "$VERSION" eq "15.5.0"; then
    die "Installing FileWave IVS 15.5.0 is not supported. Please install version 15.5.1 or newer."
fi

# Prevent downgrades (but allow reinstalling the same version).
# We compare only the semantic FileWave version (e.g., 16.3.0) because filewave-ivs does not
# encode revision, and in practice all IVS revisions are '1'.
if dpkg --compare-versions "$INSTALLED_MAIN_VERSION" gt "$VERSION"; then
    echo "Installed version ($INSTALLED_MAIN_VERSION) is newer than the requested version ($VERSION-$REVISION)."
    die "Aborting to prevent a downgrade of the FileWave IVS server."
fi

if dpkg --compare-versions "$INSTALLED_MAIN_VERSION" eq "$VERSION"; then
    echo "Requested version ($VERSION-$REVISION) matches the currently installed version ($INSTALLED_MAIN_VERSION)."
    echo "Proceeding with a reinstall of the same FileWave IVS version."
fi
################ Debian / FileWave Version Matrix ################
# Enforce supported Debian major versions (12 vs 13) based on the requested FileWave version

# Enforce supported Debian version based on target FileWave version
if dpkg --compare-versions "$VERSION" ge "$MIN_DEBIAN13_FW_VERSION"; then
    # For FileWave 16.3.0 and newer, require Debian 13
    if (( DEBIAN_MAJOR == 12 )); then
        upgrade_to_debian13
    elif (( DEBIAN_MAJOR == 13 )); then
        echo "Debian 13 detected; proceeding with FileWave $VERSION."
    else
        die "Unsupported Debian major version $DEBIAN_MAJOR for FileWave $VERSION. Debian 13 is required."
    fi
else
    # For FileWave versions older than 16.3.0, require Debian 12 and do not upgrade to Debian 13
    if (( DEBIAN_MAJOR != 12 )); then
        die "FileWave $VERSION is only supported on Debian 12. Current Debian version is $DEBIAN_VERSION."
    fi
fi

#################### MAIN ##################

# Confirm high-level IVS upgrade action with the operator (unless auto-yes mode applies)
echo "This script will update your OS and install/upgrade the FileWave IVS packages."
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

# Resolve the base download URL according to server type (prod/beta/dev)
if [ "$SERVER_TYPE" = "beta" ]; then
    BASE_URL="https://fwbetas.filewave.com"
elif [ "$SERVER_TYPE" = "dev" ]; then
    BASE_URL="https://fwdevdl.filewave.com"
else
    # Default to production download server
    BASE_URL="https://fwdl.filewave.com"
fi

###################################################
# 0. Remove legacy FileWave APT sources that were shipped with older IVS images
#    (avoids stale or conflicting repository entries during OS/FileWave upgrades)
rm -f /etc/apt/sources.list.d/filewave-beta.list > /dev/null || true
rm -f /etc/apt/sources.list.d/filewave-dev.list > /dev/null || true
rm -f /etc/apt/sources.list.d/filewave-release.list > /dev/null || true

###################################################
# 1. Ensure essential tools (python3, curl, zip, gdebi) are present for the upgrade process
echo "Installing essential tools..."
apt-get clean || die "Failed to clean apt cache."
apt-get update -y || die "Failed to update package list."
apt-get --fix-broken install -y || die "Failed to fix broken installs from apt."
apt-get autoremove -y || die "Failed to autoremove from apt."
check_boot_spaceensure_boot_space kernel_cleanup_keep2"pre OS patch upgrade kernel space check"
for dep in "python3" "curl" "zip" "gdebi"; do
    if ! command -v $dep &> /dev/null; then
        echo "$dep not found. Installing $dep..."
        apt-get update -qq 
        DEBIAN_FRONTEND=noninteractive apt-get install -y -q "$dep" || die "Failed to install $dep."
        #apt-get install -y $dep || die "Failed to install $dep."
    fi
done

# Ensure Python development headers are present so pip can build C extensions (e.g., netifaces)
# on Debian 13 and future Python versions.
if ! dpkg-query -W -f='${Status}' python3-dev 2>/dev/null | grep -q "install ok installed"; then
    echo "python3-dev not found. Installing python3-dev..."
    apt-get update -qq
    DEBIAN_FRONTEND=noninteractive apt-get install -y -q python3-dev || die "Failed to install python3-dev."
fi

# Handle iperf3 separately because it prompts to start a systemd service; preseed and enable non-interactively
if ! command -v iperf3 &>/dev/null; then
    echo "iperf3 not found. Installing iperf3 and enabling service..."
    
    # Preseed debconf to automatically accept starting the service
    echo "iperf3 iperf3/start_daemon boolean true" | debconf-set-selections
    
    # Install non-interactively
    DEBIAN_FRONTEND=noninteractive apt-get install -y -q iperf3 || die "Failed to install iperf3."
    
    # Ensure the systemd service is enabled and started (only if the unit exists)
    if systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' | grep -qx "iperf3.service"; then
        systemctl enable --now iperf3 || dieecho "[WARN] Failed to enable/start iperf3 service."
    else
        echo "[WARN] iperf3 installed but iperf3.service unit not found; skipping enable/start."
    fi
    
    # If UFW is installed and active, open TCP 5201 (iperf3). Do nothing otherwise.
    if command -v ufw >/dev/null 2>&1; then
        if ufw status | grep -q "Status: active"; then
            if ! ufw status | grep -q "5201/tcp"; then
                echo "UFW detected and active; allowing TCP 5201 for iperf3..."
                ufw allow 5201/tcp
            else
                echo "UFW rule for 5201/tcp already present; skipping."
            fi
        else
            echo "UFW installed but not active; not adding firewall rule."
        fi
    else
        echo "UFW not installed; skipping firewall configuration."
    fi
fi

if ! dpkg-query -W -f='${Status}' net-tools 2>/dev/null | grep -q "install ok installed"; then
    echo "net-tools not found. Installing net-tools..."
    apt-get install -y net-tools || die "Failed to install net-tools."
fi

# Check current version
# Determine installed FileWave IVS version from filewave-ivs package (authoritative)
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' filewave-ivs 2>/dev/null || true)
if [[ -z "$INSTALLED_VERSION" || "$INSTALLED_VERSION" == "none" ]]; then
    die "No FileWave IVS packages are installed. This script only supports upgrading an existing FileWave IVS appliance and cannot perform a fresh installation. Please deploy a supported FileWave IVS appliance image first, then re-run this script for upgrades."
fi

# filewave-ivs uses a semantic version (e.g., 16.3.0) without a '-<revision>' suffix.
# For downgrade logic, treat that semantic version as the 'main' version and assume revision 1.
INSTALLED_MAIN_VERSION="$INSTALLED_VERSION"
INSTALLED_REVISION=1

# Prevent installing FileWave IVS 15.5.0 (known unsupported release); require 15.5.1 or newer
if dpkg --compare-versions "$VERSION" eq "15.5.0"; then
    echo "Installing FileWave IVS 15.5.0 is not supported. Please install version 15.5.1 or newer."
    exit 1
fi

# Prevent downgrades (but allow reinstalling the same version).
# We compare only the semantic FileWave version (e.g., 16.3.0) because filewave-ivs does not
# encode revision, and in practice all IVS revisions are '1'.
if dpkg --compare-versions "$INSTALLED_MAIN_VERSION" gt "$VERSION"; then
    echo "Installed version ($INSTALLED_MAIN_VERSION) is newer than the requested version ($VERSION-$REVISION)."
    echo "Aborting to prevent a downgrade of the FileWave IVS server."
    exit 1
fi

if dpkg --compare-versions "$INSTALLED_MAIN_VERSION" eq "$VERSION"; then
    echo "Requested version ($VERSION-$REVISION) matches the currently installed version ($INSTALLED_MAIN_VERSION)."
    echo "Proceeding with a reinstall of the same FileWave IVS version."
fi

if dpkg --compare-versions "$INSTALLED_MAIN_VERSION" eq "$VERSION" && [[ "$INSTALLED_REVISION" -eq "$REVISION" ]]; then
    echo "Requested version ($VERSION-$REVISION) matches the currently installed version ($INSTALLED_VERSION)."
    echo "Proceeding with a reinstall of the same FileWave IVS version."
fi


install_packages() {
    echo "[INSTALL] Downloading and installing FileWave IVS packages..."
    PACKAGE_ORDER=(
        "filewave-admin_${VERSION}_amd64.deb"
        "filewave-imaging-client_${VERSION}_amd64.deb"
        "ivs-kernel-${VERSION}-${REVISION}.x86_64.deb"
        "filewave-ivs_${VERSION}_amd64.deb"
    )
    DOWNLOAD_DIR="/tmp/filewave_install_$$"
    cleanup_download_dir
    mkdir -p "$DOWNLOAD_DIR" || die "Failed to create download directory."
        trap 'rm -rf "$DOWNLOAD_DIR"' EXIT
    for package in "${PACKAGE_ORDER[@]}"; do
        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..."
        for i in {1..3}; do
            wget -q -O "$DOWNLOAD_DIR/$package" "$BASE_URL/$VERSION/$package"PACKAGE_URL" && break
            echo "[DOWNLOAD] Retrying download ($i/3)..."
            sleep 5
        done
        if [[ ! -f "$DOWNLOAD_DIR/$package" ]]; then
            die "[DOWNLOAD] Failed to download $package after multiple attempts."
        fi
        echo "Installing $package..."
        gdebi -n "$DOWNLOAD_DIR/$package" || die "Failed to install $package"
    done

trap 'rm -rf "$DOWNLOAD_DIR"' EXIT
}


# If we reach this point, the requested version is newer than the installed version.
echo "Upgrading from $INSTALLED_VERSION to $VERSION-$REVISION..."
install_packages
check_boot_spaceensure_boot_space kernel_cleanup_keep2"After IVS package install"
###################################################
# 4. OS Upgrade
#    - If a full Debian 12 → 13 upgrade already occurred, skip.
#    - Otherwise, update the current Debian release to the latest patches.
if [[ "$OS_UPGRADED" == "true" ]]; then
    echo "[OS-UPGRADE] Skipping generic OS upgrade step because a full Debian upgrade to 13 has already been performed in this run."
else
    echo "Warning: This script will upgrade the entire OS. This is required for security."
    if [[ "$auto_yes" == "true" ]]; then
        upgrade_confirm="yes"
    else
        read -p "Do you wish to proceed with the OS upgrade? (yes/no): " upgrade_confirm < /dev/tty
    fi
    if [[ ! "$upgrade_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
        echo "[OS-UPGRADE] Skipping OS upgrade at user request."
    else
        check_boot_spaceensure_boot_space kernel_cleanup_keep2"Before OS patch upgrade"
echo "[OS-UPGRADE] Upgrading the system..."
        apt-get update -y || die "[OS-UPGRADE] Failed to update package list."
        DEBIAN_FRONTEND=noninteractive apt-get upgrade -y \
            -o Dpkg::Options::="--force-confdef" \
            -o Dpkg::Options::="--force-confold" \
            --autoremove || die "[OS-UPGRADE] System upgrade failed."
    fi
fi

###################################################
# 5. Reboot to complete OS and FileWave IVS updates
echo "The system will reboot in 15 seconds to complete the installation."
if [[ "$auto_yes" == "true" ]]; then
    reboot_confirm="yes"
else
    read -p "Do you want to reboot now? (yes/no): " reboot_confirm < /dev/tty
fi
if [[ "$reboot_confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
    echo "[SUMMARY-END] Finalizing upgrade..."
    FINAL_IVS_VERSION=$(dpkg-query -W -f='${Version}' filewave-ivs 2>/dev/null || echo "unknown")
FINAL_IVS_KERNEL_VERSION=$(dpkg-query -W -f='${Version}' ivs-kernel 2>/dev/null || echo "unknown")
FINAL_KERNEL_VERSION_MAIN="$FINAL_IVS_KERNEL_VERSION"
if [[ "$FINAL_KERNEL_VERSION_MAIN" != "unknown" && -n "$FINAL_KERNEL_VERSION_MAIN" ]]; then
    FINAL_KERNEL_VERSION_MAIN="${FINAL_KERNEL_VERSION_MAIN%%-*}"
fi
echo "[SUMMARY-END] Debian Version After Upgrade: $DEBIAN_VERSION"
echo "[SUMMARY-END] /boot Usage After Upgrade: $(df -h /boot 2>/dev/null | awk 'NR==2 {print $5}' || echo \"n/a\")"
echo "[SUMMARY-END] FileWave IVS Version Installed (filewave-ivs): $FINAL_IVS_VERSION"
echo "[SUMMARY-END] IVS Kernel Package Version (ivs-kernel): $FINAL_IVS_KERNEL_VERSION"
if [[ "$FINAL_IVS_VERSION" != "unknown" && -n "$FINAL_IVS_VERSION" && "$FINAL_KERNEL_VERSION_MAIN" != "unknown" && -n "$FINAL_KERNEL_VERSION_MAIN" && "$FINAL_IVS_VERSION" != "$FINAL_KERNEL_VERSION_MAIN" ]]; then
    echo "[SUMMARY-END] WARNING: Post-upgrade mismatch between FileWave IVS version (filewave-ivs=$FINAL_IVS_VERSION) and IVS kernel package version (ivs-kernel=$FINAL_IVS_KERNEL_VERSION). This may indicate a troubleshooting kernel or an inconsistent installation state."
fi
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 "Rebooting system now (unattended mode)..."
    reboot
else
    echo "Rebooting system in 15120 seconds... Please(press rememberEnter that you can check the log file at $LOG_FILE after theto reboot to see the full details of the update.now)"
    sleepif 15read -r -t 120 _ < /dev/tty; then
        echo "[REBOOT] Rebooting now..."
    else
        echo "[REBOOT] Timeout reached; rebooting now..."
    fi
    reboot
fi
else
    echo "Skipping reboot. Please remember to reboot manually to apply all changes."
fi

# The end