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