Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Latest (super easy) method for automated flash zip backup!

Featured Replies

3 hours ago, weirdcrap said:

I noticed this broke when I updated to v7.3.0.

Same issue.

  • Replies 65
  • Views 16.1k
  • Created
  • Last Reply

Top Posters In This Topic

Most Popular Posts

  • Does your backup directory (/mnt/user/NCloud/Mihle/files/Filer/Backup/NAS Flash Backup/) already exist?   If not, it needs to be created before the script runs.  I learned this making my Ple

  • done

  • This script is built on the native unraid script that includes the zip function. However, it would be easy enough to add an unzip function to my backup script for you to use. I wrote this fairly quick

Posted Images

  • 2 weeks later...

Whats Script Version working on 7.3.1+?

  • 2 weeks later...

The expanded and corrected versions for V7.3.x and the changes to ‘boot’, ‘Internal Boot’, … instead of ‘flash’. Older operating system versions should also work, but have not really been tested as there are currently no older versions, such as V6.x available.

The enhanced, extended and improved Script with more Documentation:

New Script

#!/bin/bash

#############################################################################
# Title: Flash Boot Backup License Boot Analysis
# Description: Automates Flash or Boot Backup Creation, Retention, License and Boot Analysis
#
# Variable Naming Convention:
# --------------------------------------
# - GLOBAL VARIABLES: UPPER_SNAKE_CASE (e.g., HOST_NAME, BACKUP_RETENTION_DAYS)
# - LOCAL VARIABLES (within modules): UPPER_SNAKE_CASE (e.g., DISK_COUNT, CURRENT_TIME)
# - FUNCTIONS: UPPER_SNAKE_CASE (e.g., PRINT_MSG(), LOG_TO_FILE())
# - UI DISPLAY TEXTS: Title Case (e.g., "USB Stick", "Backup File", "TPM Mode")
#
# The readonly Keyword Explained:
# --------------------------------
# "readonly" makes a variable truly immutable (cannot be changed after assignment).
# This protects critical system paths from accidental modification during script execution.
# Example:
#   NOTIFICATION_SCRIPT="/usr/local/emhttp/webGui/scripts/notify"
#   readonly NOTIFICATION_SCRIPT
#   # Cannot later execute: NOTIFICATION_SCRIPT="/wrong/path" (will fail with error)
#
# Benefits of readonly:
# - Prevents bugs from accidental variable reassignment
# - Documents intent clearly for other developers
# - Helps with security (sensitive paths cannot be hijacked)
#
#############################################################################

# =============================================================================
# 1. CONFIGURATION & IDENTIFIERS
# =============================================================================

# -----------------------------------------------------------------------------
# GENERAL SETTINGS (USER-CUSTOMIZABLE)
# Configurable Values for Script Operation. User adjustments permitted.
# readonly keyword omitted to allow user overrides via Environment or File edit.
# -----------------------------------------------------------------------------
SCRIPT_TITLE="Flash Boot Backup License Boot Analysis"
HOST_NAME=$(hostname)
MIN_DISKS=3                          # Minimum Disks required in Array

# -----------------------------------------------------------------------------
# RETENTION POLICIES (USER-CUSTOMIZABLE)
# Controls Backup and Log File Retention Duration and Quantity Limits.
# Age-based deletion executes first, followed by count-based Enforcement.
# readonly keyword omitted to allow user Adjustments.
# -----------------------------------------------------------------------------
BACKUP_RETENTION_DAYS=150            # Backup Files kept for specified Days
MAX_BACKUPS_TO_KEEP=15               # Maximum Backup Files retained
LOG_RETENTION_DAYS=90                # Log Files kept for specified Days
MAX_LOG_FILES_TO_KEEP=30             # Maximum Log Files retained

# -----------------------------------------------------------------------------
# FEATURE FLAGS (USER-CUSTOMIZABLE - ENABLE/DISABLE)
# Controls execution of Individual Modules. Accepts TRUE or FALSE.
# readonly keyword omitted to allow user overrides.
# -----------------------------------------------------------------------------
ENABLE_LICENSE_CHECK=TRUE            # Check License Status (TPM/USB)
ENABLE_BOOT_CHECK=TRUE               # Analyze Boot Medium Type
ENABLE_HARDWARE_CHECK=TRUE           # Verify TPM/USB Hardware availability
ENABLE_SYSTEM_CHECK=TRUE             # Check Array Disks and Stale Files
ENABLE_BACKUP_CREATE=TRUE            # Create New Backup File
ENABLE_BACKUP_RETENTION=TRUE         # Apply Backup Retention Policy
ENABLE_LOG_RETENTION=TRUE            # Apply Log Retention Policy
ENABLE_NOTIFICATION=TRUE             # Send System Notification
SHOW_CLI_OUTPUT=TRUE                 # Display progress in Console/Terminal

# -----------------------------------------------------------------------------
# DIRTY CHECK OVERRIDE (USER-CUSTOMIZABLE)
# Controls behavior when Stale Temporary Files are detected.
# TRUE permits Continuation with Warning, FALSE triggers Abort.
# Default: FALSE (abort on Stale Files).
# -----------------------------------------------------------------------------
SKIP_DIRTY_CHECK=FALSE

# -----------------------------------------------------------------------------
# AUTO CLEANUP SETTINGS (USER-CUSTOMIZABLE)
# Controls automatic deletion of Stale Temporary Files at Script end.
# When FALSE (default), Stale Files trigger Abort or Warning (see SKIP_DIRTY_CHECK).
# When TRUE, Stale Files are automatically deleted AFTER all other Modules complete.
# -----------------------------------------------------------------------------
ENABLE_AUTO_CLEANUP=FALSE

# -----------------------------------------------------------------------------
# FILE NAMING CONVENTIONS (USER-CUSTOMIZABLE)
# Defines Log File Base Name and glob Pattern for Retention Matching.
# -----------------------------------------------------------------------------
LOG_BASENAME="Boot_Backup_Analysis"
LOG_FILE_PATTERN="${LOG_BASENAME}_*.log"

# -----------------------------------------------------------------------------
# NOTIFICATION SETTINGS (USER-CUSTOMIZABLE)
# Defines Title and Subject for Error Notifications via Unraid Notify Script.
# -----------------------------------------------------------------------------
NOTIFY_ERROR_TITLE="Flash Backup Aborted"
NOTIFY_ERROR_SUBJECT="Flash or Boot Backup - Error"

# -----------------------------------------------------------------------------
# USB BRAND PATTERNS (USER-CUSTOMIZABLE)
# Pipe-separated list of USB Brand Names for License Device Detection.
# Extended Lists accommodate additional USB Manufacturers.
# -----------------------------------------------------------------------------
USB_BRAND_PATTERN='ADATA|Corsair|Crucial|G.Skill|HP|Imation|Integral|Kingston|Lexar|Netac|PNY|Samsung|SanDisk|Silicon Power|Team Group|Toshiba|Transcend|Verbatim'

# -----------------------------------------------------------------------------
# MODULE TITLES (STATIC - NEVER MODIFIED AT RUNTIME)
# Fixed Labels for CLI Section Headers and Log Entries.
# readonly applied: Assigned once during initialization, No runtime modification.
# -----------------------------------------------------------------------------
readonly MOD_TITLE_1="LICENSE STATUS"
readonly MOD_TITLE_2="BOOT MEDIUM ANALYSIS"
readonly MOD_TITLE_3="HARDWARE CHECK"
readonly MOD_TITLE_4="SYSTEM INTEGRITY CHECK"
readonly MOD_TITLE_5="CREATE BACKUP"
readonly MOD_TITLE_6="APPLY BACKUP RETENTION"
readonly MOD_TITLE_7="APPLY LOG RETENTION"
readonly MOD_TITLE_8="AUTO CLEANUP"
readonly MOD_TITLE_9="SEND NOTIFICATION"

# =============================================================================
# 2. SYSTEM CONSTANTS
# =============================================================================

# -----------------------------------------------------------------------------
# SYSTEM PATHS (UNRAID-SPECIFIC - FIXED)
# Operating System-defined Paths. Immutable during Script execution.
# readonly applied: Prevents accidental or malicious Path Modification.
# -----------------------------------------------------------------------------
readonly NOTIFICATION_SCRIPT="/usr/local/emhttp/webGui/scripts/notify"
readonly VAR_INI_PATH="/var/local/emhttp/var.ini"

# -----------------------------------------------------------------------------
# DIRECTORY PATHS (DERIVED - AUTO-COMPUTED)
# Computed at Script Startup from HOST_NAME and configurable Values above.
# Represents Physical Directory Structure on Target System.
# readonly applied: Calculated once, Never modified Subsequently.
# Dependency order Preserved: Derived Paths Reference earlier Variables.
# -----------------------------------------------------------------------------
readonly BACKUP_BASE_DIR="/mnt/user/Backup_${HOST_NAME}/boot"
readonly ARCHIVE_SUBDIR="Backup_Reports"
readonly LOG_LOCAL_SUBDIR="Backup_Logs_on_Boot"
readonly ARCHIVE_LOG_DIR="${BACKUP_BASE_DIR}/${ARCHIVE_SUBDIR}"
readonly LOCAL_LOG_DIR="/boot/${LOG_LOCAL_SUBDIR}"

# -----------------------------------------------------------------------------
# LOG FILE PATHS (DERIVED - AUTO-COMPUTED PER EXECUTION)
# Unique per run via embedded Timestamp in Filename. Prevents File Collisions.
# LOG_FILE_PATH: Active Session Log (Write Target)
# ARCHIVE_LOG_PATH: Permanent Archive Copy (Final Destination)
# readonly applied: Calculated once, Never modified Subsequently.
# -----------------------------------------------------------------------------
readonly LOG_FILE_NAME="${LOG_BASENAME}_${HOST_NAME}_$(date '+%Y-%m-%d_%H-%M-%S').log"
readonly LOCAL_LOG_PATH="${LOCAL_LOG_DIR}/${LOG_FILE_NAME}"
readonly ARCHIVE_LOG_PATH="${ARCHIVE_LOG_DIR}/${LOG_FILE_NAME}"
readonly LAST_RUN_LOG="${ARCHIVE_LOG_DIR}/Last_Run.log"
readonly LOG_FILE_PATH="$LOCAL_LOG_PATH"

# =============================================================================
# 3. GLOBAL VARIABLES (RUNTIME STATE)
# =============================================================================

# NOTE: Variables below remain mutable (readonly NOT Applied).
# Purpose: Carry Initial/Default Values that are overwritten during Module execution.
# Applying readonly would cause Runtime Failure (Variable Assignment Rejection).
# Pattern: Initialize → Module Updates → Final State persisted

# -----------------------------------------------------------------------------
# LICENSE & BOOT STATE (INITIAL VALUES - MODULES 1-3 POPULATE)
# Neutral Default Values updated upon detection of actual System Configuration.
# Transition: Unknown/Empty → Detected Value (USB Flash, TPM, Migration, Invalid).
# -----------------------------------------------------------------------------
LICENSE_TYPE=""                     # Initial: empty → Set by Module 1
LICENSE_STATUS="Unknown"            # Initial: Unknown → Set by Module 1
DEVICE_BINDING="None"               # Initial: None → Set by Module 1
BOOT_MEDIUM_TYPE="Unknown"          # Initial: Unknown → Set by Module 2

# -----------------------------------------------------------------------------
# SYSTEM STATE (INITIAL VALUES - MODULE 4 POPULATES)
# Stale File detection initialized clean. Updated during Module 4 execution.
# HAS_STALE_FILES: FALSE → TRUE if Stale Files discovered
# SKIP_DIRTY_CHECK_ACTIVE: FALSE → TRUE if Bypass activated
# -----------------------------------------------------------------------------
HAS_STALE_FILES=FALSE                # Initial: FALSE → TRUE by Module 4 if Stale Files found
SKIP_DIRTY_CHECK_ACTIVE=FALSE        # Initial: FALSE → TRUE by Module 4 if Bypass activated

# -----------------------------------------------------------------------------
# BACKUP STATE (INITIAL VALUES - MODULE 5 POPULATES)
# Empty until Module 5 Creates Backup and populates with generated Values.
# -----------------------------------------------------------------------------
BACKUP_FILE=""                       # Initial: empty → Set by Module 5
DEST_FILE=""                         # Initial: empty → Set by Module 5

# -----------------------------------------------------------------------------
# CLEANUP STATE (INITIAL VALUES - MODULE 8 POPULATES)
# Tracks automatic cleanup execution and outcomes.
# Auto-computed by Module 8 based on actual file operations performed.
# -----------------------------------------------------------------------------
CLEANUP_EXECUTED=FALSE               # Initial: FALSE → TRUE by Module 8 if cleanup runs
CLEANUP_SUCCESS=0                    # Initial: 0 → Files by Module 8 successfully removed
CLEANUP_FAILED=0                     # Initial: 0 → Files by Module 8 that failed removal
CLEANUP_TOTAL=0                      # Initial: 0 → Total files by Module 8 processed

# -----------------------------------------------------------------------------
# OVERALL STATUS TRACKING (INITIAL VALUE - ALL MODULES UPDATE)
# Cumulative Result Tracker across all executed Modules.
# Escalation Priority: ERROR > WARNING > SUCCESS (Monotonic Escalation only).
# Modules may increase Severity; Deescalation not supported.
# Final Value determines completion Message and Notification Priority.
# -----------------------------------------------------------------------------
OVERALL_STATUS="SUCCESS"             # Initial: SUCCESS → WARNING or ERROR by any Module

# =============================================================================
# 4. MESSAGE TEXTS (REUSABLE CLI MESSAGES)
# =============================================================================

# -----------------------------------------------------------------------------
# LEVEL LABELS (STATIC - FIXED STRINGS)
# Visual indicators for CLI Output and Log Entries.
# readonly applied: Never modified after initialization.
# -----------------------------------------------------------------------------
readonly MSG_SUCCESS="[SUCCESS]"
readonly MSG_ERROR="[ERROR]"
readonly MSG_WARNING="[WARNING]"
readonly MSG_INFO="[INFO]"

# -----------------------------------------------------------------------------
# REUSABLE ERROR/WARNING MESSAGES (STATIC - FIXED STRINGS)
# Predefined Messages used at Multiple Points throughout Script execution.
# readonly applied: Never modified after initialization.
# Centralization ensures consistent Wording across all Modules.
# Single-Point modification enables future text adjustments.
# -----------------------------------------------------------------------------
readonly MSG_STALE_FILES_FOUND="Stale Temporary Files found."
readonly MSG_NO_STALE_FILES="No Stale Temporary Files found."
readonly MSG_ARRAY_INTEGRITY_CONFIRMED="Array Integrity confirmed."
readonly MSG_NO_USB_STICK="No USB Stick detected, but Flash License active."
readonly MSG_USB_STICK_DETECTED="USB Stick detected for License."
readonly MSG_TPM_MODE="TPM Mode: No USB Stick required for License."
readonly MSG_CONFIG_FILE_NOT_FOUND="Configuration File not found."
readonly MSG_VARINI_NOT_FOUND="var.ini not found."
readonly MSG_NO_TPM_DEVICE="No TPM Device available."
readonly MSG_UNZIP_NOT_FOUND="'unzip' Command not found. Integrity Check impossible."
readonly MSG_FLASH_BACKUP_FAILED="Flash Backup Tool failed."
readonly MSG_INVALID_FILENAME="Invalid Filename generated."
readonly MSG_SOURCE_FILE_MISSING="Source File does not exist."
readonly MSG_COPY_FAILED="Copying Backup File failed."
readonly MSG_BACKUP_CORRUPTED="Backup File corrupted! Removing defective File."
readonly MSG_CHANGE_DIR_FAILED="Change to Directory failed."
readonly MSG_PROCESS_ABORTED="Process aborted: Manual cleanup required."
readonly MSG_WARNING_IGNORED="Warning ignored."
readonly MSG_CLEANUP_STARTED="Auto cleanup initiated."
readonly MSG_CLEANUP_FAILED="Cleanup failed. No Files removed. Manual intervention required."
readonly MSG_CLEANUP_SKIPPED="Auto cleanup skipped (Disabled or no Stale Files)."

# =============================================================================
# 5. HELPER FUNCTIONS
# =============================================================================

# -----------------------------------------------------------------------------
# PRINT HEADER BANNER (CLI OUTPUT - STARTUP)
# Displays Script Title, Host Name, and Timestamp at Initialization.
# Single Invocation before any Module Execution.
# Visibility Controlled by SHOW_CLI_OUTPUT Feature Flag.
# -----------------------------------------------------------------------------
PRINT_HEADER() {
    echo "=============================================================="
    echo "   ${SCRIPT_TITLE}"
    echo "=============================================================="
    echo "HOST:              $(hostname)"
    echo "TIMESTAMP:         $(date '+%d.%m.%Y %H:%M')"
    echo "----------------------------------------"
}

# -----------------------------------------------------------------------------
# PRINT SECTION HEADER (CLI OUTPUT - BETWEEN MODULES)
# Displays formatted Section Title between Module Blocks.
# Parameter: $1 = Section Title (from MOD_TITLE_* Variables)
# Visibility Controlled by SHOW_CLI_OUTPUT Feature Flag.
# -----------------------------------------------------------------------------
PRINT_SECTION() {
    echo ""
    echo "[${1}]"
    echo "--------------------------------------------------------------"
}

# -----------------------------------------------------------------------------
# PRINT MESSAGE WITH LEVEL PREFIX (CLI OUTPUT - UNIFIED)
# Centralized Message formatting for consistent CLI presentation.
# Parameters: $1 = Level (SUCCESS/ERROR/WARNING/INFO), $2 = Message Text
# Constructs Formatted Output String in Local Variable OUTPUT.
# stdbuf Applies Line-buffered Terminal Display when SHOW_CLI_OUTPUT=TRUE.
# -----------------------------------------------------------------------------
PRINT_MSG() {
    local LEVEL="$1"
    local MSG="$2"
    local OUTPUT=""

    case "$LEVEL" in
        SUCCESS) OUTPUT="${MSG_SUCCESS} ${MSG}" ;;
        ERROR)   OUTPUT="${MSG_ERROR}   ${MSG}" ;;
        WARNING) OUTPUT="${MSG_WARNING} ${MSG}" ;;
        INFO)    OUTPUT="${MSG_INFO}    ${MSG}" ;;
        *)       OUTPUT="$MSG" ;;
    esac

    # Line-buffered Output for immediate Display
    stdbuf -oL echo "$OUTPUT"
}

# -----------------------------------------------------------------------------
# WRITE TIMESTAMPED ENTRY TO LOG FILE (LOGGING - UNIFIED)
# Appends Single Log Entry with Timestamp and Host Name.
# Parameter: $1 = Message Text
# Destination: LOG_FILE_PATH (Current Session Log)
# Invocation: All Modules (Independent of SHOW_CLI_OUTPUT Setting).
# -----------------------------------------------------------------------------
LOG_TO_FILE() {
    local MSG="$1"
    local TIMESTAMPED_MSG="[$(date '+%Y-%m-%d %H:%M:%S')] [$(hostname)] $MSG"
    echo "$TIMESTAMPED_MSG" >> "$LOG_FILE_PATH"
}

