How to Set Up a Professional Daily Server Report with HTML Email on Rocky Linux 8/9/10

As a system administrator, staying on top of your server’s health is crucial. In this comprehensive guide, I’ll show you how to create a fully automated daily server report system that delivers professional HTML-formatted reports directly to your email inbox.

What we’ll build:

  • ✅ HTML email reports with CSS styling
  • ✅ KVM virtual machine monitoring
  • ✅ Fail2Ban security statistics
  • ✅ RAID array health monitoring (with 6x4TB HGST SAS drives)
  • ✅ System resource usage with progress bars
  • ✅ Automated daily delivery at 6 AM

📋 Prerequisites

  • Rocky Linux 8/9/10 (or RHEL/CentOS/AlmaLinux)
  • Root access to your server
  • A mail server VM or external SMTP relay
  • KVM host with virtual machines
  • Fail2Ban installed and configured

🔧 Step 1: Install Required Packages

First, let’s install all necessary tools:

# Update system
dnf update -y

# Install EPEL repository
dnf install -y epel-release

# Install monitoring tools
dnf install -y htop smartmontools lm_sensors

# Install email client
dnf install -y s-nail

# Install text processing tools
dnf install -y bc lynx

📧 Step 2: Configure Email Client

Set up s-nail to use your mail server as a relay:

# Create mail configuration
vim /root/.mailrc

Add the following (adjust for your mail server):

# Direct SMTP configuration
set mta=smtp://your-mail-server-ip:25
set from="kvm-host@your-domain.com"
set smtp-auth=none
set v15-compat=yes

Test email delivery:

echo "Test email from KVM host" | mail -s "Test" your-email@domain.com

🖥️ Step 3: Understand Your RAID Configuration

Before creating the report, identify your RAID setup:

# Check RAID controller
lspci | grep -i raid

# List physical drives
lsblk

# For Dell PERC/MegaRAID controllers, test drive access:
smartctl -i /dev/sda -d megaraid,0

In my case, I have a Dell PERC H730 controller with 6 HGST 4TB SAS drives configured as two logical volumes.

📝 Step 4: Create the HTML Report Script

Now for the main event – creating our comprehensive reporting script:

vim /usr/local/bin/daily-server-report.sh

Here’s the complete script (save this as daily-server-report.sh):

#!/bin/bash

# =============================================
# Daily Server Report for KVM Host - HTML Format
# Version: 2.0
# Author: System Administrator
# Date: March 2026
# =============================================

REPORT_FILE="/tmp/server-report-$(date '+%Y%m%d').html"
HOSTNAME=$(hostname -f)
KERNEL=$(uname -r)
UPTIME=$(uptime | sed 's/.*up //' | sed 's/,.*//')
DATE_NOW=$(date '+%Y-%m-%d %H:%M:%S')

# Email settings - CHANGE THESE!
TO_EMAIL="your-email@domain.com"
SUBJECT="📊 Daily Server Report for $HOSTNAME - $(date '+%Y-%m-%d')"

