#!/bin/bash
# k9b Server Monitor v2 — Mar 17, 2026
# HTML email alerts with proper tables and styling.
# Logs every run to /var/log/server-monitor.log for history.

ALERT_EMAIL="josh+k9b-alert@yakutaconsulting.com"
LOAD_THRESHOLD=4
HOSTNAME="k9b"
LOG_FILE="/var/log/server-monitor.log"
LOCKFILE="/tmp/server-monitor-alert.lock"
COOLDOWN=900

CHECK_URLS=(
    "https://advancedinvestmentcorp.com"
    "https://dpseugene.com"
    "https://jacoblab.com"
    "https://k9bytes.net"
)

NOW=$(date '+%Y-%m-%d %H:%M:%S %Z')
NOW_EPOCH=$(date +%s)

LOAD_1=$(awk '{print $1}' /proc/loadavg)
LOAD_5=$(awk '{print $2}' /proc/loadavg)
LOAD_15=$(awk '{print $3}' /proc/loadavg)
LOAD_INT=$(awk '{print int($1)}' /proc/loadavg)

MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')
MEM_USED=$(free -m | awk '/^Mem:/{print $3}')
MEM_FREE=$(free -m | awk '/^Mem:/{print $4}')
MEM_AVAIL=$(free -m | awk '/^Mem:/{print $7}')
MEM_PCT=$(( MEM_USED * 100 / MEM_TOTAL ))
SWAP_TOTAL=$(free -m | awk '/^Swap:/{print $2}')
SWAP_USED=$(free -m | awk '/^Swap:/{print $3}')
SWAP_PCT=0
[ "$SWAP_TOTAL" -gt 0 ] && SWAP_PCT=$(( SWAP_USED * 100 / SWAP_TOTAL ))
SWAP_FREE=$((SWAP_TOTAL - SWAP_USED))

DISK_PCT=$(df -h / | awk 'NR==2{print $5}' | tr -d '%')
UPTIME=$(uptime -p 2>/dev/null || uptime | sed 's/.*up /up /' | sed 's/,.*load.*//')
CPU_CORES=$(nproc)

CONN_ESTAB=$(ss -tn | grep ESTAB | wc -l)
CONN_CLOSE_WAIT=$(ss -tn | grep CLOSE-WAIT | wc -l)
CONN_TIME_WAIT=$(ss -tn | grep TIME-WAIT | wc -l)
CONN_FIN_WAIT=$(ss -tn | grep FIN-WAIT | wc -l)

FPM_TOTAL=$(ps aux | grep 'php-fpm: pool' | grep -v grep | wc -l)
APACHE_WORKERS=$(ps aux | grep httpd | grep -v grep | wc -l)

echo "$NOW | load=$LOAD_1/$LOAD_5/$LOAD_15 | mem=${MEM_USED}M/${MEM_TOTAL}M (${MEM_PCT}%) | swap=${SWAP_USED}M/${SWAP_TOTAL}M (${SWAP_PCT}%) | disk=${DISK_PCT}% | fpm=$FPM_TOTAL | apache=$APACHE_WORKERS | conn=$CONN_ESTAB" >> "$LOG_FILE"

ALERT=false
ALERT_REASONS=()

if [ "$LOAD_INT" -ge "$LOAD_THRESHOLD" ]; then
    ALERT=true
    ALERT_REASONS+=("HIGH LOAD: $LOAD_1 (threshold: $LOAD_THRESHOLD)")
fi
if [ "$MEM_PCT" -ge 85 ]; then
    ALERT=true
    ALERT_REASONS+=("HIGH MEMORY: ${MEM_PCT}% (${MEM_USED}M / ${MEM_TOTAL}M)")
fi
if [ "$SWAP_PCT" -ge 40 ]; then
    ALERT=true
    ALERT_REASONS+=("HIGH SWAP: ${SWAP_PCT}% (${SWAP_USED}M / ${SWAP_TOTAL}M)")
fi
if [ "$DISK_PCT" -ge 96 ]; then
    ALERT=true
    ALERT_REASONS+=("DISK: ${DISK_PCT}% used")
fi
if [ "$CONN_CLOSE_WAIT" -gt 50 ]; then
    ALERT=true
    ALERT_REASONS+=("CONN PILEUP: $CONN_CLOSE_WAIT CLOSE_WAIT")
fi

