Skip to content

Latest commit

 

History

History
414 lines (344 loc) · 14.3 KB

File metadata and controls

414 lines (344 loc) · 14.3 KB

06. Capstone Project — System Health Monitor

Capstone Module Bash

🎯 Project Overview

This capstone project synthesizes everything you have learned across all four modules into a single, production-quality tool: a System Health Monitor.

The script will:

  • ✅ Check CPU, memory, and disk usage
  • ✅ Monitor critical services
  • ✅ Detect abnormal processes (high CPU/RAM)
  • ✅ Generate a timestamped HTML report
  • ✅ Alert when thresholds are exceeded
  • ✅ Be schedulable via cron
  • ✅ Be fully error-handled and logged

📐 Architecture

flowchart TD
    A[▶️ health_monitor.sh] --> B[Load Config]
    B --> C[Initialize Logging]
    C --> D{Check CPU}
    C --> E{Check Memory}
    C --> F{Check Disk}
    C --> G{Check Services}
    D --> H[Generate Report Section]
    E --> H
    F --> H
    G --> H
    H --> I[Build HTML Report]
    I --> J{Threshold Exceeded?}
    J -- Yes --> K[📧 Send Alert]
    J -- No --> L[💾 Save Report]
    K --> L
    L --> M[✅ Done]
Loading

📁 Project Structure

~/scripts/health-monitor/
├── health_monitor.sh      ← Main script
├── config.sh              ← Thresholds and settings
├── lib/
│   └── report.sh          ← HTML report functions
└── reports/               ← Generated HTML reports (auto-created)

📄 config.sh — Configuration

#!/usr/bin/env bash
# config.sh — System Health Monitor Configuration

# Alert Thresholds
readonly CPU_THRESHOLD=80       # Alert if CPU > 80%
readonly MEM_THRESHOLD=85       # Alert if memory > 85%
readonly DISK_THRESHOLD=90      # Alert if any disk partition > 90%

# Services to monitor (space-separated)
readonly SERVICES="nginx mysql ssh cron"

# Report settings
readonly REPORT_DIR="${HOME}/scripts/health-monitor/reports"
readonly LOG_FILE="/var/log/health_monitor.log"
readonly KEEP_REPORTS=30        # Keep last N reports

# Alert settings (set ALERT_EMAIL to enable email alerts)
readonly ALERT_EMAIL=""         # e.g., "admin@example.com"

📄 lib/report.sh — HTML Report Library

#!/usr/bin/env bash
# lib/report.sh — HTML report generation functions

html_header() {
    local title="$1"
    local hostname
    hostname=$(hostname)
    cat <<HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${title} — ${hostname}</title>
    <style>
        body { font-family: -apple-system, sans-serif; background: #0d1117; color: #e6edf3; margin: 0; padding: 20px; }
        h1   { color: #58a6ff; border-bottom: 1px solid #30363d; padding-bottom: 10px; }
        h2   { color: #79c0ff; margin-top: 30px; }
        .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin: 10px 0; }
        .ok      { color: #3fb950; }
        .warning { color: #d29922; }
        .critical{ color: #f85149; }
        table { width: 100%; border-collapse: collapse; }
        th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #21262d; }
        th { color: #8b949e; font-size: 0.9em; }
        .bar-bg { background: #21262d; border-radius: 4px; height: 8px; }
        .bar    { height: 8px; border-radius: 4px; }
    </style>
</head>
<body>
<h1>🖥️ System Health Report</h1>
<p style="color:#8b949e">Host: <strong style="color:#e6edf3">${hostname}</strong> &nbsp;|&nbsp; Generated: <strong style="color:#e6edf3">$(date '+%Y-%m-%d %H:%M:%S')</strong></p>
HTML
}

html_footer() {
    echo "</body></html>"
}

# color_class usage_percent threshold
color_class() {
    local usage="$1"
    local threshold="$2"
    if (( usage >= threshold )); then
        echo "critical"
    elif (( usage >= threshold - 15 )); then
        echo "warning"
    else
        echo "ok"
    fi
}

bar_color() {
    local class="$1"
    case "$class" in
        ok)       echo "#3fb950" ;;
        warning)  echo "#d29922" ;;
        critical) echo "#f85149" ;;
    esac
}

📄 health_monitor.sh — Main Script

#!/usr/bin/env bash
# =============================================================================
# health_monitor.sh — System Health Monitor
# Author: Shell Scripting Course — Capstone Project
# Usage: ./health_monitor.sh [--report-only] [--quiet]
# =============================================================================
set -euo pipefail
IFS=$'\n\t'

# ── Paths ────────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/config.sh"
source "${SCRIPT_DIR}/lib/report.sh"

# ── Flags ────────────────────────────────────────────────────────────────────
QUIET=false
REPORT_ONLY=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        --quiet)       QUIET=true ;;
        --report-only) REPORT_ONLY=true ;;
        --help) echo "Usage: $0 [--quiet] [--report-only]"; exit 0 ;;
        *) echo "Unknown option: $1" >&2; exit 1 ;;
    esac
    shift