# -----------------------------------------------------------------------------
# MASK GUID FORMAT (SENSITIVE DATA OBSCURATION)
# Preserves GUID Structure while obscuring sensitive Content.
#
# UNRAID GUID STRUCTURE (Uppercase Alphanumeric: 0-9, A-Z only):
#
#   - TPM GUIDs:  Starts with "01-", 2 Segments (1 Dash)
#     Example: "01-ABC23D123456GHIJ7890KLMN"
#     → Masked: "01-ABC23*******************"
#     Recognition: "01-" Prefix, first 5 Chars of Segment 2 visible.
#
#   - Flash GUIDs: 4 Segments (3 Dashes)
#     Example: "0789-1012-3456-789AABE12345"
#     → Masked: "0789-10**-34**-789AB*******"
#     Recognition: All 4 Segments visible, specific show/mask per Segment.
#
# MASKING LOGIC:
#   - TPM: Show "01-" + first 5 Chars of Segment 2 + rest as ****
#   - Flash Seg 1: Show all if ≤4 Chars, else first 4 + ****
#   - Flash Seg 2: Show first 2 Chars + **** (Pad to 4 total)
#   - Flash Seg 3: Show first 2 Chars + **** (Pad to 4 total)
#   - Flash Seg 4: Show first 5 Chars + rest as ****
#
# PARAMETER: $1 = Original GUID String (expected Uppercase Alphanumeric)
# RETURN:    Masked GUID String preserving original Format Structure
# -----------------------------------------------------------------------------
MASK_GUID_FORMAT() {
    local GUID="$1"
    if [[ -z "$GUID" ]]; then echo "---"; return; fi

    # TPM GUID
    if [[ "$GUID" == 01-* ]]; then
        local TPM_PART="${GUID#01-}"
        local PART_LEN=${#TPM_PART}

        if [[ ${PART_LEN} -gt 5 ]]; then
            local SHOW_CHARS="${TPM_PART:0:5}"
            local MASK_LEN=$((PART_LEN - 5))
            echo "01-${SHOW_CHARS}$(printf '*%.0s' $(seq 1 ${MASK_LEN}))"
        elif [[ ${PART_LEN} -gt 0 ]]; then
            echo "01-${TPM_PART}****"
        else
            echo "01-****"
        fi
        return
    fi

    # Flash GUID
    local -a SEGMENTS
    IFS='-' read -ra SEGMENTS <<< "$GUID"

    if [[ ${#SEGMENTS[@]} -ge 4 ]]; then
        local S1="${SEGMENTS[0]}"
        local S2="${SEGMENTS[1]}"
        local S3="${SEGMENTS[2]}"
        local S4="${SEGMENTS[3]}"

        # Segment 1: Show all if ≤4 Chars, else first 4 + ****
        local S1_LEN=${#S1}
        local MASKED_S1
        if [[ ${S1_LEN} -le 4 ]]; then
            MASKED_S1="${S1}"
        else
            MASKED_S1="${S1:0:4}$(printf '*%.0s' $(seq 1 $((S1_LEN - 4))))"
        fi

        # Segment 2: Show first 2 Chars + ** padding
        local S2_LEN=${#S2}
        local MASKED_S2
        if [[ ${S2_LEN} -ge 2 ]]; then
            MASKED_S2="${S2:0:2}**"
        elif [[ ${S2_LEN} -eq 1 ]]; then
            MASKED_S2="${S2}***"
        else
            MASKED_S2="****"
        fi

        # Segment 3: Show first 2 Chars + ** padding
        local S3_LEN=${#S3}
        local MASKED_S3
        if [[ ${S3_LEN} -ge 2 ]]; then
            MASKED_S3="${S3:0:2}**"
        elif [[ ${S3_LEN} -eq 1 ]]; then
            MASKED_S3="${S3}***"
        else
            MASKED_S3="****"
        fi

        # Segment 4: Show first 5 Chars + rest as ****
        local S4_LEN=${#S4}
        local MASKED_S4
        if [[ ${S4_LEN} -gt 5 ]]; then
            MASKED_S4="${S4:0:5}$(printf '*%.0s' $(seq 1 $((S4_LEN - 5))))"
        elif [[ ${S4_LEN} -gt 0 ]]; then
            MASKED_S4="${S4}****"
        else
            MASKED_S4="****"
        fi

        echo "${MASKED_S1}-${MASKED_S2}-${MASKED_S3}-${MASKED_S4}"

    elif [[ ${#SEGMENTS[@]} -ge 2 ]]; then
        # 2+ Segments but not 4 — generic fallback
        local FIRST="${SEGMENTS[0]}"
        local SECOND="${SEGMENTS[1]}"

        if [[ ${#SECOND} -gt 5 ]]; then
            echo "${FIRST}-${SECOND:0:5}$(printf '*%.0s' $(seq 1 $(${#SECOND} - 5)))"
        elif [[ ${#SECOND} -gt 0 ]]; then
            echo "${FIRST}-${SECOND}****"
        else
            echo "${FIRST}-****"
        fi

    else
        # Single Segment
        if [[ ${#GUID} -gt 5 ]]; then
            echo "${GUID:0:5}$(printf '*%.0s' $(seq 1 $(${#GUID} - 5)))"
        elif [[ ${#GUID} -gt 0 ]]; then
            echo "${GUID}****"
        else
            echo "****"
        fi
    fi
}

# -----------------------------------------------------------------------------
# CLEAN VENDOR NAME (DEVICE STRING NORMALIZATION)
# Extracts Primary Vendor Name from USB Device String.
# Removes Common Model Suffixes (Cruzer, DataTraveler, Flash).
# Input Example: "SanDisk Cruzer Blade" → "SanDisk"
# Parameter: $1 = Raw Vendor String from lsusb Output
# Return: Cleaned Vendor Name Only
# -----------------------------------------------------------------------------
CLEAN_VENDOR_NAME() {
    local RAW_VENDOR="$1"
    local VENDOR=$(echo "$RAW_VENDOR" | awk '{print $1}')
    if [[ "$VENDOR" == *"Cruzer"* ]]; then
        VENDOR=$(echo "$VENDOR" | sed 's/Cruzer$//g')
    elif [[ "$VENDOR" == *"DataTraveler"* ]]; then
        VENDOR=$(echo "$VENDOR" | sed 's/DataTraveler$//g')
    elif [[ "$VENDOR" == *"Flash"* ]]; then
        VENDOR=$(echo "$VENDOR" | sed 's/Flash$//g')
    fi
    echo "$VENDOR" | xargs
}

# -----------------------------------------------------------------------------
# CLEAN MODEL NAME (DEVICE STRING NORMALIZATION)
# Strips Storage Capacity Suffixes from Device Model Strings.
# Matches and Removes: GB, TB, MB, GiB, MiB with Numeric Prefixes.
# Input Example: "WD Red SN700 1TB" → "WD Red SN700"
# Parameter: $1 = Raw Model String
# Return: Cleaned Model Name without Capacity Information
# -----------------------------------------------------------------------------
CLEAN_MODEL_NAME() {
    local RAW_MODEL="$1"
    local CLEANED=$(echo "$RAW_MODEL" | sed -E 's/[[:space:]]*[0-9]+\.?[0-9]*(GB|TB|MB|GiB|MiB)[[:space:]]*$//gi')
    if [[ -z "$CLEANED" ]]; then
        echo "$RAW_MODEL"
    else
        echo "$CLEANED"
    fi
}

# -----------------------------------------------------------------------------
# SEND ERROR NOTIFICATION (CRITICAL ERROR HANDLER)
# Triggers Unraid System Notification for Critical Errors.
# Parameters: $1 = Title (Optional, Defaults to NOTIFY_ERROR_TITLE)
#             $2 = Subject (Optional, Defaults to NOTIFY_ERROR_SUBJECT)
#             $3 = Message (Required)
# Hardcoded "-i error" Icon for Unraid Notify Script Compatibility.
# -----------------------------------------------------------------------------
SEND_ERROR_NOTIFICATION() {
    local TITLE="${1:-${NOTIFY_ERROR_TITLE}}"
    local SUBJECT="${2:-${NOTIFY_ERROR_SUBJECT}}"
    local MESSAGE="$3"

    "$NOTIFICATION_SCRIPT" \
        -e "$TITLE" \
        -s "$SUBJECT" \
        -d "$MESSAGE" \
        -i "error"
}

# -----------------------------------------------------------------------------
# HANDLE CRITICAL ERROR AND EXIT (FATAL CONDITION HANDLER)
# Centralized Error Handler for Fatal Conditions in Module 5.
# Parameters: $1 = Log Message (Written to Log File)
#             $2 = Notification Message (Sent via System Alert)
# Sequential Actions: Log Entry → Notification Send → Exit with Code 1.
# Exclusive Use by Module 5 for Backup Creation Failures.
# -----------------------------------------------------------------------------
HANDLE_ERROR_EXIT() {
    local LOG_MSG="$1"
    local NOTIFY_MSG="$2"
    local LOG_PATH="$LOG_FILE_PATH"

    LOG_TO_FILE "${MSG_ERROR} ${LOG_MSG}"
    SEND_ERROR_NOTIFICATION "" "" "${NOTIFY_MSG}"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "ERROR" "${MSG_ERROR} ${LOG_MSG}"
        echo "────────────────────────────────────────────────────────────"
        echo "→ For detailed Error Information, check Log File:"
        echo "  $LOG_PATH"
        echo "────────────────────────────────────────────────────────────"
    fi

    exit 1
}

# =============================================================================
# 6. INITIALIZATION
# =============================================================================

# Create Required Directories
# Precedence: Created before any Module execution.
# Failure Condition: Script Termination with Error Notification.
# mkdir -p Behavior: Parent Directories Created Automatically as needed.
mkdir -p "$BACKUP_BASE_DIR" || {
    SEND_ERROR_NOTIFICATION "" "" "Cannot create Backup Directory."
    exit 1
}

mkdir -p "${ARCHIVE_LOG_DIR}" || {
    SEND_ERROR_NOTIFICATION "" "" "Cannot create Archive Log Directory."
    exit 1
}

mkdir -p "${LOCAL_LOG_DIR}" || {
    SEND_ERROR_NOTIFICATION "" "" "Cannot create Local Log Directory."
    exit 1
}

# Create Log Files
# Command: touch Creates Empty Files if Non-existent.
# Failure Condition: Script Termination with Error Notification.
touch "$LOCAL_LOG_PATH" 2>/dev/null || {
    SEND_ERROR_NOTIFICATION "" "" "Cannot create Local Log File."
    exit 1
}

touch "$ARCHIVE_LOG_PATH" 2>/dev/null || {
    SEND_ERROR_NOTIFICATION "" "" "Cannot create Archive Log File."
    exit 1
}

# Write Initial Log Entry
# Mechanism: Block-Redirect Overwrites Log Files with Startup Metadata.
# Purpose: Establishes Baseline for Session Tracking.
{
    echo "=========================================="
    echo "START ${SCRIPT_TITLE}"
    echo "TIMESTAMP: $(date '+%d.%m.%Y %H:%M')"
    echo "HOSTNAME: ${HOST_NAME}"
    echo "BACKUP TARGET: ${BACKUP_BASE_DIR}"
    echo "LOCAL LOG: ${LOCAL_LOG_PATH}"
    echo "ARCHIVE LOG: ${ARCHIVE_LOG_PATH}"
    echo "LAST RUN LOG: ${LAST_RUN_LOG}"
    echo "=========================================="
} > "$LOG_FILE_PATH"

# Print CLI Header (Conditional)
# Visibility Controlled by SHOW_CLI_OUTPUT Feature Flag.
if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
    PRINT_HEADER
fi

# =============================================================================
# 7. MODULES
# =============================================================================

# Execution Model: 9 Modular Blocks Process Distinct Tasks Sequentially.
# Conditional Execution: Each Module Respects Corresponding ENABLE_* Feature Flag.
# Status Accumulation: OVERALL_STATUS Collects Warnings and Errors Across Modules.
# Final Determination: Overall Status Sets Completion Message and Notification Priority.

# Functional Categorization:
# - Modules 1-4: Validation and Analysis (Pre-Backup Checks)
# - Modules 5-7: Core Operations (Backup + Retention)
# - Module 8: Auto Cleanup (Deferred Stale File removal)
# - Module 9: Send Notification (Final Report with complete Data)

# Execution Characteristics:
# - Conditional Execution Based on ENABLE_* Flags
# - Sequential Processing (Order Preserved as Listed)
# - State Propagation via Global Variables
# - Error Handling via Centralized Handlers
# - Log Recording Independent of CLI Output Setting
# - Status Escalation Preserves Highest Severity (ERROR > WARNING > SUCCESS)

# --- MODULE 1: LICENSE STATUS -----------------------------------------------
if [ "$ENABLE_LICENSE_CHECK" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 1: ${MOD_TITLE_1}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_1}"
    fi

    # Local Module Variables
    LOCAL_SERVER_NAME=""
    LOCAL_OS_VERSION=""
    LOCAL_LICENSE_TYPE=""
    LOCAL_FLASH_GUID=""
    LOCAL_TPM_GUID=""
    LOCAL_REG_GUID=""

    # Parse var.ini Configuration File
    if [ -f "$VAR_INI_PATH" ]; then
        LOCAL_SERVER_NAME=$(awk -F'=' '/^NAME=/ {gsub(/"/, "", $2); gsub(/ /, "_", $2); print $2}' "$VAR_INI_PATH")
        LOCAL_OS_VERSION=$(awk -F'=' '/^version=/ {gsub(/"/, "", $2); print $2}' "$VAR_INI_PATH")
        LOCAL_LICENSE_TYPE=$(awk -F'=' '/^regTy=/ {gsub(/"/, "", $2); print $2}' "$VAR_INI_PATH" 2>&1) || true
        if [[ "$LOCAL_LICENSE_TYPE" == *"error"* ]] || [[ "$LOCAL_LICENSE_TYPE" == *"cannot"* ]]; then
            LOG_TO_FILE "${MSG_WARNING} awk for regTy reported issues: $LOCAL_LICENSE_TYPE"
            LOCAL_LICENSE_TYPE=""
        fi
        LOCAL_FLASH_GUID=$(grep '^flashGUID=' "$VAR_INI_PATH" 2>&1 | cut -d'"' -f2 | tr -d '[:space:]') || true
        if [[ "$LOCAL_FLASH_GUID" == *"error"* ]] || [[ "$LOCAL_FLASH_GUID" == *"No such"* ]]; then
            LOG_TO_FILE "${MSG_WARNING} grep for flashGUID reported issues: $LOCAL_FLASH_GUID"
            LOCAL_FLASH_GUID=""
        fi
        LOCAL_TPM_GUID=$(grep '^tpmGUID=' "$VAR_INI_PATH" 2>&1 | cut -d'"' -f2 | tr -d '[:space:]') || true
        if [[ "$LOCAL_TPM_GUID" == *"error"* ]] || [[ "$LOCAL_TPM_GUID" == *"No such"* ]]; then
            LOG_TO_FILE "${MSG_WARNING} grep for tpmGUID reported issues: $LOCAL_TPM_GUID"
            LOCAL_TPM_GUID=""
        fi
        LOCAL_REG_GUID=$(grep '^regGUID=' "$VAR_INI_PATH" 2>&1 | cut -d'"' -f2 | tr -d '[:space:]') || true
        if [[ "$LOCAL_REG_GUID" == *"error"* ]] || [[ "$LOCAL_REG_GUID" == *"No such"* ]]; then
            LOG_TO_FILE "${MSG_WARNING} grep for regGUID reported issues: $LOCAL_REG_GUID"
            LOCAL_REG_GUID=""
        fi
        LICENSE_TYPE="$LOCAL_LICENSE_TYPE"
    else
        PRINT_MSG "ERROR" "${MSG_ERROR} ${MSG_CONFIG_FILE_NOT_FOUND}"
        LOG_TO_FILE "${MSG_WARNING} ${MSG_VARINI_NOT_FOUND}"
    fi

    # Determine License Binding Type
    LICENSE_STATUS="Unknown"
    DEVICE_BINDING="None"

    if [[ "$LOCAL_FLASH_GUID" == "$LOCAL_REG_GUID" ]] && [[ "$LOCAL_FLASH_GUID" != "$LOCAL_TPM_GUID" || -z "$LOCAL_TPM_GUID" ]]; then
        LICENSE_STATUS="USB_FLASH"
        DEVICE_BINDING="USB Stick"
        if [[ -n "$LOCAL_TPM_GUID" ]] && [[ "$LOCAL_FLASH_GUID" != "$LOCAL_TPM_GUID" ]]; then
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "INFO" "TPM Key present, currently not used."
            fi
            LOG_TO_FILE "${MSG_INFO} TPM Key present, currently not used."
        fi
    elif [[ "$LOCAL_FLASH_GUID" == "$LOCAL_TPM_GUID" ]] && [[ "$LOCAL_FLASH_GUID" == "$LOCAL_REG_GUID" ]]; then
        LICENSE_STATUS="FULL_TPM"
        DEVICE_BINDING="TPM"
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "SUCCESS" "TPM Key used for Device Binding and TPM Licensing is active."
        fi
        LOG_TO_FILE "${MSG_SUCCESS} TPM Key used for Device Binding and TPM Licensing is active."
    elif [[ "$LOCAL_REG_GUID" == "$LOCAL_TPM_GUID" ]]; then
        LICENSE_STATUS="TPM_MIGRATION"
        DEVICE_BINDING="TPM"
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "SUCCESS" "TPM Key used for Device Binding and TPM Licensing is available."
        fi
        LOG_TO_FILE "${MSG_INFO} TPM Key used for Device Binding and TPM Licensing is available."
    else
        LICENSE_STATUS="INVALID_CONFIG"
        DEVICE_BINDING="Unknown"
        LOG_TO_FILE "${MSG_WARNING} GUID Configuration could not be uniquely identified."
    fi

    # Mask GUIDs for Secure Display
    LOCAL_FLASH_MASKED=$(MASK_GUID_FORMAT "$LOCAL_FLASH_GUID")
    LOCAL_TPM_MASKED=$(MASK_GUID_FORMAT "$LOCAL_TPM_GUID")
    LOCAL_REG_MASKED=$(MASK_GUID_FORMAT "$LOCAL_REG_GUID")

    # CLI Output (Consistent Format - Title Case for UI Terms)
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        echo "USB LICENSE ID: ${LOCAL_FLASH_MASKED:-N/A}"
        echo "TPM KEY: ${LOCAL_TPM_MASKED:-N/A}"
        echo "ACTIVE REGISTRATION: ${LOCAL_REG_MASKED:-N/A}"
        echo "----------------------------------------"
        echo "SERVER NAME: ${LOCAL_SERVER_NAME:-Unavailable}"
        echo "OS VERSION: ${LOCAL_OS_VERSION:-Unavailable}"
        echo "LICENSE TYPE: ${LOCAL_LICENSE_TYPE:-Not detected or invalid}"
        echo "DETECTED BINDING: ${DEVICE_BINDING} (${LICENSE_STATUS})"
        echo "----------------------------------------"
        echo "DETECTED MODE: ${LICENSE_STATUS}"
        echo "LICENSE DEVICE TYPE: ${DEVICE_BINDING}"
    fi

    # Check License Status and Update Overall Status
    if [[ "$LICENSE_STATUS" == "INVALID_CONFIG" ]]; then
        LOG_TO_FILE "${MSG_WARNING} License Configuration invalid"
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "WARNING" "License Configuration invalid"
        fi
        OVERALL_STATUS="WARNING"
    fi

    # Log Module Results
    LOG_TO_FILE "USB LICENSE ID: ${LOCAL_FLASH_MASKED:-N/A}"
    LOG_TO_FILE "TPM KEY: ${LOCAL_TPM_MASKED:-N/A}"
    LOG_TO_FILE "ACTIVE REGISTRATION: ${LOCAL_REG_MASKED:-N/A}"
    LOG_TO_FILE "SERVER NAME: ${LOCAL_SERVER_NAME:-Unavailable}"
    LOG_TO_FILE "OS VERSION: ${LOCAL_OS_VERSION:-Unavailable}"
    LOG_TO_FILE "LICENSE TYPE: ${LOCAL_LICENSE_TYPE:-Undetermined}"
    LOG_TO_FILE "DETECTED BINDING: ${DEVICE_BINDING} (${LICENSE_STATUS})"

    LOG_TO_FILE "---[${MOD_TITLE_1} COMPLETED]---"
    LOG_TO_FILE ""
else
    LOG_TO_FILE "---[MODULE 1: ${MOD_TITLE_1}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
fi

# --- MODULE 2: BOOT MEDIUM ANALYSIS -----------------------------------------
if [ "$ENABLE_BOOT_CHECK" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 2: ${MOD_TITLE_2}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_2}"
    fi

    BOOT_MEDIUM_TYPE="Unknown"

    # Local Variables for Boot Analysis
    BOOT_DEVICE_NAME=""
    PARENT_DEVICE=""
    TRANSPORT=""
    SYSTEM_DEV_FINAL=""
    SYS_BOOT_TYPE="Unknown"
    SYS_VENDOR=""
    SYS_MODEL_CLEAN=""

    # Detect Storage Types (NVMe, SATA, MMC)
    LSBLK_OUTPUT=$(lsblk -d -no NAME,TYPE 2>&1) || true
    if echo "$LSBLK_OUTPUT" | grep -qiE "error|cannot"; then
        LOG_TO_FILE "${MSG_WARNING} lsblk reported issues: $LSBLK_OUTPUT"
    fi
    NVME_DEV_GLOBAL=$(echo "$LSBLK_OUTPUT" | grep 'nvme' | awk '{print $1}' | head -1)
    SDA_DEV_GLOBAL=$(echo "$LSBLK_OUTPUT" | grep -E '^sd[a-z]$' | awk '{print $1}' | head -1)
    MMC_DEV_GLOBAL=$(echo "$LSBLK_OUTPUT" | grep 'mmc' | awk '{print $1}' | head -1)

    # Analyze Flash Label Link
    if [ -e "/dev/disk/by-label/flash" ]; then
        REAL_LINK=$(readlink -f "/dev/disk/by-label/flash")
        BOOT_DEVICE_NAME=$(basename "$REAL_LINK")

        # Remove Partition Number Suffix
        if [[ "$BOOT_DEVICE_NAME" =~ [0-9]+$ ]]; then
            PARENT_DEVICE="${BOOT_DEVICE_NAME%[0-9]*}"
        else
            PARENT_DEVICE="$BOOT_DEVICE_NAME"
        fi

        # Determine Transport Protocol (USB, SAS, etc.)
        TRANSPORT=$(lsblk -no TRAN "$PARENT_DEVICE" 2>&1 | tr -d '[:space:]') || true
        if [[ "$TRANSPORT" == *"error"* ]] || [[ "$TRANSPORT" == *"cannot"* ]]; then
            LOG_TO_FILE "${MSG_WARNING} lsblk for transport reported issues: $TRANSPORT"
            TRANSPORT=""
        fi

        case "$TRANSPORT" in
            usb)
                BOOT_MEDIUM_TYPE="USB Stick Boot"
                ;;
            *)
                SYS_PATH="/sys/block/$PARENT_DEVICE/device"
                if [ -L "$SYS_PATH" ]; then
                    FULL_PATH=$(readlink -f "$SYS_PATH")
                    if [[ "$FULL_PATH" =~ usb ]]; then
                        BOOT_MEDIUM_TYPE="USB Stick Boot"
                    else
                        BOOT_MEDIUM_TYPE="Internal Boot"
                    fi
                else
                    BOOT_MEDIUM_TYPE="Internal Boot"
                fi
                ;;
        esac

        # Get Device Details for Internal Boot
        if [[ "$BOOT_MEDIUM_TYPE" == "Internal Boot" ]]; then
            if [[ -n "$NVME_DEV_GLOBAL" ]]; then
                SYSTEM_DEV_FINAL="$NVME_DEV_GLOBAL"
            elif [[ -n "$SDA_DEV_GLOBAL" ]]; then
                SYSTEM_DEV_FINAL="$SDA_DEV_GLOBAL"
            elif [[ -n "$MMC_DEV_GLOBAL" ]]; then
                SYSTEM_DEV_FINAL="$MMC_DEV_GLOBAL"
            fi

            SYS_BOOT_TYPE="Internal Boot"

            if [[ -n "$SYSTEM_DEV_FINAL" ]] && [[ -b "/dev/$SYSTEM_DEV_FINAL" ]]; then
                VENDOR_RAW=$(lsblk -no VENDOR "/dev/$SYSTEM_DEV_FINAL" 2>&1 | head -1 | xargs) || true
                MODEL_RAW=$(lsblk -no MODEL "/dev/$SYSTEM_DEV_FINAL" 2>&1 | head -1 | xargs) || true
                if [[ "$VENDOR_RAW" == *"error"* ]] || [[ "$MODEL_RAW" == *"error"* ]]; then
                    LOG_TO_FILE "${MSG_WARNING} lsblk for vendor/model reported issues: VENDOR=$VENDOR_RAW MODEL=$MODEL_RAW"
                fi

                if [[ -z "$VENDOR_RAW" || "$VENDOR_RAW" == "" ]]; then
                    SYS_VENDOR="(see Model)"
                else
                    SYS_VENDOR="$VENDOR_RAW"
                fi

                SYS_MODEL_CLEAN=$(CLEAN_MODEL_NAME "$MODEL_RAW")
            fi
        fi

    else
        LOG_TO_FILE "${MSG_WARNING} /dev/disk/by-label/flash not found."
    fi

    # Gather License Device Information
    LIC_VENDOR="-"
    LIC_MODEL_CLEAN="-"
    LIC_TYPE=""

    if [[ "$DEVICE_BINDING" == "USB Stick" ]]; then
        LSUSB_OUTPUT=$(lsusb 2>&1) || true
        if echo "$LSUSB_OUTPUT" | grep -qiE "error|command not found"; then
            LOG_TO_FILE "${MSG_WARNING} lsusb reported issues: $LSUSB_OUTPUT"
        fi
        USB_LINE=$(echo "$LSUSB_OUTPUT" | grep -iE "$USB_BRAND_PATTERN" | head -1)

        if [[ -n "$USB_LINE" ]]; then
            RAW_FULL=$(echo "$USB_LINE" | sed 's/.*ID [0-9a-f:]* //' | sed 's/ *Corp\. //g' | sed 's/ *Inc\. //g' | xargs)
            LIC_VENDOR=$(echo "$RAW_FULL" | awk '{print $1}')
            LIC_VENDOR=$(CLEAN_VENDOR_NAME "$LIC_VENDOR")

            if [[ "$LIC_VENDOR" == "$RAW_FULL" ]]; then
                LIC_MODEL_CLEAN="Unknown USB Device"
            else
                MOD_PART=$(echo "$RAW_FULL" | sed "s/^${LIC_VENDOR}[[:space:]]*//")
                LIC_MODEL_CLEAN=$(CLEAN_MODEL_NAME "$MOD_PART")
            fi
        fi

        [[ -z "$LIC_VENDOR" ]] && LIC_VENDOR="Unknown"
        [[ -z "$LIC_MODEL_CLEAN" ]] && LIC_MODEL_CLEAN="Unknown"
        LIC_TYPE="USB Stick"
    else
        LIC_TYPE="TPM"
    fi

        LOG_TO_FILE "BOOT MEDIUM TYPE: ${BOOT_MEDIUM_TYPE}"
        LOG_TO_FILE  ""
        LOG_TO_FILE "LICENSE DEVICE"
        LOG_TO_FILE "TYPE: ${LIC_TYPE}"
        LOG_TO_FILE "MANUFACTURER: ${LIC_VENDOR}"
        LOG_TO_FILE "MODEL: ${LIC_MODEL_CLEAN}"
        LOG_TO_FILE ""
        LOG_TO_FILE "SYSTEM DEVICE (OS)"
        LOG_TO_FILE "TYPE: ${SYS_BOOT_TYPE}"
        LOG_TO_FILE "MANUFACTURER: ${SYS_VENDOR}"
        LOG_TO_FILE "MODEL: ${SYS_MODEL_CLEAN}"

    # CLI Output (Title Case for UI Terms: USB Stick, TPM, Internal Boot, Backup File, etc.)
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        echo "BOOT MEDIUM TYPE: ${BOOT_MEDIUM_TYPE}"
        echo ""
        echo "LICENSE DEVICE"
        echo "TYPE: ${LIC_TYPE}"
        echo "MANUFACTURER: ${LIC_VENDOR}"
        echo "MODEL: ${LIC_MODEL_CLEAN}"
        echo ""
        echo "SYSTEM DEVICE (OS)"
        echo "TYPE: ${SYS_BOOT_TYPE}"
        echo "MANUFACTURER: ${SYS_VENDOR}"
        echo "MODEL: ${SYS_MODEL_CLEAN}"
    fi

    # Check Boot Medium Status
    if [[ "$BOOT_MEDIUM_TYPE" == "Unknown" ]]; then
        LOG_TO_FILE "${MSG_WARNING} Boot Medium could not be identified"
        OVERALL_STATUS="WARNING"
    fi

    LOG_TO_FILE "---[${MOD_TITLE_2} COMPLETED]---"
    LOG_TO_FILE ""
else
    LOG_TO_FILE "---[MODULE 2: ${MOD_TITLE_2}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
fi

# --- MODULE 3: HARDWARE CHECK -----------------------------------------------
if [ "$ENABLE_HARDWARE_CHECK" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 3: ${MOD_TITLE_3}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_3}"
    fi

    # TPM Device Check
    if [ -c /dev/tpm0 ] || [ -c /dev/tpmrm0 ]; then
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "SUCCESS" "TPM Hardware detected."
        fi
        LOG_TO_FILE "${MSG_SUCCESS} TPM Hardware detected."
    else
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "WARNING" "${MSG_NO_TPM_DEVICE}"
        fi
        LOG_TO_FILE "${MSG_WARNING} ${MSG_NO_TPM_DEVICE}"
    fi

    # USB Stick Check (Only Relevant for USB Flash License)
    if [[ "$LICENSE_STATUS" == "USB_FLASH" ]]; then
        LSUSB_OUTPUT=$(lsusb 2>&1) || true
        if [[ $(echo "$LSUSB_OUTPUT" | grep -ciE "$USB_BRAND_PATTERN") -eq 0 ]]; then
            if echo "$LSUSB_OUTPUT" | grep -qiE "error|command not found"; then
                LOG_TO_FILE "${MSG_WARNING} lsusb reported issues during notification: $LSUSB_OUTPUT"
            fi
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "ERROR" "${MSG_NO_USB_STICK}"
            fi
            LOG_TO_FILE "${MSG_ERROR} ${MSG_NO_USB_STICK}"
        else
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "INFO" "${MSG_USB_STICK_DETECTED}"
            fi
            LOG_TO_FILE "${MSG_INFO} ${MSG_USB_STICK_DETECTED}"
        fi
    else
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "${MSG_TPM_MODE}"
        fi
        LOG_TO_FILE "${MSG_INFO} ${MSG_TPM_MODE}"
    fi

    # Missing USB Device for Flash License
    LSUSB_FINAL=$(lsusb 2>&1) || true
    if [[ "$LICENSE_STATUS" == "USB_FLASH" ]] && [[ $(echo "$LSUSB_FINAL" | grep -ciE "$USB_BRAND_PATTERN") -eq 0 ]]; then
        if echo "$LSUSB_FINAL" | grep -qiE "error|command not found"; then
            LOG_TO_FILE "${MSG_WARNING} lsusb reported issues during final USB check: $LSUSB_FINAL"
        fi
        OVERALL_STATUS="ERROR"
    fi

    LOG_TO_FILE "---[${MOD_TITLE_3} COMPLETED]---"
    LOG_TO_FILE ""
else
    LOG_TO_FILE "---[MODULE 3: ${MOD_TITLE_3}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
fi

# --- MODULE 4: SYSTEM INTEGRITY CHECK ---------------------------------------
if [ "$ENABLE_SYSTEM_CHECK" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 4: ${MOD_TITLE_4}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_4}"
    fi

    # Show Dirty Check Status
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        echo "DIRTY CHECK OVERRIDE:       ${SKIP_DIRTY_CHECK}"
        echo "AUTO CLEANUP:               ${ENABLE_AUTO_CLEANUP}"
        echo "----------------------------------------"
    fi
    LOG_TO_FILE "${MSG_INFO} SKIP_DIRTY_CHECK: ${SKIP_DIRTY_CHECK}"
    LOG_TO_FILE "${MSG_INFO} AUTO CLEANUP: ${ENABLE_AUTO_CLEANUP}"

    # CHECK 1: Array Disk Count
    DISK_COUNT=$(ls -d /mnt/disk* 2>/dev/null | wc -l)
    if [ "$DISK_COUNT" -lt "$MIN_DISKS" ]; then
        LOG_TO_FILE "${MSG_ERROR} Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."

        SEND_ERROR_NOTIFICATION "" "" "Process aborted: Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "ERROR" "Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."
        fi
        exit 1
    fi

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "SUCCESS" "${MSG_ARRAY_INTEGRITY_CONFIRMED} (${DISK_COUNT} Disks present)"
    fi
    LOG_TO_FILE "${MSG_SUCCESS} ${MSG_ARRAY_INTEGRITY_CONFIRMED} (${DISK_COUNT} Disks present)"

    # CHECK 2: Stale Temporary Files
    HAS_STALE_FILES=FALSE
    declare -a STALE_FILES=()

    # Find Stale Files in WebGUI Temp Directory
    FIND_OUTPUT=$(find /usr/local/emhttp/ -maxdepth 1 -name "*boot-backup*.zip" \( -type f -o -type l \) 2>&1)
    FIND_EXIT=$?

    # Log non-zero exit if not just expected permission denied
    if [ $FIND_EXIT -ne 0 ] || echo "$FIND_OUTPUT" | grep -qiE "permission denied|no such file|error"; then
        LOG_TO_FILE "${MSG_WARNING} find in /usr/local/emhttp reported issues:"
        LOG_TO_FILE "[DEBUG] $FIND_OUTPUT"

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "WARNING" "find encountered issues: $FIND_OUTPUT"
        fi
    fi

    while IFS= read -r F; do
        [[ -n "$F" ]] && STALE_FILES+=("WEBGUI|$F")
    done <<< "$FIND_OUTPUT"

    # Find Stale Files in RootFS Directory
    FIND_OUTPUT=$(find / -maxdepth 1 -name "*boot-backup*.zip" -type f 2>&1)
    FIND_EXIT=$?

    if [ $FIND_EXIT -ne 0 ] || echo "$FIND_OUTPUT" | grep -qiE "permission denied|no such file|error"; then
        LOG_TO_FILE "${MSG_WARNING} find in / reported issues:"
        LOG_TO_FILE "[DEBUG] $FIND_OUTPUT"

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "WARNING" "find encountered issues: $FIND_OUTPUT"
        fi
    fi

    while IFS= read -r F; do
        [[ -n "$F" ]] && STALE_FILES+=("ROOTFS|$F")
    done <<< "$FIND_OUTPUT"

    STALE_COUNT=${#STALE_FILES[@]}

    if [ "$STALE_COUNT" -gt 0 ]; then
        HAS_STALE_FILES=TRUE
        LOG_TO_FILE "${MSG_WARNING} ${STALE_COUNT} ${MSG_STALE_FILES_FOUND}"

        # Abort if SKIP_DIRTY_CHECK not set
        if [ -z "$SKIP_DIRTY_CHECK" ] || [ "$SKIP_DIRTY_CHECK" = "FALSE" ]; then
            OVERALL_STATUS="ERROR"
            LOG_TO_FILE "${MSG_ERROR} ${MSG_PROCESS_ABORTED}"
            LOG_TO_FILE "${MSG_INFO} SKIP_DIRTY_CHECK is DISABLED - Aborting"

            SEND_ERROR_NOTIFICATION "" "" "Process aborted: ${STALE_COUNT} stale Temporary Files found. Manual cleanup required."

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "ERROR" "${STALE_COUNT} ${MSG_STALE_FILES_FOUND} Manual cleanup required."
                echo "────────────────────────────────────────────────────────────"
                for ENTRY in "${STALE_FILES[@]}"; do
                    SOURCE_TYPE="${ENTRY%%|*}"
                    FILEPATH="${ENTRY#*|}"

                    case "$SOURCE_TYPE" in
                        WEBGUI) PREFIX="[WebGUI Temp] " ;;
                        ROOTFS) PREFIX="[RootFS Temp] " ;;
                    esac

                    if [[ -f "$FILEPATH" ]]; then
                        # Safe stat with error handling
                        STAT_OUTPUT=$(stat -c '%s %y' "$FILEPATH" 2>&1)
                        if [ $? -ne 0 ]; then
                            LOG_TO_FILE "${MSG_WARNING} Cannot stat File for display: $FILEPATH"
                            SIZE_DISPLAY="Unknown"
                            MTIME="Unknown"
                        else
                            SIZE_BYTES=$(echo "$STAT_OUTPUT" | awk '{print $1}')
                            MTIME=$(echo "$STAT_OUTPUT" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i}' | cut -d'.' -f1 | xargs)

                            if [ -n "$SIZE_BYTES" ] && [ "$SIZE_BYTES" -gt 1073741824 ]; then
                                SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024/1024}")" GB"
                            elif [ -n "$SIZE_BYTES" ]; then
                                SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024}")" MB"
                            else
                                SIZE_DISPLAY="Unknown"
                            fi
                        fi

                        echo "  ${PREFIX}${FILEPATH}"
                        echo "    Size: ${SIZE_DISPLAY} | Modified: ${MTIME}"
                    elif [[ -L "$FILEPATH" ]]; then
                        echo "  ${PREFIX}SYMLINK → $(readlink "$FILEPATH" 2>/dev/null)"
                    else
                        echo "  ${PREFIX}${FILEPATH} (Missing/Corrupted)"
                    fi
                done
                echo "────────────────────────────────────────────────────────────"
                echo "NOTE: Temporary Files will be cleaned on Reboot."
                echo "→ Manual cleanup required."
                echo "→ Set SKIP_DIRTY_CHECK=TRUE to ignore."
                echo "→ Set ENABLE_AUTO_CLEANUP=TRUE for automatic removal after all Modules complete."
            fi

            exit 1
        else
            HAS_STALE_FILES=TRUE
            SKIP_DIRTY_CHECK_ACTIVE=TRUE
            OVERALL_STATUS="WARNING"
            LOG_TO_FILE "${MSG_WARNING} ${MSG_WARNING_IGNORED} (SKIP_DIRTY_CHECK=TRUE)"
            LOG_TO_FILE "${MSG_WARNING} Dirty Check Bypassed - Continuing anyway."

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "WARNING" "${STALE_COUNT} ${MSG_STALE_FILES_FOUND}"
                PRINT_MSG "WARNING" "${MSG_WARNING_IGNORED} (SKIP_DIRTY_CHECK=TRUE)"
                PRINT_MSG "WARNING" "Dirty Check Bypassed - Continuing anyway."
                echo "────────────────────────────────────────────────────────────"
                for ENTRY in "${STALE_FILES[@]}"; do
                    SOURCE_TYPE="${ENTRY%%|*}"
                    FILEPATH="${ENTRY#*|}"

                    case "$SOURCE_TYPE" in
                        WEBGUI) PREFIX="[WebGUI Temp] " ;;
                        ROOTFS) PREFIX="[RootFS Temp] " ;;
                    esac

                    if [[ -f "$FILEPATH" ]]; then
                        # Safe stat with error handling
                        STAT_OUTPUT=$(stat -c '%s %y' "$FILEPATH" 2>&1)
                        STAT_EXIT=$?

                        if [ $STAT_EXIT -ne 0 ]; then
                            LOG_TO_FILE "${MSG_WARNING} Cannot stat file for display: $FILEPATH"
                            SIZE_DISPLAY="Unknown"
                            MTIME="Unknown"
                        else
                            SIZE_BYTES=$(echo "$STAT_OUTPUT" | awk '{print $1}')
                            MTIME=$(echo "$STAT_OUTPUT" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i}' | cut -d'.' -f1 | xargs)

                            if [ -n "$SIZE_BYTES" ] && [ "$SIZE_BYTES" -gt 1073741824 ]; then
                                SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024/1024}")" GB"
                            elif [ -n "$SIZE_BYTES" ]; then
                                SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024}")" MB"
                            else
                                SIZE_DISPLAY="Unknown"
                            fi
                        fi

                        echo "  ${PREFIX}${FILEPATH}"
                        echo "    Size: ${SIZE_DISPLAY} | Modified: ${MTIME}"
                    elif [[ -L "$FILEPATH" ]]; then
                        LINK_TARGET=$(readlink "$FILEPATH" 2>&1)
                        if [ $? -ne 0 ]; then
                            LOG_TO_FILE "${MSG_WARNING} Cannot resolve Symlink Target: $FILEPATH"
                            echo "  ${PREFIX}SYMLINK → [Unable to resolve]"
                        else
                            echo "  ${PREFIX}SYMLINK → $LINK_TARGET"
                        fi
                    else
                        LOG_TO_FILE "${MSG_WARNING} File disappeared during listing: $FILEPATH"
                        echo "  ${PREFIX}${FILEPATH} (Missing/Disappeared)"
                    fi
                done
                echo "────────────────────────────────────────────────────────────"
                echo "Session Log contains full Details and Error tracking: $LOG_FILE_PATH"
                echo ""
                echo "AUTO CLEANUP STATUS: ${ENABLE_AUTO_CLEANUP}"
                if [ "$ENABLE_AUTO_CLEANUP" = "TRUE" ]; then
                    echo "→ Will run at end of Script through ${MOD_TITLE_8}."
                    echo "→ Files removed after Backup & Retention completes."
                else
                    echo "→ Currently DISABLED."
                    echo "→ Set ENABLE_AUTO_CLEANUP=TRUE for automatic removal."
                    echo ""
                    echo "NOTE: ${STALE_COUNT} Stale Files remain on system."
                fi
                echo ""
                PRINT_MSG "WARNING" "Skipping cleanup and continuing despite Stale Files."
            fi
        fi
    else
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "SUCCESS" "${MSG_NO_STALE_FILES}"
        fi
        LOG_TO_FILE "${MSG_SUCCESS} ${MSG_NO_STALE_FILES}"
    fi

    LOG_TO_FILE "---[${MOD_TITLE_4} COMPLETED]---"
    LOG_TO_FILE ""
else
    LOG_TO_FILE "---[MODULE 4: ${MOD_TITLE_4}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
fi

# --- MODULE 5: CREATE BACKUP ------------------------------------------------
BACKUP_FILE=""
if [ "$ENABLE_BACKUP_CREATE" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 5: ${MOD_TITLE_5}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_5}"
    fi

    LOG_TO_FILE "Target Directory: ${BACKUP_BASE_DIR}"
    LOG_TO_FILE "Starting Native Flash Backup Script..."

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
    echo "Target Directory: ${BACKUP_BASE_DIR}"
    echo "Starting Native Flash Backup Script..."
    fi

    # Check 1: unzip Utility available
    if ! command -v unzip >/dev/null 2>&1; then
        HANDLE_ERROR_EXIT "'unzip' Command not found. Integrity Check impossible." \
                          "'unzip' Command not found. Integrity Check impossible."
    fi

    # Execute Native Flash Backup PHP Script
    BACKUP_RAW_OUTPUT=$(/usr/bin/php -q /usr/local/emhttp/webGui/scripts/flash_backup 2>&1)
    PHP_EXIT_CODE=$?

    if [ $PHP_EXIT_CODE -ne 0 ]; then
        LOG_TO_FILE "[DEBUG] PHP Output: ${BACKUP_RAW_OUTPUT}"
        HANDLE_ERROR_EXIT "Flash Backup Tool failed with Exit Code ${PHP_EXIT_CODE}." \
                          "Flash Backup Tool failed with Exit Code ${PHP_EXIT_CODE}."
    fi

    BACKUP_FILE=$(echo "$BACKUP_RAW_OUTPUT" | tail -n 1 | xargs)

    if [ -z "$BACKUP_FILE" ]; then
        LOG_TO_FILE "[DEBUG] PHP Output: ${BACKUP_RAW_OUTPUT}"
        HANDLE_ERROR_EXIT "Invalid Filename generated (Empty Output)." \
                          "Invalid Filename generated (Empty Output)."
    fi

    SYMLINK_PATH="/usr/local/emhttp/${BACKUP_FILE}"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "SUCCESS" "PHP flash_backup Script executed and Symlink at ${SYMLINK_PATH} created."
    fi
    LOG_TO_FILE "${MSG_SUCCESS} PHP flash_backup Script executed and Symlink at ${SYMLINK_PATH} created."

    # Check 2: Symlink exists
    if [ ! -f "$SYMLINK_PATH" ] && [ ! -L "$SYMLINK_PATH" ]; then
        HANDLE_ERROR_EXIT "Source File does not exist: ${SYMLINK_PATH}." \
                          "Source File does not exist: ${SYMLINK_PATH}."
    fi

    # Resolve Real Path
    READLINK_OUTPUT=$(readlink -f "$SYMLINK_PATH" 2>&1) || true
    if [[ -z "$READLINK_OUTPUT" ]] || [[ "$READLINK_OUTPUT" == *"error"* ]] || [[ "$READLINK_OUTPUT" == *"cannot"* ]]; then
        LOG_TO_FILE "${MSG_WARNING} readlink failed, falling back to symlink path: $READLINK_OUTPUT"
        SOURCE_FILE="$SYMLINK_PATH"
    else
        SOURCE_FILE="$READLINK_OUTPUT"
    fi
    DEST_FILE="${BACKUP_BASE_DIR}/${BACKUP_FILE}"

    # Check 3: Resolved Source File exists
    if [ ! -f "$SOURCE_FILE" ]; then
        HANDLE_ERROR_EXIT "Resolved Source File missing: ${SOURCE_FILE}." \
                          "Resolved Source File missing: ${SOURCE_FILE}."
    fi

    # Copy Backup File
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "INFO" "Copying Backup File to: ${DEST_FILE}"
    fi

    # Capture ALL Output including Errors
    CP_OUTPUT=$(cp "$SOURCE_FILE" "$DEST_FILE" 2>&1)
    COPY_EXIT=$?

    if [ $COPY_EXIT -ne 0 ]; then
        LOG_TO_FILE "${MSG_ERROR} Copy Operation failed with Exit Code ${COPY_EXIT}."
        LOG_TO_FILE "[DEBUG] cp Output: $CP_OUTPUT"

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "ERROR" "${MSG_COPY_FAILED}"
            echo "→ See Session Log for Details: $LOG_FILE_PATH"
        fi

        HANDLE_ERROR_EXIT "${MSG_COPY_FAILED}" \
                          "Copy Error Details logged in Session Log."
    fi

    LOG_TO_FILE "Copied Backup File to: ${DEST_FILE}"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "SUCCESS" "Copy completed successfully."
    fi

    # Integrity Check
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "INFO" "Checking Integrity of File: ${DEST_FILE}"
    fi

    # Capture ALL Output including Errors
    UNZIP_OUTPUT=$(unzip -t -q "$DEST_FILE" 2>&1)
    UNZIP_EXIT=$?

    if [ $UNZIP_EXIT -ne 0 ]; then
        LOG_TO_FILE "${MSG_ERROR} Integrity Check failed with Exit Code ${UNZIP_EXIT}."
        LOG_TO_FILE "[DEBUG] unzip Output: $UNZIP_OUTPUT"
        LOG_TO_FILE "${MSG_INFO} Removing corrupted Backup File: $(basename "$DEST_FILE")"
        rm -f "$DEST_FILE"

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "ERROR" "${MSG_BACKUP_CORRUPTED}: $(basename "${DEST_FILE}")."
            echo "→ Corruption Details in Session Log: $LOG_FILE_PATH"
        fi

        HANDLE_ERROR_EXIT "${MSG_BACKUP_CORRUPTED}: $(basename "${DEST_FILE}")." \
                          "Corruption Details logged in Session Log."
    fi

    LOG_TO_FILE "${MSG_SUCCESS} Integrity Check passed."
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "SUCCESS" "Integrity Check passed."
    fi

    # Cleanup Temporary Files
    if [ -f "$SOURCE_FILE" ]; then
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "Temporary Backup File Location: ${SOURCE_FILE}"
        fi

        rm -f "$SOURCE_FILE"
        LOG_TO_FILE "${MSG_INFO} Temporary Backup File Location: ${SOURCE_FILE}"
        LOG_TO_FILE "${MSG_SUCCESS} Temporary Backup File removed: $(basename "$SOURCE_FILE")"
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "SUCCESS" "Temporary Backup File removed: $(basename "$SOURCE_FILE")"
        fi
    fi

    if [ -L "$SYMLINK_PATH" ]; then
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "Temporary Symlink Location: ${SYMLINK_PATH}"
        fi

        rm -f "$SYMLINK_PATH"
        LOG_TO_FILE "${MSG_INFO} Temporary Symlink Location: ${SYMLINK_PATH}"
        LOG_TO_FILE "${MSG_SUCCESS} Temporary Symlink removed."
        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "SUCCESS" "Temporary Symlink removed."
        fi
    fi

    LOG_TO_FILE "---[BACKUP CREATION COMPLETED]---"
    LOG_TO_FILE ""

    # Update Last Run Log
    LOG_TO_FILE "---[UPDATE ARCHIVE LOG]---"
    {
        echo "=== LAST RUN: ${HOST_NAME} ==="
        echo "TIMESTAMP: $(date '+%Y-%m-%d %H:%M:%S')"
        echo "BACKUP FILE: ${BACKUP_FILE}"
        echo "BACKUP PATH: ${DEST_FILE}"
        echo "ARCHIVE LOG PATH: ${ARCHIVE_LOG_PATH}"
        echo "STATUS: SUCCESSFUL"
        [ -n "$LOCAL_LICENSE_TYPE" ] && echo "License Type: ${LOCAL_LICENSE_TYPE}"
        [ "${HAS_STALE_FILES:-FALSE}" = "TRUE" ] && echo "[WARNING] Stale Files present"
        echo "==============================="
    } > "$LAST_RUN_LOG"

    LOG_TO_FILE "Status Log saved: ${LAST_RUN_LOG}"
    LOG_TO_FILE "Session Log archived: ${ARCHIVE_LOG_PATH}"
    LOG_TO_FILE "---[UPDATE ARCHIVE LOG COMPLETED]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "UPDATE ARCHIVE LOG"
        echo "Status Log saved: ${LAST_RUN_LOG}"
        echo "Session Log archived: ${ARCHIVE_LOG_PATH}"
    fi
else
    LOG_TO_FILE "---[MODULE 5: ${MOD_TITLE_5}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_5} SKIPPED"
    fi
fi

# --- MODULE 6: APPLY BACKUP RETENTION ---------------------------------------
if [ "$ENABLE_BACKUP_RETENTION" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 6: ${MOD_TITLE_6}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_6}"
    fi

    cd "$BACKUP_BASE_DIR" || {
        HANDLE_ERROR_EXIT "${MSG_CHANGE_DIR_FAILED}" \
                          "Change to Backup Directory failed."
    }

    # Delete Backups older than Retention Period
    FIND_AGE_OUTPUT=$(find . -maxdepth 1 -name "*boot-backup*.zip" -type f -mtime +"${BACKUP_RETENTION_DAYS}" -print 2>&1)
    FIND_AGE_EXIT=$?
    DELETED_BY_AGE=$(echo "$FIND_AGE_OUTPUT" | grep -c "\.zip$" || true)
    if [ "$FIND_AGE_EXIT" -ne 0 ]; then
        LOG_TO_FILE "${MSG_WARNING} find for age-based deletion reported issues: $FIND_AGE_OUTPUT"
    fi
    if [ "$DELETED_BY_AGE" -gt 0 ]; then
        LOG_TO_FILE "${MSG_INFO} ${DELETED_BY_AGE} Backup Files older than ${BACKUP_RETENTION_DAYS} Days removed."
        find . -maxdepth 1 -name "*boot-backup*.zip" -type f -mtime +"${BACKUP_RETENTION_DAYS}" -delete

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "${DELETED_BY_AGE} Backup Files older than ${BACKUP_RETENTION_DAYS} Days removed."
        fi
    fi

    # Enforce Maximum Backup Count Limit
    if [[ -n "$MAX_BACKUPS_TO_KEEP" ]] && [[ "$MAX_BACKUPS_TO_KEEP" -gt 0 ]]; then
        LS_OUTPUT=$(ls -1 *boot-backup*.zip 2>&1) || true
        CURRENT_COUNT=$(echo "$LS_OUTPUT" | grep -c "\.zip$" || true)
        if echo "$LS_OUTPUT" | grep -qiE "error|cannot|no such"; then
            LOG_TO_FILE "${MSG_WARNING} ls reported issues: $LS_OUTPUT"
        fi

        if [ "$CURRENT_COUNT" -gt "$MAX_BACKUPS_TO_KEEP" ]; then
            TO_DELETE=$((CURRENT_COUNT - MAX_BACKUPS_TO_KEEP))
            LOG_TO_FILE "${MSG_INFO} Current Backup Files: ${CURRENT_COUNT} | Max: ${MAX_BACKUPS_TO_KEEP} → Deleting ${TO_DELETE} Files."

            mapfile -t BACKUP_FILES_TO_DELETE < <(ls -t *boot-backup*.zip 2>&1 | tail -n +$((MAX_BACKUPS_TO_KEEP + 1)))

            for OLD_BACKUP in "${BACKUP_FILES_TO_DELETE[@]}"; do
                if [ -n "$OLD_BACKUP" ]; then
                    rm -f "$OLD_BACKUP"
                    LOG_TO_FILE "${MSG_INFO} Deleted: $(basename "$OLD_BACKUP")"
                fi
            done

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "INFO" "Deleted ${TO_DELETE} old Backup Files to stay within limit of ${MAX_BACKUPS_TO_KEEP}."
                for OLD_BACKUP in "${BACKUP_FILES_TO_DELETE[@]}"; do
                    if [ -n "$OLD_BACKUP" ]; then
                        PRINT_MSG "INFO" "Deleted: $(basename "$OLD_BACKUP")"
                    fi
                done
            fi
        else
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "INFO" "Backup File Count (${CURRENT_COUNT}) is within limit (${MAX_BACKUPS_TO_KEEP})."
            fi
        fi
    fi

    FINAL_LS_OUTPUT=$(ls -1 *boot-backup*.zip 2>&1) || true
    FINAL_COUNT=$(echo "$FINAL_LS_OUTPUT" | grep -c "\.zip$" || true)
    LOG_TO_FILE "${MSG_INFO} Result: ${FINAL_COUNT} Flash or Boot Backup Files remaining."
    LOG_TO_FILE "---[${MOD_TITLE_6} COMPLETED]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "INFO" "Total Backup Files remaining: ${FINAL_COUNT}"
    fi
else
    LOG_TO_FILE "---[MODULE 6: ${MOD_TITLE_6}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_6} SKIPPED"
    fi
fi

# --- MODULE 7: APPLY LOG RETENTION ------------------------------------------
if [ "$ENABLE_LOG_RETENTION" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 7: ${MOD_TITLE_7}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_7}"
    fi

    cd "$ARCHIVE_LOG_DIR" || {
        HANDLE_ERROR_EXIT "${MSG_CHANGE_DIR_FAILED}" \
                          "Change to Archive Log Directory failed."
    }

    # Delete Logs older than Retention Period (Exclude Last_Run.log)
    FIND_LOG_AGE_OUTPUT=$(find . -maxdepth 1 -name "*.log" -type f ! -name "Last_Run.log" -mtime +"${LOG_RETENTION_DAYS}" -print 2>&1)
    FIND_LOG_AGE_EXIT=$?
    DELETED_LOGS_BY_AGE=$(echo "$FIND_LOG_AGE_OUTPUT" | grep -c "\.log$" || true)
    if [ "$FIND_LOG_AGE_EXIT" -ne 0 ]; then
        LOG_TO_FILE "${MSG_WARNING} find for log age-based deletion reported issues: $FIND_LOG_AGE_OUTPUT"
    fi
    if [ "$DELETED_LOGS_BY_AGE" -gt 0 ]; then
        LOG_TO_FILE "${MSG_INFO} ${DELETED_LOGS_BY_AGE} Log Files older than ${LOG_RETENTION_DAYS} Days removed."
        find . -maxdepth 1 -name "*.log" -type f ! -name "Last_Run.log" -mtime +"${LOG_RETENTION_DAYS}" -delete

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "${DELETED_LOGS_BY_AGE} Log Files older than ${LOG_RETENTION_DAYS} Days removed."
        fi
    fi

    # Enforce Maximum Log File Count
    if [[ -n "$MAX_LOG_FILES_TO_KEEP" ]] && [[ "$MAX_LOG_FILES_TO_KEEP" -gt 0 ]]; then
        FIND_COUNT_OUTPUT=$(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>&1) || true
        LOG_FILE_COUNT=$(echo "$FIND_COUNT_OUTPUT" | grep -c "\.log$" || true)
        if echo "$FIND_COUNT_OUTPUT" | grep -qiE "error|cannot|permission"; then
            LOG_TO_FILE "${MSG_WARNING} find for Log count reported issues: $FIND_COUNT_OUTPUT"
        fi

        if [ "$LOG_FILE_COUNT" -gt "$MAX_LOG_FILES_TO_KEEP" ]; then
            TO_DELETE_COUNT=$((LOG_FILE_COUNT - MAX_LOG_FILES_TO_KEEP))
            LOG_TO_FILE "${MSG_INFO} Current Log Files: ${LOG_FILE_COUNT} | Max: ${MAX_LOG_FILES_TO_KEEP} → Deleting ${TO_DELETE_COUNT} Files."

            # Timestamp based sorting for accurate oldest first deletion
            mapfile -t FILES_TO_DELETE < <(
                for F in $(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>&1); do
                    TS=$(stat -c '%Y' "$F" 2>&1)
                    [[ -z "$TS" ]] && TS=0
                    printf '%s %s\n' "$TS" "$F"
                done | sort -n | head -n "$TO_DELETE_COUNT" | awk '{print $2}'
            )

            for OLD_LOG in "${FILES_TO_DELETE[@]}"; do
                if [ -n "$OLD_LOG" ]; then
                    rm -f "$OLD_LOG"
                    LOG_TO_FILE "${MSG_INFO} Deleted: $(basename "$OLD_LOG")"
                fi
            done

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "INFO" "Deleted ${TO_DELETE_COUNT} old Log Files to stay within limit of ${MAX_LOG_FILES_TO_KEEP}."
                for OLD_LOG in "${FILES_TO_DELETE[@]}"; do
                    if [ -n "$OLD_LOG" ]; then
                        PRINT_MSG "INFO" "Deleted: $(basename "$OLD_LOG")"
                    fi
                done
            fi
        else
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "INFO" "Log File Count (${LOG_FILE_COUNT}) is within limit (${MAX_LOG_FILES_TO_KEEP})."
            fi
        fi
    fi

    FINAL_FIND_OUTPUT=$(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>&1) || true
    FINAL_LOG_COUNT=$(echo "$FINAL_FIND_OUTPUT" | grep -c "\.log$" || true)

    if echo "$FINAL_FIND_OUTPUT" | grep -qiE "error|cannot|permission"; then
        LOG_TO_FILE "${MSG_WARNING} find for final Log count reported issues: $FINAL_FIND_OUTPUT"

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "WARNING" "Log File search encountered issues: $FINAL_FIND_OUTPUT"
        fi
    fi
    LOG_TO_FILE "${MSG_INFO} Result: ${FINAL_LOG_COUNT} Log Files remaining (Excluding Last_Run.log)."
    LOG_TO_FILE "---[${MOD_TITLE_7} COMPLETED]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "INFO" "Total Log Files remaining: ${FINAL_LOG_COUNT}"
    fi
else
    LOG_TO_FILE "---[MODULE 7: ${MOD_TITLE_7}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_7} SKIPPED"
    fi
fi

# --- MODULE 8: AUTO CLEANUP -----------------------------------------------
if [ "$ENABLE_AUTO_CLEANUP" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 8: ${MOD_TITLE_8}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_8}"
        echo "AUTO CLEANUP STATUS: ENABLED"
        echo "STALE FILES FROM ${MOD_TITLE_4}: ${HAS_STALE_FILES:-N/A}"
        echo "----------------------------------------"
    fi

    # Proceed only if Stale Files were detected (from Module 4) OR new ones exist
    declare -a ALL_STALE_FILES=()

    # WebGUI Temp Directory
    CLEANUP_FIND_WEBGUI=$(find /usr/local/emhttp/ -maxdepth 1 -name "*boot-backup*.zip" \( -type f -o -type l \) 2>&1)
    if echo "$CLEANUP_FIND_WEBGUI" | grep -qiE "error|cannot|permission"; then
        LOG_TO_FILE "${MSG_WARNING} find in /usr/local/emhttp during cleanup reported issues: $CLEANUP_FIND_WEBGUI"
    fi
    while IFS= read -r F; do
        [[ -n "$F" ]] && ALL_STALE_FILES+=("WEBGUI|$F")
    done <<< "$CLEANUP_FIND_WEBGUI"

    # RootFS Directory
    CLEANUP_FIND_ROOTFS=$(find / -maxdepth 1 -name "*boot-backup*.zip" -type f 2>&1)
    if echo "$CLEANUP_FIND_ROOTFS" | grep -qiE "error|cannot|permission"; then
        LOG_TO_FILE "${MSG_WARNING} find in / during cleanup reported issues: $CLEANUP_FIND_ROOTFS"
    fi
    while IFS= read -r F; do
        [[ -n "$F" ]] && ALL_STALE_FILES+=("ROOTFS|$F")
    done <<< "$CLEANUP_FIND_ROOTFS"

    CLEANUP_TOTAL=${#ALL_STALE_FILES[@]}
    CLEANUP_SUCCESS=0
    CLEANUP_FAILED=0

    # No Stale Files — Skip Cleanup
    if [ $CLEANUP_TOTAL -eq 0 ]; then
        LOG_TO_FILE "${MSG_INFO} No Stale Files detected. Cleanup not required."

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "No Stale Files detected. Cleanup not required."
        fi

        CLEANUP_EXECUTED=FALSE
        LOG_TO_FILE "---[${MOD_TITLE_8} SKIPPED - NO STALE FILES]---"
        LOG_TO_FILE ""

        # Continue to next Module (Notification)
    else
        # Stale Files exist — proceed with cleanup
        CLEANUP_EXECUTED=TRUE
        LOG_TO_FILE "${MSG_INFO} ${MSG_CLEANUP_STARTED}"
        LOG_TO_FILE "${MSG_INFO} Total files to process: ${CLEANUP_TOTAL}"

        if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
            PRINT_MSG "INFO" "${MSG_CLEANUP_STARTED}"
            echo "Total Stale Files to process: ${CLEANUP_TOTAL}"
        fi

        # Attempt Removal
        DELETED_FILES=()
        FAILED_FILES=()

        for ENTRY in "${ALL_STALE_FILES[@]}"; do
            SOURCE_TYPE="${ENTRY%%|*}"
            FILEPATH="${ENTRY#*|}"

            if [[ -f "$FILEPATH" ]] || [[ -L "$FILEPATH" ]]; then
                # Capture rm Output for logging
                RM_OUTPUT=$(rm -f "$FILEPATH" 2>&1)
                DELETE_EXIT=$?

                if [ $DELETE_EXIT -eq 0 ]; then
                    ((CLEANUP_SUCCESS++))

                    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                        case "$SOURCE_TYPE" in
                            WEBGUI) echo "  [SUCCESS] Removed: [WebGUI Temp] $(basename "$FILEPATH")" ;;
                            ROOTFS) echo "  [SUCCESS] Removed: [RootFS Temp] $(basename "$FILEPATH")" ;;
                        esac
                    fi

                    LOG_TO_FILE "${MSG_SUCCESS} Removed: ${SOURCE_TYPE}|${FILEPATH}"
                    DELETED_FILES+=("${FILEPATH}")
                else
                    ((CLEANUP_FAILED++))
                    FAILED_FILES+=("${SOURCE_TYPE}|${FILEPATH}")
                    LOG_TO_FILE "${MSG_ERROR} Failed to remove: ${SOURCE_TYPE}|${FILEPATH}"
                    if [ -n "$RM_OUTPUT" ]; then
                        LOG_TO_FILE "[DEBUG] rm output: $RM_OUTPUT"
                    fi
                fi
            else
                # File already gone or inaccessible
                ((CLEANUP_FAILED++))
                FAILED_FILES+=("${SOURCE_TYPE}|${FILEPATH}")
                LOG_TO_FILE "${MSG_WARNING} File inaccessible (already removed?): ${SOURCE_TYPE}|${FILEPATH}"
            fi
        done

        # Final Status determination
        if [ $CLEANUP_SUCCESS -gt 0 ] && [ $CLEANUP_FAILED -eq 0 ]; then
            # Perfect cleanup
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "SUCCESS" "All ${CLEANUP_SUCCESS} Stale Files removed successfully."
            fi

            LOG_TO_FILE "${MSG_SUCCESS} All ${CLEANUP_SUCCESS} Files removed successfully."

        elif [ $CLEANUP_SUCCESS -gt 0 ] && [ $CLEANUP_FAILED -gt 0 ]; then
            # Partial success — WARNING
            if [ "$OVERALL_STATUS" != "ERROR" ]; then
                OVERALL_STATUS="WARNING"
            fi

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "WARNING" "${CLEANUP_SUCCESS} removed, ${CLEANUP_FAILED} failed. Partial cleanup completed."
            fi

            LOG_TO_FILE "${MSG_WARNING} Partial cleanup: ${CLEANUP_SUCCESS} removed, ${CLEANUP_FAILED} failed"

            # List failed Files for user reference
            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                echo "  Failed Files (manual cleanup required):"
                for FAIL_ENTRY in "${FAILED_FILES[@]}"; do
                    FILE_PATH="${FAIL_ENTRY#*|}"
                    echo "    ${FILE_PATH}"
                done
            fi

        else
            # Complete Failure
            if [ "$OVERALL_STATUS" != "ERROR" ]; then
                OVERALL_STATUS="ERROR"
            fi

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                PRINT_MSG "ERROR" "Cleanup failed. No Files removed. Manual intervention required."
            fi

            LOG_TO_FILE "${MSG_ERROR} Cleanup failed. No Files removed. Manual intervention required."
            LOG_TO_FILE "${MSG_ERROR} ${CLEANUP_TOTAL} Stale Files remain. Manual cleanup required."

            # Send Error Notification immediately for cleanup failure
            SEND_ERROR_NOTIFICATION "Flash Backup Auto cleanup failed" \
                                    "Flash Backup Auto cleanup" \
                                    "Failed to remove ${CLEANUP_TOTAL} Stale Temporary Files. Manual cleanup required. See Log: $LOG_FILE_PATH"
        fi

        LOG_TO_FILE "---[${MOD_TITLE_8} COMPLETED]---"
        LOG_TO_FILE "Stats: ${CLEANUP_TOTAL} total, ${CLEANUP_SUCCESS} succeeded, ${CLEANUP_FAILED} failed"
        LOG_TO_FILE ""

        # NOTE: HAS_STALE_FILES remains TRUE to preserve Module 4's Original detection State
    fi

else
    # Auto cleanup disabled
    LOG_TO_FILE "---[MODULE 8: ${MOD_TITLE_8}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
    LOG_TO_FILE "${MSG_INFO} ${MSG_CLEANUP_SKIPPED}"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_8}"
        echo "AUTO CLEANUP STATUS: DISABLED"
        PRINT_MSG "INFO" "${MSG_CLEANUP_SKIPPED}"
    fi

    CLEANUP_EXECUTED=FALSE
    CLEANUP_SUCCESS=0
    CLEANUP_FAILED=0
    CLEANUP_TOTAL=0
fi

# --- MODULE 9: SEND NOTIFICATION --------------------------------------------
if [ "$ENABLE_NOTIFICATION" = "TRUE" ]; then
    LOG_TO_FILE "---[MODULE 9: ${MOD_TITLE_9}]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_9}"
    fi

    # Initialize Notification Variables
    NOTIFY_TITLE="Flash or Boot Backup completed - ${HOST_NAME}"
    NOTIFY_MSG="Flash or Boot Backup '${BACKUP_FILE:-N/A}' successfully created."
    NOTIFY_PRIORITY="success"
    NOTIFY_SUBJECT="Flash or Boot Backup success"
    DETAIL_MSG=""

    # Condition 1: Invalid License Configuration
    if [ "$ENABLE_LICENSE_CHECK" = "TRUE" ] && [[ "$LICENSE_STATUS" == "INVALID_CONFIG" ]]; then
        NOTIFY_PRIORITY="error"
        NOTIFY_SUBJECT="Flash or Boot Backup - Error"
        DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[ERROR] License Configuration invalid"
    fi

    # Condition 2: Missing USB License Device
    if [ "$ENABLE_HARDWARE_CHECK" = "TRUE" ] && [[ "$LICENSE_STATUS" == "USB_FLASH" ]]; then
        LSUSB_OUTPUT=$(lsusb 2>&1) || true
        if [[ $(echo "$LSUSB_OUTPUT" | grep -ciE "$USB_BRAND_PATTERN") -eq 0 ]]; then
            if echo "$LSUSB_OUTPUT" | grep -qiE "error|command not found"; then
                LOG_TO_FILE "${MSG_WARNING} lsusb reported issues during notification: $LSUSB_OUTPUT"
            fi
            NOTIFY_PRIORITY="error"
            NOTIFY_SUBJECT="Flash or Boot Backup - Error"
            DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[ERROR] Required USB License Device not detected"
        fi
    fi

    # Condition 3: Stale Temporary Files (Warning if no Error)
    if [[ "${HAS_STALE_FILES:-FALSE}" == "TRUE" ]]; then
        if [[ "$NOTIFY_PRIORITY" != "error" ]]; then
            NOTIFY_PRIORITY="warning"
            NOTIFY_SUBJECT="Flash or Boot Backup - Warning"
        fi
        DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[WARNING] Stale Temporary Files present"
    fi

    # Condition 4: Unknown Boot Medium (Warning if Success)
    if [ "$ENABLE_BOOT_CHECK" = "TRUE" ] && [[ "$BOOT_MEDIUM_TYPE" == "Unknown" ]]; then
        if [[ "$NOTIFY_PRIORITY" == "success" ]]; then
            NOTIFY_PRIORITY="warning"
            NOTIFY_SUBJECT="Flash or Boot Backup - Warning"
        fi
        DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[WARNING] Boot Medium could not be identified"
    fi

    # Add License Information
    if [ "$ENABLE_LICENSE_CHECK" = "TRUE" ]; then
        case "$DEVICE_BINDING" in
            "USB Stick") DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }License: ${LICENSE_TYPE:-Unknown} (USB)" ;;
            "TPM")       DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }License: ${LICENSE_TYPE:-Unknown} (TPM)" ;;
        esac
    fi

    # Add Boot Medium Information
    if [ "$ENABLE_BOOT_CHECK" = "TRUE" ] && [[ "$BOOT_MEDIUM_TYPE" != "Unknown" ]]; then
        DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }Boot: ${BOOT_MEDIUM_TYPE}"
    fi

    # Calculate Backup File Size
    BACKUP_SIZE_DISPLAY="N/A"
    if [ -f "$DEST_FILE" ]; then
        STAT_SIZE_OUTPUT=$(stat -c '%s' "$DEST_FILE" 2>&1)
        if echo "$STAT_SIZE_OUTPUT" | grep -qiE "error|cannot|no such"; then
            LOG_TO_FILE "${MSG_WARNING} stat for backup size failed: $STAT_SIZE_OUTPUT"
            BACKUP_SIZE_BYTES=""
        else
            BACKUP_SIZE_BYTES="$STAT_SIZE_OUTPUT"
        fi
        if [[ -n "$BACKUP_SIZE_BYTES" ]]; then
            BACKUP_SIZE_MB=$((BACKUP_SIZE_BYTES / 1024 / 1024))
            BACKUP_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $BACKUP_SIZE_BYTES / 1024 / 1024 / 1024}")

            if [ "$BACKUP_SIZE_BYTES" -gt 1073741824 ]; then
                BACKUP_SIZE_DISPLAY="${BACKUP_SIZE_GB} GB"
            else
                BACKUP_SIZE_DISPLAY="${BACKUP_SIZE_MB} MB"
            fi

            if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
                echo "Backup File Size:           ${BACKUP_SIZE_DISPLAY}"
            fi
            LOG_TO_FILE "Backup File Size: ${BACKUP_SIZE_DISPLAY} / ${BACKUP_SIZE_BYTES} bytes"
        fi
    fi

    # ADD Final Cleanup Information
    if [ "$ENABLE_AUTO_CLEANUP" = "TRUE" ]; then
        if [[ "${CLEANUP_EXECUTED:-FALSE}" == "TRUE" ]]; then
            if [ $CLEANUP_FAILED -eq 0 ]; then
                DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[SUCCESS] Cleaned ${CLEANUP_SUCCESS} Stale Files"
            elif [ $CLEANUP_SUCCESS -gt 0 ]; then
                DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[PARTIAL] Cleaned ${CLEANUP_SUCCESS}/${CLEANUP_TOTAL} (${CLEANUP_FAILED} failed)"
            else
                DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[FAILED] Auto cleanup unsuccessful"
            fi
        else
            DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[NOT ACTIVE] No stale files to clean"
        fi
    fi

    DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }Size: ${BACKUP_SIZE_DISPLAY}"

    # Send Notification
    "$NOTIFICATION_SCRIPT" \
        -e "$NOTIFY_TITLE" \
        -s "$NOTIFY_SUBJECT" \
        -d "${NOTIFY_MSG} - ${DETAIL_MSG}" \
        -i "$NOTIFY_PRIORITY"

    LOG_TO_FILE "${MSG_INFO} Notification sent (Priority: ${NOTIFY_PRIORITY})."
    LOG_TO_FILE "---[${MOD_TITLE_9} COMPLETED]---"

    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_MSG "INFO" "Notification sent successfully (Priority: ${NOTIFY_PRIORITY})."
    fi
else
    LOG_TO_FILE "---[MODULE 9: ${MOD_TITLE_9}]---"
    LOG_TO_FILE "MODULE [DISABLED]"
    if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
        PRINT_SECTION "${MOD_TITLE_9} SKIPPED"
    fi
fi

# =============================================================================
# 8. FINALIZATION & STATUS REPORT
# =============================================================================

# Determine Final Status Text based on OVERALL_STATUS
# Mapping: Internal Status Value → Human-readable Messages and Symbols.
# States: SUCCESS, WARNING, ERROR, DEFAULT (Catch-all Fallback).
case "$OVERALL_STATUS" in
    SUCCESS)
        FINAL_MESSAGE="All Routine Steps completed successfully."
        FINAL_SYMBOL="[SUCCESS]"
        ;;
    WARNING)
        FINAL_MESSAGE="All Routine Steps completed. One or more Warnings occurred."
        FINAL_SYMBOL="[WARNING]"
        ;;
    ERROR)
        FINAL_MESSAGE="All Routine Steps completed. One or more Errors occurred."
        FINAL_SYMBOL="[ERROR]"
        ;;
    *)
        FINAL_MESSAGE="All Routine Steps completed."
        FINAL_SYMBOL="[INFO]"
        ;;
esac

# Final CLI Banner
# Display Condition: SHOW_CLI_OUTPUT=TRUE required.
# Timing: After all Modules complete.
if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then
    echo ""
    echo "=============================================================="

    # Visual differentiation based on OVERALL_STATUS
    if [[ "$OVERALL_STATUS" == "ERROR" ]]; then
        echo "   *** ERROR - SCRIPT ABORTED ***"
        echo "=============================================================="
        echo "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"
        echo "──────────────────────────────────────────────────────────────"
        echo "WARNING: Manual intervention required immediately!"
    elif [[ "$OVERALL_STATUS" == "WARNING" ]]; then
        echo "   *** COMPLETED WITH WARNINGS ***"
        echo "=============================================================="
        echo "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"
        echo "──────────────────────────────────────────────────────────────"
        echo "NOTE: Review Warnings in Log File below."
    else
        echo "   *** BACKUP, ANALYSIS & MAINTENANCE COMPLETED ***"
        echo "=============================================================="
        echo "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"
        echo "──────────────────────────────────────────────────────────────"
    fi

    # Add Log Path hint if Warnings or Errors occurred
    if [[ "$OVERALL_STATUS" == "WARNING" ]] || [[ "$OVERALL_STATUS" == "ERROR" ]]; then
        echo "→ For Details, check Session Log: $LOG_FILE_PATH"
        echo "→ Or view Archive Logs: $ARCHIVE_LOG_DIR"
    fi
    echo "=============================================================="
fi

# Final Logging
# Action: Completion Event Recorded to Log File.
# Visual Display Covered by Final Banner Above.
LOG_TO_FILE "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"

# Final Archive Log Sync
# Action: Local Session Log Copied to Archive Location.
# Purpose: Permanent Record Established in Reports Directory.
# NOTE: Cleanup details already logged in Module 8; Session Log preserves full history.
LOG_TO_FILE "Final Archive Log Sync completed."
cp "$LOCAL_LOG_PATH" "$ARCHIVE_LOG_PATH"

# Normal Script Termination
# Exit Code 0: Successful Completion (Warnings Permitted).
exit 0

Old Script

#!/bin/bash

#############################################################################

# Flash or Boot Backup, License & Boot Analysis

# Description: Automates Flash or Boot Backup Creation, Retention, License & Boot Analysis

#############################################################################

# =============================================================================

# 1. CONFIGURATION & IDENTIFIERS

# =============================================================================

# -----------------------------------------------------------------------------

# GENERAL SETTINGS

# -----------------------------------------------------------------------------

HOST_NAME=$(hostname)

MIN_DISKS=3

# -----------------------------------------------------------------------------

# CENTRALIZED IDENTIFIER

# -----------------------------------------------------------------------------

SCRIPT_TITLE="Flash or Boot Backup, License & Boot Analysis"

# File Naming Conventions

LOG_BASENAME="Boot_Backup_Analysis"

LOG_FILE_PATTERN="${LOG_BASENAME}_*.log"

# -----------------------------------------------------------------------------

# RETENTION POLICIES

# -----------------------------------------------------------------------------

BACKUP_RETENTION_DAYS=150

MAX_BACKUPS_TO_KEEP=15

LOG_RETENTION_DAYS=90

MAX_LOG_FILES_TO_KEEP=30

# -----------------------------------------------------------------------------

# FEATURE FLAGS

# -----------------------------------------------------------------------------

ENABLE_LICENSE_CHECK=true

ENABLE_BOOT_CHECK=true

ENABLE_HARDWARE_CHECK=true

ENABLE_SYSTEM_CHECK=true

ENABLE_BACKUP_CREATE=true

ENABLE_BACKUP_RETENTION=true

ENABLE_LOG_RETENTION=true

ENABLE_NOTIFICATION=true

SHOW_CLI_OUTPUT=true

# -----------------------------------------------------------------------------

# PATH DEFINITIONS

# -----------------------------------------------------------------------------

BACKUP_BASE_DIR="/mnt/user/Backup_${HOST_NAME}/boot"

ARCHIVE_SUBDIR="Backup_Reports"

LOG_LOCAL_SUBDIR="Backup_Logs_on_Boot"

ARCHIVE_LOG_DIR="${BACKUP_BASE_DIR}/${ARCHIVE_SUBDIR}"

LOCAL_LOG_DIR="/boot/${LOG_LOCAL_SUBDIR}"

LOG_FILE_NAME="${LOG_BASENAME}_${HOST_NAME}_$(date '+%Y-%m-%d_%H-%M-%S').log"

LOCAL_LOG_PATH="${LOCAL_LOG_DIR}/${LOG_FILE_NAME}"

ARCHIVE_LOG_PATH="${ARCHIVE_LOG_DIR}/${LOG_FILE_NAME}"

LAST_RUN_LOG="${ARCHIVE_LOG_DIR}/Last_Run.log"

LOG_FILE_PATH="$ARCHIVE_LOG_PATH"

VAR_INI_PATH="/var/local/emhttp/var.ini"

# -----------------------------------------------------------------------------

# MODULE NAMES

# -----------------------------------------------------------------------------

MOD_TITLE_1="LICENSE STATUS"

MOD_TITLE_2="BOOT MEDIUM ANALYSIS"

MOD_TITLE_3="HARDWARE CHECK"

MOD_TITLE_4="SYSTEM INTEGRITY CHECK"

MOD_TITLE_5="CREATE BACKUP"

MOD_TITLE_6="APPLY BACKUP RETENTION"

MOD_TITLE_7="APPLY LOG RETENTION"

MOD_TITLE_8="SEND NOTIFICATION"

# -----------------------------------------------------------------------------

# MESSAGE TEXTS

# -----------------------------------------------------------------------------

MSG_SUCCESS="SUCCESS"

MSG_ERROR="ERROR"

MSG_WARNING="WARNING"

MSG_INFO="INFO"

MSG_COPYING_BACKUP="Copying Backup to:"

MSG_SUCCESS_COPY="Copy completed successfully."

MSG_INTEGRITY_CHECK="Checking Integrity Check of"

MSG_SUCCESS_INTEGRITY="Integrity Check passed."

MSG_SYMLINK_REMOVED="Temporary Symlink removed."

MSG_BACKUP_CREATED="Backup File created:"

MSG_LOCATION="Location:"

MSG_SUCCESS_ALL_STEPS="All Routine Steps completed successfully."

MSG_BACKUP_ANALYSIS_MAINTENANCE_COMPLETED="Backup, Analysis & Maintenance completed."

MSG_CONFIG_FILE_NOT_FOUND="Configuration File not found."

MSG_VARINI_NOT_FOUND="var.ini not found."

MSG_NO_TPM_DEVICE="No TPM Device available."

MSG_USB_STICK_DETECTED="USB Stick detected for License."

MSG_NO_USB_STICK="No USB Stick detected, but Flash License active!"

MSG_TP_M_MODE="TPM Mode: No USB Stick required for License."

MSG_UNZIP_NOT_FOUND="'unzip' Command not found. Integrity Check impossible."

MSG_FLASH_BACKUP_FAILED="Flash Backup Tool failed."

MSG_INVALID_FILENAME="Invalid Filename generated."

MSG_SOURCE_FILE_MISSING="Source File does not exist."

MSG_COPY_FAILED="Copying Backup File failed."

MSG_BACKUP_CORRUPTED="Backup File corrupted! Removing defective File."

MSG_CHANGE_DIR_FAILED="Change to Directory failed."

MSG_PROCESS_ABORTED="Process aborted: Manual Cleanup required."

MSG_WARNING_IGNORED="Warning ignored."

MSG_NO_STALE_FILES="No stale temporary Files found."

MSG_STALE_FILES_FOUND="stale temporary Files found."

MSG_ARRAY_INTEGRITY_CONFIRMED="Array Integrity confirmed."

MSG_NO_LICENSE_DETECTED="License Type not detected or invalid."

# =============================================================================

# 2. HELPER FUNCTIONS

# =============================================================================

print_header() {

echo "=============================================================="

echo " ${SCRIPT_TITLE}"

echo "=============================================================="

echo "HOST: $(hostname)"

echo "TIMESTAMP: $(date '+%d.%m.%Y %H:%M')"

echo "----------------------------------------"

}

print_section() {

echo ""

echo "[${1}]"

echo "--------------------------------------------------------------"

}

print_msg() {

local level="$1" msg="$2"

case "$level" in

SUCCESS) echo "[${MSG_SUCCESS}] $msg" ;;

ERROR) echo "[${MSG_ERROR}] $msg" ;;