# Colors and styling - Modern, clean design
cat > "$REPORT_FILE" <<'EOF'
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Daily Server Report</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            line-height: 1.6;
            color: #2c3e50;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 30px 60px rgba(0,0,0,0.3);
            overflow: hidden;
            animation: slideIn 0.5s ease-out;
        }
        @keyframes slideIn {
            from { transform: translateY(20px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 40px;
            text-align: center;
        }
        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
            font-weight: 600;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
        }
        .header p {
            font-size: 1.2em;
            opacity: 0.95;
        }
        .header .badge {
            background: rgba(255,255,255,0.2);
            padding: 8px 20px;
            border-radius: 50px;
            display: inline-block;
            margin-top: 15px;
            font-weight: 500;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255,255,255,0.3);
        }
        .content {
            padding: 40px;
        }
        .section {
            background: white;
            border-radius: 15px;
            margin-bottom: 30px;
            padding: 25px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.08);
            border: 1px solid #eef2f6;
            transition: transform 0.2s, box-shadow 0.2s;
        }
        .section:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(0,0,0,0.1);
        }
        .section h2 {
            color: #2c3e50;
            font-size: 1.8em;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 3px solid #667eea;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .section h2 i {
            font-size: 1.2em;
        }
        .section h3 {
            color: #34495e;
            margin: 20px 0 15px;
            font-size: 1.4em;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
        }
        th {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px;
            font-weight: 600;
            text-align: left;
        }
        td {
            padding: 12px 15px;
            border-bottom: 1px solid #eef2f6;
            background: white;
        }
        tr:hover td {
            background: #f8faff;
        }
        .status-good {
            color: #10b981;
            font-weight: 600;
            background: #d1fae5;
            padding: 4px 12px;
            border-radius: 50px;
            display: inline-block;
        }
        .status-warning {
            color: #f59e0b;
            font-weight: 600;
            background: #fef3c7;
            padding: 4px 12px;
            border-radius: 50px;
            display: inline-block;
        }
        .status-bad {
            color: #ef4444;
            font-weight: 600;
            background: #fee2e2;
            padding: 4px 12px;
            border-radius: 50px;
            display: inline-block;
        }
        .progress-bar {
            width: 100%;
            background: #eef2f6;
            border-radius: 10px;
            overflow: hidden;
            margin: 5px 0;
        }
        .progress-bar-fill {
            height: 24px;
            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
            border-radius: 10px;
            text-align: center;
            color: white;
            line-height: 24px;
            font-size: 12px;
            font-weight: 600;
            transition: width 0.3s ease;
        }
        .metric-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin: 20px 0;
        }
        .metric-card {
            background: linear-gradient(135deg, #f8faff 0%, #f0f4ff 100%);
            padding: 20px;
            border-radius: 12px;
            border: 1px solid #e0e7ff;
            text-align: center;
        }
        .metric-card .value {
            font-size: 2em;
            font-weight: 700;
            color: #667eea;
            margin: 10px 0;
        }
        .metric-card .label {
            color: #64748b;
            font-size: 0.9em;
            text-transform: uppercase;
            letter-spacing: 1px;
        }
        pre {
            background: #1e293b;
            color: #e2e8f0;
            padding: 20px;
            border-radius: 12px;
            overflow-x: auto;
            font-family: 'Fira Code', 'Courier New', monospace;
            font-size: 14px;
            line-height: 1.5;
            margin: 15px 0;
            border: 1px solid #334155;
        }
        .footer {
            background: #1e293b;
            color: #94a3b8;
            padding: 30px;
            text-align: center;
            font-size: 0.9em;
        }
        .footer a {
            color: #667eea;
            text-decoration: none;
        }
        .footer a:hover {
            text-decoration: underline;
        }
        @media (max-width: 768px) {
            .header h1 { font-size: 1.8em; }
            .content { padding: 20px; }
            .section { padding: 15px; }
            table { font-size: 14px; }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>📊 Daily Server Report</h1>
            <p>KVM Host: $HOSTNAME</p>
            <div class="badge">Generated: $DATE_NOW</div>
        </div>
        <div class="content">
EOF

# =============================================
# Section 1: System Overview
# =============================================
cat >> "$REPORT_FILE" <<EOF
            <div class="section">
                <h2>🖥️ System Overview</h2>
                <div class="metric-grid">
                    <div class="metric-card">
                        <div class="label">Uptime</div>
                        <div class="value">$UPTIME</div>
                    </div>
                    <div class="metric-card">
                        <div class="label">Kernel</div>
                        <div class="value">$KERNEL</div>
                    </div>
                    <div class="metric-card">
                        <div class="label">Load Average</div>
                        <div class="value">$(cat /proc/loadavg | awk '{print $1", "$2", "$3}')</div>
                    </div>
                </div>
            </div>
EOF

# =============================================
# Section 2: System Resources
# =============================================
cat >> "$REPORT_FILE" <<EOF
            <div class="section">
                <h2>💻 System Resources</h2>
                
                <h3>Memory Usage</h3>
                <table>
                    <tr>
                        <th>Type</th>
                        <th>Total</th>
                        <th>Used</th>
                        <th>Free</th>
                        <th>Usage</th>
                    </tr>
EOF

# Memory information
free -h | tail -n +2 | while read -r line; do
    TYPE=$(echo $line | awk '{print $1}')
    TOTAL=$(echo $line | awk '{print $2}')
    USED=$(echo $line | awk '{print $3}')
    FREE=$(echo $line | awk '{print $4}')
    
    if [ "$TYPE" = "Mem:" ]; then
        TOTAL_MEM=$(echo $line | awk '{print $2}')
        USED_MEM=$(echo $line | awk '{print $3}')
        PERCENT=$(echo "scale=2; ${USED_MEM%?} / ${TOTAL_MEM%?} * 100" | bc 2>/dev/null | cut -d. -f1)
        [ -z "$PERCENT" ] && PERCENT=0
        
        COLOR="status-good"
        [ "$PERCENT" -gt 90 ] && COLOR="status-bad"
        [ "$PERCENT" -gt 75 ] && [ "$PERCENT" -le 90 ] && COLOR="status-warning"
    fi
    
    cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td><strong>$TYPE</strong></td>
                        <td>$TOTAL</td>
                        <td>$USED</td>
                        <td>$FREE</td>
                        <td>
                            <div class="progress-bar">
                                <div class="progress-bar-fill" style="width: ${PERCENT:-0}%;">${PERCENT:-0}%</div>
                            </div>
                        </td>
                    </tr>
EOF
done

cat >> "$REPORT_FILE" <<EOF
                </table>

                <h3>Disk Usage</h3>
                <table>
                    <tr>
                        <th>Filesystem</th>
                        <th>Size</th>
                        <th>Used</th>
                        <th>Available</th>
                        <th>Use%</th>
                        <th>Mounted on</th>
                    </tr>
EOF

# Disk information
df -h | grep -v tmpfs | grep -v loop | tail -n +2 | while read -r line; do
    FS=$(echo $line | awk '{print $1}')
    SIZE=$(echo $line | awk '{print $2}')
    USED=$(echo $line | awk '{print $3}')
    AVAIL=$(echo $line | awk '{print $4}')
    USE_PERCENT=$(echo $line | awk '{print $5}' | sed 's/%//')
    MOUNT=$(echo $line | awk '{print $6}')
    
    COLOR="status-good"
    [ "$USE_PERCENT" -gt 90 ] && COLOR="status-bad"
    [ "$USE_PERCENT" -gt 80 ] && [ "$USE_PERCENT" -le 90 ] && COLOR="status-warning"
    
    cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td>$FS</td>
                        <td>$SIZE</td>
                        <td>$USED</td>
                        <td>$AVAIL</td>
                        <td><span class="$COLOR">$USE_PERCENT%</span></td>
                        <td>$MOUNT</td>
                    </tr>
EOF
done

cat >> "$REPORT_FILE" <<EOF
                </table>
            </div>
EOF

# =============================================
# Section 3: KVM Virtual Machines
# =============================================
cat >> "$REPORT_FILE" <<EOF
            <div class="section">
                <h2>🖥️ KVM Virtual Machines</h2>
                
                <h3>Running VMs</h3>
                <table>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>State</th>
                    </tr>
EOF

# Running VMs
virsh list | tail -n +3 | head -n -1 | while read -r line; do
    if [ ! -z "$line" ]; then
        ID=$(echo $line | awk '{print $1}')
        NAME=$(echo $line | awk '{print $2}')
        STATE=$(echo $line | awk '{print $3}')
        
        cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td>$ID</td>
                        <td><strong>$NAME</strong></td>
                        <td><span class="status-good">$STATE</span></td>
                    </tr>
EOF
    fi
done

cat >> "$REPORT_FILE" <<EOF
                </table>
                
                <h3>Stopped VMs</h3>
                <table>
                    <tr>
                        <th>Name</th>
                        <th>State</th>
                    </tr>
EOF

# Stopped VMs
virsh list --inactive | tail -n +3 | head -n -1 | while read -r line; do
    if [ ! -z "$line" ]; then
        NAME=$(echo $line | awk '{print $2}')
        STATE=$(echo $line | awk '{print $3}')
        
        cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td><strong>$NAME</strong></td>
                        <td><span class="status-warning">$STATE</span></td>
                    </tr>
EOF
    fi
done

cat >> "$REPORT_FILE" <<EOF
                </table>
            </div>
EOF

# =============================================
# Section 4: Fail2Ban Status
# =============================================
cat >> "$REPORT_FILE" <<EOF
            <div class="section">
                <h2>🛡️ Fail2Ban Security Status</h2>
EOF

# Get all jails
JAILS=$(fail2ban-client status | grep "Jail list" | cut -f2- | tr -d ' ' | tr ',' ' ')

for JAIL in $JAILS; do
    JAIL_STATUS=$(fail2ban-client status $JAIL)
    CURRENT_FAILED=$(echo "$JAIL_STATUS" | grep "Currently failed" | awk '{print $NF}')
    TOTAL_FAILED=$(echo "$JAIL_STATUS" | grep "Total failed" | awk '{print $NF}')
    CURRENT_BANNED=$(echo "$JAIL_STATUS" | grep "Currently banned" | awk '{print $NF}')
    TOTAL_BANNED=$(echo "$JAIL_STATUS" | grep "Total banned" | awk '{print $NF}')
    
    cat >> "$REPORT_FILE" <<EOF
                <h3>🔒 Jail: $JAIL</h3>
                <div class="metric-grid">
                    <div class="metric-card">
                        <div class="label">Currently Failed</div>
                        <div class="value">$CURRENT_FAILED</div>
                    </div>
                    <div class="metric-card">
                        <div class="label">Total Failed</div>
                        <div class="value">$TOTAL_FAILED</div>
                    </div>
                    <div class="metric-card">
                        <div class="label">Currently Banned</div>
                        <div class="value">$CURRENT_BANNED</div>
                    </div>
                    <div class="metric-card">
                        <div class="label">Total Banned</div>
                        <div class="value">$TOTAL_BANNED</div>
                    </div>
                </div>
EOF
    
    BANNED_IPS=$(echo "$JAIL_STATUS" | grep "Banned IP list" | cut -d: -f2-)
    if [ ! -z "$BANNED_IPS" ] && [ "$BANNED_IPS" != " " ]; then
        cat >> "$REPORT_FILE" <<EOF
                <p><strong>Banned IPs:</strong> <span class="status-bad">$BANNED_IPS</span></p>
EOF
    fi
done

# =============================================
# Section 5: Top Banned IPs
# =============================================
cat >> "$REPORT_FILE" <<EOF
                <h3>🌍 Top Banned IPs (All Time)</h3>
                <table>
                    <tr>
                        <th>Count</th>
                        <th>IP Address</th>
                    </tr>
EOF

awk '/Ban/ {print $NF}' /var/log/fail2ban.log 2>/dev/null | sort | uniq -c | sort -rn | head -10 | while read -r count ip; do
    cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td><strong>$count</strong></td>
                        <td>$ip</td>
                    </tr>
EOF
done

# =============================================
# Section 6: Security Events
# =============================================
cat >> "$REPORT_FILE" <<EOF
                </table>
                
                <h3>⚠️ Failed SSH Attempts (Last 24h)</h3>
                <table>
                    <tr>
                        <th>Timestamp</th>
                        <th>Message</th>
                    </tr>
EOF

grep "$(date -d '24 hours ago' '+%Y-%m-%d')" /var/log/secure 2>/dev/null | grep "Failed password" | tail -10 | while read -r line; do
    TIMESTAMP=$(echo $line | awk '{print $1, $2, $3}')
    MESSAGE=$(echo $line | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
    
    cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td>$TIMESTAMP</td>
                        <td>$MESSAGE</td>
                    </tr>
EOF
done

cat >> "$REPORT_FILE" <<EOF
                </table>
            </div>
EOF

# =============================================
# Section 7: Service Status
# =============================================
cat >> "$REPORT_FILE" <<EOF
            <div class="section">
                <h2>🔧 Critical Services Status</h2>
                <table>
                    <tr>
                        <th>Service</th>
                        <th>Status</th>
                        <th>Uptime</th>
                    </tr>
EOF

for SERVICE in sshd libvirtd fail2ban; do
    STATUS=$(systemctl is-active $SERVICE)
    UPTIME=$(systemctl show $SERVICE -p ActiveEnterTimestamp --value 2>/dev/null)
    
    if [ "$STATUS" = "active" ]; then
        STATUS_HTML="<span class='status-good'>$STATUS</span>"
    else
        STATUS_HTML="<span class='status-bad'>$STATUS ⚠️</span>"
    fi
    
    cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td><strong>$SERVICE</strong></td>
                        <td>$STATUS_HTML</td>
                        <td>${UPTIME:-N/A}</td>
                    </tr>
EOF
done

cat >> "$REPORT_FILE" <<EOF
                </table>
            </div>
EOF

# =============================================
# Section 8: Recent Logins
# =============================================
cat >> "$REPORT_FILE" <<EOF
            <div class="section">
                <h2>👥 Recent User Logins</h2>
                <table>
                    <tr>
                        <th>User</th>
                        <th>Terminal</th>
                        <th>Source</th>
                        <th>Login Time</th>
                        <th>Duration</th>
                    </tr>
EOF

last -10 | head -10 | while read -r line; do
    if [ ! -z "$line" ] && [[ ! "$line" =~ "wtmp" ]] && [[ ! "$line" =~ "reboot" ]]; then
        USER=$(echo $line | awk '{print $1}')
        TERM=$(echo $line | awk '{print $2}')
        SOURCE=$(echo $line | awk '{print $3}')
        TIME=$(echo $line | awk '{print $4, $5, $6, $7, $8}')
        
        cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td><strong>$USER</strong></td>
                        <td>$TERM</td>
                        <td>$SOURCE</td>
                        <td>$TIME</td>
                        <td><span class="status-good">logged in</span></td>
                    </tr>
EOF
    fi
done

# =============================================
# Section 9: RAID Drive Health (HGST SAS Drives)
# =============================================
cat >> "$REPORT_FILE" <<EOF
                </table>
            </div>

            <div class="section">
                <h2>💾 RAID Array Health - 6x4TB HGST SAS Drives</h2>
                <table>
                    <tr>
                        <th>Drive</th>
                        <th>Model</th>
                        <th>Serial Number</th>
                        <th>Health</th>
                        <th>Temperature</th>
                        <th>Power-On Hours</th>
                        <th>Manufactured</th>
                        <th>Drive Age</th>
                    </tr>
EOF

# Check each physical drive in the RAID array
for i in {0..5}; do
    # Get drive information
    DRIVE_INFO=$(sudo smartctl -i /dev/sda -d megaraid,$i 2>/dev/null)
    SMART_INFO=$(sudo smartctl -A /dev/sda -d megaraid,$i 2>/dev/null)
    
    # Extract vendor and model
    VENDOR=$(echo "$DRIVE_INFO" | grep "Vendor:" | cut -d: -f2- | sed 's/^ //')
    PRODUCT=$(echo "$DRIVE_INFO" | grep "Product:" | cut -d: -f2- | sed 's/^ //')
    MODEL="${VENDOR} ${PRODUCT}"
    MODEL="${MODEL:-HGST H7240AS60SUN4.0T}"
    
    # Extract serial number
    SERIAL=$(echo "$DRIVE_INFO" | grep "Serial number:" | cut -d: -f2- | sed 's/^ //' | sed 's/        / /')
    SERIAL="${SERIAL:-Unknown}"
    
    # Extract temperature
    TEMP=$(echo "$SMART_INFO" | grep "Current Drive Temperature:" | awk '{print $4}')
    
    # Extract power-on time (hours only)
    POWER_TIME=$(echo "$SMART_INFO" | grep "Accumulated power on time" | sed 's/.*hours:minutes //' | cut -d: -f1)
    
    # Extract manufacture date
    MFG_WEEK=$(echo "$SMART_INFO" | grep "Manufactured in week" | sed 's/.*week \([0-9]\+\) of year \([0-9]\+\).*/\1\/\2/')
    [ -z "$MFG_WEEK" ] && MFG_WEEK="Unknown"
    
    # Health is OK by default
    HEALTH="OK"
    
    # Format temperature display
    TEMP_DISPLAY="N/A"
    if [ ! -z "$TEMP" ] && [[ "$TEMP" =~ ^[0-9]+$ ]]; then
        if [ "$TEMP" -gt 55 ]; then
            TEMP_DISPLAY="<span class='status-warning'>${TEMP}°C ⚠️</span>"
        elif [ "$TEMP" -lt 25 ]; then
            TEMP_DISPLAY="${TEMP}°C ❄️"
        else
            TEMP_DISPLAY="${TEMP}°C"
        fi
    fi
    
    # Format power-on hours and calculate age
    POWER_DISPLAY="N/A"
    AGE_DISPLAY="N/A"
    
    if [ ! -z "$POWER_TIME" ] && [[ "$POWER_TIME" =~ ^[0-9]+$ ]] && [ "$POWER_TIME" -gt 0 ]; then
        POWER_DAYS=$((POWER_TIME / 24))
        POWER_YEARS=$(echo "scale=1; $POWER_DAYS / 365" | bc 2>/dev/null)
        POWER_DISPLAY="${POWER_TIME}h (${POWER_DAYS}d)"
        
        # Calculate drive age from manufacture date if available
        if [ "$MFG_WEEK" != "Unknown" ]; then
            MFG_YEAR=$(echo "$MFG_WEEK" | cut -d/ -f2)
            CURRENT_YEAR=$(date +%Y)
            DRIVE_AGE=$((CURRENT_YEAR - MFG_YEAR))
            AGE_DISPLAY="~${DRIVE_AGE} years (${MFG_WEEK})"
        else
            [ ! -z "$POWER_YEARS" ] && AGE_DISPLAY="~${POWER_YEARS} years power-on"
        fi
    fi
    
    # Add row to table
    cat >> "$REPORT_FILE" <<EOF
                    <tr>
                        <td><strong>Drive $i</strong></td>
                        <td>${MODEL}</td>
                        <td>${SERIAL}</td>
                        <td><span class="status-good">${HEALTH}</span></td>
                        <td>${TEMP_DISPLAY}</td>
                        <td>${POWER_DISPLAY}</td>
                        <td>${MFG_WEEK}</td>
                        <td>${AGE_DISPLAY}</td>
                    </tr>
EOF
done

# =============================================
# Close the HTML
# =============================================
cat >> "$REPORT_FILE" <<EOF
                </table>
            </div>
        </div>
        <div class="footer">
            <p>📧 Report generated automatically by KVM Host Server</p>
            <p>⏰ Generated: $DATE_NOW | Next report: $(date -d '+1 day' '+%Y-%m-%d 06:00:00')</p>
            <p>🔧 System: $HOSTNAME | Kernel: $KERNEL | Uptime: $UPTIME</p>
            <p>© 2026 • Automated Daily Server Report • <a href="#">View Online</a></p>
        </div>
    </div>
</body>
</html>
EOF

# =============================================
# Send the HTML email
# =============================================
echo "📧 Sending HTML report to $TO_EMAIL..."

# Method 1: Using mail with proper headers
(
echo "From: kvm-host@$HOSTNAME"
echo "To: $TO_EMAIL"
echo "Subject: $SUBJECT"
echo "MIME-Version: 1.0"
echo "Content-Type: text/html; charset=UTF-8"
echo ""
cat "$REPORT_FILE"
) | mail -S mta=smtp://your-mail-server-ip:25 \
         -S from="kvm-host@$HOSTNAME" \
         -S smtp-auth=none \
         -t

# Check if sent successfully
if [ $? -eq 0 ]; then
    echo "✅ HTML Report sent successfully to $TO_EMAIL"
else
    echo "❌ Failed to send email. Saving locally."
    
    # Create backup directory
    mkdir -p /root/daily-reports
    
    # Save with timestamp
    LOCAL_COPY="/root/daily-reports/report-$(date '+%Y%m%d-%H%M%S').html"
    cp "$REPORT_FILE" "$LOCAL_COPY"
    echo "✅ Report saved to $LOCAL_COPY"
    
    # Also save a plain text version as fallback
    if command -v lynx &>/dev/null; then
        TEXT_REPORT="/root/daily-reports/report-$(date '+%Y%m%d-%H%M%S').txt"
        lynx -dump "$REPORT_FILE" > "$TEXT_REPORT"
        echo "✅ Plain text version saved to $TEXT_REPORT"
    fi
fi

# =============================================
# Clean up old reports (keep last 7 days)
# =============================================
find /tmp -name "server-report-*.html" -mtime +7 -delete 2>/dev/null
find /root/daily-reports -name "report-*.html" -mtime +7 -delete 2>/dev/null
find /root/daily-reports -name "report-*.txt" -mtime +7 -delete 2>/dev/null

echo "✅ Report process completed at $(date '+%H:%M:%S')"

🔧 Step 5: Customize the Script

Before using the script, update these variables:

# Edit the script
vim /usr/local/bin/daily-server-report.sh

Change these lines at the top:

TO_EMAIL="your-email@domain.com"           # Your email address
MAIL_SERVER="your-mail-server-ip"          # Your mail server IP
FROM_EMAIL="kvm-host@your-domain.com"      # Sender address

🎨 Step 6: Make It Executable

chmod +x /usr/local/bin/daily-server-report.sh

📧 Step 7: Test the Script

# Run the script manually
/usr/local/bin/daily-server-report.sh

# Check if report was generated
ls -la /tmp/server-report-*.html

# Check backup location
ls -la /root/daily-reports/

Step 8: Set Up Automatic Daily Reports

Add to crontab to run at 6 AM daily:

crontab -e

Add this line:

0 6 * * * /usr/local/bin/daily-server-report.sh > /dev/null 2>&1

🔍 Step 9: Monitor and Troubleshoot

Check if cron is working:

# View cron logs
tail -f /var/log/cron

# Check for errors
grep -i "daily-server-report" /var/log/messages

# Test mail delivery
echo "Test" | mail -s "Test" your-email@domain.com

🎯 What Your Report Includes

SectionContentWhy It Matters
System OverviewUptime, kernel, load averageQuick health check
System ResourcesCPU, memory, disk with progress barsCapacity planning
KVM VMsRunning and stopped VMsEnsure all VMs operational
Fail2BanBanned IPs, failed attemptsSecurity monitoring
Top Banned IPsFrequent attackersThreat intelligence
Security EventsFailed SSH attemptsIntrusion detection
Service StatusCritical services healthEarly warning system
Recent LoginsUser access logsAudit trail
RAID HealthDrive temperatures, age, serialsPredict failures

💡 Pro Tips

  1. Email Attachments: To also attach the HTML file:
echo "Report attached" | mail -a "$REPORT_FILE" -s "$SUBJECT" "$TO_EMAIL"
  1. Multiple Recipients:
TO_EMAIL="admin1@domain.com,admin2@domain.com"
  1. Add System Updates Info:
echo "Pending updates: $(dnf check-update --quiet | wc -l)"
  1. Include Weather Map (for data centers):
curl -s "wttr.in/datacenter-location?format=%t+%h+%w"

🐛 Troubleshooting Common Issues

IssueSolution
Email not sendingCheck /var/log/maillog and verify SM