declare -A SITE_RESULTS
for URL in "${CHECK_URLS[@]}"; do
    DOMAIN=$(echo "$URL" | awk -F/ '{print $3}')
    HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 5 -A "server-monitor/1.0" "$URL" 2>/dev/null)
    RESP_TIME=$(curl -s -o /dev/null -w "%{time_total}" -m 5 -A "server-monitor/1.0" "$URL" 2>/dev/null)
    if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "301" ] && [ "$HTTP_CODE" != "302" ] && [ "$HTTP_CODE" != "307" ]; then
        sleep 3
        HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 5 -A "server-monitor/1.0" "$URL" 2>/dev/null)
        RESP_TIME=$(curl -s -o /dev/null -w "%{time_total}" -m 5 -A "server-monitor/1.0" "$URL" 2>/dev/null)
        if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "301" ] && [ "$HTTP_CODE" != "302" ] && [ "$HTTP_CODE" != "307" ]; then
            ALERT=true
            ALERT_REASONS+=("SITE DOWN: $DOMAIN returned HTTP $HTTP_CODE")
        fi
    fi
    SITE_RESULTS["$DOMAIN"]="$HTTP_CODE|$RESP_TIME"
done

if [ "$ALERT" = true ]; then
    echo "$NOW | ALERT | ${ALERT_REASONS[*]}" >> "$LOG_FILE"

    if [ ! -f "$LOCKFILE" ] || [ $(( NOW_EPOCH - $(stat -c %Y "$LOCKFILE" 2>/dev/null || echo 0) )) -gt $COOLDOWN ]; then

        FPM_ROWS=""
        while IFS= read -r line; do
            COUNT=$(echo "$line" | awk '{print $1}')
            POOL=$(echo "$line" | awk '{print $2}')
            FPM_ROWS+="<tr><td style=\"padding:4px 12px;border-bottom:1px solid #eee;\">$POOL</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:right;\">$COUNT</td></tr>"
        done < <(ps aux | grep 'php-fpm: pool' | grep -v grep | awk '{print $NF}' | sort | uniq -c | sort -rn)

        CPU_ROWS=""
        while IFS= read -r line; do
            USER=$(echo "$line" | awk '{print $1}')
            CPU=$(echo "$line" | awk '{print $2}')
            MEM=$(echo "$line" | awk '{print $3}')
            CMD=$(echo "$line" | awk '{print $4}')
            CPU_ROWS+="<tr><td style=\"padding:4px 12px;border-bottom:1px solid #eee;\">$USER</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:right;\">$CPU%</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:right;\">$MEM%</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;\">$CMD</td></tr>"
        done < <(ps aux --sort=-%cpu | awk 'NR>1 && $3>1.0{print $1, $3, $4, $11}' | head -8)

        MEM_ROWS=""
        while IFS= read -r line; do
            USER=$(echo "$line" | awk '{print $1}')
            MPCT=$(echo "$line" | awk '{print $2}')
            MBS=$(echo "$line" | awk '{print $3}')
            CMD=$(echo "$line" | awk '{print $4}')
            MEM_ROWS+="<tr><td style=\"padding:4px 12px;border-bottom:1px solid #eee;\">$USER</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:right;\">$MPCT%</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:right;\">${MBS}M</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;\">$CMD</td></tr>"
        done < <(ps aux --sort=-%mem | awk 'NR>1 && NR<=6{print $1, $4, int($6/1024), $11}')

        SITE_ROWS=""
        for DOMAIN in "${!SITE_RESULTS[@]}"; do
            IFS='|' read -r CODE RTIME <<< "${SITE_RESULTS[$DOMAIN]}"
            if [ "$CODE" = "200" ] || [ "$CODE" = "301" ] || [ "$CODE" = "302" ] || [ "$CODE" = "307" ]; then
                COLOR="#2d8a4e"; ICON="&#10003;"
            else
                COLOR="#cc3333"; ICON="&#10007;"
            fi
            SITE_ROWS+="<tr><td style=\"padding:4px 12px;border-bottom:1px solid #eee;\">$DOMAIN</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:center;color:$COLOR;font-weight:bold;\">$ICON $CODE</td><td style=\"padding:4px 12px;border-bottom:1px solid #eee;text-align:right;\">${RTIME}s</td></tr>"
        done

        REASON_HTML=""
        for R in "${ALERT_REASONS[@]}"; do
            REASON_HTML+="<div style=\"background:#cc3333;color:#fff;padding:6px 14px;border-radius:4px;margin:4px 0;font-weight:600;font-size:14px;\">$R</div>"
        done

        LOAD_COLOR="#333"
        [ "$LOAD_INT" -ge "$LOAD_THRESHOLD" ] && LOAD_COLOR="#cc3333"
        MEM_COLOR="#333"
        [ "$MEM_PCT" -ge 75 ] && MEM_COLOR="#cc7a00"
        [ "$MEM_PCT" -ge 85 ] && MEM_COLOR="#cc3333"
        SWAP_COLOR="#333"
        [ "$SWAP_PCT" -ge 30 ] && SWAP_COLOR="#cc7a00"
        [ "$SWAP_PCT" -ge 40 ] && SWAP_COLOR="#cc3333"
        DISK_COLOR="#333"
        [ "$DISK_PCT" -ge 90 ] && DISK_COLOR="#cc7a00"
        [ "$DISK_PCT" -ge 96 ] && DISK_COLOR="#cc3333"

        SUBJECT="$HOSTNAME Alert: $LOAD_1 load"

        BODY="<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body style=\"margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;\">