WARNING) echo "[${MSG_WARNING}] $msg" ;;

INFO) echo "[${MSG_INFO}] $msg" ;;

*) echo "$msg" ;;

esac

}

log_to_file() {

local msg="$1"

local timestamped_msg="[$(date '+%Y-%m-%d %H:%M:%S')] [$(hostname)] $msg"

echo "$timestamped_msg" >> "$LOG_FILE_PATH"

}

mask_guid_format() {

local guid="$1"

if [[ -z "$guid" ]]; then echo "---"; return; fi

local result="" sep=""

IFS='-' read -ra PARTS <<< "$guid"

for part in "${PARTS[@]}"; do

if [[ ${#part} -gt 2 ]]; then result+="${sep}${part:0:2}-****"

else result+="${sep}${part}-****"; fi

sep="-"

done

echo "$result"

}

clean_vendor_name() {

local raw_vendor="$1"

local vendor=$(echo "$raw_vendor" | awk '{print $1}')

if [[ "$vendor" == "Cruzer" ]]; then vendor=$(echo "$vendor" | sed 's/Cruzer$//g')

elif [[ "$vendor" == "DataTraveler" ]]; then vendor=$(echo "$vendor" | sed 's/DataTraveler$//g')

elif [[ "$vendor" == "Flash" ]]; then vendor=$(echo "$vendor" | sed 's/Flash$//g'); fi

echo "$vendor" | xargs

}

clean_model_name() {

local raw_model="$1"

local cleaned=$(echo "$raw_model" | sed -E 's/[[:space:]]*[0-9]+\.?[0-9]*(GB|TB|MB|GiB|MiB)[[:space:]]*$//gi')

if [[ -z "$cleaned" ]]; then echo "$raw_model"; else echo "$cleaned"; fi

}

# =============================================================================

# 3. INITIALIZATION

# =============================================================================

mkdir -p "$BACKUP_BASE_DIR" || { echo "[${MSG_ERROR}] Cannot create Backup Directory."; exit 1; }

mkdir -p "${ARCHIVE_LOG_DIR}" || { echo "[${MSG_ERROR}] Cannot create Archive Log Directory."; exit 1; }

mkdir -p "${LOCAL_LOG_DIR}" || { echo "[${MSG_ERROR}] Cannot create Local Log Directory."; exit 1; }

touch "$LOCAL_LOG_PATH" 2>/dev/null || exit 1

touch "$ARCHIVE_LOG_PATH" 2>/dev/null || exit 1

{

echo "=========================================="

echo "START ${SCRIPT_TITLE}"

echo "Hostname: ${HOST_NAME}"

echo "Backup Target: ${BACKUP_BASE_DIR}"

echo "Local Log: ${LOCAL_LOG_PATH}"

echo "Archive Log: ${ARCHIVE_LOG_PATH}"

echo "=========================================="

} > "$LOG_FILE_PATH"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_header

fi

# =============================================================================

# 4. MODULES

# =============================================================================

# --- MODULE 1: LICENSE STATUS ---

if [ "$ENABLE_LICENSE_CHECK" = true ]; then

log_to_file "---[MODULE 1: ${MOD_TITLE_1}]---"

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_section "${MOD_TITLE_1}"; fi

SERVER_NAME="" OS_VERSION="" LICENSE_TYPE="" REGTY_RAW=""

FLASH_GUID="" TPM_GUID="" REG_GUID=""

if [ -f "$VAR_INI_PATH" ]; then

SERVER_NAME=$(awk -F'=' '/^NAME=/ {gsub(/"/, "", $2); gsub(/ /, "_", $2); print $2}' "$VAR_INI_PATH")

OS_VERSION=$(awk -F'=' '/^version=/ {gsub(/"/, "", $2); print $2}' "$VAR_INI_PATH")

REGTY_RAW=$(awk -F'=' '/^regTy=/ {gsub(/"/, "", $2); print $2}' "$VAR_INI_PATH" 2>/dev/null)

LICENSE_TYPE="$REGTY_RAW"

FLASH_GUID=$(grep '^flashGUID=' "$VAR_INI_PATH" 2>/dev/null | cut -d'"' -f2 | tr -d '[:space:]')

TPM_GUID=$(grep '^tpmGUID=' "$VAR_INI_PATH" 2>/dev/null | cut -d'"' -f2 | tr -d '[:space:]')

REG_GUID=$(grep '^regGUID=' "$VAR_INI_PATH" 2>/dev/null | cut -d'"' -f2 | tr -d '[:space:]')

else

print_msg "$MSG_ERROR" "$MSG_CONFIG_FILE_NOT_FOUND"

log_to_file "[${MSG_WARNING}] $MSG_VARINI_NOT_FOUND"

fi

LICENSE_STATUS="UNKNOWN" DEVICE_BINDING="NONE"

if [[ "$FLASH_GUID" == "$REG_GUID" ]] && [[ "$FLASH_GUID" != "$TPM_GUID" || -z "$TPM_GUID" ]]; then

LICENSE_STATUS="USB_FLASH" DEVICE_BINDING="USB Stick"

if [[ -n "$TPM_GUID" ]] && [[ "$FLASH_GUID" != "$TPM_GUID" ]]; then

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_INFO" "TPM Key present, currently not used."; fi

log_to_file "[${MSG_INFO}] TPM Key present, currently not used."

fi

elif [[ "$FLASH_GUID" "$TPM_GUID" ]] && [[ "$FLASH_GUID" "$REG_GUID" ]]; then

LICENSE_STATUS="FULL_TPM" DEVICE_BINDING="TPM"

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_SUCCESS" "System running on full TPM Basis."; fi

log_to_file "Binding detected: TPM."

elif [[ "$REG_GUID" == "$TPM_GUID" ]]; then

LICENSE_STATUS="TPM_MIGRATION" DEVICE_BINDING="TPM"

log_to_file "Binding detected: Migration to TPM."

else

LICENSE_STATUS="INVALID_CONFIG" DEVICE_BINDING="UNKNOWN"

log_to_file "[${MSG_WARNING}] GUID Configuration could not be uniquely identified."

fi

FLASH_MASKED=$(mask_guid_format "$FLASH_GUID")

TPM_MASKED=$(mask_guid_format "$TPM_GUID")

REG_MASKED=$(mask_guid_format "$REG_GUID")

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "USB LICENSE ID: $FLASH_MASKED"

echo "TPM KEY: $TPM_MASKED"

echo "ACTIVE REGISTRATION: $REG_MASKED"

print_section "${MOD_TITLE_1}"

echo "SERVER NAME: ${SERVER_NAME:-Unavailable}"

echo "OS VERSION: ${OS_VERSION:-Unavailable}"

echo "LICENSE TYPE: ${LICENSE_TYPE:-$MSG_NO_LICENSE_DETECTED}"

echo "DETECTED BINDING: ${DEVICE_BINDING} (${LICENSE_STATUS})"

echo "----------------------------------------"

echo "DETECTED MODE: $LICENSE_STATUS"

echo "LICENSE DEVICE TYPE: ${DEVICE_BINDING}"

fi

log_to_file "Server Name: ${SERVER_NAME:-Unavailable}"

log_to_file "OS Version: ${OS_VERSION:-Unavailable}"

log_to_file "License Type: ${LICENSE_TYPE:-UNDETERMINED}"

log_to_file "Detected Binding: ${DEVICE_BINDING} (${LICENSE_STATUS})"

else

log_to_file "---[MODULE 1: ${MOD_TITLE_1}]---"

log_to_file "[DISABLED]"

fi

# --- MODULE 2: BOOT MEDIUM ANALYSIS ---

if [ "$ENABLE_BOOT_CHECK" = true ]; then

log_to_file "---[MODULE 2: ${MOD_TITLE_2}]---"

BOOT_MEDIUM_TYPE="UNKNOWN"

system_dev_final="" sys_boot_type="Unknown" sys_vendor="" sys_model_clean=""

nvme_dev_global=$(lsblk -d -no NAME,TYPE 2>/dev/null | grep 'nvme' | awk '{print $1}' | head -1)

sda_dev_global=$(lsblk -d -no NAME,TYPE 2>/dev/null | grep -E '^sd[a-z]$' | awk '{print $1}' | head -1)

mmc_dev_global=$(lsblk -d -no NAME,TYPE 2>/dev/null | grep 'mmc' | awk '{print $1}' | head -1)

if [ -e "/dev/disk/by-label/flash" ]; then

REAL_LINK=$(readlink -f "/dev/disk/by-label/flash")

DEVICE_NAME=$(basename "$REAL_LINK")

if [[ "$DEVICE_NAME" =~ [0-9]+$ ]]; then PARENT_DEVICE="${DEVICE_NAME%[0-9]*}"; else PARENT_DEVICE="$DEVICE_NAME"; fi

TRANSPORT=$(lsblk -no TRAN "$PARENT_DEVICE" 2>/dev/null | tr -d '[:space:]')

case "$TRANSPORT" in

usb) BOOT_MEDIUM_TYPE="USB Stick Boot" ;;

*)

SYS_PATH="/sys/block/$PARENT_DEVICE/device"

if [ -L "$SYS_PATH" ]; then

FULL_PATH=$(readlink -f "$SYS_PATH")

if [[ "$FULL_PATH" =~ usb ]]; then

BOOT_MEDIUM_TYPE="USB Stick Boot"

else

BOOT_MEDIUM_TYPE="Internal Boot"

fi

else

BOOT_MEDIUM_TYPE="Internal Boot"

fi

;;

esac

if [[ "$BOOT_MEDIUM_TYPE" == "Internal Boot" ]]; then

if [[ -n "$nvme_dev_global" ]]; then system_dev_final="$nvme_dev_global"

elif [[ -n "$sda_dev_global" ]]; then system_dev_final="$sda_dev_global"

elif [[ -n "$mmc_dev_global" ]]; then system_dev_final="$mmc_dev_global"; fi

sys_boot_type="Internal Boot"

if [[ -n "$system_dev_final" ]] && [[ -b "/dev/$system_dev_final" ]]; then

vendor_raw=$(lsblk -no VENDOR "/dev/$system_dev_final" 2>/dev/null | head -1 | xargs)

model_raw=$(lsblk -no MODEL "/dev/$system_dev_final" 2>/dev/null | head -1 | xargs)

if [[ -z "$vendor_raw" || "$vendor_raw" == "" ]]; then sys_vendor="(see Model)"

else sys_vendor="$vendor_raw"; fi

sys_model_clean=$(clean_model_name "$model_raw")

fi

fi

log_to_file "Boot Medium Type: ${BOOT_MEDIUM_TYPE}"

else

log_to_file "[${MSG_WARNING}] /dev/disk/by-label/flash not found."

fi

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "BOOT MEDIUM TYPE: ${BOOT_MEDIUM_TYPE}"

echo "----------------------------------------"

print_section "${MOD_TITLE_2}"

lic_vendor="-" lic_model_clean="-" lic_type=""

if [[ "$DEVICE_BINDING" == "USB Stick" ]]; then

usb_line=$(lsusb 2>/dev/null | grep -iE 'Kingston|Sandisk|Lexar|Transcend|ADATA|SanDisk' | head -1)

if [[ -n "$usb_line" ]]; then

raw_full=$(echo "$usb_line" | sed 's/.*ID [0-9a-f:]* //' | sed 's/ Corp\. //g' | sed 's/ Inc\. //g' | xargs)

lic_vendor=$(echo "$raw_full" | awk '{print $1}')

lic_vendor=$(clean_vendor_name "$lic_vendor")

if [[ "$lic_vendor" == "$raw_full" ]]; then lic_model_clean="Unknown USB Device"

else mod_part=$(echo "$raw_full" | sed "s/^${lic_vendor}[[:space:]]*//"); lic_model_clean=$(clean_model_name "$mod_part"); fi

fi

[[ -z "$lic_vendor" ]] && lic_vendor="Unknown"

[[ -z "$lic_model_clean" ]] && lic_model_clean="Unknown"

lic_type="USB Stick"

else

lic_type="TPM"

fi

echo "LICENSE DEVICE:"

echo " TYPE: $lic_type"

echo " MANUFACTURER: $lic_vendor"

echo " MODEL: $lic_model_clean"

echo ""

echo "SYSTEM DEVICE (OS):"

echo " TYPE: $sys_boot_type"

echo " MANUFACTURER: $sys_vendor"

echo " MODEL: $sys_model_clean"

fi

else

log_to_file "---[MODULE 2: ${MOD_TITLE_2}]---"

log_to_file "[DISABLED]"

fi

# --- MODULE 3: HARDWARE CHECK ---

if [ "$ENABLE_HARDWARE_CHECK" = true ]; then

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_section "${MOD_TITLE_3}"; fi

log_to_file "---[MODULE 3: ${MOD_TITLE_3}]---"

if [ -c /dev/tpm0 ] 2>/dev/null || [ -c /dev/tpmrm0 ] 2>/dev/null; then

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_SUCCESS" "TPM Hardware detected and usable."; fi

log_to_file "[${MSG_SUCCESS}] TPM Hardware detected."

else

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_WARNING" "$MSG_NO_TPM_DEVICE"; fi

log_to_file "[${MSG_WARNING}] $MSG_NO_TPM_DEVICE"

fi

if [[ "$LICENSE_STATUS" == "USB_FLASH" ]]; then

if [[ $(lsusb 2>/dev/null | grep -ciE 'Kingston|Sandisk|Lexar') -eq 0 ]]; then

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_ERROR" "$MSG_NO_USB_STICK"; fi

log_to_file "[${MSG_ERROR}] $MSG_NO_USB_STICK"

else

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_INFO" "$MSG_USB_STICK_DETECTED"; fi

log_to_file "[${MSG_INFO}] $MSG_USB_STICK_DETECTED"

fi

else

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_INFO" "$MSG_TP_M_MODE"; fi

log_to_file "[${MSG_INFO}] $MSG_TP_M_MODE"

fi

log_to_file "---[${MOD_TITLE_3} COMPLETED]---"

log_to_file ""

else

log_to_file "---[MODULE 3: ${MOD_TITLE_3}]---"

log_to_file "[DISABLED]"

fi

# --- MODULE 4: SYSTEM INTEGRITY CHECK ---

if [ "$ENABLE_SYSTEM_CHECK" = true ]; then

log_to_file "---[MODULE 4: ${MOD_TITLE_4}]---"

STALE_COUNT=$(find /usr/local/emhttp/ -maxdepth 1 -name "*boot-backup*.zip" \( -type f -o -xtype l \) 2>/dev/null | wc -l)

if [ "$STALE_COUNT" -gt 0 ]; then

log_to_file "[${MSG_WARNING}] ${STALE_COUNT} $MSG_STALE_FILES_FOUND"

HAS_STALE_FILES=true

if [ -z "$SKIP_DIRTY_CHECK" ] || [ "$SKIP_DIRTY_CHECK" = false ]; then

log_to_file "[${MSG_ERROR}] $MSG_PROCESS_ABORTED"

exit 1

else

log_to_file "[${MSG_INFO}] $MSG_WARNING_IGNORED (SKIP_DIRTY_CHECK=true)."

fi

else

HAS_STALE_FILES=false

log_to_file "[${MSG_SUCCESS}] $MSG_NO_STALE_FILES"

fi

if [ "$MIN_DISKS" -gt 0 ]; then

DISK_COUNT=$(ls -d /mnt/disk* 2>/dev/null | wc -l)

if [ "$DISK_COUNT" -lt "$MIN_DISKS" ]; then

log_to_file "[${MSG_ERROR}] Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."

exit 1

fi

log_to_file "[${MSG_SUCCESS}] $MSG_ARRAY_INTEGRITY_CONFIRMED (${DISK_COUNT} Disks present)."

fi

log_to_file "---[${MOD_TITLE_4} COMPLETED]---"

log_to_file ""

else

log_to_file "---[MODULE 4: ${MOD_TITLE_4}]---"

log_to_file "[DISABLED]"

fi

# --- MODULE 5: CREATE BACKUP ---

BACKUP_FILE=""

if [ "$ENABLE_BACKUP_CREATE" = true ]; then

log_to_file "---[MODULE 5: ${MOD_TITLE_5}]---"

log_to_file "Target Directory: ${BACKUP_BASE_DIR}"

log_to_file "Starting native Flash Backup Script..."

if ! command -v unzip >/dev/null 2>&1; then

log_to_file "[${MSG_ERROR}] $MSG_UNZIP_NOT_FOUND"

exit 1

fi

BACKUP_RAW_OUTPUT=$(/usr/bin/php -q /usr/local/emhttp/webGui/scripts/flash_backup 2>&1)

PHP_EXIT_CODE=$?

if [ $PHP_EXIT_CODE -ne 0 ]; then

log_to_file "[${MSG_ERROR}] $MSG_FLASH_BACKUP_FAILED (Exit Code: ${PHP_EXIT_CODE})."

log_to_file "[DEBUG] PHP Output: ${BACKUP_RAW_OUTPUT}"

exit 1

fi

BACKUP_FILE=$(echo "$BACKUP_RAW_OUTPUT" | tail -n 1 | xargs)

if [ -z "$BACKUP_FILE" ]; then

log_to_file "[${MSG_ERROR}] Invalid Filename generated (Empty output)."

log_to_file "[DEBUG] PHP Output: ${BACKUP_RAW_OUTPUT}"

exit 1

fi

SYMLINK_PATH="/usr/local/emhttp/${BACKUP_FILE}"

if [ ! -f "$SYMLINK_PATH" ] && [ ! -L "$SYMLINK_PATH" ]; then

log_to_file "[${MSG_ERROR}] Source File does not exist: ${SYMLINK_PATH}"

exit 1

fi

SOURCE_FILE=$(readlink -f "$SYMLINK_PATH" 2>/dev/null || echo "$SYMLINK_PATH")

DEST_FILE="${BACKUP_BASE_DIR}/${BACKUP_FILE}"

if [ ! -f "$SOURCE_FILE" ]; then

log_to_file "[${MSG_ERROR}] Resolved Source File missing: ${SOURCE_FILE}"

exit 1

fi

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] ${MSG_COPYING_BACKUP} ${DEST_FILE}"