done

# ── Logging ──────────────────────────────────────────────────────────────────
log() {
    local level="$1"; shift
    local msg="$*"
    local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
    $QUIET || printf "[%s] [%-8s] %s\n" "$ts" "$level" "$msg"
    printf "[%s] [%-8s] %s\n" "$ts" "$level" "$msg" >> "$LOG_FILE"
}
log_info()  { log "INFO"    "$@"; }
log_warn()  { log "WARNING" "$@"; }
log_error() { log "ERROR"   "$@" >&2; }

# ── Setup ────────────────────────────────────────────────────────────────────
mkdir -p "$REPORT_DIR"
REPORT_FILE="${REPORT_DIR}/health_$(date '+%Y%m%d_%H%M%S').html"
ALERTS=()

log_info "Starting health check on $(hostname)"

# ── Metrics Collection ───────────────────────────────────────────────────────
get_cpu_usage() {
    # Get idle percentage, subtract from 100
    local idle
    idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | tr -d '%')
    echo $(( 100 - ${idle%.*} ))
}

get_mem_usage() {
    free | awk '/^Mem:/ {printf "%.0f", $3/$2*100}'
}

get_mem_details() {
    free -h | awk '/^Mem:/ {printf "%s used of %s total", $3, $2}'
}

get_disk_sections() {
    # Returns lines of: usage% mountpoint device
    df -h --output=pcent,target,source | tail -n +2 | grep -v "tmpfs\|udev" | \
        sed 's/%//' | awk '{print $1, $2, $3}'
}

check_service() {
    local svc="$1"
    if systemctl is-active --quiet "$svc" 2>/dev/null; then
        echo "running"
    else
        echo "stopped"
    fi
}