<div style=\"max-width:600px;margin:0 auto;background:#fff;\">

<div style=\"background:#1a1a2e;color:#fff;padding:20px 24px;\">
  <div style=\"font-size:22px;font-weight:700;\">$HOSTNAME Server Alert</div>
  <div style=\"font-size:13px;color:#999;margin-top:4px;\">$NOW</div>
</div>

<div style=\"padding:16px 24px;background:#fff5f5;border-bottom:2px solid #cc3333;\">
  $REASON_HTML
</div>

<div style=\"padding:16px 24px;\">
  <div style=\"font-size:11px;font-weight:700;color:#999;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;\">System</div>
  <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">
    <tr><td style=\"padding:6px 0;color:#666;width:120px;\">Uptime</td><td style=\"padding:6px 0;\">$UPTIME</td></tr>
    <tr><td style=\"padding:6px 0;color:#666;\">CPU Cores</td><td style=\"padding:6px 0;\">$CPU_CORES</td></tr>
    <tr><td style=\"padding:6px 0;color:#666;\">Apache Workers</td><td style=\"padding:6px 0;\">$APACHE_WORKERS</td></tr>
  </table>
</div>

<div style=\"padding:0 24px 16px;\">
  <table style=\"width:100%;border-collapse:collapse;\">
    <tr>
      <td style=\"width:25%;padding:8px;vertical-align:top;\">
        <div style=\"background:#f8f9fa;border-radius:8px;padding:12px;text-align:center;\">
          <div style=\"font-size:11px;color:#999;text-transform:uppercase;letter-spacing:0.5px;\">Load (1m)</div>
          <div style=\"font-size:24px;font-weight:700;color:$LOAD_COLOR;margin:4px 0;\">$LOAD_1</div>
          <div style=\"font-size:11px;color:#999;\">5m: $LOAD_5 &nbsp; 15m: $LOAD_15</div>
        </div>
      </td>
      <td style=\"width:25%;padding:8px;vertical-align:top;\">
        <div style=\"background:#f8f9fa;border-radius:8px;padding:12px;text-align:center;\">
          <div style=\"font-size:11px;color:#999;text-transform:uppercase;letter-spacing:0.5px;\">Memory</div>
          <div style=\"font-size:24px;font-weight:700;color:$MEM_COLOR;margin:4px 0;\">${MEM_PCT}%</div>
          <div style=\"font-size:11px;color:#999;\">${MEM_USED}M / ${MEM_TOTAL}M</div>
        </div>
      </td>
      <td style=\"width:25%;padding:8px;vertical-align:top;\">
        <div style=\"background:#f8f9fa;border-radius:8px;padding:12px;text-align:center;\">
          <div style=\"font-size:11px;color:#999;text-transform:uppercase;letter-spacing:0.5px;\">Swap</div>
          <div style=\"font-size:24px;font-weight:700;color:$SWAP_COLOR;margin:4px 0;\">${SWAP_PCT}%</div>
          <div style=\"font-size:11px;color:#999;\">${SWAP_USED}M / ${SWAP_TOTAL}M</div>
        </div>
      </td>
      <td style=\"width:25%;padding:8px;vertical-align:top;\">
        <div style=\"background:#f8f9fa;border-radius:8px;padding:12px;text-align:center;\">
          <div style=\"font-size:11px;color:#999;text-transform:uppercase;letter-spacing:0.5px;\">Disk</div>
          <div style=\"font-size:24px;font-weight:700;color:$DISK_COLOR;margin:4px 0;\">${DISK_PCT}%</div>
          <div style=\"font-size:11px;color:#999;\">of root partition</div>
        </div>
      </td>
    </tr>
  </table>
</div>