fi

cp -v "$SOURCE_FILE" "$DEST_FILE" > /dev/null 2>&1

COPY_EXIT=$?

if [ $COPY_EXIT -ne 0 ]; then

log_to_file "[${MSG_ERROR}] $MSG_COPY_FAILED"

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_ERROR" "$MSG_COPY_FAILED"; fi

exit 1

fi

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_SUCCESS}] ${MSG_SUCCESS_COPY}"

fi

log_to_file "Copied Backup to: ${DEST_FILE}"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] ${MSG_INTEGRITY_CHECK} ${BACKUP_FILE}..."

fi

if ! unzip -t -q "$DEST_FILE" >/dev/null 2>&1; then

log_to_file "[${MSG_ERROR}] $MSG_BACKUP_CORRUPTED"

rm -f "$DEST_FILE"

if [ "$SHOW_CLI_OUTPUT" = true ]; then print_msg "$MSG_ERROR" "$MSG_BACKUP_CORRUPTED"; fi

exit 1

fi

log_to_file "[${MSG_SUCCESS}] ${MSG_SUCCESS_INTEGRITY}"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_SUCCESS}] ${MSG_SUCCESS_INTEGRITY}"

fi

if [ -L "$SYMLINK_PATH" ]; then

rm -f "$SYMLINK_PATH"

