471 lines
23 KiB
Bash
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 <span style=\"font-weight:400;color:#6b7280;\">$MODEL · $CAPACITY · 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 / $PENDING / $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;\">✅ 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;\">⚠️ 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;\">🚨 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 | 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 | $HOSTNAME_LABEL | 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 ====="
|