nas08-scripts/omv/fstrim-report.sh
2026-07-26 21:34:22 -04:00

471 lines
23 KiB
Bash

#!/bin/bash
# =============================================================================
# NAS08 Weekly TRIM Report Script
# =============================================================================
# Runs fstrim on all mounted filesystems, captures results, gathers SMART
# health data for each SSD, and sends an HTML email report matching the
# KingDezigns backup monitor style.
#
# Triggered by: systemd fstrim.service (via drop-in override)
# Schedule: Weekly (Monday ~12:33 AM — managed by fstrim.timer)
#
# Script location: /usr/scripts/omv/fstrim-report.sh
# Log location: /var/log/fstrim/
# =============================================================================
set -uo pipefail
# -----------------------------------------------------------------------------
# CONFIGURATION
# -----------------------------------------------------------------------------
HOSTNAME_LABEL=$(hostname -s 2>/dev/null || echo "NAS08")
REPORT_TO="rufus.king@kingdezigns.com"
REPORT_FROM="trim-monitor@nas08.local" # Display only — OMV uses your configured SMTP relay
LOG_DIR="/var/log/fstrim"
KEEP_LOGS=90 # days to retain log files
DATE=$(date +%Y-%m-%d)
REPORT_TIME=$(date "+%Y-%m-%d %H:%M:%S")
LOG_FILE="${LOG_DIR}/fstrim-${DATE}.log"
# -----------------------------------------------------------------------------
# STATUS TRACKING
# OVERALL_STATUS: SUCCESS | WARNING | FAILED
# -----------------------------------------------------------------------------
OVERALL_STATUS="SUCCESS"
SECTION_ROWS_HTML=""
WARNING_COUNT=0
ERROR_COUNT=0
# Append a result row to the section table
# Usage: add_section_row "Section name" "STATUS" "Detail message"
# STATUS: OK | WARNING | ERROR | SKIPPED
add_section_row() {
local section="$1" status="$2" detail="$3"
case "$status" in
OK)
BADGE="<span style=\"background:#d1fae5;color:#065f46;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;\">✓ OK</span>"
;;
WARNING)
BADGE="<span style=\"background:#fef3c7;color:#92400e;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;\">⚠ WARNING</span>"
WARNING_COUNT=$((WARNING_COUNT + 1))
[[ "$OVERALL_STATUS" == "SUCCESS" ]] && OVERALL_STATUS="WARNING"
;;
ERROR)
BADGE="<span style=\"background:#fee2e2;color:#991b1b;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;\">✗ ERROR</span>"
ERROR_COUNT=$((ERROR_COUNT + 1))
OVERALL_STATUS="FAILED"
;;
SKIPPED)
BADGE="<span style=\"background:#f3f4f6;color:#6b7280;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;\">— SKIPPED</span>"
;;
esac
SECTION_ROWS_HTML+="
<tr>
<td style=\"padding:9px 14px;border-bottom:1px solid #f3f4f6;font-size:13px;color:#111827;font-weight:600;\">$section</td>
<td style=\"padding:9px 14px;border-bottom:1px solid #f3f4f6;\">$BADGE</td>
<td style=\"padding:9px 14px;border-bottom:1px solid #f3f4f6;font-size:12px;color:#6b7280;\">$detail</td>
</tr>"
}
# -----------------------------------------------------------------------------
# LOGGING
# -----------------------------------------------------------------------------
mkdir -p "$LOG_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log_section() {
echo "" | tee -a "$LOG_FILE"
echo "------------------------------------------------------------" | tee -a "$LOG_FILE"
log "$1"
echo "------------------------------------------------------------" | tee -a "$LOG_FILE"
}
# -----------------------------------------------------------------------------
# SECTION 1: RUN FSTRIM
# -----------------------------------------------------------------------------
log_section "Running fstrim on all mounted filesystems"
TRIM_RAW=$(fstrim -av 2>&1)
TRIM_EXIT=$?
TRIM_ROWS_HTML=""
TOTAL_BYTES=0
VOLUME_COUNT=0
while IFS= read -r line; do
if [[ "$line" =~ ^(/.+):[[:space:]]+(.+)[[:space:]]+\(([0-9]+)[[:space:]]bytes\) ]]; then
MOUNT="${BASH_REMATCH[1]}"
HUMAN="${BASH_REMATCH[2]}"
BYTES="${BASH_REMATCH[3]}"
TOTAL_BYTES=$((TOTAL_BYTES + BYTES))
VOLUME_COUNT=$((VOLUME_COUNT + 1))
log "Trimmed ${MOUNT}: ${HUMAN}"
TRIM_ROWS_HTML+="
<tr>
<td style=\"padding:8px 14px;border-bottom:1px solid #f3f4f6;font-size:13px;font-family:monospace;color:#111827;\">$MOUNT</td>
<td style=\"padding:8px 14px;border-bottom:1px solid #f3f4f6;font-size:13px;color:#374151;font-weight:600;\">$HUMAN freed</td>
</tr>"
fi
done <<< "$TRIM_RAW"
# Convert total bytes to human readable
if [ $TOTAL_BYTES -ge 1073741824 ]; then
TOTAL_HUMAN=$(awk "BEGIN {printf \"%.1f GiB\", $TOTAL_BYTES/1073741824}")
elif [ $TOTAL_BYTES -ge 1048576 ]; then
TOTAL_HUMAN=$(awk "BEGIN {printf \"%.1f MiB\", $TOTAL_BYTES/1048576}")
else
TOTAL_HUMAN="${TOTAL_BYTES} bytes"
fi
if [ $TRIM_EXIT -eq 0 ]; then
log "fstrim completed — $VOLUME_COUNT volume(s) · $TOTAL_HUMAN total freed"
add_section_row "TRIM Execution" "OK" "$VOLUME_COUNT volume(s) processed · $TOTAL_HUMAN total freed"
else
log "ERROR: fstrim exited with code $TRIM_EXIT"
add_section_row "TRIM Execution" "ERROR" "fstrim exited with code $TRIM_EXIT"
fi
# -----------------------------------------------------------------------------
# SECTION 2: SSD SMART HEALTH
# -----------------------------------------------------------------------------
log_section "Gathering SSD SMART health data"
SMART_ROWS_HTML=""
SMART_WARN=0
SMART_ERR=0
for DISK in $(lsblk -dpno NAME | grep -E '^/dev/sd|^/dev/nvme'); do
MODEL=$(smartctl -i "$DISK" 2>/dev/null | awk -F': ' '/Device Model|Model Number/{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}')
SERIAL=$(smartctl -i "$DISK" 2>/dev/null | awk -F': ' '/Serial Number/{gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}')
CAPACITY=$(lsblk -dno SIZE "$DISK" 2>/dev/null)
HEALTH=$(smartctl -H "$DISK" 2>/dev/null | awk '/overall-health|test result/{print $NF}')
# Temperature: +0 forces numeric conversion, handling "32 (Min/Max 25/56)" Crucial format
TEMP=$(smartctl -A "$DISK" 2>/dev/null | awk '/Temperature_Celsius|Temperature_Internal/{print $10+0; exit}')
POWER_ON=$(smartctl -A "$DISK" 2>/dev/null | awk '/Power_On_Hours/{print $10; exit}')
# Wear: PNY=SSD_Life_Left, Crucial=Percent_Lifetime_Remain, Samsung/WD=Wear_Leveling_Count, Intel=Media_Wearout_Indicator
WEAR=$(smartctl -A "$DISK" 2>/dev/null | awk '/SSD_Life_Left|Percent_Lifetime_Remain|Wear_Leveling_Count|Media_Wearout_Indicator/{print $10; exit}')
# Reallocated: PNY=Bad_Blk_Ct_Lat/Erl (RAW_VALUE is "0/88" — take first number), Crucial=Reallocate_NAND_Blk_Cnt, others=Reallocated_Sector_Ct
REALLOCATED=$(smartctl -A "$DISK" 2>/dev/null | awk '/Bad_Blk_Ct_Lat\/Erl/{split($10,a,"/"); print a[1]; exit} /Reallocate_NAND_Blk_Cnt|Reallocated_Sector_Ct|Reallocated_Sector/{print $10; exit}')
# Pending: Crucial=Current_Pending_ECC_Cnt, others=Current_Pending_Sector (PNY has no equivalent — will show N/A)
PENDING=$(smartctl -A "$DISK" 2>/dev/null | awk '/Current_Pending_ECC_Cnt|Current_Pending_Sector/{print $10; exit}')
# Uncorrectable: Crucial=Reported_Uncorrect, others=Offline_Uncorrectable (PNY has no equivalent — will show N/A)
UNCORRECTABLE=$(smartctl -A "$DISK" 2>/dev/null | awk '/Reported_Uncorrect|Offline_Uncorrectable/{print $10; exit}')
[ -z "$MODEL" ] && MODEL="Unknown"
[ -z "$SERIAL" ] && SERIAL="Unknown"
[ -z "$HEALTH" ] && HEALTH="Unknown"
[ -z "$TEMP" ] && TEMP="N/A"
[ -z "$POWER_ON" ] && POWER_ON="N/A"
[ -z "$WEAR" ] && WEAR="N/A"
[ -z "$REALLOCATED" ] && REALLOCATED="N/A"
[ -z "$PENDING" ] && PENDING="N/A"
[ -z "$UNCORRECTABLE" ] && UNCORRECTABLE="N/A"
# Health colour
if [ "$HEALTH" = "PASSED" ]; then
HEALTH_COLOUR="#065f46"
HEALTH_BG="#d1fae5"
DISK_STATUS="OK"
else
HEALTH_COLOUR="#991b1b"
HEALTH_BG="#fee2e2"
DISK_STATUS="WARNING"
SMART_WARN=$((SMART_WARN + 1))
fi
# Flag non-zero reallocated/pending/uncorrectable sectors
SECTOR_NOTE=""
if [ "$REALLOCATED" != "N/A" ] && [ "$REALLOCATED" -gt 0 ] 2>/dev/null; then
SECTOR_NOTE+=" ⚠ Reallocated: $REALLOCATED"
SMART_WARN=$((SMART_WARN + 1))
[[ "$OVERALL_STATUS" == "SUCCESS" ]] && OVERALL_STATUS="WARNING"
fi
if [ "$PENDING" != "N/A" ] && [ "$PENDING" -gt 0 ] 2>/dev/null; then
SECTOR_NOTE+=" ⚠ Pending: $PENDING"
SMART_WARN=$((SMART_WARN + 1))
[[ "$OVERALL_STATUS" == "SUCCESS" ]] && OVERALL_STATUS="WARNING"
fi
if [ "$UNCORRECTABLE" != "N/A" ] && [ "$UNCORRECTABLE" -gt 0 ] 2>/dev/null; then
SECTOR_NOTE+=" 🚨 Uncorrectable: $UNCORRECTABLE"
SMART_ERR=$((SMART_ERR + 1))
OVERALL_STATUS="FAILED"
fi
log "Drive $DISK ($MODEL) — Health: $HEALTH | Temp: ${TEMP}°C | Wear: $WEAR | Power-on: ${POWER_ON}h${SECTOR_NOTE}"
SMART_ROWS_HTML+="
<tr>
<td colspan=\"2\" style=\"padding:10px 14px 4px;font-size:13px;font-weight:700;color:#111827;border-top:2px solid #e5e7eb;\">
$DISK &nbsp;<span style=\"font-weight:400;color:#6b7280;\">$MODEL &nbsp;·&nbsp; $CAPACITY &nbsp;·&nbsp; S/N: $SERIAL</span>
</td>
</tr>
<tr>
<td style=\"padding:4px 14px 4px 24px;font-size:12px;color:#6b7280;width:30%;\">Overall health</td>
<td style=\"padding:4px 14px;\"><span style=\"background:${HEALTH_BG};color:${HEALTH_COLOUR};padding:2px 10px;border-radius:20px;font-size:11px;font-weight:700;\">$HEALTH</span>${SECTOR_NOTE:+<span style=\"font-size:11px;color:#b45309;margin-left:8px;\">$SECTOR_NOTE</span>}</td>
</tr>
<tr>
<td style=\"padding:4px 14px 4px 24px;font-size:12px;color:#6b7280;\">Temperature</td>
<td style=\"padding:4px 14px;font-size:12px;color:#374151;font-weight:600;\">${TEMP}°C</td>
</tr>
<tr>
<td style=\"padding:4px 14px 4px 24px;font-size:12px;color:#6b7280;\">Power-on hours</td>
<td style=\"padding:4px 14px;font-size:12px;color:#374151;font-weight:600;\">${POWER_ON} hrs</td>
</tr>
<tr>
<td style=\"padding:4px 14px 4px 24px;font-size:12px;color:#6b7280;\">Wear leveling</td>
<td style=\"padding:4px 14px;font-size:12px;color:#374151;font-weight:600;\">$WEAR</td>
</tr>
<tr>
<td style=\"padding:4px 14px 8px 24px;font-size:12px;color:#6b7280;\">Sectors (realloc / pending / uncorrectable)</td>
<td style=\"padding:4px 14px 8px;font-size:12px;color:#374151;font-weight:600;\">$REALLOCATED &nbsp;/&nbsp; $PENDING &nbsp;/&nbsp; $UNCORRECTABLE</td>
</tr>"
done
if [ $SMART_ERR -gt 0 ]; then
add_section_row "SSD SMART Health" "ERROR" "$SMART_ERR drive(s) with uncorrectable sectors — immediate attention required"
elif [ $SMART_WARN -gt 0 ]; then
add_section_row "SSD SMART Health" "WARNING" "$SMART_WARN warning(s) — review drive details below"
else
add_section_row "SSD SMART Health" "OK" "All drives PASSED · no sector errors detected"
fi
# -----------------------------------------------------------------------------
# SECTION 3: FILESYSTEM USAGE
# -----------------------------------------------------------------------------
log_section "Filesystem usage"
FS_ROWS_HTML=""
FS_WARN=0
while IFS= read -r line; do
USE_PCT=$(echo "$line" | awk '{print $5}' | tr -d '%')
MOUNT_PT=$(echo "$line" | awk '{print $6}')
USED=$(echo "$line" | awk '{print $3}')
SIZE=$(echo "$line" | awk '{print $2}')
if [ "$USE_PCT" -ge 90 ] 2>/dev/null; then
ROW_COLOUR="#fee2e2"; PCT_COLOUR="#991b1b"
FS_WARN=$((FS_WARN + 1))
[[ "$OVERALL_STATUS" == "SUCCESS" ]] && OVERALL_STATUS="WARNING"
elif [ "$USE_PCT" -ge 75 ] 2>/dev/null; then
ROW_COLOUR="#fef3c7"; PCT_COLOUR="#92400e"
FS_WARN=$((FS_WARN + 1))
[[ "$OVERALL_STATUS" == "SUCCESS" ]] && OVERALL_STATUS="WARNING"
else
ROW_COLOUR="transparent"; PCT_COLOUR="#065f46"
fi
log "Filesystem $MOUNT_PT: ${USED} used of ${SIZE} (${USE_PCT}%)"
FS_ROWS_HTML+="
<tr style=\"background:${ROW_COLOUR};\">
<td style=\"padding:8px 14px;font-size:13px;font-family:monospace;color:#111827;\">$MOUNT_PT</td>
<td style=\"padding:8px 14px;font-size:13px;color:#374151;\">$USED used of $SIZE</td>
<td style=\"padding:8px 14px;font-size:13px;font-weight:700;color:${PCT_COLOUR};\">${USE_PCT}%</td>
</tr>"
done < <(df -h | grep -E '^/dev/')
if [ $FS_WARN -gt 0 ]; then
add_section_row "Filesystem Usage" "WARNING" "$FS_WARN volume(s) above 75% — review recommended"
else
add_section_row "Filesystem Usage" "OK" "All volumes within normal range"
fi
# -----------------------------------------------------------------------------
# ROTATE OLD LOGS
# -----------------------------------------------------------------------------
find "$LOG_DIR" -name "fstrim-*.log" -mtime +${KEEP_LOGS} -delete
# -----------------------------------------------------------------------------
# SYSTEM CONTEXT
# -----------------------------------------------------------------------------
OMV_VER=$(dpkg -l openmediavault 2>/dev/null | awk '/^ii/{print $3}' || echo "Unknown")
KERNEL_VER=$(uname -r)
HOST_IP=$(hostname -I | awk '{print $1}')
UPTIME_STR=$(uptime -p)
LOAD_STR=$(uptime | awk -F'load average:' '{print $2}' | xargs)
MEM_USED=$(free -h | awk '/^Mem:/{print $3}')
MEM_TOTAL=$(free -h | awk '/^Mem:/{print $2}')
NEXT_RUN=$(systemctl show fstrim.timer --property=NextElapseUSecRealtime 2>/dev/null \
| awk -F= '{print $2}' || echo "See: systemctl status fstrim.timer")
log_section "Report complete — Overall: $OVERALL_STATUS"
# -----------------------------------------------------------------------------
# BUILD HTML EMAIL (style: KingDezigns monitor)
# -----------------------------------------------------------------------------
# Overall banner & badge
case "$OVERALL_STATUS" in
SUCCESS)
BADGE_HTML="<div style=\"background:#1a7f4b;color:white;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:700;\">✅ SUCCESS</div>"
BANNER_HTML="<tr><td style=\"background:#1a7f4b;padding:12px 32px;\"><span style=\"color:white;font-size:14px;font-weight:600;\">✅ &nbsp;TRIM completed successfully — no action required.</span></td></tr>"
;;
WARNING)
BADGE_HTML="<div style=\"background:#b45309;color:white;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:700;\">⚠️ WARNING</div>"
BANNER_HTML="<tr><td style=\"background:#b45309;padding:12px 32px;\"><span style=\"color:white;font-size:14px;font-weight:600;\">⚠️ &nbsp;TRIM completed with warnings — review recommended.</span></td></tr>"
;;
FAILED)
BADGE_HTML="<div style=\"background:#b91c1c;color:white;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:700;\">🚨 FAILED</div>"
BANNER_HTML="<tr><td style=\"background:#b91c1c;padding:12px 32px;\"><span style=\"color:white;font-size:14px;font-weight:600;\">🚨 &nbsp;TRIM run failed — immediate attention required!</span></td></tr>"
;;
esac
HTML_BODY=$(cat <<EOF
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
<body style="margin:0;padding:0;background:#f1f5f9;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
<table width="100%" cellspacing="0" cellpadding="0" style="background:#f1f5f9;padding:30px 0;">
<tr><td align="center">
<table width="680" cellspacing="0" cellpadding="0" style="max-width:680px;width:100%;">
<!-- Header -->
<tr><td style="background:#0f172a;border-radius:10px 10px 0 0;padding:28px 32px;">
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td>
<div style="font-size:11px;color:#94a3b8;letter-spacing:1px;text-transform:uppercase;">SSD Maintenance Monitor</div>
<div style="font-size:26px;font-weight:800;color:white;margin-top:4px;">💽 $HOSTNAME_LABEL</div>
<div style="font-size:13px;color:#94a3b8;margin-top:4px;">$REPORT_TIME &nbsp;|&nbsp; Weekly TRIM · $WARNING_COUNT warning(s) · $ERROR_COUNT error(s)</div>
</td>
<td align="right">$BADGE_HTML</td>
</tr>
</table>
</td></tr>
<!-- Status banner -->
$BANNER_HTML
<!-- Section results table -->
<tr><td style="padding:28px 24px 8px;">
<div style="background:white;border-radius:10px;box-shadow:0 1px 6px rgba(0,0,0,.10);overflow:hidden;border:1px solid #e5e7eb;">
<div style="background:#f8fafc;padding:14px 22px;border-bottom:1px solid #e5e7eb;">
<span style="font-size:13px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:.5px;">📋 Run Summary</span>
</div>
<table width="100%" cellspacing="0" cellpadding="0">
<tr style="background:#f9fafb;">
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;width:30%;">Section</th>
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;width:15%;">Status</th>
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;">Detail</th>
</tr>
$SECTION_ROWS_HTML
</table>
</div>
</td></tr>
<!-- TRIM volumes card -->
<tr><td style="padding:0 24px 8px;">
<div style="background:white;border-radius:10px;border:1px solid #e5e7eb;padding:18px 22px;">
<div style="font-size:13px;font-weight:700;color:#374151;margin-bottom:12px;text-transform:uppercase;letter-spacing:.5px;">✂️ TRIM Results — $TOTAL_HUMAN Total Freed</div>
<table width="100%" cellspacing="0" cellpadding="0">
<tr style="background:#f9fafb;">
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;">Mount point</th>
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;">Space freed</th>
</tr>
$TRIM_ROWS_HTML
</table>
</div>
</td></tr>
<!-- SMART health card -->
<tr><td style="padding:0 24px 8px;">
<div style="background:white;border-radius:10px;border:1px solid #e5e7eb;padding:18px 22px;">
<div style="font-size:13px;font-weight:700;color:#374151;margin-bottom:12px;text-transform:uppercase;letter-spacing:.5px;">🔬 SSD SMART Health</div>
<table width="100%" cellspacing="0" cellpadding="0">
$SMART_ROWS_HTML
</table>
</div>
</td></tr>
<!-- Filesystem usage card -->
<tr><td style="padding:0 24px 8px;">
<div style="background:white;border-radius:10px;border:1px solid #e5e7eb;padding:18px 22px;">
<div style="font-size:13px;font-weight:700;color:#374151;margin-bottom:12px;text-transform:uppercase;letter-spacing:.5px;">💾 Filesystem Usage</div>
<table width="100%" cellspacing="0" cellpadding="0">
<tr style="background:#f9fafb;">
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;width:35%;">Mount point</th>
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;">Usage</th>
<th style="text-align:left;padding:8px 14px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;font-weight:600;border-bottom:1px solid #e5e7eb;width:15%;">Used %</th>
</tr>
$FS_ROWS_HTML
</table>
</div>
</td></tr>
<!-- System info card -->
<tr><td style="padding:0 24px 28px;">
<div style="background:white;border-radius:10px;border:1px solid #e5e7eb;padding:18px 22px;">
<div style="font-size:13px;font-weight:700;color:#374151;margin-bottom:10px;text-transform:uppercase;letter-spacing:.5px;">🖥️ System Info</div>
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td style="font-size:13px;color:#6b7280;padding:4px 0;width:30%;">OMV</td>
<td style="font-size:13px;color:#111827;font-weight:600;">${OMV_VER}</td>
<td style="font-size:13px;color:#6b7280;padding:4px 0;width:30%;">Kernel</td>
<td style="font-size:13px;color:#111827;font-weight:600;">${KERNEL_VER}</td>
</tr>
<tr><td colspan="4" style="height:6px;"></td></tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding:4px 0;">IP Address</td>
<td style="font-size:13px;color:#111827;font-weight:600;font-family:monospace;">${HOST_IP}</td>
<td style="font-size:13px;color:#6b7280;padding:4px 0;">Uptime</td>
<td style="font-size:13px;color:#111827;font-weight:600;">${UPTIME_STR}</td>
</tr>
<tr><td colspan="4" style="height:6px;"></td></tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding:4px 0;">Load average</td>
<td style="font-size:13px;color:#111827;font-weight:600;">${LOAD_STR}</td>
<td style="font-size:13px;color:#6b7280;padding:4px 0;">Memory</td>
<td style="font-size:13px;color:#111827;font-weight:600;">${MEM_USED} / ${MEM_TOTAL}</td>
</tr>
<tr><td colspan="4" style="height:6px;"></td></tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding:4px 0;">Log file</td>
<td colspan="3" style="font-size:13px;color:#111827;font-weight:600;font-family:monospace;">${LOG_FILE}</td>
</tr>
<tr><td colspan="4" style="height:6px;"></td></tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding:4px 0;">Next TRIM run</td>
<td colspan="3" style="font-size:13px;color:#111827;font-weight:600;">${NEXT_RUN}</td>
</tr>
</table>
</div>
</td></tr>
<!-- Footer -->
<tr><td style="background:#0f172a;border-radius:0 0 10px 10px;padding:16px 32px;text-align:center;">
<span style="font-size:11px;color:#64748b;">SSD Maintenance Monitor &nbsp;|&nbsp; $HOSTNAME_LABEL &nbsp;|&nbsp; KingDezigns Infrastructure</span>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
EOF
)
# -----------------------------------------------------------------------------
# SEND EMAIL
# -----------------------------------------------------------------------------
SUBJECT="[TRIM] $HOSTNAME_LABEL$OVERALL_STATUS | $REPORT_TIME"
{
echo "To: $REPORT_TO"
echo "From: $REPORT_FROM"
echo "Subject: $SUBJECT"
echo "MIME-Version: 1.0"
echo "Content-Type: text/html; charset=UTF-8"
echo ""
echo "$HTML_BODY"
} | sendmail -t
log "Email sent to $REPORT_TO — Overall status: $OVERALL_STATUS"
log "===== fstrim-report.sh finished ====="