log_to_file "[${MSG_SUCCESS}] ${MSG_SYMLINK_REMOVED}"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_SUCCESS}] ${MSG_SYMLINK_REMOVED}"

fi

fi

log_to_file "---[BACKUP CREATION COMPLETED]---"

log_to_file ""

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_5} COMPLETED"

echo "${MSG_BACKUP_CREATED} ${BACKUP_FILE}"

echo "${MSG_LOCATION} ${DEST_FILE}"

fi

log_to_file "---[UPDATE ARCHIVE LOG]---"

{

echo "=== LAST RUN: ${HOST_NAME} ==="

echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"

echo "Backup File: ${BACKUP_FILE}"

echo "Backup Path: ${DEST_FILE}"

echo "Archive Log Path: ${ARCHIVE_LOG_PATH}"

echo "Status: SUCCESSFUL"

[ -n "$LICENSE_TYPE" ] && echo "License Type: ${LICENSE_TYPE}"

[ "${HAS_STALE_FILES:-false}" = true ] && echo "WARNING: Stale Files present"

echo "==============================="

} > "$LAST_RUN_LOG"

cp "$LOCAL_LOG_PATH" "$ARCHIVE_LOG_PATH"

log_to_file "Status Log saved: ${LAST_RUN_LOG}"

log_to_file "Session Log archived: ${ARCHIVE_LOG_PATH}"

log_to_file "---[ARCHIVE LOG UPDATED]---"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "ARCHIVE LOG UPDATED"

echo "Log saved to: ${LAST_RUN_LOG}"

fi

else

log_to_file "---[MODULE 5: ${MOD_TITLE_5}]---"

log_to_file "[DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_5} SKIPPED"

fi

fi

# --- MODULE 6: APPLY BACKUP RETENTION ---

if [ "$ENABLE_BACKUP_RETENTION" = true ]; then

log_to_file "---[MODULE 6: ${MOD_TITLE_6}]---"

cd "$BACKUP_BASE_DIR" || { log_to_file "[${MSG_ERROR}] $MSG_CHANGE_DIR_FAILED"; exit 1; }

DELETED_BY_AGE=$(find . -maxdepth 1 -name "*boot-backup*.zip" -type f -mtime +"${BACKUP_RETENTION_DAYS}" -print 2>/dev/null | wc -l)

if [ "$DELETED_BY_AGE" -gt 0 ]; then

log_to_file "[${MSG_INFO}] ${DELETED_BY_AGE} Backups older than ${BACKUP_RETENTION_DAYS} days removed."

find . -maxdepth 1 -name "*boot-backup*.zip" -type f -mtime +"${BACKUP_RETENTION_DAYS}" -delete 2>/dev/null

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] Removed ${DELETED_BY_AGE} old Backups (older than ${BACKUP_RETENTION_DAYS} days)."

fi

fi

if [ -n "$MAX_BACKUPS_TO_KEEP" ] && [ "$MAX_BACKUPS_TO_KEEP" -gt 0 ]; then

CURRENT_COUNT=$(ls -1 boot-backup.zip 2>/dev/null | wc -l)

if [ "$CURRENT_COUNT" -gt "$MAX_BACKUPS_TO_KEEP" ]; then

TO_DELETE=$((CURRENT_COUNT - MAX_BACKUPS_TO_KEEP))

log_to_file "[${MSG_INFO}] Current Backups: ${CURRENT_COUNT} | Max: ${MAX_BACKUPS_TO_KEEP} → Deleting ${TO_DELETE} Files."

ls -t boot-backup.zip 2>/dev/null | tail -n +$((MAX_BACKUPS_TO_KEEP + 1)) | while read -r old_backup; do

rm -f "$old_backup"

log_to_file "[${MSG_INFO}] Deleted: $(basename "$old_backup")"

done

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] Deleted ${TO_DELETE} old Backups to stay within Limit of ${MAX_BACKUPS_TO_KEEP}."

fi

else

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] Backup Count (${CURRENT_COUNT}) is within Limit (${MAX_BACKUPS_TO_KEEP})."

fi

fi

fi

FINAL_COUNT=$(ls -1 boot-backup.zip 2>/dev/null | wc -l)

log_to_file "[${MSG_INFO}] Result: ${FINAL_COUNT} Flash or Boot Backups remaining."

log_to_file "---[${MOD_TITLE_6} COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_6} COMPLETED"

echo "Total Backups remaining: ${FINAL_COUNT}"

fi

else

log_to_file "---[MODULE 6: ${MOD_TITLE_6}]---"

log_to_file "[DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_6} SKIPPED"

fi

fi

# --- MODULE 7: APPLY LOG RETENTION ---

if [ "$ENABLE_LOG_RETENTION" = true ]; then

log_to_file "---[MODULE 7: ${MOD_TITLE_7}]---"

cd "$ARCHIVE_LOG_DIR" || { log_to_file "[${MSG_ERROR}] $MSG_CHANGE_DIR_FAILED"; exit 1; }

DELETED_LOGS_BY_AGE=$(find . -maxdepth 1 -name "*.log" -type f ! -name "last_run.log" -mtime +"${LOG_RETENTION_DAYS}" -print 2>/dev/null | wc -l)

if [ "$DELETED_LOGS_BY_AGE" -gt 0 ]; then

log_to_file "[${MSG_INFO}] ${DELETED_LOGS_BY_AGE} Log Files older than ${LOG_RETENTION_DAYS} Days removed."

find . -maxdepth 1 -name "*.log" -type f ! -name "last_run.log" -mtime +"${LOG_RETENTION_DAYS}" -delete 2>/dev/null

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] Removed ${DELETED_LOGS_BY_AGE} old Logs (older than ${LOG_RETENTION_DAYS} Days)."

fi

fi

if [ -n "$MAX_LOG_FILES_TO_KEEP" ] && [ "$MAX_LOG_FILES_TO_KEEP" -gt 0 ]; then

LOG_FILE_COUNT=$(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>/dev/null | wc -l)

if [ "$LOG_FILE_COUNT" -gt "$MAX_LOG_FILES_TO_KEEP" ]; then

TO_DELETE_COUNT=$((LOG_FILE_COUNT - MAX_LOG_FILES_TO_KEEP))

log_to_file "[${MSG_INFO}] Current Logs: ${LOG_FILE_COUNT} | Max: ${MAX_LOG_FILES_TO_KEEP} → Deleting ${TO_DELETE_COUNT} Files."

mapfile -t files_to_delete < <(

for f in $(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f); do

ts=$(stat -c '%Y' "$f" 2>/dev/null || stat -f '%m' "$f" 2>/dev/null)

printf '%s %s\n' "$ts" "$f"

done | sort -n | head -n "$TO_DELETE_COUNT" | awk '{print $2}'

)

for old_log in "${files_to_delete[@]}"; do

if [ -n "$old_log" ]; then

rm -f "$old_log"

log_to_file "[${MSG_INFO}] Deleted: $(basename "$old_log")"

fi

done

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] Deleted ${TO_DELETE_COUNT} old Logs to stay within Limit of ${MAX_LOG_FILES_TO_KEEP}."

fi

else

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo "[${MSG_INFO}] Log Count (${LOG_FILE_COUNT}) is within Limit (${MAX_LOG_FILES_TO_KEEP})."

fi

fi

fi

FINAL_LOG_COUNT=$(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f | wc -l)

log_to_file "[${MSG_INFO}] Result: ${FINAL_LOG_COUNT} Log Files remaining (excl. last_run.log)."

log_to_file "---[${MOD_TITLE_7} COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_7} COMPLETED"

echo "Total Logs remaining: ${FINAL_LOG_COUNT}"

fi

else

log_to_file "---[MODULE 7: ${MOD_TITLE_7}]---"

log_to_file "[DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_7} SKIPPED"

fi

fi

# --- MODULE 8: SEND NOTIFICATION ---

if [ "$ENABLE_NOTIFICATION" = true ]; then

log_to_file "---[MODULE 8: ${MOD_TITLE_8}]---"

NOTIFY_TITLE="Flash or Boot Backup Completed - ${HOST_NAME}"

NOTIFY_MSG="Flash or Boot Backup '${BACKUP_FILE}' successfully created."

DETAIL_MSG=""

if [ "$ENABLE_LICENSE_CHECK" = true ]; then

case "$DEVICE_BINDING" in

"USB Stick") DETAIL_MSG="License: ${LICENSE_TYPE:-Unknown} (USB)" ;;

"TPM") DETAIL_MSG="License: ${LICENSE_TYPE:-Unknown} (TPM)" ;;

esac

fi

if [ "$ENABLE_BOOT_CHECK" = true ] && [ "$BOOT_MEDIUM_TYPE" != "UNKNOWN" ]; then

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }Boot: ${BOOT_MEDIUM_TYPE}"

fi

if [ "${HAS_STALE_FILES:-false}" = true ]; then

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }WARNING: Stale Files"

fi

/usr/local/emhttp/webGui/scripts/notify -e "$NOTIFY_TITLE" \

-s "Flash or Boot Backup Success" \

-d "${NOTIFY_MSG} - ${DETAIL_MSG}" \

-i "normal"

log_to_file "[${MSG_INFO}] Notification sent."

log_to_file "---[${MOD_TITLE_8} COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_8} SENT"

echo "Notification sent successfully."

fi

else

log_to_file "---[MODULE 8: ${MOD_TITLE_8}]---"

log_to_file "[DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_section "${MOD_TITLE_8} SKIPPED"

fi

fi

# Final Output

if [ "$SHOW_CLI_OUTPUT" = true ]; then

echo ""

echo "=============================================================="

echo " ${MSG_BACKUP_ANALYSIS_MAINTENANCE_COMPLETED}"

echo "=============================================================="

fi

log_to_file "[${MSG_SUCCESS}] ${MSG_SUCCESS_ALL_STEPS}"

if [ "$SHOW_CLI_OUTPUT" = true ]; then

print_msg "$MSG_SUCCESS" "${MSG_SUCCESS_ALL_STEPS}"

fi

exit 0

Edited by Revan335

  • 1 month later...
On 6/26/2026 at 3:26 PM, Revan335 said:

The expanded and corrected versions for V7.3.x and the changes to ‘boot’, ‘Internal Boot’, … instead of ‘flash’. Older operating system versions should also work, but have not really been tested as there are currently no older versions, such as V6.x available.

Trying to figure out where to enter my UD remote destination?

5 hours ago, wgstarks said:

Trying to figure out where to enter my UD remote destination?

What do you mean?

The Backup or Log Destination are declared at the PATH DEFINITIONS Config Arrier at the Beginning.

22 minutes ago, Revan335 said:

What do you mean?

The Backup or Log Destination are declared at the PATH DEFINITIONS Config Arrier at the Beginning.

Is that BACKUP_BASE_DIR=?

And I’m guessing I need to replace everything after the =?

3 hours ago, wgstarks said:

Is that BACKUP_BASE_DIR=?

And I’m guessing I need to replace everything after the =?

Yes!

BACKUP_BASE_DIR="/mnt/user/Backup_${HOST_NAME}/boot"

For example "/mnt/disk/WD_Elements/Backup_${HOST_NAME}/boot" Hostname is your Unraid Server Name like Tower ... but you can used a Path what you like with or without the Variable or this Schema.

  • 3 weeks later...

@wgstarks The New Script Version have more Documentation.

1 hour ago, Revan335 said:

@wgstarks The New Script Version have more Documentation.

Switched to the new version. I got an email notification on the first run but not on repeat runs. The only difference I see is that the script aborted on the first run due to stale files so I'm wondering if the script only notifies on an abort and how to change it to always notify?

On 8/15/2026 at 3:16 AM, wgstarks said:

Switched to the new version. I got an email notification on the first run but not on repeat runs. The only difference I see is that the script aborted on the first run due to stale files so I'm wondering if the script only notifies on an abort and how to change it to always notify?

When you have Stale Files or other Errors and Warnings you have Details in the Log Files.
You mean the E-Mail Notification?

20 minutes ago, Revan335 said:

When you have Stale Files or other Errors and Warnings you have Details in the Log Files.
You mean the E-Mail Notification?

Yes. I got an email notification with the first run (which aborted due to stale files) but not with subsequent runs with cleanup enabled. They completed successfully but I didn’t get an email. These were all being run using the run script button in the user scripts plugin.

On 8/16/2026 at 9:21 PM, wgstarks said:

Yes. I got an email notification with the first run (which aborted due to stale files) but not with subsequent runs with cleanup enabled. They completed successfully but I didn’t get an email. These were all being run using the run script button in the user scripts plugin.

Can you send the Log and CLI Output?

PN is a Option when its not published for all.

1 hour ago, Revan335 said:

Can you send the Log and CLI Output?

PN is a Option when its not published for all.

log-

[INFO] Temporary Symlink Location: /usr/local/emhttp/brunnhildetwo-v7.3.2-boot-backup-20260817-1635.zip
[SUCCESS] Temporary Symlink removed.

[UPDATE ARCHIVE LOG]
--------------------------------------------------------------
Status Log saved: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup//Backup_Reports/Last_Run.log
Session Log archived: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup//Backup_Reports/Boot_Backup_Analysis__2026-08-17_16-35-41.log

[APPLY BACKUP RETENTION]
--------------------------------------------------------------
[INFO] Backup File Count (7) is within limit (15).
[INFO] Total Backup Files remaining: 7

[APPLY LOG RETENTION]
--------------------------------------------------------------
[INFO] Log File Count (8) is within limit (15).
[INFO] Total Log Files remaining: 8

[AUTO CLEANUP]
--------------------------------------------------------------
AUTO CLEANUP STATUS: ENABLED
STALE FILES FROM SYSTEM INTEGRITY CHECK: FALSE
----------------------------------------
[INFO] No Stale Files detected. Cleanup not required.

[SEND NOTIFICATION]
--------------------------------------------------------------
Backup File Size: 1.99 GB
[INFO] Notification sent successfully (Priority: success).

==============================================================
*** BACKUP, ANALYSIS & MAINTENANCE COMPLETED ***
==============================================================
FINAL STATUS: [SUCCESS] All Routine Steps completed successfully.
──────────────────────────────────────────────────────────────
==============================================================
Script Finished Aug 17, 2026 16:36.53

Full logs for this script are available at /tmp/user.scripts/tmpScripts/boot backup/log.txt

CLI-

Script location: /tmp/user.scripts/tmpScripts/boot backup/script
Note that closing this window will abort the execution of this script
==============================================================
Flash Boot Backup License Boot Analysis
==============================================================
HOST: BrunnhildeTwo
TIMESTAMP: 17.08.2026 16:45
----------------------------------------

[LICENSE STATUS]
--------------------------------------------------------------
[SUCCESS] TPM Key used for Device Binding and TPM Licensing is active.
USB LICENSE ID: 01-ENHAW*******************
TPM KEY: 01-ENHAW*******************
ACTIVE REGISTRATION: 01-ENHAW*******************
----------------------------------------
SERVER NAME: BrunnhildeTwo
OS VERSION: 7.3.2
LICENSE TYPE: Lifetime
DETECTED BINDING: TPM (FULL_TPM)
----------------------------------------
DETECTED MODE: FULL_TPM
LICENSE DEVICE TYPE: TPM

[BOOT MEDIUM ANALYSIS]
--------------------------------------------------------------
BOOT MEDIUM TYPE: Internal Boot

LICENSE DEVICE
TYPE: TPM
MANUFACTURER: -
MODEL: -

SYSTEM DEVICE (OS)
TYPE: Internal Boot
MANUFACTURER: (see Model)
MODEL: Samsung SSD 990 PRO

[HARDWARE CHECK]
--------------------------------------------------------------
[SUCCESS] TPM Hardware detected.
[INFO] TPM Mode: No USB Stick required for License.

[SYSTEM INTEGRITY CHECK]
--------------------------------------------------------------
DIRTY CHECK OVERRIDE: TRUE
AUTO CLEANUP: TRUE
----------------------------------------
[SUCCESS] Array Integrity confirmed. (10 Disks present)
[SUCCESS] No Stale Temporary Files found.

[CREATE BACKUP]
--------------------------------------------------------------
Target Directory: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup/
Starting Native Flash Backup Script...
[SUCCESS] PHP flash_backup Script executed and Symlink at /usr/local/emhttp/brunnhildetwo-v7.3.2-boot-backup-20260817-1645.zip created.
[INFO] Copying Backup File to: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup//brunnhildetwo-v7.3.2-boot-backup-20260817-1645.zip
[SUCCESS] Copy completed successfully.
[INFO] Checking Integrity of File: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup//brunnhildetwo-v7.3.2-boot-backup-20260817-1645.zip
[SUCCESS] Integrity Check passed.
[INFO] Temporary Backup File Location: /brunnhildetwo-v7.3.2-boot-backup-20260817-1645.zip
[SUCCESS] Temporary Backup File removed: brunnhildetwo-v7.3.2-boot-backup-20260817-1645.zip
[INFO] Temporary Symlink Location: /usr/local/emhttp/brunnhildetwo-v7.3.2-boot-backup-20260817-1645.zip
[SUCCESS] Temporary Symlink removed.

[UPDATE ARCHIVE LOG]
--------------------------------------------------------------
Status Log saved: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup//Backup_Reports/Last_Run.log
Session Log archived: /mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup//Backup_Reports/Boot_Backup_Analysis_BrunnhildeTwo_2026-08-17_16-45-17.log

[APPLY BACKUP RETENTION]
--------------------------------------------------------------
[INFO] Backup File Count (9) is within limit (15).
[INFO] Total Backup Files remaining: 9

[APPLY LOG RETENTION]
--------------------------------------------------------------
[INFO] Log File Count (10) is within limit (15).
[INFO] Total Log Files remaining: 10

[AUTO CLEANUP]
--------------------------------------------------------------
AUTO CLEANUP STATUS: ENABLED
STALE FILES FROM SYSTEM INTEGRITY CHECK: FALSE
----------------------------------------
[INFO] No Stale Files detected. Cleanup not required.

[SEND NOTIFICATION]
--------------------------------------------------------------
Backup File Size: 1.99 GB
[INFO] Notification sent successfully (Priority: success).

==============================================================
*** BACKUP, ANALYSIS & MAINTENANCE COMPLETED ***
==============================================================
FINAL STATUS: [SUCCESS] All Routine Steps completed successfully.
──────────────────────────────────────────────────────────────
==============================================================

DONE

Current customized script-

#!/bin/bash

#############################################################################

# Title: Flash Boot Backup License Boot Analysis

# Description: Automates Flash or Boot Backup Creation, Retention, License and Boot Analysis

#

# Variable Naming Convention:

# --------------------------------------

# - GLOBAL VARIABLES: UPPER_SNAKE_CASE (e.g., HOST_NAME, BACKUP_RETENTION_DAYS)

# - LOCAL VARIABLES (within modules): UPPER_SNAKE_CASE (e.g., DISK_COUNT, CURRENT_TIME)

# - FUNCTIONS: UPPER_SNAKE_CASE (e.g., PRINT_MSG(), LOG_TO_FILE())

# - UI DISPLAY TEXTS: Title Case (e.g., "USB Stick", "Backup File", "TPM Mode")

#

# The readonly Keyword Explained:

# --------------------------------

# "readonly" makes a variable truly immutable (cannot be changed after assignment).

# This protects critical system paths from accidental modification during script execution.

# Example:

# NOTIFICATION_SCRIPT="/usr/local/emhttp/webGui/scripts/notify"

# readonly NOTIFICATION_SCRIPT

# # Cannot later execute: NOTIFICATION_SCRIPT="/wrong/path" (will fail with error)

#

# Benefits of readonly:

# - Prevents bugs from accidental variable reassignment

# - Documents intent clearly for other developers

# - Helps with security (sensitive paths cannot be hijacked)

#

#############################################################################

# =============================================================================

# 1. CONFIGURATION & IDENTIFIERS

# =============================================================================

# -----------------------------------------------------------------------------

# GENERAL SETTINGS (USER-CUSTOMIZABLE)

# Configurable Values for Script Operation. User adjustments permitted.

# readonly keyword omitted to allow user overrides via Environment or File edit.

# -----------------------------------------------------------------------------

SCRIPT_TITLE="Flash Boot Backup License Boot Analysis"

HOST_NAME=$(hostname)

MIN_DISKS=3 # Minimum Disks required in Array

# -----------------------------------------------------------------------------

# RETENTION POLICIES (USER-CUSTOMIZABLE)

# Controls Backup and Log File Retention Duration and Quantity Limits.

# Age-based deletion executes first, followed by count-based Enforcement.

# readonly keyword omitted to allow user Adjustments.

# -----------------------------------------------------------------------------

BACKUP_RETENTION_DAYS=90 # Backup Files kept for specified Days

MAX_BACKUPS_TO_KEEP=15 # Maximum Backup Files retained

LOG_RETENTION_DAYS=30 # Log Files kept for specified Days

MAX_LOG_FILES_TO_KEEP=15 # Maximum Log Files retained

# -----------------------------------------------------------------------------

# FEATURE FLAGS (USER-CUSTOMIZABLE - ENABLE/DISABLE)

# Controls execution of Individual Modules. Accepts TRUE or FALSE.

# readonly keyword omitted to allow user overrides.

# -----------------------------------------------------------------------------

ENABLE_LICENSE_CHECK=TRUE # Check License Status (TPM/USB)

ENABLE_BOOT_CHECK=TRUE # Analyze Boot Medium Type

ENABLE_HARDWARE_CHECK=TRUE # Verify TPM/USB Hardware availability

ENABLE_SYSTEM_CHECK=TRUE # Check Array Disks and Stale Files

ENABLE_BACKUP_CREATE=TRUE # Create New Backup File

ENABLE_BACKUP_RETENTION=TRUE # Apply Backup Retention Policy

ENABLE_LOG_RETENTION=TRUE # Apply Log Retention Policy

ENABLE_NOTIFICATION=TRUE # Send System Notification

SHOW_CLI_OUTPUT=TRUE # Display progress in Console/Terminal

# -----------------------------------------------------------------------------

# DIRTY CHECK OVERRIDE (USER-CUSTOMIZABLE)

# Controls behavior when Stale Temporary Files are detected.

# TRUE permits Continuation with Warning, FALSE triggers Abort.

# Default: FALSE (abort on Stale Files).

# -----------------------------------------------------------------------------

SKIP_DIRTY_CHECK=TRUE

# -----------------------------------------------------------------------------

# AUTO CLEANUP SETTINGS (USER-CUSTOMIZABLE)

# Controls automatic deletion of Stale Temporary Files at Script end.

# When FALSE (default), Stale Files trigger Abort or Warning (see SKIP_DIRTY_CHECK).

# When TRUE, Stale Files are automatically deleted AFTER all other Modules complete.

# -----------------------------------------------------------------------------

ENABLE_AUTO_CLEANUP=TRUE

# -----------------------------------------------------------------------------

# FILE NAMING CONVENTIONS (USER-CUSTOMIZABLE)

# Defines Log File Base Name and glob Pattern for Retention Matching.

# -----------------------------------------------------------------------------

LOG_BASENAME="Boot_Backup_Analysis"

LOG_FILE_PATTERN="${LOG_BASENAME}_*.log"

# -----------------------------------------------------------------------------

# NOTIFICATION SETTINGS (USER-CUSTOMIZABLE)

# Defines Title and Subject for Error Notifications via Unraid Notify Script.

# -----------------------------------------------------------------------------

NOTIFY_ERROR_TITLE="Flash Backup Aborted"

NOTIFY_ERROR_SUBJECT="Flash or Boot Backup - Error"

# -----------------------------------------------------------------------------

# USB BRAND PATTERNS (USER-CUSTOMIZABLE)

# Pipe-separated list of USB Brand Names for License Device Detection.