<div style=\"padding:0 24px 16px;\">
  <div style=\"font-size:11px;font-weight:700;color:#999;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;\">Connections</div>
  <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">
    <tr>
      <td style=\"padding:4px 12px;background:#f8f9fa;\">ESTABLISHED</td>
      <td style=\"padding:4px 12px;background:#f8f9fa;text-align:right;font-weight:600;\">$CONN_ESTAB</td>
      <td style=\"padding:4px 12px;background:#f8f9fa;\">CLOSE_WAIT</td>
      <td style=\"padding:4px 12px;background:#f8f9fa;text-align:right;font-weight:600;\">$CONN_CLOSE_WAIT</td>
    </tr>
    <tr>
      <td style=\"padding:4px 12px;\">TIME_WAIT</td>
      <td style=\"padding:4px 12px;text-align:right;font-weight:600;\">$CONN_TIME_WAIT</td>
      <td style=\"padding:4px 12px;\">FIN_WAIT</td>
      <td style=\"padding:4px 12px;text-align:right;font-weight:600;\">$CONN_FIN_WAIT</td>
    </tr>
  </table>
</div>

$(if [ -n "$FPM_ROWS" ]; then echo "
<div style=\"padding:0 24px 16px;\">
  <div style=\"font-size:11px;font-weight:700;color:#999;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;\">PHP-FPM &mdash; $FPM_TOTAL workers</div>
  <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">
    <tr style=\"background:#f8f9fa;\"><th style=\"padding:6px 12px;text-align:left;font-weight:600;\">Pool</th><th style=\"padding:6px 12px;text-align:right;font-weight:600;\">Workers</th></tr>
    $FPM_ROWS
  </table>
</div>"; fi)

$(if [ -n "$CPU_ROWS" ]; then echo "
<div style=\"padding:0 24px 16px;\">
  <div style=\"font-size:11px;font-weight:700;color:#999;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;\">Top CPU</div>
  <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">
    <tr style=\"background:#f8f9fa;\"><th style=\"padding:6px 12px;text-align:left;font-weight:600;\">User</th><th style=\"padding:6px 12px;text-align:right;font-weight:600;\">CPU</th><th style=\"padding:6px 12px;text-align:right;font-weight:600;\">MEM</th><th style=\"padding:6px 12px;text-align:left;font-weight:600;\">Process</th></tr>
    $CPU_ROWS
  </table>
</div>"; fi)

<div style=\"padding:0 24px 16px;\">
  <div style=\"font-size:11px;font-weight:700;color:#999;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;\">Top Memory</div>
  <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">
    <tr style=\"background:#f8f9fa;\"><th style=\"padding:6px 12px;text-align:left;font-weight:600;\">User</th><th style=\"padding:6px 12px;text-align:right;font-weight:600;\">MEM</th><th style=\"padding:6px 12px;text-align:right;font-weight:600;\">Size</th><th style=\"padding:6px 12px;text-align:left;font-weight:600;\">Process</th></tr>
    $MEM_ROWS
  </table>
</div>

<div style=\"padding:0 24px 16px;\">
  <div style=\"font-size:11px;font-weight:700;color:#999;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;\">Site Checks</div>
  <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">
    <tr style=\"background:#f8f9fa;\"><th style=\"padding:6px 12px;text-align:left;font-weight:600;\">Domain</th><th style=\"padding:6px 12px;text-align:center;font-weight:600;\">Status</th><th style=\"padding:6px 12px;text-align:right;font-weight:600;\">Response</th></tr>
    $SITE_ROWS
  </table>
</div>

<div style=\"padding:16px 24px;background:#f8f9fa;font-size:11px;color:#999;border-top:1px solid #eee;\">
  $HOSTNAME &bull; $(hostname) &bull; Alert threshold: load &ge; $LOAD_THRESHOLD
</div>

</div></body></html>"

        {
            echo "To: $ALERT_EMAIL"
            echo "From: $HOSTNAME Server <k9b-monitor@k9bytes.net>"
            echo "Subject: $SUBJECT"
            echo "MIME-Version: 1.0"
            echo "Content-Type: text/html; charset=utf-8"
            echo ""
            echo "$BODY"
        } | /usr/sbin/sendmail -t

        touch "$LOCKFILE"
        logger "server-monitor: Alert sent - load $LOAD_1 - ${ALERT_REASONS[0]}"
    else
        REMAINING=$(( COOLDOWN - (NOW_EPOCH - $(stat -c %Y "$LOCKFILE" 2>/dev/null || echo 0)) ))
        echo "$NOW | SUPPRESSED (${REMAINING}s cooldown) | load=$LOAD_1" >> "$LOG_FILE"
    fi
fi