# ── Report Building ──────────────────────────────────────────────────────────
build_report() {
    html_header "System Health Report" > "$REPORT_FILE"

    # ── CPU ──
    local cpu_usage
    cpu_usage=$(get_cpu_usage)
    local cpu_class; cpu_class=$(color_class "$cpu_usage" "$CPU_THRESHOLD")
    local cpu_color; cpu_color=$(bar_color "$cpu_class")

    log_info "CPU Usage: ${cpu_usage}%"
    (( cpu_usage >= CPU_THRESHOLD )) && {
        ALERTS+=("⚠️  CPU usage critical: ${cpu_usage}%")
        log_warn "CPU at ${cpu_usage}% exceeds threshold of ${CPU_THRESHOLD}%"
    }

    cat <<HTML >> "$REPORT_FILE"
<h2>⚙️ CPU</h2>
<div class="card">
  <p>Usage: <strong class="${cpu_class}">${cpu_usage}%</strong></p>
  <div class="bar-bg"><div class="bar" style="width:${cpu_usage}%;background:${cpu_color}"></div></div>
  <p style="color:#8b949e;font-size:0.9em;margin-top:8px">Threshold: ${CPU_THRESHOLD}%</p>
</div>
HTML

    # ── Memory ──
    local mem_usage; mem_usage=$(get_mem_usage)
    local mem_details; mem_details=$(get_mem_details)
    local mem_class; mem_class=$(color_class "$mem_usage" "$MEM_THRESHOLD")
    local mem_color; mem_color=$(bar_color "$mem_class")

    log_info "Memory Usage: ${mem_usage}% (${mem_details})"
    (( mem_usage >= MEM_THRESHOLD )) && {
        ALERTS+=("⚠️  Memory usage critical: ${mem_usage}%")
        log_warn "Memory at ${mem_usage}% exceeds threshold of ${MEM_THRESHOLD}%"
    }

    cat <<HTML >> "$REPORT_FILE"
<h2>💾 Memory</h2>
<div class="card">
  <p>Usage: <strong class="${mem_class}">${mem_usage}%</strong> — ${mem_details}</p>
  <div class="bar-bg"><div class="bar" style="width:${mem_usage}%;background:${mem_color}"></div></div>
  <p style="color:#8b949e;font-size:0.9em;margin-top:8px">Threshold: ${MEM_THRESHOLD}%</p>
</div>
HTML

    # ── Disk ──
    echo "<h2>🗄️ Disk</h2><div class=\"card\"><table><tr><th>Mount</th><th>Device</th><th>Usage</th><th>Bar</th></tr>" >> "$REPORT_FILE"
    while IFS=' ' read -r usage mountpoint device; do
        local disk_class; disk_class=$(color_class "$usage" "$DISK_THRESHOLD")
        local disk_color; disk_color=$(bar_color "$disk_class")

        log_info "Disk $mountpoint: ${usage}%"
        (( usage >= DISK_THRESHOLD )) && {
            ALERTS+=("⚠️  Disk ${mountpoint} critical: ${usage}%")
            log_warn "Disk $mountpoint at ${usage}% exceeds threshold of ${DISK_THRESHOLD}%"
        }

        cat <<HTML >> "$REPORT_FILE"
<tr>
  <td>${mountpoint}</td>
  <td style="color:#8b949e">${device}</td>
  <td><strong class="${disk_class}">${usage}%</strong></td>
  <td style="width:200px"><div class="bar-bg"><div class="bar" style="width:${usage}%;background:${disk_color}"></div></div></td>
</tr>
HTML
    done < <(get_disk_sections)
    echo "</table></div>" >> "$REPORT_FILE"

    # ── Services ──
    echo "<h2>🔧 Services</h2><div class=\"card\"><table><tr><th>Service</th><th>Status</th></tr>" >> "$REPORT_FILE"
    for svc in $SERVICES; do
        local status; status=$(check_service "$svc")
        local svc_class="ok"
        [[ "$status" == "stopped" ]] && {
            svc_class="critical"
            ALERTS+=("🚨 Service DOWN: ${svc}")
            log_warn "Service $svc is STOPPED"
        }
        echo "<tr><td>${svc}</td><td><strong class=\"${svc_class}\">${status}</strong></td></tr>" >> "$REPORT_FILE"
    done
    echo "</table></div>" >> "$REPORT_FILE"

    # ── Alert Summary ──
    if [[ ${#ALERTS[@]} -gt 0 ]]; then
        echo "<h2>🚨 Alert Summary</h2><div class=\"card critical\">" >> "$REPORT_FILE"
        for alert in "${ALERTS[@]}"; do
            echo "<p>${alert}</p>" >> "$REPORT_FILE"
        done
        echo "</div>" >> "$REPORT_FILE"
    else
        echo "<h2>✅ All Systems Normal</h2><div class=\"card ok\"><p>No thresholds exceeded.</p></div>" >> "$REPORT_FILE"
    fi

    html_footer >> "$REPORT_FILE"
}

# ── Alert Dispatch ───────────────────────────────────────────────────────────
send_alerts() {
    [[ ${#ALERTS[@]} -eq 0 ]] && return
    [[ -z "$ALERT_EMAIL" ]] && return

    local subject="[ALERT] $(hostname) Health Monitor — $(date '+%Y-%m-%d %H:%M')"
    local body
    printf -v body "System Health Alerts:\n\n%s\n\nFull report: %s" \
        "$(printf '%s\n' "${ALERTS[@]}")" "$REPORT_FILE"

    echo "$body" | mail -s "$subject" "$ALERT_EMAIL"
    log_info "Alert email sent to $ALERT_EMAIL"
}

# ── Cleanup Old Reports ───────────────────────────────────────────────────────
cleanup_old_reports() {
    local count
    count=$(ls -1 "${REPORT_DIR}"/health_*.html 2>/dev/null | wc -l)
    if (( count > KEEP_REPORTS )); then
        ls -1t "${REPORT_DIR}"/health_*.html | tail -n +"$(( KEEP_REPORTS + 1 ))" | xargs rm -f
        log_info "Cleaned up old reports (kept last $KEEP_REPORTS)"
    fi
}

# ── Main ─────────────────────────────────────────────────────────────────────
main() {
    build_report
    send_alerts
    cleanup_old_reports
    log_info "Report saved: $REPORT_FILE"
    echo "$REPORT_FILE"
}

main "$@"

🚀 Setup and Deployment

# 1. Create the project structure
mkdir -p ~/scripts/health-monitor/lib
mkdir -p ~/scripts/health-monitor/reports

# 2. Create and edit all three files as shown above, then:
chmod +x ~/scripts/health-monitor/health_monitor.sh

# 3. Test it manually
~/scripts/health-monitor/health_monitor.sh

# 4. Open the generated report in a browser
xdg-open "$(~/scripts/health-monitor/health_monitor.sh --quiet)"

# 5. Schedule with cron — every 15 minutes
crontab -e
# Add:
# */15 * * * * /home/akash278/scripts/health-monitor/health_monitor.sh --quiet

🏆 What This Project Demonstrates

Concept Learned Where It's Used
Shebang + set -euo pipefail Line 1–2 of main script
Variables, readonly, defaults config.sh
Functions + local + return All lib/report.sh and collectors
while/case loops Arg parsing, service loop, disk loop
getopts-style flag parsing --quiet, --report-only flags
Arrays + += ALERTS array
String operations + heredocs HTML generation
Process substitution < <() Disk section parsing
trap EXIT Implicit via set -e + structured logging
Cron scheduling Deployment step
Security: quoting, validation Throughout

🎓 Congratulations! You have completed the Shell Scripting Professional Course. You now have the foundational knowledge to write production-quality Bash scripts for automation, DevOps, system administration, and beyond.