# Extended Lists accommodate additional USB Manufacturers.

# -----------------------------------------------------------------------------

USB_BRAND_PATTERN='ADATA|Corsair|Crucial|G.Skill|HP|Imation|Integral|Kingston|Lexar|Netac|PNY|Samsung|SanDisk|Silicon Power|Team Group|Toshiba|Transcend|Verbatim'

# -----------------------------------------------------------------------------

# MODULE TITLES (STATIC - NEVER MODIFIED AT RUNTIME)

# Fixed Labels for CLI Section Headers and Log Entries.

# readonly applied: Assigned once during initialization, No runtime modification.

# -----------------------------------------------------------------------------

readonly MOD_TITLE_1="LICENSE STATUS"

readonly MOD_TITLE_2="BOOT MEDIUM ANALYSIS"

readonly MOD_TITLE_3="HARDWARE CHECK"

readonly MOD_TITLE_4="SYSTEM INTEGRITY CHECK"

readonly MOD_TITLE_5="CREATE BACKUP"

readonly MOD_TITLE_6="APPLY BACKUP RETENTION"

readonly MOD_TITLE_7="APPLY LOG RETENTION"

readonly MOD_TITLE_8="AUTO CLEANUP"

readonly MOD_TITLE_9="SEND NOTIFICATION"

# =============================================================================

# 2. SYSTEM CONSTANTS

# =============================================================================

# -----------------------------------------------------------------------------

# SYSTEM PATHS (UNRAID-SPECIFIC - FIXED)

# Operating System-defined Paths. Immutable during Script execution.

# readonly applied: Prevents accidental or malicious Path Modification.

# -----------------------------------------------------------------------------

readonly NOTIFICATION_SCRIPT="/usr/local/emhttp/webGui/scripts/notify"

readonly VAR_INI_PATH="/var/local/emhttp/var.ini"

# -----------------------------------------------------------------------------

# DIRECTORY PATHS (DERIVED - AUTO-COMPUTED)

# Computed at Script Startup from HOST_NAME and configurable Values above.

# Represents Physical Directory Structure on Target System.

# readonly applied: Calculated once, Never modified Subsequently.

# Dependency order Preserved: Derived Paths Reference earlier Variables.

# -----------------------------------------------------------------------------

readonly BACKUP_BASE_DIR="/mnt/remotes/WALTERS-M4-MINI.DAHONEY.ME_brunnhilde/boot_backup/"

readonly ARCHIVE_SUBDIR="Backup_Reports"

readonly LOG_LOCAL_SUBDIR="Backup_Logs_on_Boot"

readonly ARCHIVE_LOG_DIR="${BACKUP_BASE_DIR}/${ARCHIVE_SUBDIR}"

readonly LOCAL_LOG_DIR="/boot/${LOG_LOCAL_SUBDIR}"

# -----------------------------------------------------------------------------

# LOG FILE PATHS (DERIVED - AUTO-COMPUTED PER EXECUTION)

# Unique per run via embedded Timestamp in Filename. Prevents File Collisions.

# LOG_FILE_PATH: Active Session Log (Write Target)

# ARCHIVE_LOG_PATH: Permanent Archive Copy (Final Destination)

# readonly applied: Calculated once, Never modified Subsequently.

# -----------------------------------------------------------------------------

readonly LOG_FILE_NAME="${LOG_BASENAME}_${HOST_NAME}_$(date '+%Y-%m-%d_%H-%M-%S').log"

readonly LOCAL_LOG_PATH="${LOCAL_LOG_DIR}/${LOG_FILE_NAME}"

readonly ARCHIVE_LOG_PATH="${ARCHIVE_LOG_DIR}/${LOG_FILE_NAME}"

readonly LAST_RUN_LOG="${ARCHIVE_LOG_DIR}/Last_Run.log"

readonly LOG_FILE_PATH="$LOCAL_LOG_PATH"

# =============================================================================

# 3. GLOBAL VARIABLES (RUNTIME STATE)

# =============================================================================

# NOTE: Variables below remain mutable (readonly NOT Applied).

# Purpose: Carry Initial/Default Values that are overwritten during Module execution.

# Applying readonly would cause Runtime Failure (Variable Assignment Rejection).

# Pattern: Initialize → Module Updates → Final State persisted

# -----------------------------------------------------------------------------

# LICENSE & BOOT STATE (INITIAL VALUES - MODULES 1-3 POPULATE)

# Neutral Default Values updated upon detection of actual System Configuration.

# Transition: Unknown/Empty → Detected Value (USB Flash, TPM, Migration, Invalid).

# -----------------------------------------------------------------------------

LICENSE_TYPE="" # Initial: empty → Set by Module 1

LICENSE_STATUS="Unknown" # Initial: Unknown → Set by Module 1

DEVICE_BINDING="None" # Initial: None → Set by Module 1

BOOT_MEDIUM_TYPE="Unknown" # Initial: Unknown → Set by Module 2

# -----------------------------------------------------------------------------

# SYSTEM STATE (INITIAL VALUES - MODULE 4 POPULATES)

# Stale File detection initialized clean. Updated during Module 4 execution.

# HAS_STALE_FILES: FALSE → TRUE if Stale Files discovered

# SKIP_DIRTY_CHECK_ACTIVE: FALSE → TRUE if Bypass activated

# -----------------------------------------------------------------------------

HAS_STALE_FILES=FALSE # Initial: FALSE → TRUE by Module 4 if Stale Files found

SKIP_DIRTY_CHECK_ACTIVE=FALSE # Initial: FALSE → TRUE by Module 4 if Bypass activated

# -----------------------------------------------------------------------------

# BACKUP STATE (INITIAL VALUES - MODULE 5 POPULATES)

# Empty until Module 5 Creates Backup and populates with generated Values.

# -----------------------------------------------------------------------------

BACKUP_FILE="" # Initial: empty → Set by Module 5

DEST_FILE="" # Initial: empty → Set by Module 5

# -----------------------------------------------------------------------------

# CLEANUP STATE (INITIAL VALUES - MODULE 8 POPULATES)

# Tracks automatic cleanup execution and outcomes.

# Auto-computed by Module 8 based on actual file operations performed.

# -----------------------------------------------------------------------------

CLEANUP_EXECUTED=FALSE # Initial: FALSE → TRUE by Module 8 if cleanup runs

CLEANUP_SUCCESS=0 # Initial: 0 → Files by Module 8 successfully removed

CLEANUP_FAILED=0 # Initial: 0 → Files by Module 8 that failed removal

CLEANUP_TOTAL=0 # Initial: 0 → Total files by Module 8 processed

# -----------------------------------------------------------------------------

# OVERALL STATUS TRACKING (INITIAL VALUE - ALL MODULES UPDATE)

# Cumulative Result Tracker across all executed Modules.

# Escalation Priority: ERROR > WARNING > SUCCESS (Monotonic Escalation only).

# Modules may increase Severity; Deescalation not supported.

# Final Value determines completion Message and Notification Priority.

# -----------------------------------------------------------------------------

OVERALL_STATUS="SUCCESS" # Initial: SUCCESS → WARNING or ERROR by any Module

# =============================================================================

# 4. MESSAGE TEXTS (REUSABLE CLI MESSAGES)

# =============================================================================

# -----------------------------------------------------------------------------

# LEVEL LABELS (STATIC - FIXED STRINGS)

# Visual indicators for CLI Output and Log Entries.

# readonly applied: Never modified after initialization.

# -----------------------------------------------------------------------------

readonly MSG_SUCCESS="[SUCCESS]"

readonly MSG_ERROR="[ERROR]"

readonly MSG_WARNING="[WARNING]"

readonly MSG_INFO="[INFO]"

# -----------------------------------------------------------------------------

# REUSABLE ERROR/WARNING MESSAGES (STATIC - FIXED STRINGS)

# Predefined Messages used at Multiple Points throughout Script execution.

# readonly applied: Never modified after initialization.

# Centralization ensures consistent Wording across all Modules.

# Single-Point modification enables future text adjustments.

# -----------------------------------------------------------------------------

readonly MSG_STALE_FILES_FOUND="Stale Temporary Files found."

readonly MSG_NO_STALE_FILES="No Stale Temporary Files found."

readonly MSG_ARRAY_INTEGRITY_CONFIRMED="Array Integrity confirmed."

readonly MSG_NO_USB_STICK="No USB Stick detected, but Flash License active."

readonly MSG_USB_STICK_DETECTED="USB Stick detected for License."

readonly MSG_TPM_MODE="TPM Mode: No USB Stick required for License."

readonly MSG_CONFIG_FILE_NOT_FOUND="Configuration File not found."

readonly MSG_VARINI_NOT_FOUND="var.ini not found."

readonly MSG_NO_TPM_DEVICE="No TPM Device available."

readonly MSG_UNZIP_NOT_FOUND="'unzip' Command not found. Integrity Check impossible."

readonly MSG_FLASH_BACKUP_FAILED="Flash Backup Tool failed."

readonly MSG_INVALID_FILENAME="Invalid Filename generated."

readonly MSG_SOURCE_FILE_MISSING="Source File does not exist."

readonly MSG_COPY_FAILED="Copying Backup File failed."

readonly MSG_BACKUP_CORRUPTED="Backup File corrupted! Removing defective File."

readonly MSG_CHANGE_DIR_FAILED="Change to Directory failed."

readonly MSG_PROCESS_ABORTED="Process aborted: Manual cleanup required."

readonly MSG_WARNING_IGNORED="Warning ignored."

readonly MSG_CLEANUP_STARTED="Auto cleanup initiated."

readonly MSG_CLEANUP_FAILED="Cleanup failed. No Files removed. Manual intervention required."

readonly MSG_CLEANUP_SKIPPED="Auto cleanup skipped (Disabled or no Stale Files)."

# =============================================================================

# 5. HELPER FUNCTIONS

# =============================================================================

# -----------------------------------------------------------------------------

# PRINT HEADER BANNER (CLI OUTPUT - STARTUP)

# Displays Script Title, Host Name, and Timestamp at Initialization.

# Single Invocation before any Module Execution.

# Visibility Controlled by SHOW_CLI_OUTPUT Feature Flag.

# -----------------------------------------------------------------------------

PRINT_HEADER() {

echo "=============================================================="

echo " ${SCRIPT_TITLE}"

echo "=============================================================="

echo "HOST: $(hostname)"

echo "TIMESTAMP: $(date '+%d.%m.%Y %H:%M')"

echo "----------------------------------------"

}

# -----------------------------------------------------------------------------

# PRINT SECTION HEADER (CLI OUTPUT - BETWEEN MODULES)

# Displays formatted Section Title between Module Blocks.

# Parameter: $1 = Section Title (from MOD_TITLE_* Variables)

# Visibility Controlled by SHOW_CLI_OUTPUT Feature Flag.

# -----------------------------------------------------------------------------

PRINT_SECTION() {

echo ""

echo "[${1}]"

echo "--------------------------------------------------------------"

}

# -----------------------------------------------------------------------------

# PRINT MESSAGE WITH LEVEL PREFIX (CLI OUTPUT - UNIFIED)

# Centralized Message formatting for consistent CLI presentation.

# Parameters: $1 = Level (SUCCESS/ERROR/WARNING/INFO), $2 = Message Text

# Constructs Formatted Output String in Local Variable OUTPUT.

# stdbuf Applies Line-buffered Terminal Display when SHOW_CLI_OUTPUT=TRUE.

# -----------------------------------------------------------------------------

PRINT_MSG() {

local LEVEL="$1"

local MSG="$2"

local OUTPUT=""

case "$LEVEL" in

SUCCESS) OUTPUT="${MSG_SUCCESS} ${MSG}" ;;

ERROR) OUTPUT="${MSG_ERROR} ${MSG}" ;;

WARNING) OUTPUT="${MSG_WARNING} ${MSG}" ;;

INFO) OUTPUT="${MSG_INFO} ${MSG}" ;;

*) OUTPUT="$MSG" ;;

esac

# Line-buffered Output for immediate Display

stdbuf -oL echo "$OUTPUT"

}

# -----------------------------------------------------------------------------

# WRITE TIMESTAMPED ENTRY TO LOG FILE (LOGGING - UNIFIED)

# Appends Single Log Entry with Timestamp and Host Name.

# Parameter: $1 = Message Text

# Destination: LOG_FILE_PATH (Current Session Log)

# Invocation: All Modules (Independent of SHOW_CLI_OUTPUT Setting).

# -----------------------------------------------------------------------------

LOG_TO_FILE() {

local MSG="$1"

local TIMESTAMPED_MSG="[$(date '+%Y-%m-%d %H:%M:%S')] [$(hostname)] $MSG"

echo "$TIMESTAMPED_MSG" >> "$LOG_FILE_PATH"

}

# -----------------------------------------------------------------------------

# MASK GUID FORMAT (SENSITIVE DATA OBSCURATION)

# Preserves GUID Structure while obscuring sensitive Content.

#

# UNRAID GUID STRUCTURE (Uppercase Alphanumeric: 0-9, A-Z only):

#

# - TPM GUIDs: Starts with "01-", 2 Segments (1 Dash)

# Example: "01-ABC23D123456GHIJ7890KLMN"

# → Masked: "01-ABC23*******************"

# Recognition: "01-" Prefix, first 5 Chars of Segment 2 visible.

#

# - Flash GUIDs: 4 Segments (3 Dashes)

# Example: "0789-1012-3456-789AABE12345"

# → Masked: "0789-10**-34**-789AB*******"

# Recognition: All 4 Segments visible, specific show/mask per Segment.

#

# MASKING LOGIC:

# - TPM: Show "01-" + first 5 Chars of Segment 2 + rest as ****

# - Flash Seg 1: Show all if ≤4 Chars, else first 4 + ****

# - Flash Seg 2: Show first 2 Chars + **** (Pad to 4 total)

# - Flash Seg 3: Show first 2 Chars + **** (Pad to 4 total)

# - Flash Seg 4: Show first 5 Chars + rest as ****

#

# PARAMETER: $1 = Original GUID String (expected Uppercase Alphanumeric)

# RETURN: Masked GUID String preserving original Format Structure

# -----------------------------------------------------------------------------

