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 53
  • Views 14.7k
  • 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.

#!/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

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.