MASK_GUID_FORMAT() {

local GUID="$1"

if [[ -z "$GUID" ]]; then echo "---"; return; fi

# TPM GUID

if [[ "$GUID" == 01-* ]]; then

local TPM_PART="${GUID#01-}"

local PART_LEN=${#TPM_PART}

if [[ ${PART_LEN} -gt 5 ]]; then

local SHOW_CHARS="${TPM_PART:0:5}"

local MASK_LEN=$((PART_LEN - 5))

echo "01-${SHOW_CHARS}$(printf '*%.0s' $(seq 1 ${MASK_LEN}))"

elif [[ ${PART_LEN} -gt 0 ]]; then

echo "01-${TPM_PART}****"

else

echo "01-****"

fi

return

fi

# Flash GUID

local -a SEGMENTS

IFS='-' read -ra SEGMENTS <<< "$GUID"

if [[ ${#SEGMENTS[@]} -ge 4 ]]; then

local S1="${SEGMENTS[0]}"

local S2="${SEGMENTS[1]}"

local S3="${SEGMENTS[2]}"

local S4="${SEGMENTS[3]}"

# Segment 1: Show all if ≤4 Chars, else first 4 + ****

local S1_LEN=${#S1}

local MASKED_S1

if [[ ${S1_LEN} -le 4 ]]; then

MASKED_S1="${S1}"

else

MASKED_S1="${S1:0:4}$(printf '*%.0s' $(seq 1 $((S1_LEN - 4))))"

fi

# Segment 2: Show first 2 Chars + ** padding

local S2_LEN=${#S2}

local MASKED_S2

if [[ ${S2_LEN} -ge 2 ]]; then

MASKED_S2="${S2:0:2}**"

elif [[ ${S2_LEN} -eq 1 ]]; then

MASKED_S2="${S2}***"

else

MASKED_S2="****"

fi

# Segment 3: Show first 2 Chars + ** padding

local S3_LEN=${#S3}

local MASKED_S3

if [[ ${S3_LEN} -ge 2 ]]; then

MASKED_S3="${S3:0:2}**"

elif [[ ${S3_LEN} -eq 1 ]]; then

MASKED_S3="${S3}***"

else

MASKED_S3="****"

fi

# Segment 4: Show first 5 Chars + rest as ****

local S4_LEN=${#S4}

local MASKED_S4

if [[ ${S4_LEN} -gt 5 ]]; then

MASKED_S4="${S4:0:5}$(printf '*%.0s' $(seq 1 $((S4_LEN - 5))))"

elif [[ ${S4_LEN} -gt 0 ]]; then

MASKED_S4="${S4}****"

else

MASKED_S4="****"

fi

echo "${MASKED_S1}-${MASKED_S2}-${MASKED_S3}-${MASKED_S4}"

elif [[ ${#SEGMENTS[@]} -ge 2 ]]; then

# 2+ Segments but not 4 — generic fallback

local FIRST="${SEGMENTS[0]}"

local SECOND="${SEGMENTS[1]}"

if [[ ${#SECOND} -gt 5 ]]; then

echo "${FIRST}-${SECOND:0:5}$(printf '*%.0s' $(seq 1 $(${#SECOND} - 5)))"

elif [[ ${#SECOND} -gt 0 ]]; then

echo "${FIRST}-${SECOND}****"

else

echo "${FIRST}-****"

fi

else

# Single Segment

if [[ ${#GUID} -gt 5 ]]; then

echo "${GUID:0:5}$(printf '*%.0s' $(seq 1 $(${#GUID} - 5)))"

elif [[ ${#GUID} -gt 0 ]]; then

echo "${GUID}****"

else

echo "****"

fi

fi

}

# -----------------------------------------------------------------------------

# CLEAN VENDOR NAME (DEVICE STRING NORMALIZATION)

# Extracts Primary Vendor Name from USB Device String.

# Removes Common Model Suffixes (Cruzer, DataTraveler, Flash).

# Input Example: "SanDisk Cruzer Blade" → "SanDisk"

# Parameter: $1 = Raw Vendor String from lsusb Output

# Return: Cleaned Vendor Name Only

# -----------------------------------------------------------------------------

CLEAN_VENDOR_NAME() {

local RAW_VENDOR="$1"

local VENDOR=$(echo "$RAW_VENDOR" | awk '{print $1}')

if [[ "$VENDOR" == "Cruzer" ]]; then

VENDOR=$(echo "$VENDOR" | sed 's/Cruzer$//g')

elif [[ "$VENDOR" == "DataTraveler" ]]; then

VENDOR=$(echo "$VENDOR" | sed 's/DataTraveler$//g')

elif [[ "$VENDOR" == "Flash" ]]; then

VENDOR=$(echo "$VENDOR" | sed 's/Flash$//g')

fi

echo "$VENDOR" | xargs

}

# -----------------------------------------------------------------------------

# CLEAN MODEL NAME (DEVICE STRING NORMALIZATION)

# Strips Storage Capacity Suffixes from Device Model Strings.

# Matches and Removes: GB, TB, MB, GiB, MiB with Numeric Prefixes.

# Input Example: "WD Red SN700 1TB" → "WD Red SN700"

# Parameter: $1 = Raw Model String

# Return: Cleaned Model Name without Capacity Information

# -----------------------------------------------------------------------------

CLEAN_MODEL_NAME() {

local RAW_MODEL="$1"

local CLEANED=$(echo "$RAW_MODEL" | sed -E 's/[[:space:]]*[0-9]+\.?[0-9]*(GB|TB|MB|GiB|MiB)[[:space:]]*$//gi')

if [[ -z "$CLEANED" ]]; then

echo "$RAW_MODEL"

else

echo "$CLEANED"

fi

}

# -----------------------------------------------------------------------------

# SEND ERROR NOTIFICATION (CRITICAL ERROR HANDLER)

# Triggers Unraid System Notification for Critical Errors.

# Parameters: $1 = Title (Optional, Defaults to NOTIFY_ERROR_TITLE)

# $2 = Subject (Optional, Defaults to NOTIFY_ERROR_SUBJECT)

# $3 = Message (Required)

# Hardcoded "-i error" Icon for Unraid Notify Script Compatibility.

# -----------------------------------------------------------------------------

SEND_ERROR_NOTIFICATION() {

local TITLE="${1:-${NOTIFY_ERROR_TITLE}}"

local SUBJECT="${2:-${NOTIFY_ERROR_SUBJECT}}"

local MESSAGE="$3"

"$NOTIFICATION_SCRIPT" \

-e "$TITLE" \

-s "$SUBJECT" \

-d "$MESSAGE" \

-i "error"

}

# -----------------------------------------------------------------------------

# HANDLE CRITICAL ERROR AND EXIT (FATAL CONDITION HANDLER)

# Centralized Error Handler for Fatal Conditions in Module 5.

# Parameters: $1 = Log Message (Written to Log File)

# $2 = Notification Message (Sent via System Alert)

# Sequential Actions: Log Entry → Notification Send → Exit with Code 1.

# Exclusive Use by Module 5 for Backup Creation Failures.

# -----------------------------------------------------------------------------

HANDLE_ERROR_EXIT() {

local LOG_MSG="$1"

local NOTIFY_MSG="$2"

local LOG_PATH="$LOG_FILE_PATH"

LOG_TO_FILE "${MSG_ERROR} ${LOG_MSG}"

SEND_ERROR_NOTIFICATION "" "" "${NOTIFY_MSG}"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "${MSG_ERROR} ${LOG_MSG}"

echo "────────────────────────────────────────────────────────────"

echo "→ For detailed Error Information, check Log File:"

echo " $LOG_PATH"

echo "────────────────────────────────────────────────────────────"

fi

exit 1

}

# =============================================================================

# 6. INITIALIZATION

# =============================================================================

# Create Required Directories

# Precedence: Created before any Module execution.

# Failure Condition: Script Termination with Error Notification.

# mkdir -p Behavior: Parent Directories Created Automatically as needed.

mkdir -p "$BACKUP_BASE_DIR" || {

SEND_ERROR_NOTIFICATION "" "" "Cannot create Backup Directory."

exit 1

}

mkdir -p "${ARCHIVE_LOG_DIR}" || {

SEND_ERROR_NOTIFICATION "" "" "Cannot create Archive Log Directory."

exit 1

}

mkdir -p "${LOCAL_LOG_DIR}" || {

SEND_ERROR_NOTIFICATION "" "" "Cannot create Local Log Directory."

exit 1

}

# Create Log Files

# Command: touch Creates Empty Files if Non-existent.

# Failure Condition: Script Termination with Error Notification.

touch "$LOCAL_LOG_PATH" 2>/dev/null || {

SEND_ERROR_NOTIFICATION "" "" "Cannot create Local Log File."

exit 1

}

touch "$ARCHIVE_LOG_PATH" 2>/dev/null || {

SEND_ERROR_NOTIFICATION "" "" "Cannot create Archive Log File."

exit 1

}

# Write Initial Log Entry

# Mechanism: Block-Redirect Overwrites Log Files with Startup Metadata.

# Purpose: Establishes Baseline for Session Tracking.

{

echo "=========================================="

echo "START ${SCRIPT_TITLE}"

echo "TIMESTAMP: $(date '+%d.%m.%Y %H:%M')"

echo "HOSTNAME: ${HOST_NAME}"

echo "BACKUP TARGET: ${BACKUP_BASE_DIR}"

echo "LOCAL LOG: ${LOCAL_LOG_PATH}"

echo "ARCHIVE LOG: ${ARCHIVE_LOG_PATH}"

echo "LAST RUN LOG: ${LAST_RUN_LOG}"

echo "=========================================="

} > "$LOG_FILE_PATH"

# Print CLI Header (Conditional)

# Visibility Controlled by SHOW_CLI_OUTPUT Feature Flag.

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_HEADER

fi

# =============================================================================

# 7. MODULES

# =============================================================================

# Execution Model: 9 Modular Blocks Process Distinct Tasks Sequentially.

# Conditional Execution: Each Module Respects Corresponding ENABLE_* Feature Flag.

# Status Accumulation: OVERALL_STATUS Collects Warnings and Errors Across Modules.

# Final Determination: Overall Status Sets Completion Message and Notification Priority.

# Functional Categorization:

# - Modules 1-4: Validation and Analysis (Pre-Backup Checks)

# - Modules 5-7: Core Operations (Backup + Retention)

# - Module 8: Auto Cleanup (Deferred Stale File removal)

# - Module 9: Send Notification (Final Report with complete Data)

# Execution Characteristics:

# - Conditional Execution Based on ENABLE_* Flags

# - Sequential Processing (Order Preserved as Listed)

# - State Propagation via Global Variables

# - Error Handling via Centralized Handlers

# - Log Recording Independent of CLI Output Setting

# - Status Escalation Preserves Highest Severity (ERROR > WARNING > SUCCESS)

# --- MODULE 1: LICENSE STATUS -----------------------------------------------

if [ "$ENABLE_LICENSE_CHECK" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 1: ${MOD_TITLE_1}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_1}"

fi

# Local Module Variables

LOCAL_SERVER_NAME=""

LOCAL_OS_VERSION=""

LOCAL_LICENSE_TYPE=""

LOCAL_FLASH_GUID=""

LOCAL_TPM_GUID=""

LOCAL_REG_GUID=""

# Parse var.ini Configuration File

if [ -f "$VAR_INI_PATH" ]; then

LOCAL_SERVER_NAME=$(awk -F'=' '/^NAME=/ {gsub(/"/, "", $2); gsub(/ /, "_", $2); print $2}' "$VAR_INI_PATH")

LOCAL_OS_VERSION=$(awk -F'=' '/^version=/ {gsub(/"/, "", $2); print $2}' "$VAR_INI_PATH")

LOCAL_LICENSE_TYPE=$(awk -F'=' '/^regTy=/ {gsub(/"/, "", $2); print $2}' "$VAR_INI_PATH" 2>&1) || true

if [[ "$LOCAL_LICENSE_TYPE" "error" ]] || [[ "$LOCAL_LICENSE_TYPE" "cannot" ]]; then

LOG_TO_FILE "${MSG_WARNING} awk for regTy reported issues: $LOCAL_LICENSE_TYPE"

LOCAL_LICENSE_TYPE=""

fi

LOCAL_FLASH_GUID=$(grep '^flashGUID=' "$VAR_INI_PATH" 2>&1 | cut -d'"' -f2 | tr -d '[:space:]') || true

if [[ "$LOCAL_FLASH_GUID" "error" ]] || [[ "$LOCAL_FLASH_GUID" "No such" ]]; then

LOG_TO_FILE "${MSG_WARNING} grep for flashGUID reported issues: $LOCAL_FLASH_GUID"

LOCAL_FLASH_GUID=""

fi

LOCAL_TPM_GUID=$(grep '^tpmGUID=' "$VAR_INI_PATH" 2>&1 | cut -d'"' -f2 | tr -d '[:space:]') || true

if [[ "$LOCAL_TPM_GUID" "error" ]] || [[ "$LOCAL_TPM_GUID" "No such" ]]; then

LOG_TO_FILE "${MSG_WARNING} grep for tpmGUID reported issues: $LOCAL_TPM_GUID"

LOCAL_TPM_GUID=""

fi

LOCAL_REG_GUID=$(grep '^regGUID=' "$VAR_INI_PATH" 2>&1 | cut -d'"' -f2 | tr -d '[:space:]') || true

if [[ "$LOCAL_REG_GUID" "error" ]] || [[ "$LOCAL_REG_GUID" "No such" ]]; then

LOG_TO_FILE "${MSG_WARNING} grep for regGUID reported issues: $LOCAL_REG_GUID"

LOCAL_REG_GUID=""

fi

LICENSE_TYPE="$LOCAL_LICENSE_TYPE"

else

PRINT_MSG "ERROR" "${MSG_ERROR} ${MSG_CONFIG_FILE_NOT_FOUND}"

LOG_TO_FILE "${MSG_WARNING} ${MSG_VARINI_NOT_FOUND}"

fi

# Determine License Binding Type

LICENSE_STATUS="Unknown"

DEVICE_BINDING="None"

if [[ "$LOCAL_FLASH_GUID" == "$LOCAL_REG_GUID" ]] && [[ "$LOCAL_FLASH_GUID" != "$LOCAL_TPM_GUID" || -z "$LOCAL_TPM_GUID" ]]; then

LICENSE_STATUS="USB_FLASH"

DEVICE_BINDING="USB Stick"

if [[ -n "$LOCAL_TPM_GUID" ]] && [[ "$LOCAL_FLASH_GUID" != "$LOCAL_TPM_GUID" ]]; then

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "TPM Key present, currently not used."

fi

LOG_TO_FILE "${MSG_INFO} TPM Key present, currently not used."

fi

elif [[ "$LOCAL_FLASH_GUID" "$LOCAL_TPM_GUID" ]] && [[ "$LOCAL_FLASH_GUID" "$LOCAL_REG_GUID" ]]; then

LICENSE_STATUS="FULL_TPM"

DEVICE_BINDING="TPM"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "TPM Key used for Device Binding and TPM Licensing is active."

fi

LOG_TO_FILE "${MSG_SUCCESS} TPM Key used for Device Binding and TPM Licensing is active."

elif [[ "$LOCAL_REG_GUID" == "$LOCAL_TPM_GUID" ]]; then

LICENSE_STATUS="TPM_MIGRATION"

DEVICE_BINDING="TPM"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "TPM Key used for Device Binding and TPM Licensing is available."

fi

LOG_TO_FILE "${MSG_INFO} TPM Key used for Device Binding and TPM Licensing is available."

else

LICENSE_STATUS="INVALID_CONFIG"

DEVICE_BINDING="Unknown"

LOG_TO_FILE "${MSG_WARNING} GUID Configuration could not be uniquely identified."

fi

# Mask GUIDs for Secure Display

LOCAL_FLASH_MASKED=$(MASK_GUID_FORMAT "$LOCAL_FLASH_GUID")

LOCAL_TPM_MASKED=$(MASK_GUID_FORMAT "$LOCAL_TPM_GUID")

LOCAL_REG_MASKED=$(MASK_GUID_FORMAT "$LOCAL_REG_GUID")

# CLI Output (Consistent Format - Title Case for UI Terms)

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo "USB LICENSE ID: ${LOCAL_FLASH_MASKED:-N/A}"

echo "TPM KEY: ${LOCAL_TPM_MASKED:-N/A}"

echo "ACTIVE REGISTRATION: ${LOCAL_REG_MASKED:-N/A}"

echo "----------------------------------------"

echo "SERVER NAME: ${LOCAL_SERVER_NAME:-Unavailable}"

echo "OS VERSION: ${LOCAL_OS_VERSION:-Unavailable}"

echo "LICENSE TYPE: ${LOCAL_LICENSE_TYPE:-Not detected or invalid}"

echo "DETECTED BINDING: ${DEVICE_BINDING} (${LICENSE_STATUS})"

echo "----------------------------------------"

echo "DETECTED MODE: ${LICENSE_STATUS}"

echo "LICENSE DEVICE TYPE: ${DEVICE_BINDING}"

fi

# Check License Status and Update Overall Status

if [[ "$LICENSE_STATUS" == "INVALID_CONFIG" ]]; then

LOG_TO_FILE "${MSG_WARNING} License Configuration invalid"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "License Configuration invalid"

fi

OVERALL_STATUS="WARNING"

fi

# Log Module Results

LOG_TO_FILE "USB LICENSE ID: ${LOCAL_FLASH_MASKED:-N/A}"

LOG_TO_FILE "TPM KEY: ${LOCAL_TPM_MASKED:-N/A}"

LOG_TO_FILE "ACTIVE REGISTRATION: ${LOCAL_REG_MASKED:-N/A}"

LOG_TO_FILE "SERVER NAME: ${LOCAL_SERVER_NAME:-Unavailable}"

LOG_TO_FILE "OS VERSION: ${LOCAL_OS_VERSION:-Unavailable}"

LOG_TO_FILE "LICENSE TYPE: ${LOCAL_LICENSE_TYPE:-Undetermined}"

LOG_TO_FILE "DETECTED BINDING: ${DEVICE_BINDING} (${LICENSE_STATUS})"

LOG_TO_FILE "---[${MOD_TITLE_1} COMPLETED]---"

LOG_TO_FILE ""

else

LOG_TO_FILE "---[MODULE 1: ${MOD_TITLE_1}]---"

LOG_TO_FILE "MODULE [DISABLED]"

fi

# --- MODULE 2: BOOT MEDIUM ANALYSIS -----------------------------------------

if [ "$ENABLE_BOOT_CHECK" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 2: ${MOD_TITLE_2}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_2}"

fi

BOOT_MEDIUM_TYPE="Unknown"

# Local Variables for Boot Analysis

BOOT_DEVICE_NAME=""

PARENT_DEVICE=""

TRANSPORT=""

SYSTEM_DEV_FINAL=""

SYS_BOOT_TYPE="Unknown"

SYS_VENDOR=""

SYS_MODEL_CLEAN=""

# Detect Storage Types (NVMe, SATA, MMC)

LSBLK_OUTPUT=$(lsblk -d -no NAME,TYPE 2>&1) || true

if echo "$LSBLK_OUTPUT" | grep -qiE "error|cannot"; then

LOG_TO_FILE "${MSG_WARNING} lsblk reported issues: $LSBLK_OUTPUT"

fi

NVME_DEV_GLOBAL=$(echo "$LSBLK_OUTPUT" | grep 'nvme' | awk '{print $1}' | head -1)

SDA_DEV_GLOBAL=$(echo "$LSBLK_OUTPUT" | grep -E '^sd[a-z]$' | awk '{print $1}' | head -1)

MMC_DEV_GLOBAL=$(echo "$LSBLK_OUTPUT" | grep 'mmc' | awk '{print $1}' | head -1)

# Analyze Flash Label Link

if [ -e "/dev/disk/by-label/flash" ]; then

REAL_LINK=$(readlink -f "/dev/disk/by-label/flash")

BOOT_DEVICE_NAME=$(basename "$REAL_LINK")

# Remove Partition Number Suffix

if [[ "$BOOT_DEVICE_NAME" =~ [0-9]+$ ]]; then

PARENT_DEVICE="${BOOT_DEVICE_NAME%[0-9]*}"

else

PARENT_DEVICE="$BOOT_DEVICE_NAME"

fi

# Determine Transport Protocol (USB, SAS, etc.)

TRANSPORT=$(lsblk -no TRAN "$PARENT_DEVICE" 2>&1 | tr -d '[:space:]') || true

if [[ "$TRANSPORT" "error" ]] || [[ "$TRANSPORT" "cannot" ]]; then

LOG_TO_FILE "${MSG_WARNING} lsblk for transport reported issues: $TRANSPORT"

TRANSPORT=""

fi

case "$TRANSPORT" in

usb)

BOOT_MEDIUM_TYPE="USB Stick Boot"

;;

*)

SYS_PATH="/sys/block/$PARENT_DEVICE/device"

if [ -L "$SYS_PATH" ]; then

FULL_PATH=$(readlink -f "$SYS_PATH")

if [[ "$FULL_PATH" =~ usb ]]; then

BOOT_MEDIUM_TYPE="USB Stick Boot"

else

BOOT_MEDIUM_TYPE="Internal Boot"

fi

else

BOOT_MEDIUM_TYPE="Internal Boot"

fi

;;

esac

# Get Device Details for Internal Boot

if [[ "$BOOT_MEDIUM_TYPE" == "Internal Boot" ]]; then

if [[ -n "$NVME_DEV_GLOBAL" ]]; then

SYSTEM_DEV_FINAL="$NVME_DEV_GLOBAL"

elif [[ -n "$SDA_DEV_GLOBAL" ]]; then

SYSTEM_DEV_FINAL="$SDA_DEV_GLOBAL"

elif [[ -n "$MMC_DEV_GLOBAL" ]]; then

SYSTEM_DEV_FINAL="$MMC_DEV_GLOBAL"

fi

SYS_BOOT_TYPE="Internal Boot"

if [[ -n "$SYSTEM_DEV_FINAL" ]] && [[ -b "/dev/$SYSTEM_DEV_FINAL" ]]; then

VENDOR_RAW=$(lsblk -no VENDOR "/dev/$SYSTEM_DEV_FINAL" 2>&1 | head -1 | xargs) || true

MODEL_RAW=$(lsblk -no MODEL "/dev/$SYSTEM_DEV_FINAL" 2>&1 | head -1 | xargs) || true

if [[ "$VENDOR_RAW" "error" ]] || [[ "$MODEL_RAW" "error" ]]; then

LOG_TO_FILE "${MSG_WARNING} lsblk for vendor/model reported issues: VENDOR=$VENDOR_RAW MODEL=$MODEL_RAW"

fi

if [[ -z "$VENDOR_RAW" || "$VENDOR_RAW" == "" ]]; then

SYS_VENDOR="(see Model)"

else

SYS_VENDOR="$VENDOR_RAW"

fi

SYS_MODEL_CLEAN=$(CLEAN_MODEL_NAME "$MODEL_RAW")

fi

fi

else

LOG_TO_FILE "${MSG_WARNING} /dev/disk/by-label/flash not found."

fi

# Gather License Device Information

LIC_VENDOR="-"

LIC_MODEL_CLEAN="-"

LIC_TYPE=""

if [[ "$DEVICE_BINDING" == "USB Stick" ]]; then

LSUSB_OUTPUT=$(lsusb 2>&1) || true

if echo "$LSUSB_OUTPUT" | grep -qiE "error|command not found"; then

LOG_TO_FILE "${MSG_WARNING} lsusb reported issues: $LSUSB_OUTPUT"

fi

USB_LINE=$(echo "$LSUSB_OUTPUT" | grep -iE "$USB_BRAND_PATTERN" | head -1)

if [[ -n "$USB_LINE" ]]; then

RAW_FULL=$(echo "$USB_LINE" | sed 's/.*ID [0-9a-f:]* //' | sed 's/ Corp\. //g' | sed 's/ Inc\. //g' | xargs)

LIC_VENDOR=$(echo "$RAW_FULL" | awk '{print $1}')

LIC_VENDOR=$(CLEAN_VENDOR_NAME "$LIC_VENDOR")

if [[ "$LIC_VENDOR" == "$RAW_FULL" ]]; then

LIC_MODEL_CLEAN="Unknown USB Device"

else

MOD_PART=$(echo "$RAW_FULL" | sed "s/^${LIC_VENDOR}[[:space:]]*//")

LIC_MODEL_CLEAN=$(CLEAN_MODEL_NAME "$MOD_PART")

fi

fi

[[ -z "$LIC_VENDOR" ]] && LIC_VENDOR="Unknown"

[[ -z "$LIC_MODEL_CLEAN" ]] && LIC_MODEL_CLEAN="Unknown"

LIC_TYPE="USB Stick"

else

LIC_TYPE="TPM"

fi

LOG_TO_FILE "BOOT MEDIUM TYPE: ${BOOT_MEDIUM_TYPE}"

LOG_TO_FILE ""

LOG_TO_FILE "LICENSE DEVICE"

LOG_TO_FILE "TYPE: ${LIC_TYPE}"

LOG_TO_FILE "MANUFACTURER: ${LIC_VENDOR}"

LOG_TO_FILE "MODEL: ${LIC_MODEL_CLEAN}"

LOG_TO_FILE ""

LOG_TO_FILE "SYSTEM DEVICE (OS)"

LOG_TO_FILE "TYPE: ${SYS_BOOT_TYPE}"

LOG_TO_FILE "MANUFACTURER: ${SYS_VENDOR}"

LOG_TO_FILE "MODEL: ${SYS_MODEL_CLEAN}"

# CLI Output (Title Case for UI Terms: USB Stick, TPM, Internal Boot, Backup File, etc.)

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo "BOOT MEDIUM TYPE: ${BOOT_MEDIUM_TYPE}"

echo ""

echo "LICENSE DEVICE"

echo "TYPE: ${LIC_TYPE}"

echo "MANUFACTURER: ${LIC_VENDOR}"

echo "MODEL: ${LIC_MODEL_CLEAN}"

echo ""

echo "SYSTEM DEVICE (OS)"

echo "TYPE: ${SYS_BOOT_TYPE}"

echo "MANUFACTURER: ${SYS_VENDOR}"

echo "MODEL: ${SYS_MODEL_CLEAN}"

fi

# Check Boot Medium Status

if [[ "$BOOT_MEDIUM_TYPE" == "Unknown" ]]; then

LOG_TO_FILE "${MSG_WARNING} Boot Medium could not be identified"

OVERALL_STATUS="WARNING"

fi

LOG_TO_FILE "---[${MOD_TITLE_2} COMPLETED]---"

LOG_TO_FILE ""

else

LOG_TO_FILE "---[MODULE 2: ${MOD_TITLE_2}]---"

LOG_TO_FILE "MODULE [DISABLED]"

fi

# --- MODULE 3: HARDWARE CHECK -----------------------------------------------

if [ "$ENABLE_HARDWARE_CHECK" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 3: ${MOD_TITLE_3}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_3}"

fi

# TPM Device Check

if [ -c /dev/tpm0 ] || [ -c /dev/tpmrm0 ]; then

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "TPM Hardware detected."

fi

LOG_TO_FILE "${MSG_SUCCESS} TPM Hardware detected."

else

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "${MSG_NO_TPM_DEVICE}"

fi

LOG_TO_FILE "${MSG_WARNING} ${MSG_NO_TPM_DEVICE}"

fi

# USB Stick Check (Only Relevant for USB Flash License)

if [[ "$LICENSE_STATUS" == "USB_FLASH" ]]; then

LSUSB_OUTPUT=$(lsusb 2>&1) || true

if [[ $(echo "$LSUSB_OUTPUT" | grep -ciE "$USB_BRAND_PATTERN") -eq 0 ]]; then

if echo "$LSUSB_OUTPUT" | grep -qiE "error|command not found"; then

LOG_TO_FILE "${MSG_WARNING} lsusb reported issues during notification: $LSUSB_OUTPUT"

fi

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "${MSG_NO_USB_STICK}"

fi

LOG_TO_FILE "${MSG_ERROR} ${MSG_NO_USB_STICK}"

else

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "${MSG_USB_STICK_DETECTED}"

fi

LOG_TO_FILE "${MSG_INFO} ${MSG_USB_STICK_DETECTED}"

fi

else

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "${MSG_TPM_MODE}"

fi

LOG_TO_FILE "${MSG_INFO} ${MSG_TPM_MODE}"

fi

# Missing USB Device for Flash License

LSUSB_FINAL=$(lsusb 2>&1) || true

if [[ "$LICENSE_STATUS" == "USB_FLASH" ]] && [[ $(echo "$LSUSB_FINAL" | grep -ciE "$USB_BRAND_PATTERN") -eq 0 ]]; then

if echo "$LSUSB_FINAL" | grep -qiE "error|command not found"; then

LOG_TO_FILE "${MSG_WARNING} lsusb reported issues during final USB check: $LSUSB_FINAL"

fi

OVERALL_STATUS="ERROR"

fi

LOG_TO_FILE "---[${MOD_TITLE_3} COMPLETED]---"

LOG_TO_FILE ""

else

LOG_TO_FILE "---[MODULE 3: ${MOD_TITLE_3}]---"

LOG_TO_FILE "MODULE [DISABLED]"

fi

# --- MODULE 4: SYSTEM INTEGRITY CHECK ---------------------------------------

if [ "$ENABLE_SYSTEM_CHECK" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 4: ${MOD_TITLE_4}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_4}"

fi

# Show Dirty Check Status

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo "DIRTY CHECK OVERRIDE: ${SKIP_DIRTY_CHECK}"

echo "AUTO CLEANUP: ${ENABLE_AUTO_CLEANUP}"

echo "----------------------------------------"

fi

LOG_TO_FILE "${MSG_INFO} SKIP_DIRTY_CHECK: ${SKIP_DIRTY_CHECK}"

LOG_TO_FILE "${MSG_INFO} AUTO CLEANUP: ${ENABLE_AUTO_CLEANUP}"

# CHECK 1: Array Disk Count

DISK_COUNT=$(ls -d /mnt/disk* 2>/dev/null | wc -l)

if [ "$DISK_COUNT" -lt "$MIN_DISKS" ]; then

LOG_TO_FILE "${MSG_ERROR} Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."

SEND_ERROR_NOTIFICATION "" "" "Process aborted: Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "Only ${DISK_COUNT} Disks found. Minimum ${MIN_DISKS} required."

fi

exit 1

fi

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "${MSG_ARRAY_INTEGRITY_CONFIRMED} (${DISK_COUNT} Disks present)"

fi

LOG_TO_FILE "${MSG_SUCCESS} ${MSG_ARRAY_INTEGRITY_CONFIRMED} (${DISK_COUNT} Disks present)"

# CHECK 2: Stale Temporary Files

HAS_STALE_FILES=FALSE

declare -a STALE_FILES=()

# Find Stale Files in WebGUI Temp Directory

FIND_OUTPUT=$(find /usr/local/emhttp/ -maxdepth 1 -name "*boot-backup*.zip" \( -type f -o -type l \) 2>&1)

FIND_EXIT=$?

# Log non-zero exit if not just expected permission denied

if [ $FIND_EXIT -ne 0 ] || echo "$FIND_OUTPUT" | grep -qiE "permission denied|no such file|error"; then

LOG_TO_FILE "${MSG_WARNING} find in /usr/local/emhttp reported issues:"

LOG_TO_FILE "[DEBUG] $FIND_OUTPUT"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "find encountered issues: $FIND_OUTPUT"

fi

fi

while IFS= read -r F; do

[[ -n "$F" ]] && STALE_FILES+=("WEBGUI|$F")

done <<< "$FIND_OUTPUT"

# Find Stale Files in RootFS Directory

FIND_OUTPUT=$(find / -maxdepth 1 -name "*boot-backup*.zip" -type f 2>&1)

FIND_EXIT=$?

if [ $FIND_EXIT -ne 0 ] || echo "$FIND_OUTPUT" | grep -qiE "permission denied|no such file|error"; then

LOG_TO_FILE "${MSG_WARNING} find in / reported issues:"

LOG_TO_FILE "[DEBUG] $FIND_OUTPUT"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "find encountered issues: $FIND_OUTPUT"

fi

fi

while IFS= read -r F; do

[[ -n "$F" ]] && STALE_FILES+=("ROOTFS|$F")

done <<< "$FIND_OUTPUT"

STALE_COUNT=${#STALE_FILES[@]}

if [ "$STALE_COUNT" -gt 0 ]; then

HAS_STALE_FILES=TRUE

LOG_TO_FILE "${MSG_WARNING} ${STALE_COUNT} ${MSG_STALE_FILES_FOUND}"

# Abort if SKIP_DIRTY_CHECK not set

if [ -z "$SKIP_DIRTY_CHECK" ] || [ "$SKIP_DIRTY_CHECK" = "FALSE" ]; then

OVERALL_STATUS="ERROR"

LOG_TO_FILE "${MSG_ERROR} ${MSG_PROCESS_ABORTED}"

LOG_TO_FILE "${MSG_INFO} SKIP_DIRTY_CHECK is DISABLED - Aborting"

SEND_ERROR_NOTIFICATION "" "" "Process aborted: ${STALE_COUNT} stale Temporary Files found. Manual cleanup required."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "${STALE_COUNT} ${MSG_STALE_FILES_FOUND} Manual cleanup required."

echo "────────────────────────────────────────────────────────────"

for ENTRY in "${STALE_FILES[@]}"; do

SOURCE_TYPE="${ENTRY%%|*}"

FILEPATH="${ENTRY#*|}"

case "$SOURCE_TYPE" in

WEBGUI) PREFIX="[WebGUI Temp] " ;;

ROOTFS) PREFIX="[RootFS Temp] " ;;

esac

if [[ -f "$FILEPATH" ]]; then

# Safe stat with error handling

STAT_OUTPUT=$(stat -c '%s %y' "$FILEPATH" 2>&1)

if [ $? -ne 0 ]; then

LOG_TO_FILE "${MSG_WARNING} Cannot stat File for display: $FILEPATH"

SIZE_DISPLAY="Unknown"

MTIME="Unknown"

else

SIZE_BYTES=$(echo "$STAT_OUTPUT" | awk '{print $1}')

MTIME=$(echo "$STAT_OUTPUT" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i}' | cut -d'.' -f1 | xargs)

if [ -n "$SIZE_BYTES" ] && [ "$SIZE_BYTES" -gt 1073741824 ]; then

SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024/1024}")" GB"

elif [ -n "$SIZE_BYTES" ]; then

SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024}")" MB"

else

SIZE_DISPLAY="Unknown"

fi

fi

echo " ${PREFIX}${FILEPATH}"

echo " Size: ${SIZE_DISPLAY} | Modified: ${MTIME}"

elif [[ -L "$FILEPATH" ]]; then

echo " ${PREFIX}SYMLINK → $(readlink "$FILEPATH" 2>/dev/null)"

else

echo " ${PREFIX}${FILEPATH} (Missing/Corrupted)"

fi

done

echo "────────────────────────────────────────────────────────────"

echo "NOTE: Temporary Files will be cleaned on Reboot."

echo "→ Manual cleanup required."

echo "→ Set SKIP_DIRTY_CHECK=TRUE to ignore."

echo "→ Set ENABLE_AUTO_CLEANUP=TRUE for automatic removal after all Modules complete."

fi

exit 1

else

HAS_STALE_FILES=TRUE

SKIP_DIRTY_CHECK_ACTIVE=TRUE

OVERALL_STATUS="WARNING"

LOG_TO_FILE "${MSG_WARNING} ${MSG_WARNING_IGNORED} (SKIP_DIRTY_CHECK=TRUE)"

LOG_TO_FILE "${MSG_WARNING} Dirty Check Bypassed - Continuing anyway."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "${STALE_COUNT} ${MSG_STALE_FILES_FOUND}"

PRINT_MSG "WARNING" "${MSG_WARNING_IGNORED} (SKIP_DIRTY_CHECK=TRUE)"

PRINT_MSG "WARNING" "Dirty Check Bypassed - Continuing anyway."

echo "────────────────────────────────────────────────────────────"

for ENTRY in "${STALE_FILES[@]}"; do

SOURCE_TYPE="${ENTRY%%|*}"

FILEPATH="${ENTRY#*|}"

case "$SOURCE_TYPE" in

WEBGUI) PREFIX="[WebGUI Temp] " ;;

ROOTFS) PREFIX="[RootFS Temp] " ;;

esac

if [[ -f "$FILEPATH" ]]; then

# Safe stat with error handling

STAT_OUTPUT=$(stat -c '%s %y' "$FILEPATH" 2>&1)

STAT_EXIT=$?

if [ $STAT_EXIT -ne 0 ]; then

LOG_TO_FILE "${MSG_WARNING} Cannot stat file for display: $FILEPATH"

SIZE_DISPLAY="Unknown"

MTIME="Unknown"

else

SIZE_BYTES=$(echo "$STAT_OUTPUT" | awk '{print $1}')

MTIME=$(echo "$STAT_OUTPUT" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i}' | cut -d'.' -f1 | xargs)

if [ -n "$SIZE_BYTES" ] && [ "$SIZE_BYTES" -gt 1073741824 ]; then

SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024/1024}")" GB"

elif [ -n "$SIZE_BYTES" ]; then

SIZE_DISPLAY=$(awk "BEGIN {printf \"%.2f\", $SIZE_BYTES/1024/1024}")" MB"

else

SIZE_DISPLAY="Unknown"

fi

fi

echo " ${PREFIX}${FILEPATH}"

echo " Size: ${SIZE_DISPLAY} | Modified: ${MTIME}"

elif [[ -L "$FILEPATH" ]]; then

LINK_TARGET=$(readlink "$FILEPATH" 2>&1)

if [ $? -ne 0 ]; then

LOG_TO_FILE "${MSG_WARNING} Cannot resolve Symlink Target: $FILEPATH"

echo " ${PREFIX}SYMLINK → [Unable to resolve]"

else

echo " ${PREFIX}SYMLINK → $LINK_TARGET"

fi

else

LOG_TO_FILE "${MSG_WARNING} File disappeared during listing: $FILEPATH"

echo " ${PREFIX}${FILEPATH} (Missing/Disappeared)"

fi

done

echo "────────────────────────────────────────────────────────────"

echo "Session Log contains full Details and Error tracking: $LOG_FILE_PATH"

echo ""

echo "AUTO CLEANUP STATUS: ${ENABLE_AUTO_CLEANUP}"

if [ "$ENABLE_AUTO_CLEANUP" = "TRUE" ]; then

echo "→ Will run at end of Script through ${MOD_TITLE_8}."

echo "→ Files removed after Backup & Retention completes."

else

echo "→ Currently DISABLED."

echo "→ Set ENABLE_AUTO_CLEANUP=TRUE for automatic removal."

echo ""

echo "NOTE: ${STALE_COUNT} Stale Files remain on system."

fi

echo ""

PRINT_MSG "WARNING" "Skipping cleanup and continuing despite Stale Files."

fi

fi

else

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "${MSG_NO_STALE_FILES}"

fi

LOG_TO_FILE "${MSG_SUCCESS} ${MSG_NO_STALE_FILES}"

fi

LOG_TO_FILE "---[${MOD_TITLE_4} COMPLETED]---"

LOG_TO_FILE ""

else

LOG_TO_FILE "---[MODULE 4: ${MOD_TITLE_4}]---"

LOG_TO_FILE "MODULE [DISABLED]"

fi

# --- MODULE 5: CREATE BACKUP ------------------------------------------------

BACKUP_FILE=""

if [ "$ENABLE_BACKUP_CREATE" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 5: ${MOD_TITLE_5}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_5}"

fi

LOG_TO_FILE "Target Directory: ${BACKUP_BASE_DIR}"

LOG_TO_FILE "Starting Native Flash Backup Script..."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo "Target Directory: ${BACKUP_BASE_DIR}"

echo "Starting Native Flash Backup Script..."

fi

# Check 1: unzip Utility available

if ! command -v unzip >/dev/null 2>&1; then

HANDLE_ERROR_EXIT "'unzip' Command not found. Integrity Check impossible." \

"'unzip' Command not found. Integrity Check impossible."

fi

# Execute Native Flash Backup PHP Script

BACKUP_RAW_OUTPUT=$(/usr/bin/php -q /usr/local/emhttp/webGui/scripts/flash_backup 2>&1)

PHP_EXIT_CODE=$?

if [ $PHP_EXIT_CODE -ne 0 ]; then

LOG_TO_FILE "[DEBUG] PHP Output: ${BACKUP_RAW_OUTPUT}"

HANDLE_ERROR_EXIT "Flash Backup Tool failed with Exit Code ${PHP_EXIT_CODE}." \

"Flash Backup Tool failed with Exit Code ${PHP_EXIT_CODE}."

fi

BACKUP_FILE=$(echo "$BACKUP_RAW_OUTPUT" | tail -n 1 | xargs)

if [ -z "$BACKUP_FILE" ]; then

LOG_TO_FILE "[DEBUG] PHP Output: ${BACKUP_RAW_OUTPUT}"

HANDLE_ERROR_EXIT "Invalid Filename generated (Empty Output)." \

"Invalid Filename generated (Empty Output)."

fi

SYMLINK_PATH="/usr/local/emhttp/${BACKUP_FILE}"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "PHP flash_backup Script executed and Symlink at ${SYMLINK_PATH} created."

fi

LOG_TO_FILE "${MSG_SUCCESS} PHP flash_backup Script executed and Symlink at ${SYMLINK_PATH} created."

# Check 2: Symlink exists

if [ ! -f "$SYMLINK_PATH" ] && [ ! -L "$SYMLINK_PATH" ]; then

HANDLE_ERROR_EXIT "Source File does not exist: ${SYMLINK_PATH}." \

"Source File does not exist: ${SYMLINK_PATH}."

fi

# Resolve Real Path

READLINK_OUTPUT=$(readlink -f "$SYMLINK_PATH" 2>&1) || true

if [[ -z "$READLINK_OUTPUT" ]] || [[ "$READLINK_OUTPUT" "error" ]] || [[ "$READLINK_OUTPUT" "cannot" ]]; then

LOG_TO_FILE "${MSG_WARNING} readlink failed, falling back to symlink path: $READLINK_OUTPUT"

SOURCE_FILE="$SYMLINK_PATH"

else

SOURCE_FILE="$READLINK_OUTPUT"

fi

DEST_FILE="${BACKUP_BASE_DIR}/${BACKUP_FILE}"

# Check 3: Resolved Source File exists

if [ ! -f "$SOURCE_FILE" ]; then

HANDLE_ERROR_EXIT "Resolved Source File missing: ${SOURCE_FILE}." \

"Resolved Source File missing: ${SOURCE_FILE}."

fi

# Copy Backup File

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Copying Backup File to: ${DEST_FILE}"

fi

# Capture ALL Output including Errors

CP_OUTPUT=$(cp "$SOURCE_FILE" "$DEST_FILE" 2>&1)

COPY_EXIT=$?

if [ $COPY_EXIT -ne 0 ]; then

LOG_TO_FILE "${MSG_ERROR} Copy Operation failed with Exit Code ${COPY_EXIT}."

LOG_TO_FILE "[DEBUG] cp Output: $CP_OUTPUT"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "${MSG_COPY_FAILED}"

echo "→ See Session Log for Details: $LOG_FILE_PATH"

fi

HANDLE_ERROR_EXIT "${MSG_COPY_FAILED}" \

"Copy Error Details logged in Session Log."

fi

LOG_TO_FILE "Copied Backup File to: ${DEST_FILE}"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "Copy completed successfully."

fi

# Integrity Check

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Checking Integrity of File: ${DEST_FILE}"

fi

# Capture ALL Output including Errors

UNZIP_OUTPUT=$(unzip -t -q "$DEST_FILE" 2>&1)

UNZIP_EXIT=$?

if [ $UNZIP_EXIT -ne 0 ]; then

LOG_TO_FILE "${MSG_ERROR} Integrity Check failed with Exit Code ${UNZIP_EXIT}."

LOG_TO_FILE "[DEBUG] unzip Output: $UNZIP_OUTPUT"

LOG_TO_FILE "${MSG_INFO} Removing corrupted Backup File: $(basename "$DEST_FILE")"

rm -f "$DEST_FILE"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "${MSG_BACKUP_CORRUPTED}: $(basename "${DEST_FILE}")."

echo "→ Corruption Details in Session Log: $LOG_FILE_PATH"

fi

HANDLE_ERROR_EXIT "${MSG_BACKUP_CORRUPTED}: $(basename "${DEST_FILE}")." \

"Corruption Details logged in Session Log."

fi

LOG_TO_FILE "${MSG_SUCCESS} Integrity Check passed."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "Integrity Check passed."

fi

# Cleanup Temporary Files

if [ -f "$SOURCE_FILE" ]; then

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Temporary Backup File Location: ${SOURCE_FILE}"

fi

rm -f "$SOURCE_FILE"

LOG_TO_FILE "${MSG_INFO} Temporary Backup File Location: ${SOURCE_FILE}"

LOG_TO_FILE "${MSG_SUCCESS} Temporary Backup File removed: $(basename "$SOURCE_FILE")"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "Temporary Backup File removed: $(basename "$SOURCE_FILE")"

fi

fi

if [ -L "$SYMLINK_PATH" ]; then

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Temporary Symlink Location: ${SYMLINK_PATH}"

fi

rm -f "$SYMLINK_PATH"

LOG_TO_FILE "${MSG_INFO} Temporary Symlink Location: ${SYMLINK_PATH}"

LOG_TO_FILE "${MSG_SUCCESS} Temporary Symlink removed."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "Temporary Symlink removed."

fi

fi

LOG_TO_FILE "---[BACKUP CREATION COMPLETED]---"

LOG_TO_FILE ""

# Update Last Run Log

LOG_TO_FILE "---[UPDATE ARCHIVE LOG]---"

{

echo "=== LAST RUN: ${HOST_NAME} ==="

echo "TIMESTAMP: $(date '+%Y-%m-%d %H:%M:%S')"

echo "BACKUP FILE: ${BACKUP_FILE}"

echo "BACKUP PATH: ${DEST_FILE}"

echo "ARCHIVE LOG PATH: ${ARCHIVE_LOG_PATH}"

echo "STATUS: SUCCESSFUL"

[ -n "$LOCAL_LICENSE_TYPE" ] && echo "License Type: ${LOCAL_LICENSE_TYPE}"

[ "${HAS_STALE_FILES:-FALSE}" = "TRUE" ] && echo "[WARNING] Stale Files present"

echo "==============================="

} > "$LAST_RUN_LOG"

LOG_TO_FILE "Status Log saved: ${LAST_RUN_LOG}"

LOG_TO_FILE "Session Log archived: ${ARCHIVE_LOG_PATH}"

LOG_TO_FILE "---[UPDATE ARCHIVE LOG COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "UPDATE ARCHIVE LOG"

echo "Status Log saved: ${LAST_RUN_LOG}"

echo "Session Log archived: ${ARCHIVE_LOG_PATH}"

fi

else

LOG_TO_FILE "---[MODULE 5: ${MOD_TITLE_5}]---"

LOG_TO_FILE "MODULE [DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_5} SKIPPED"

fi

fi

# --- MODULE 6: APPLY BACKUP RETENTION ---------------------------------------

if [ "$ENABLE_BACKUP_RETENTION" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 6: ${MOD_TITLE_6}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_6}"

fi

cd "$BACKUP_BASE_DIR" || {

HANDLE_ERROR_EXIT "${MSG_CHANGE_DIR_FAILED}" \

"Change to Backup Directory failed."

}

# Delete Backups older than Retention Period

FIND_AGE_OUTPUT=$(find . -maxdepth 1 -name "*boot-backup*.zip" -type f -mtime +"${BACKUP_RETENTION_DAYS}" -print 2>&1)

FIND_AGE_EXIT=$?

DELETED_BY_AGE=$(echo "$FIND_AGE_OUTPUT" | grep -c "\.zip$" || true)

if [ "$FIND_AGE_EXIT" -ne 0 ]; then

LOG_TO_FILE "${MSG_WARNING} find for age-based deletion reported issues: $FIND_AGE_OUTPUT"

fi

if [ "$DELETED_BY_AGE" -gt 0 ]; then

LOG_TO_FILE "${MSG_INFO} ${DELETED_BY_AGE} Backup Files older than ${BACKUP_RETENTION_DAYS} Days removed."

find . -maxdepth 1 -name "*boot-backup*.zip" -type f -mtime +"${BACKUP_RETENTION_DAYS}" -delete

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "${DELETED_BY_AGE} Backup Files older than ${BACKUP_RETENTION_DAYS} Days removed."

fi

fi

# Enforce Maximum Backup Count Limit

if [[ -n "$MAX_BACKUPS_TO_KEEP" ]] && [[ "$MAX_BACKUPS_TO_KEEP" -gt 0 ]]; then

LS_OUTPUT=$(ls -1 boot-backup.zip 2>&1) || true

CURRENT_COUNT=$(echo "$LS_OUTPUT" | grep -c "\.zip$" || true)

if echo "$LS_OUTPUT" | grep -qiE "error|cannot|no such"; then

LOG_TO_FILE "${MSG_WARNING} ls reported issues: $LS_OUTPUT"

fi

if [ "$CURRENT_COUNT" -gt "$MAX_BACKUPS_TO_KEEP" ]; then

TO_DELETE=$((CURRENT_COUNT - MAX_BACKUPS_TO_KEEP))

LOG_TO_FILE "${MSG_INFO} Current Backup Files: ${CURRENT_COUNT} | Max: ${MAX_BACKUPS_TO_KEEP} → Deleting ${TO_DELETE} Files."

mapfile -t BACKUP_FILES_TO_DELETE < <(ls -t boot-backup.zip 2>&1 | tail -n +$((MAX_BACKUPS_TO_KEEP + 1)))

for OLD_BACKUP in "${BACKUP_FILES_TO_DELETE[@]}"; do

if [ -n "$OLD_BACKUP" ]; then

rm -f "$OLD_BACKUP"

LOG_TO_FILE "${MSG_INFO} Deleted: $(basename "$OLD_BACKUP")"

fi

done

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Deleted ${TO_DELETE} old Backup Files to stay within limit of ${MAX_BACKUPS_TO_KEEP}."

for OLD_BACKUP in "${BACKUP_FILES_TO_DELETE[@]}"; do

if [ -n "$OLD_BACKUP" ]; then

PRINT_MSG "INFO" "Deleted: $(basename "$OLD_BACKUP")"

fi

done

fi

else

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Backup File Count (${CURRENT_COUNT}) is within limit (${MAX_BACKUPS_TO_KEEP})."

fi

fi

fi

FINAL_LS_OUTPUT=$(ls -1 boot-backup.zip 2>&1) || true

FINAL_COUNT=$(echo "$FINAL_LS_OUTPUT" | grep -c "\.zip$" || true)

LOG_TO_FILE "${MSG_INFO} Result: ${FINAL_COUNT} Flash or Boot Backup Files remaining."

LOG_TO_FILE "---[${MOD_TITLE_6} COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Total Backup Files remaining: ${FINAL_COUNT}"

fi

else

LOG_TO_FILE "---[MODULE 6: ${MOD_TITLE_6}]---"

LOG_TO_FILE "MODULE [DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_6} SKIPPED"

fi

fi

# --- MODULE 7: APPLY LOG RETENTION ------------------------------------------

if [ "$ENABLE_LOG_RETENTION" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 7: ${MOD_TITLE_7}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_7}"

fi

cd "$ARCHIVE_LOG_DIR" || {

HANDLE_ERROR_EXIT "${MSG_CHANGE_DIR_FAILED}" \

"Change to Archive Log Directory failed."

}

# Delete Logs older than Retention Period (Exclude Last_Run.log)

FIND_LOG_AGE_OUTPUT=$(find . -maxdepth 1 -name "*.log" -type f ! -name "Last_Run.log" -mtime +"${LOG_RETENTION_DAYS}" -print 2>&1)

FIND_LOG_AGE_EXIT=$?

DELETED_LOGS_BY_AGE=$(echo "$FIND_LOG_AGE_OUTPUT" | grep -c "\.log$" || true)

if [ "$FIND_LOG_AGE_EXIT" -ne 0 ]; then

LOG_TO_FILE "${MSG_WARNING} find for log age-based deletion reported issues: $FIND_LOG_AGE_OUTPUT"

fi

if [ "$DELETED_LOGS_BY_AGE" -gt 0 ]; then

LOG_TO_FILE "${MSG_INFO} ${DELETED_LOGS_BY_AGE} Log Files older than ${LOG_RETENTION_DAYS} Days removed."

find . -maxdepth 1 -name "*.log" -type f ! -name "Last_Run.log" -mtime +"${LOG_RETENTION_DAYS}" -delete

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "${DELETED_LOGS_BY_AGE} Log Files older than ${LOG_RETENTION_DAYS} Days removed."

fi

fi

# Enforce Maximum Log File Count

if [[ -n "$MAX_LOG_FILES_TO_KEEP" ]] && [[ "$MAX_LOG_FILES_TO_KEEP" -gt 0 ]]; then

FIND_COUNT_OUTPUT=$(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>&1) || true

LOG_FILE_COUNT=$(echo "$FIND_COUNT_OUTPUT" | grep -c "\.log$" || true)

if echo "$FIND_COUNT_OUTPUT" | grep -qiE "error|cannot|permission"; then

LOG_TO_FILE "${MSG_WARNING} find for Log count reported issues: $FIND_COUNT_OUTPUT"

fi

if [ "$LOG_FILE_COUNT" -gt "$MAX_LOG_FILES_TO_KEEP" ]; then

TO_DELETE_COUNT=$((LOG_FILE_COUNT - MAX_LOG_FILES_TO_KEEP))

LOG_TO_FILE "${MSG_INFO} Current Log Files: ${LOG_FILE_COUNT} | Max: ${MAX_LOG_FILES_TO_KEEP} → Deleting ${TO_DELETE_COUNT} Files."

# Timestamp based sorting for accurate oldest first deletion

mapfile -t FILES_TO_DELETE < <(

for F in $(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>&1); do

TS=$(stat -c '%Y' "$F" 2>&1)

[[ -z "$TS" ]] && TS=0

printf '%s %s\n' "$TS" "$F"

done | sort -n | head -n "$TO_DELETE_COUNT" | awk '{print $2}'

)

for OLD_LOG in "${FILES_TO_DELETE[@]}"; do

if [ -n "$OLD_LOG" ]; then

rm -f "$OLD_LOG"

LOG_TO_FILE "${MSG_INFO} Deleted: $(basename "$OLD_LOG")"

fi

done

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Deleted ${TO_DELETE_COUNT} old Log Files to stay within limit of ${MAX_LOG_FILES_TO_KEEP}."

for OLD_LOG in "${FILES_TO_DELETE[@]}"; do

if [ -n "$OLD_LOG" ]; then

PRINT_MSG "INFO" "Deleted: $(basename "$OLD_LOG")"

fi

done

fi

else

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Log File Count (${LOG_FILE_COUNT}) is within limit (${MAX_LOG_FILES_TO_KEEP})."

fi

fi

fi

FINAL_FIND_OUTPUT=$(find . -maxdepth 1 -name "${LOG_FILE_PATTERN}" -type f 2>&1) || true

FINAL_LOG_COUNT=$(echo "$FINAL_FIND_OUTPUT" | grep -c "\.log$" || true)

if echo "$FINAL_FIND_OUTPUT" | grep -qiE "error|cannot|permission"; then

LOG_TO_FILE "${MSG_WARNING} find for final Log count reported issues: $FINAL_FIND_OUTPUT"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "Log File search encountered issues: $FINAL_FIND_OUTPUT"

fi

fi

LOG_TO_FILE "${MSG_INFO} Result: ${FINAL_LOG_COUNT} Log Files remaining (Excluding Last_Run.log)."

LOG_TO_FILE "---[${MOD_TITLE_7} COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Total Log Files remaining: ${FINAL_LOG_COUNT}"

fi

else

LOG_TO_FILE "---[MODULE 7: ${MOD_TITLE_7}]---"

LOG_TO_FILE "MODULE [DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_7} SKIPPED"

fi

fi

# --- MODULE 8: AUTO CLEANUP -----------------------------------------------

if [ "$ENABLE_AUTO_CLEANUP" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 8: ${MOD_TITLE_8}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_8}"

echo "AUTO CLEANUP STATUS: ENABLED"

echo "STALE FILES FROM ${MOD_TITLE_4}: ${HAS_STALE_FILES:-N/A}"

echo "----------------------------------------"

fi

# Proceed only if Stale Files were detected (from Module 4) OR new ones exist

declare -a ALL_STALE_FILES=()

# WebGUI Temp Directory

CLEANUP_FIND_WEBGUI=$(find /usr/local/emhttp/ -maxdepth 1 -name "*boot-backup*.zip" \( -type f -o -type l \) 2>&1)

if echo "$CLEANUP_FIND_WEBGUI" | grep -qiE "error|cannot|permission"; then

LOG_TO_FILE "${MSG_WARNING} find in /usr/local/emhttp during cleanup reported issues: $CLEANUP_FIND_WEBGUI"

fi

while IFS= read -r F; do

[[ -n "$F" ]] && ALL_STALE_FILES+=("WEBGUI|$F")

done <<< "$CLEANUP_FIND_WEBGUI"

# RootFS Directory

CLEANUP_FIND_ROOTFS=$(find / -maxdepth 1 -name "*boot-backup*.zip" -type f 2>&1)

if echo "$CLEANUP_FIND_ROOTFS" | grep -qiE "error|cannot|permission"; then

LOG_TO_FILE "${MSG_WARNING} find in / during cleanup reported issues: $CLEANUP_FIND_ROOTFS"

fi

while IFS= read -r F; do

[[ -n "$F" ]] && ALL_STALE_FILES+=("ROOTFS|$F")

done <<< "$CLEANUP_FIND_ROOTFS"

CLEANUP_TOTAL=${#ALL_STALE_FILES[@]}

CLEANUP_SUCCESS=0

CLEANUP_FAILED=0

# No Stale Files — Skip Cleanup

if [ $CLEANUP_TOTAL -eq 0 ]; then

LOG_TO_FILE "${MSG_INFO} No Stale Files detected. Cleanup not required."

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "No Stale Files detected. Cleanup not required."

fi

CLEANUP_EXECUTED=FALSE

LOG_TO_FILE "---[${MOD_TITLE_8} SKIPPED - NO STALE FILES]---"

LOG_TO_FILE ""

# Continue to next Module (Notification)

else

# Stale Files exist — proceed with cleanup

CLEANUP_EXECUTED=TRUE

LOG_TO_FILE "${MSG_INFO} ${MSG_CLEANUP_STARTED}"

LOG_TO_FILE "${MSG_INFO} Total files to process: ${CLEANUP_TOTAL}"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "${MSG_CLEANUP_STARTED}"

echo "Total Stale Files to process: ${CLEANUP_TOTAL}"

fi

# Attempt Removal

DELETED_FILES=()

FAILED_FILES=()

for ENTRY in "${ALL_STALE_FILES[@]}"; do

SOURCE_TYPE="${ENTRY%%|*}"

FILEPATH="${ENTRY#*|}"

if [[ -f "$FILEPATH" ]] || [[ -L "$FILEPATH" ]]; then

# Capture rm Output for logging

RM_OUTPUT=$(rm -f "$FILEPATH" 2>&1)

DELETE_EXIT=$?

if [ $DELETE_EXIT -eq 0 ]; then

((CLEANUP_SUCCESS++))

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

case "$SOURCE_TYPE" in

WEBGUI) echo " [SUCCESS] Removed: [WebGUI Temp] $(basename "$FILEPATH")" ;;

ROOTFS) echo " [SUCCESS] Removed: [RootFS Temp] $(basename "$FILEPATH")" ;;

esac

fi

LOG_TO_FILE "${MSG_SUCCESS} Removed: ${SOURCE_TYPE}|${FILEPATH}"

DELETED_FILES+=("${FILEPATH}")

else

((CLEANUP_FAILED++))

FAILED_FILES+=("${SOURCE_TYPE}|${FILEPATH}")

LOG_TO_FILE "${MSG_ERROR} Failed to remove: ${SOURCE_TYPE}|${FILEPATH}"

if [ -n "$RM_OUTPUT" ]; then

LOG_TO_FILE "[DEBUG] rm output: $RM_OUTPUT"

fi

fi

else

# File already gone or inaccessible

((CLEANUP_FAILED++))

FAILED_FILES+=("${SOURCE_TYPE}|${FILEPATH}")

LOG_TO_FILE "${MSG_WARNING} File inaccessible (already removed?): ${SOURCE_TYPE}|${FILEPATH}"

fi

done

# Final Status determination

if [ $CLEANUP_SUCCESS -gt 0 ] && [ $CLEANUP_FAILED -eq 0 ]; then

# Perfect cleanup

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "SUCCESS" "All ${CLEANUP_SUCCESS} Stale Files removed successfully."

fi

LOG_TO_FILE "${MSG_SUCCESS} All ${CLEANUP_SUCCESS} Files removed successfully."

elif [ $CLEANUP_SUCCESS -gt 0 ] && [ $CLEANUP_FAILED -gt 0 ]; then

# Partial success — WARNING

if [ "$OVERALL_STATUS" != "ERROR" ]; then

OVERALL_STATUS="WARNING"

fi

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "WARNING" "${CLEANUP_SUCCESS} removed, ${CLEANUP_FAILED} failed. Partial cleanup completed."

fi

LOG_TO_FILE "${MSG_WARNING} Partial cleanup: ${CLEANUP_SUCCESS} removed, ${CLEANUP_FAILED} failed"

# List failed Files for user reference

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo " Failed Files (manual cleanup required):"

for FAIL_ENTRY in "${FAILED_FILES[@]}"; do

FILE_PATH="${FAIL_ENTRY#*|}"

echo " ${FILE_PATH}"

done

fi

else

# Complete Failure

if [ "$OVERALL_STATUS" != "ERROR" ]; then

OVERALL_STATUS="ERROR"

fi

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "ERROR" "Cleanup failed. No Files removed. Manual intervention required."

fi

LOG_TO_FILE "${MSG_ERROR} Cleanup failed. No Files removed. Manual intervention required."

LOG_TO_FILE "${MSG_ERROR} ${CLEANUP_TOTAL} Stale Files remain. Manual cleanup required."

# Send Error Notification immediately for cleanup failure

SEND_ERROR_NOTIFICATION "Flash Backup Auto cleanup failed" \

"Flash Backup Auto cleanup" \

"Failed to remove ${CLEANUP_TOTAL} Stale Temporary Files. Manual cleanup required. See Log: $LOG_FILE_PATH"

fi

LOG_TO_FILE "---[${MOD_TITLE_8} COMPLETED]---"

LOG_TO_FILE "Stats: ${CLEANUP_TOTAL} total, ${CLEANUP_SUCCESS} succeeded, ${CLEANUP_FAILED} failed"

LOG_TO_FILE ""

# NOTE: HAS_STALE_FILES remains TRUE to preserve Module 4's Original detection State

fi

else

# Auto cleanup disabled

LOG_TO_FILE "---[MODULE 8: ${MOD_TITLE_8}]---"

LOG_TO_FILE "MODULE [DISABLED]"

LOG_TO_FILE "${MSG_INFO} ${MSG_CLEANUP_SKIPPED}"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_8}"

echo "AUTO CLEANUP STATUS: DISABLED"

PRINT_MSG "INFO" "${MSG_CLEANUP_SKIPPED}"

fi

CLEANUP_EXECUTED=FALSE

CLEANUP_SUCCESS=0

CLEANUP_FAILED=0

CLEANUP_TOTAL=0

fi

# --- MODULE 9: SEND NOTIFICATION --------------------------------------------

if [ "$ENABLE_NOTIFICATION" = "TRUE" ]; then

LOG_TO_FILE "---[MODULE 9: ${MOD_TITLE_9}]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_9}"

fi

# Initialize Notification Variables

NOTIFY_TITLE="Flash or Boot Backup completed - ${HOST_NAME}"

NOTIFY_MSG="Flash or Boot Backup '${BACKUP_FILE:-N/A}' successfully created."

NOTIFY_PRIORITY="success"

NOTIFY_SUBJECT="Flash or Boot Backup success"

DETAIL_MSG=""

# Condition 1: Invalid License Configuration

if [ "$ENABLE_LICENSE_CHECK" = "TRUE" ] && [[ "$LICENSE_STATUS" == "INVALID_CONFIG" ]]; then

NOTIFY_PRIORITY="error"

NOTIFY_SUBJECT="Flash or Boot Backup - Error"

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[ERROR] License Configuration invalid"

fi

# Condition 2: Missing USB License Device

if [ "$ENABLE_HARDWARE_CHECK" = "TRUE" ] && [[ "$LICENSE_STATUS" == "USB_FLASH" ]]; then

LSUSB_OUTPUT=$(lsusb 2>&1) || true

if [[ $(echo "$LSUSB_OUTPUT" | grep -ciE "$USB_BRAND_PATTERN") -eq 0 ]]; then

if echo "$LSUSB_OUTPUT" | grep -qiE "error|command not found"; then

LOG_TO_FILE "${MSG_WARNING} lsusb reported issues during notification: $LSUSB_OUTPUT"

fi

NOTIFY_PRIORITY="error"

NOTIFY_SUBJECT="Flash or Boot Backup - Error"

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[ERROR] Required USB License Device not detected"

fi

fi

# Condition 3: Stale Temporary Files (Warning if no Error)

if [[ "${HAS_STALE_FILES:-FALSE}" == "TRUE" ]]; then

if [[ "$NOTIFY_PRIORITY" != "error" ]]; then

NOTIFY_PRIORITY="warning"

NOTIFY_SUBJECT="Flash or Boot Backup - Warning"

fi

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[WARNING] Stale Temporary Files present"

fi

# Condition 4: Unknown Boot Medium (Warning if Success)

if [ "$ENABLE_BOOT_CHECK" = "TRUE" ] && [[ "$BOOT_MEDIUM_TYPE" == "Unknown" ]]; then

if [[ "$NOTIFY_PRIORITY" == "success" ]]; then

NOTIFY_PRIORITY="warning"

NOTIFY_SUBJECT="Flash or Boot Backup - Warning"

fi

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[WARNING] Boot Medium could not be identified"

fi

# Add License Information

if [ "$ENABLE_LICENSE_CHECK" = "TRUE" ]; then

case "$DEVICE_BINDING" in

"USB Stick") DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }License: ${LICENSE_TYPE:-Unknown} (USB)" ;;

"TPM") DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }License: ${LICENSE_TYPE:-Unknown} (TPM)" ;;

esac

fi

# Add Boot Medium Information

if [ "$ENABLE_BOOT_CHECK" = "TRUE" ] && [[ "$BOOT_MEDIUM_TYPE" != "Unknown" ]]; then

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }Boot: ${BOOT_MEDIUM_TYPE}"

fi

# Calculate Backup File Size

BACKUP_SIZE_DISPLAY="N/A"

if [ -f "$DEST_FILE" ]; then

STAT_SIZE_OUTPUT=$(stat -c '%s' "$DEST_FILE" 2>&1)

if echo "$STAT_SIZE_OUTPUT" | grep -qiE "error|cannot|no such"; then

LOG_TO_FILE "${MSG_WARNING} stat for backup size failed: $STAT_SIZE_OUTPUT"

BACKUP_SIZE_BYTES=""

else

BACKUP_SIZE_BYTES="$STAT_SIZE_OUTPUT"

fi

if [[ -n "$BACKUP_SIZE_BYTES" ]]; then

BACKUP_SIZE_MB=$((BACKUP_SIZE_BYTES / 1024 / 1024))

BACKUP_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $BACKUP_SIZE_BYTES / 1024 / 1024 / 1024}")

if [ "$BACKUP_SIZE_BYTES" -gt 1073741824 ]; then

BACKUP_SIZE_DISPLAY="${BACKUP_SIZE_GB} GB"

else

BACKUP_SIZE_DISPLAY="${BACKUP_SIZE_MB} MB"

fi

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo "Backup File Size: ${BACKUP_SIZE_DISPLAY}"

fi

LOG_TO_FILE "Backup File Size: ${BACKUP_SIZE_DISPLAY} / ${BACKUP_SIZE_BYTES} bytes"

fi

fi

# ADD Final Cleanup Information

if [ "$ENABLE_AUTO_CLEANUP" = "TRUE" ]; then

if [[ "${CLEANUP_EXECUTED:-FALSE}" == "TRUE" ]]; then

if [ $CLEANUP_FAILED -eq 0 ]; then

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[SUCCESS] Cleaned ${CLEANUP_SUCCESS} Stale Files"

elif [ $CLEANUP_SUCCESS -gt 0 ]; then

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[PARTIAL] Cleaned ${CLEANUP_SUCCESS}/${CLEANUP_TOTAL} (${CLEANUP_FAILED} failed)"

else

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[FAILED] Auto cleanup unsuccessful"

fi

else

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }[NOT ACTIVE] No stale files to clean"

fi

fi

DETAIL_MSG="${DETAIL_MSG:+${DETAIL_MSG} | }Size: ${BACKUP_SIZE_DISPLAY}"

# Send Notification

"$NOTIFICATION_SCRIPT" \

-e "$NOTIFY_TITLE" \

-s "$NOTIFY_SUBJECT" \

-d "${NOTIFY_MSG} - ${DETAIL_MSG}" \

-i "$NOTIFY_PRIORITY"

LOG_TO_FILE "${MSG_INFO} Notification sent (Priority: ${NOTIFY_PRIORITY})."

LOG_TO_FILE "---[${MOD_TITLE_9} COMPLETED]---"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_MSG "INFO" "Notification sent successfully (Priority: ${NOTIFY_PRIORITY})."

fi

else

LOG_TO_FILE "---[MODULE 9: ${MOD_TITLE_9}]---"

LOG_TO_FILE "MODULE [DISABLED]"

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

PRINT_SECTION "${MOD_TITLE_9} SKIPPED"

fi

fi

# =============================================================================

# 8. FINALIZATION & STATUS REPORT

# =============================================================================

# Determine Final Status Text based on OVERALL_STATUS

# Mapping: Internal Status Value → Human-readable Messages and Symbols.

# States: SUCCESS, WARNING, ERROR, DEFAULT (Catch-all Fallback).

case "$OVERALL_STATUS" in

SUCCESS)

FINAL_MESSAGE="All Routine Steps completed successfully."

FINAL_SYMBOL="[SUCCESS]"

;;

WARNING)

FINAL_MESSAGE="All Routine Steps completed. One or more Warnings occurred."

FINAL_SYMBOL="[WARNING]"

;;

ERROR)

FINAL_MESSAGE="All Routine Steps completed. One or more Errors occurred."

FINAL_SYMBOL="[ERROR]"

;;

*)

FINAL_MESSAGE="All Routine Steps completed."

FINAL_SYMBOL="[INFO]"

;;

esac

# Final CLI Banner

# Display Condition: SHOW_CLI_OUTPUT=TRUE required.

# Timing: After all Modules complete.

if [ "$SHOW_CLI_OUTPUT" = "TRUE" ]; then

echo ""

echo "=============================================================="

# Visual differentiation based on OVERALL_STATUS

if [[ "$OVERALL_STATUS" == "ERROR" ]]; then

echo " *** ERROR - SCRIPT ABORTED ***"

echo "=============================================================="

echo "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"

echo "──────────────────────────────────────────────────────────────"

echo "WARNING: Manual intervention required immediately!"

elif [[ "$OVERALL_STATUS" == "WARNING" ]]; then

echo " *** COMPLETED WITH WARNINGS ***"

echo "=============================================================="

echo "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"

echo "──────────────────────────────────────────────────────────────"

echo "NOTE: Review Warnings in Log File below."

else

echo " *** BACKUP, ANALYSIS & MAINTENANCE COMPLETED ***"

echo "=============================================================="

echo "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"

echo "──────────────────────────────────────────────────────────────"

fi

# Add Log Path hint if Warnings or Errors occurred

if [[ "$OVERALL_STATUS" "WARNING" ]] || [[ "$OVERALL_STATUS" "ERROR" ]]; then

echo "→ For Details, check Session Log: $LOG_FILE_PATH"

echo "→ Or view Archive Logs: $ARCHIVE_LOG_DIR"

fi

echo "=============================================================="

fi

# Final Logging

# Action: Completion Event Recorded to Log File.

# Visual Display Covered by Final Banner Above.

LOG_TO_FILE "FINAL STATUS: ${FINAL_SYMBOL} ${FINAL_MESSAGE}"

# Final Archive Log Sync

# Action: Local Session Log Copied to Archive Location.

# Purpose: Permanent Record Established in Reports Directory.

# NOTE: Cleanup details already logged in Module 8; Session Log preserves full history.

LOG_TO_FILE "Final Archive Log Sync completed."

cp "$LOCAL_LOG_PATH" "$ARCHIVE_LOG_PATH"

# Normal Script Termination

# Exit Code 0: Successful Completion (Warnings Permitted).

exit 0

The backup zip files are being created at the location specified. I'm just not getting an email notification. There also isn't any "sent email" entry in the system log.

Edited by wgstarks

1 hour ago, Revan335 said:

PN is a Option when its not published for all.

Not sure what this means?

15 hours ago, wgstarks said:

Not sure what this means?

Private Notification on the Forum.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.