###############################################################################
# pve-config-audit.sh - Proxmox VE Configuration & Performance Audit
#
# Analyzes a Proxmox node's configuration and provides actionable
# recommendations for performance tuning, best practices, and hardware
# upgrades. Designed to complement pve-health-check.sh (operational health)
# and pve-memcheck.sh (memory monitoring).
#
# Usage:
#   ./pve-config-audit.sh              # Print to stdout
#   ./pve-config-audit.sh -o /path     # Also save report to /path
#
# Exit codes:
#   0 = No recommendations
#   1 = Recommendations found
#   2 = Critical misconfigurations detected
###############################################################################

set +e

# --- Configuration -----------------------------------------------------------
# CPU
WARN_VCPU_OVERCOMMIT=2.0       # vCPU:pCPU ratio warning threshold
CRIT_VCPU_OVERCOMMIT=4.0       # vCPU:pCPU ratio critical threshold

# Memory
WARN_MEM_OVERCOMMIT_PCT=90     # Allocated vs physical RAM warning
CRIT_MEM_OVERCOMMIT_PCT=100    # Allocated vs physical RAM critical

# ZFS ARC
MIN_ARC_RAM_PCT=10             # ARC should be at least this % of total RAM
MAX_ARC_RAM_PCT=50             # ARC should not exceed this % on mixed-use nodes

# Storage
WARN_ZFS_FRAG_PCT=30           # ZFS fragmentation warning
CRIT_ZFS_FRAG_PCT=50           # ZFS fragmentation critical
WARN_POOL_CAP_PCT=75           # Pool capacity warning for performance
# -----------------------------------------------------------------------------

VERSION="1.2.0"
HOSTNAME=$(hostname)
DATE=$(date '+%Y-%m-%d %H:%M:%S')
DATE_SHORT=$(date '+%Y%m%d_%H%M%S')
OUTPUT_FILE=""
EXIT_CODE=0
RECOMMENDATIONS=0
CRITICALS=0

# --- Argument Parsing --------------------------------------------------------
while getopts "o:h" opt; do
    case $opt in
        o) OUTPUT_FILE="$OPTARG" ;;
        h)
            echo "Usage: $0 [-o output_path]"
            exit 0
            ;;
        *) echo "Usage: $0 [-o output_path]"; exit 1 ;;
    esac
done

# --- Helpers -----------------------------------------------------------------
REPORT=""

log() {
    REPORT+="$1"$'\n'
}

header() {
    log ""
    log "==============================================================================="
    log "  $1"
    log "==============================================================================="
}

subheader() {
    log ""
    log "--- $1 ---"
}

ok()   { log "  [OK]       $1"; }
rec()  { log "  [RECOMMEND] $1"; RECOMMENDATIONS=$((RECOMMENDATIONS + 1)); }
crit() { log "  [CRITICAL]  $1"; CRITICALS=$((CRITICALS + 1)); }
info() { log "  [INFO]      $1"; }

# --- Pre-flight --------------------------------------------------------------
if [[ $EUID -ne 0 ]]; then
    echo "ERROR: This script must be run as root." >&2
    exit 1
fi

header "PROXMOX VE CONFIGURATION & PERFORMANCE AUDIT"
log "  Host:    $HOSTNAME"
log "  Date:    $DATE"
log "  Script:  v$VERSION"
log "  PVE:     $(pveversion 2>/dev/null || echo 'N/A')"
log "  Kernel:  $(uname -r)"

###############################################################################
# 1. CPU CONFIGURATION
###############################################################################
header "1. CPU CONFIGURATION & OVERCOMMIT"

# Physical CPU info
phys_sockets=$(lscpu | grep "^Socket(s):" | awk '{print $2}')
cores_per_socket=$(lscpu | grep "^Core(s) per socket:" | awk '{print $NF}')
threads_per_core=$(lscpu | grep "^Thread(s) per core:" | awk '{print $NF}')
total_threads=$(nproc)
total_phys_cores=$((phys_sockets * cores_per_socket))
cpu_model=$(lscpu | grep "Model name:" | sed 's/Model name:\s*//')
cpu_flags=$(grep -m1 "^flags" /proc/cpuinfo | tr ' ' '\n')

info "CPU: $cpu_model"
info "Sockets: $phys_sockets | Cores: $total_phys_cores | Threads: $total_threads"

# Virtualization extensions
if echo "$cpu_flags" | grep -q "vmx\|svm"; then
    ok "Hardware virtualization (VT-x/AMD-V) supported"
else
    crit "Hardware virtualization NOT detected in CPU flags"
fi

# IOMMU check
iommu_enabled=false
if dmesg 2>/dev/null | grep -qi "IOMMU enabled\|DMAR:.*IOMMU\|AMD-Vi:.*IOMMU"; then
    iommu_enabled=true
    ok "IOMMU is enabled"
else
    if grep -qE "intel_iommu=on|amd_iommu=on" /proc/cmdline 2>/dev/null; then
        iommu_enabled=true
        ok "IOMMU enabled via kernel command line"
    else
        info "IOMMU is not enabled. Required for PCIe passthrough (GPU, NIC, etc.)"
        passthrough_count=$(grep -rl "hostpci" /etc/pve/qemu-server/ 2>/dev/null | wc -l)
        if (( passthrough_count > 0 )); then
            crit "IOMMU not enabled but $passthrough_count VM(s) have PCIe passthrough configured"
        fi
    fi
fi

# AES-NI check
if echo "$cpu_flags" | grep -q "aes"; then
    ok "AES-NI supported (hardware crypto acceleration)"
else
    info "AES-NI not available. ZFS encryption and VPN workloads will use software crypto."
fi

# CPU type configuration check for VMs
subheader "VM CPU Type Configuration"
vm_cpu_issues=0
while IFS= read -r conf; do
    [[ -f "$conf" ]] || continue
    vmid=$(basename "$conf" .conf)
    cpu_type=$(grep "^cpu:" "$conf" 2>/dev/null | awk '{print $2}' | cut -d, -f1)
    if [[ -z "$cpu_type" || "$cpu_type" == "kvm64" || "$cpu_type" == "qemu64" ]]; then
        vm_name=$(grep "^name:" "$conf" 2>/dev/null | awk '{print $2}')
        rec "VM $vmid ($vm_name): CPU type is '${cpu_type:-default}'. Set to 'host' for best performance unless live migration is needed."
        vm_cpu_issues=$((vm_cpu_issues + 1))
    fi
done < <(ls /etc/pve/qemu-server/*.conf 2>/dev/null)

if (( vm_cpu_issues == 0 )); then
    ok "All VMs use optimized CPU types"
fi

# vCPU overcommit (workload-aware: VMs and CTs evaluated separately)
subheader "vCPU Overcommit Ratio"
vm_vcpus_total=0
ct_vcpus_total=0
running_vms=0
running_cts=0

while IFS= read -r vmid; do
    [[ -z "$vmid" ]] && continue
    cores=$(qm config "$vmid" 2>/dev/null | grep "^cores:" | awk '{print $2}')
    sockets=$(qm config "$vmid" 2>/dev/null | grep "^sockets:" | awk '{print $2}')
    cores=${cores:-1}
    sockets=${sockets:-1}
    vm_vcpus_total=$((vm_vcpus_total + (cores * sockets)))
    running_vms=$((running_vms + 1))
done < <(qm list 2>/dev/null | awk 'NR>1 && $3=="running" {print $1}')

while IFS= read -r ctid; do
    [[ -z "$ctid" ]] && continue
    cores=$(pct config "$ctid" 2>/dev/null | grep "^cores:" | awk '{print $2}')
    cores=${cores:-$total_threads}
    ct_vcpus_total=$((ct_vcpus_total + cores))
    running_cts=$((running_cts + 1))
done < <(pct list 2>/dev/null | awk 'NR>1 && $2=="running" {print $1}')

total_vcpus=$((vm_vcpus_total + ct_vcpus_total))

if (( total_threads > 0 && total_vcpus > 0 )); then
    combined_ratio=$(awk "BEGIN {printf \"%.2f\", $total_vcpus / $total_threads}")
    info "Total vCPUs: $total_vcpus (${vm_vcpus_total} VM, ${ct_vcpus_total} CT) | Physical: $total_threads | Combined: ${combined_ratio}:1"

    # Evaluate VM and CT pools independently
    # VMs: stricter (warn 2:1, crit 4:1) -- full kernel, dedicated memory
    # CTs: relaxed (warn 6:1, crit 10:1) -- shared kernel, minimal overhead
    overalloc_status="ok"
    overalloc_details=""

    if (( vm_vcpus_total > 0 )); then
        vm_ratio=$(awk "BEGIN {printf \"%.1f\", $vm_vcpus_total / $total_threads}")
        if (( vm_vcpus_total >= total_threads * 4 )); then
            overalloc_status="crit"
            overalloc_details="VM vCPUs critically overallocated: ${vm_vcpus_total} across ${running_vms} VM(s) (${vm_ratio}:1, threshold: 4:1)"
        elif (( vm_vcpus_total >= total_threads * 2 )); then
            overalloc_status="rec"
            overalloc_details="VM vCPUs overallocated: ${vm_vcpus_total} across ${running_vms} VM(s) (${vm_ratio}:1, threshold: 2:1)"
        fi
    fi

    if (( ct_vcpus_total > 0 )); then
        ct_ratio=$(awk "BEGIN {printf \"%.1f\", $ct_vcpus_total / $total_threads}")
        if (( ct_vcpus_total >= total_threads * 10 )); then
            overalloc_status="crit"
            overalloc_details+="${overalloc_details:+; }CT vCPUs critically overallocated: ${ct_vcpus_total} across ${running_cts} CT(s) (${ct_ratio}:1, threshold: 10:1)"
        elif (( ct_vcpus_total >= total_threads * 6 )); then
            [[ "$overalloc_status" != "crit" ]] && overalloc_status="rec"
            overalloc_details+="${overalloc_details:+; }CT vCPUs overallocated: ${ct_vcpus_total} across ${running_cts} CT(s) (${ct_ratio}:1, threshold: 6:1)"
        fi
    fi

    if [[ "$overalloc_status" == "crit" ]]; then
        crit "vCPU overcommit: $overalloc_details"
    elif [[ "$overalloc_status" == "rec" ]]; then
        rec "vCPU overcommit: $overalloc_details"
    else
        ok "vCPU allocation within acceptable range (VM ${vm_vcpus_total}/${total_threads}, CT ${ct_vcpus_total}/${total_threads})"
    fi
fi

# NUMA check
numa_nodes=$(lscpu | grep "^NUMA node(s):" | awk '{print $NF}')
if (( numa_nodes > 1 )); then
    subheader "NUMA Configuration"
    info "System has $numa_nodes NUMA nodes"
    numa_disabled_vms=0
    while IFS= read -r conf; do
        [[ -f "$conf" ]] || continue
        vmid=$(basename "$conf" .conf)
        numa_set=$(grep "^numa:" "$conf" 2>/dev/null | awk '{print $2}')
        if [[ "$numa_set" != "1" ]]; then
            vm_name=$(grep "^name:" "$conf" 2>/dev/null | awk '{print $2}')
            mem=$(grep "^memory:" "$conf" 2>/dev/null | awk '{print $2}')
            mem=${mem:-0}
            if (( mem >= 4096 )); then
                rec "VM $vmid ($vm_name): NUMA not enabled. Enable for VMs with 4+ GiB RAM on multi-NUMA systems."
                numa_disabled_vms=$((numa_disabled_vms + 1))
            fi
        fi
    done < <(ls /etc/pve/qemu-server/*.conf 2>/dev/null)

    if (( numa_disabled_vms == 0 )); then
        ok "All large VMs have NUMA enabled or system is single-NUMA"
    fi
fi

###############################################################################
# 2. MEMORY CONFIGURATION
###############################################################################
header "2. MEMORY CONFIGURATION & OVERCOMMIT"

mem_total_mb=$(free -m | awk '/^Mem:/ {print $2}')
mem_total_gb=$((mem_total_mb / 1024))

info "Physical RAM: ${mem_total_gb} GiB (${mem_total_mb} MiB)"

# Calculate memory allocation (workload-aware):
#
# VM memory: Reserved -- KVM allocates the full memory value at startup.
#   balloon: 0       = full memory always reserved
#   balloon: N       = minimum N guaranteed, can inflate to memory max
#   balloon not set  = default behavior (driver can reclaim, but full allocation at start)
#
# CT memory: Capped -- LXC containers use memory on-demand from the host.
#   The memory setting is an upper limit, not a reservation.
#   Containers typically use 30-70% of their configured limit.
#   Thresholds should be more relaxed for CT-heavy nodes.
#
# Evaluation logic:
#   VM allocation alone:  warn at 85% of physical, crit at 95%
#   VM + CT combined:     warn at 90% of physical, crit at 110%
#     (CT overcommit is safe because containers share host memory on-demand)

running_vm_eff_mb=0
running_vm_max_mb=0
running_ct_mb=0
autostart_vm_eff_mb=0
autostart_vm_max_mb=0
autostart_ct_mb=0
total_alloc_mb=0
balloon_summary=""

# QEMU VMs
while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    vmid=$(echo "$line" | awk '{print $1}')
    status=$(echo "$line" | awk '{print $3}')
    mem=$(qm config "$vmid" 2>/dev/null | grep "^memory:" | awk '{print $2}')
    mem=${mem:-0}
    balloon=$(qm config "$vmid" 2>/dev/null | grep "^balloon:" | awk '{print $2}')
    onboot=$(qm config "$vmid" 2>/dev/null | grep "^onboot:" | awk '{print $2}')
    vm_name=$(qm config "$vmid" 2>/dev/null | grep "^name:" | awk '{print $2}')

    if [[ "$balloon" == "0" ]]; then
        effective_mem=$mem
        balloon_note="balloon off"
    elif [[ -n "$balloon" && "$balloon" -gt 0 ]]; then
        effective_mem=$balloon
        balloon_note="balloon ${balloon}M min / ${mem}M max"
    else
        effective_mem=$mem
        balloon_note="balloon on (default min)"
    fi

    total_alloc_mb=$((total_alloc_mb + mem))
    if [[ "$status" == "running" ]]; then
        running_vm_eff_mb=$((running_vm_eff_mb + effective_mem))
        running_vm_max_mb=$((running_vm_max_mb + mem))
        autostart_vm_eff_mb=$((autostart_vm_eff_mb + effective_mem))
        autostart_vm_max_mb=$((autostart_vm_max_mb + mem))
        balloon_summary+="  VM  ${vmid}  ${vm_name}: ${effective_mem} MiB effective / ${mem} MiB max ($balloon_note)"$'\n'
    elif [[ "$onboot" == "1" ]]; then
        autostart_vm_eff_mb=$((autostart_vm_eff_mb + effective_mem))
        autostart_vm_max_mb=$((autostart_vm_max_mb + mem))
        balloon_summary+="  VM  ${vmid}  ${vm_name}: ${effective_mem} MiB effective / ${mem} MiB max ($balloon_note) [autostart]"$'\n'
    fi
done < <(qm list 2>/dev/null | awk 'NR>1')

# LXC Containers (memory is a ceiling, not a reservation)
while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    ctid=$(echo "$line" | awk '{print $1}')
    status=$(echo "$line" | awk '{print $2}')
    mem=$(pct config "$ctid" 2>/dev/null | grep "^memory:" | awk '{print $2}')
    mem=${mem:-0}
    onboot=$(pct config "$ctid" 2>/dev/null | grep "^onboot:" | awk '{print $2}')
    ct_name=$(pct config "$ctid" 2>/dev/null | grep "^hostname:" | awk '{print $2}')
    total_alloc_mb=$((total_alloc_mb + mem))
    if [[ "$status" == "running" ]]; then
        running_ct_mb=$((running_ct_mb + mem))
        autostart_ct_mb=$((autostart_ct_mb + mem))
        balloon_summary+="  CT  ${ctid}  ${ct_name}: ${mem} MiB cap (on-demand)"$'\n'
    elif [[ "$onboot" == "1" ]]; then
        autostart_ct_mb=$((autostart_ct_mb + mem))
        balloon_summary+="  CT  ${ctid}  ${ct_name}: ${mem} MiB cap (on-demand) [autostart]"$'\n'
    fi
done < <(pct list 2>/dev/null | awk 'NR>1')

# Calculate combined totals
running_eff_mb=$((running_vm_eff_mb + running_ct_mb))
running_max_mb=$((running_vm_max_mb + running_ct_mb))
autostart_eff_mb=$((autostart_vm_eff_mb + autostart_ct_mb))
autostart_max_mb=$((autostart_vm_max_mb + autostart_ct_mb))

# Percentages
running_vm_pct=$((running_vm_eff_mb * 100 / mem_total_mb))
autostart_vm_pct=$((autostart_vm_eff_mb * 100 / mem_total_mb))
running_combined_pct=$((running_eff_mb * 100 / mem_total_mb))
autostart_combined_pct=$((autostart_eff_mb * 100 / mem_total_mb))
alloc_pct=$((total_alloc_mb * 100 / mem_total_mb))

info "VM reserved (autostart):  ${autostart_vm_eff_mb} MiB ($((autostart_vm_eff_mb / 1024)) GiB) -- ${autostart_vm_pct}% of physical (balloon-aware)"
info "CT capped (autostart):    ${autostart_ct_mb} MiB ($((autostart_ct_mb / 1024)) GiB) -- on-demand, not reserved"
info "Combined (autostart):     ${autostart_eff_mb} MiB ($((autostart_eff_mb / 1024)) GiB) -- ${autostart_combined_pct}% of physical"
if (( autostart_max_mb != autostart_eff_mb )); then
    autostart_max_pct=$((autostart_max_mb * 100 / mem_total_mb))
    info "Combined max:             ${autostart_max_mb} MiB ($((autostart_max_mb / 1024)) GiB) -- ${autostart_max_pct}% if all VMs inflate"
fi
info "Total inventory:          ${total_alloc_mb} MiB ($((total_alloc_mb / 1024)) GiB) -- ${alloc_pct}% if everything started at max"

# Evaluate VM-only allocation (strict -- VMs actually reserve RAM)
if (( autostart_vm_pct >= 95 )); then
    crit "VM reserved memory at ${autostart_vm_pct}% of physical. VM allocation alone nearly exhausts RAM."
    rec "Reduce VM memory, enable ballooning, or add RAM."
elif (( autostart_vm_pct >= 85 )); then
    rec "VM reserved memory at ${autostart_vm_pct}% of physical. Limited headroom for host OS, ZFS ARC, and containers."
else
    ok "VM reserved memory at ${autostart_vm_pct}% of physical. Adequate headroom for host and containers."
fi

# Evaluate combined allocation (relaxed -- CT memory is on-demand)
if (( autostart_combined_pct >= 130 )); then
    crit "Combined allocation at ${autostart_combined_pct}% of physical. Even with on-demand CT memory, risk of OOM under load."
    rec "Reduce CT memory caps or VM allocations."
elif (( autostart_combined_pct >= 110 )); then
    rec "Combined allocation at ${autostart_combined_pct}% of physical. Safe if CTs stay below their caps, but monitor for swap pressure."
else
    ok "Combined allocation at ${autostart_combined_pct}% of physical. Containers use memory on-demand -- actual usage will be lower."
fi

# Print per-guest memory breakdown
if [[ -n "$balloon_summary" ]]; then
    log ""
    log "  Guest Memory Breakdown:"
    log "$balloon_summary"
fi

# Ballooning check
subheader "Memory Ballooning"
balloon_disabled=0
balloon_enabled=0
while IFS= read -r conf; do
    [[ -f "$conf" ]] || continue
    vmid=$(basename "$conf" .conf)
    balloon=$(grep "^balloon:" "$conf" 2>/dev/null | awk '{print $2}')
    if [[ "$balloon" == "0" ]]; then
        balloon_disabled=$((balloon_disabled + 1))
    else
        balloon_enabled=$((balloon_enabled + 1))
    fi
done < <(ls /etc/pve/qemu-server/*.conf 2>/dev/null)

info "Ballooning: $balloon_enabled VMs enabled, $balloon_disabled VMs disabled"
if (( autostart_combined_pct >= 110 && balloon_disabled > 0 )); then
    rec "Max memory allocation is tight. Enable ballooning on VMs to allow dynamic memory reclaim."
fi

# KSM (Kernel Same-page Merging) status
subheader "KSM (Kernel Same-page Merging)"
if [[ -f /sys/kernel/mm/ksm/run ]]; then
    ksm_run=$(cat /sys/kernel/mm/ksm/run)
    if [[ "$ksm_run" == "1" ]]; then
        ksm_sharing=$(cat /sys/kernel/mm/ksm/pages_sharing 2>/dev/null || echo 0)
        ksm_shared=$(cat /sys/kernel/mm/ksm/pages_shared 2>/dev/null || echo 0)
        ksm_saved_mb=$(( (ksm_sharing - ksm_shared) * 4 / 1024 ))
        ok "KSM is active. Saving approximately ${ksm_saved_mb} MiB via page deduplication."
    else
        if (( autostart_combined_pct >= 110 )); then
            rec "KSM is disabled. Enable it to reclaim memory via page deduplication: echo 1 > /sys/kernel/mm/ksm/run"
        else
            info "KSM is disabled. Not needed with current memory headroom."
        fi
    fi
fi

# Hugepages check
subheader "Hugepages"
hugepages_total=$(grep HugePages_Total /proc/meminfo 2>/dev/null | awk '{print $2}')
if [[ -n "$hugepages_total" ]] && (( hugepages_total > 0 )); then
    hugepages_free=$(grep HugePages_Free /proc/meminfo 2>/dev/null | awk '{print $2}')
    hugepage_size=$(grep Hugepagesize /proc/meminfo 2>/dev/null | awk '{print $2}')
    info "Hugepages: ${hugepages_total} total (${hugepages_free} free) -- ${hugepage_size} kB each"
else
    info "Hugepages not configured. Consider for latency-sensitive VMs (databases, real-time workloads)."
fi

###############################################################################
# 3. STORAGE CONFIGURATION
###############################################################################
header "3. STORAGE CONFIGURATION"

subheader "Storage Type Audit"

dir_on_zfs_found=false
while IFS= read -r line; do
    storage_id=$(echo "$line" | awk '{print $1}')
    storage_type=$(echo "$line" | awk '{print $2}')

    if [[ "$storage_type" == "dir" ]]; then
        storage_path=$(pvesm path "$storage_id" 2>/dev/null | head -1 || true)
        if [[ -z "$storage_path" ]]; then
            storage_path=$(awk "/^dir: ${storage_id}\$/,/^\$/" /etc/pve/storage.cfg 2>/dev/null | grep "path " | awk '{print $2}')
        fi

        if [[ -n "$storage_path" ]]; then
            mount_fs=$(df -T "$storage_path" 2>/dev/null | tail -1 | awk '{print $2}')
            if [[ "$mount_fs" == "zfs" ]]; then
                content=$(awk "/^dir: ${storage_id}\$/,/^\$/" /etc/pve/storage.cfg 2>/dev/null | grep "content " | awk '{print $2}')
                if echo "$content" | grep -qE "images|rootdir"; then
                    crit "Storage '$storage_id' is type 'dir' on ZFS filesystem at '$storage_path'"
                    rec "  Convert to type 'zfspool' for native zvol support, snapshot capability, and better performance."
                    rec "  This requires migrating VMs off, removing the dir definition, and re-adding as zfspool."
                    dir_on_zfs_found=true
                fi
            fi
        fi
    fi
done < <(pvesm status 2>/dev/null | tail -n +2)

if ! $dir_on_zfs_found; then
    ok "No directory stores found running on ZFS with VM/CT content (no type mismatch)"
fi

# Stale systemd mount units
subheader "Stale Mount Units"
stale_mounts=0
for mount_unit in /etc/systemd/system/mnt-pve-*.mount; do
    [[ -f "$mount_unit" ]] || continue
    mount_where=$(grep "^Where=" "$mount_unit" 2>/dev/null | cut -d= -f2)
    if [[ -n "$mount_where" ]] && ! mountpoint -q "$mount_where" 2>/dev/null; then
        unit_name=$(basename "$mount_unit")
        rec "Stale systemd mount unit: $unit_name (target $mount_where is not mounted)"
        rec "  Remove with: systemctl disable '$unit_name' && rm '$mount_unit' && systemctl daemon-reload"
        stale_mounts=$((stale_mounts + 1))
    fi
done

if (( stale_mounts == 0 )); then
    ok "No stale systemd mount units found under /etc/systemd/system/"
fi

###############################################################################
# 4. ZFS TUNING
###############################################################################
header "4. ZFS TUNING"

if command -v zfs &>/dev/null && zpool list -H -o name &>/dev/null 2>&1; then

    subheader "ARC Configuration"
    arc_max_bytes=$(cat /sys/module/zfs/parameters/zfs_arc_max 2>/dev/null || echo 0)
    arc_current_bytes=$(awk '/^size/ {print $3}' /proc/spl/kstat/zfs/arcstats 2>/dev/null || echo 0)
    arc_max_mb=$((arc_max_bytes / 1024 / 1024))
    arc_current_mb=$((arc_current_bytes / 1024 / 1024))

    if (( arc_max_bytes == 0 )); then
        rec "ZFS ARC max is UNCAPPED. Set a limit to prevent ARC from starving VMs of RAM."
        rec "  Recommended: 10-25% of total RAM for mixed-use nodes."
        rec "  Set in /etc/modprobe.d/zfs.conf: options zfs zfs_arc_max=<bytes>"
        rec "  For ${mem_total_gb} GiB RAM, consider: $((mem_total_gb * 1024 * 1024 * 1024 / 4)) bytes (~$((mem_total_gb / 4)) GiB)"
    else
        arc_pct=$((arc_max_mb * 100 / mem_total_mb))
        info "ARC Max: ${arc_max_mb} MiB (${arc_pct}% of RAM) | Current: ${arc_current_mb} MiB"

        if (( arc_pct < MIN_ARC_RAM_PCT )); then
            rec "ARC max is ${arc_pct}% of RAM (below ${MIN_ARC_RAM_PCT}% recommended minimum). ZFS read performance may suffer."
        elif (( arc_pct > MAX_ARC_RAM_PCT )); then
            rec "ARC max is ${arc_pct}% of RAM (above ${MAX_ARC_RAM_PCT}% recommended for mixed-use). May starve VMs."
        else
            ok "ARC max at ${arc_pct}% of RAM is within recommended range (${MIN_ARC_RAM_PCT}-${MAX_ARC_RAM_PCT}%)"
        fi
    fi

    arc_min_bytes=$(cat /sys/module/zfs/parameters/zfs_arc_min 2>/dev/null || echo 0)
    arc_meta_limit=$(cat /sys/module/zfs/parameters/zfs_arc_meta_limit 2>/dev/null || echo 0)
    l2arc_present=false

    while IFS= read -r pool; do
        [[ -z "$pool" ]] && continue
        subheader "Pool: $pool"

        ashift=$(zpool get -H -o value ashift "$pool" 2>/dev/null)
        if [[ -n "$ashift" ]]; then
            if (( ashift < 12 )); then
                rec "Pool '$pool' ashift=$ashift. Modern drives perform best with ashift=12 or 13."
                rec "  ashift cannot be changed after pool creation. This requires pool recreation."
            else
                ok "Pool '$pool' ashift=$ashift (optimal for modern drives)"
            fi
        fi

        compression=$(zfs get -H -o value compression "$pool" 2>/dev/null)
        if [[ "$compression" == "off" ]]; then
            rec "Pool '$pool' has compression disabled. Enable lz4 for transparent space savings."
            rec "  Run: zfs set compression=lz4 $pool"
        elif [[ "$compression" == "lz4" || "$compression" == "zstd" ]]; then
            ok "Pool '$pool' compression: $compression"
        else
            info "Pool '$pool' compression: $compression"
        fi

        recordsize=$(zfs get -H -o value recordsize "$pool" 2>/dev/null)
        info "Pool '$pool' default recordsize: $recordsize"

        autotrim=$(zpool get -H -o value autotrim "$pool" 2>/dev/null)
        pool_devs=$(zpool status "$pool" 2>/dev/null | awk '/ONLINE/{print $1}' | grep -v "^$pool\|^raidz\|^mirror\|^NAME\|^state\|^config\|^errors\|^scan\|^spares\|^logs\|^cache\|^special" | head -5)
        is_ssd=false
        for pdev in $pool_devs; do
            base_dev=$(echo "$pdev" | sed 's/[0-9]*$//' | sed 's|.*/||')
            if [[ -f "/sys/block/${base_dev}/queue/rotational" ]]; then
                rot=$(cat "/sys/block/${base_dev}/queue/rotational" 2>/dev/null)
                if [[ "$rot" == "0" ]]; then
                    is_ssd=true
                    break
                fi
            fi
        done

        if $is_ssd && [[ "$autotrim" != "on" ]]; then
            rec "Pool '$pool' is on SSD but autotrim is off. Enable to maintain SSD performance."
            rec "  Run: zpool set autotrim=on $pool"
        elif $is_ssd && [[ "$autotrim" == "on" ]]; then
            ok "Pool '$pool' autotrim is enabled (SSD pool)"
        elif ! $is_ssd; then
            info "Pool '$pool' is on rotational media. Autotrim: $autotrim"
        fi

        frag=$(zpool list -H -o frag "$pool" 2>/dev/null | tr -d '%')
        if [[ -n "$frag" ]] && [[ "$frag" != "-" ]]; then
            if (( frag >= CRIT_ZFS_FRAG_PCT )); then
                crit "Pool '$pool' fragmentation: ${frag}% (performance degradation likely)"
            elif (( frag >= WARN_ZFS_FRAG_PCT )); then
                rec "Pool '$pool' fragmentation: ${frag}%. Performance may degrade."
            else
                ok "Pool '$pool' fragmentation: ${frag}%"
            fi
        fi

        vdev_type=""
        data_vdevs=0
        has_slog=false
        has_l2arc=false
        has_special=false
        has_spare=false

        while IFS= read -r status_line; do
            if echo "$status_line" | grep -q "^   raidz1"; then
                vdev_type="raidz1"; data_vdevs=$((data_vdevs + 1))
            elif echo "$status_line" | grep -q "^         raidz2"; then
                vdev_type="raidz2"; data_vdevs=$((data_vdevs + 1))
            elif echo "$status_line" | grep -q "^         raidz3"; then
                vdev_type="raidz3"; data_vdevs=$((data_vdevs + 1))
            elif echo "$status_line" | grep -q "^         mirror"; then
                vdev_type="mirror"; data_vdevs=$((data_vdevs + 1))
            elif echo "$status_line" | grep -q "^       logs"; then has_slog=true
            elif echo "$status_line" | grep -q "^       cache"; then has_l2arc=true; l2arc_present=true
            elif echo "$status_line" | grep -q "^       special"; then has_special=true
            elif echo "$status_line" | grep -q "^       spares"; then has_spare=true
            fi
        done < <(zpool status "$pool" 2>/dev/null)

        disk_count=$(zpool status "$pool" 2>/dev/null | awk '/ONLINE/{print $1}' | grep -vcE "^${pool}$|^raidz|^mirror|^NAME|^state|^config|^errors|^scan|^spares|^logs|^cache|^special" || true)

        if [[ -z "$vdev_type" ]] && (( disk_count == 1 )); then
            vdev_type="stripe (single disk)"
            rec "Pool '$pool' is a single-disk stripe. No redundancy -- a drive failure loses all data."
        elif [[ "$vdev_type" == "raidz1" ]]; then
            info "Pool '$pool' topology: RAIDZ1 ($disk_count devices)"
            if (( disk_count > 5 )); then
                rec "RAIDZ1 with $disk_count drives has long resilver times. Consider RAIDZ2."
            fi
        elif [[ "$vdev_type" == "raidz2" ]]; then
            ok "Pool '$pool' topology: RAIDZ2 ($disk_count devices)"
        elif [[ "$vdev_type" == "mirror" ]]; then
            ok "Pool '$pool' topology: Mirror ($disk_count devices)"
        fi

        if ! $has_spare && [[ "$vdev_type" == raidz* ]]; then
            info "Pool '$pool' has no hot spare configured."
        fi

        pool_size_bytes=$(zpool list -Hp -o size "$pool" 2>/dev/null)
        pool_size_gb=$((pool_size_bytes / 1024 / 1024 / 1024))

        if ! $has_slog && (( pool_size_gb > 500 )) && ! $is_ssd; then
            info "Pool '$pool' (${pool_size_gb} GiB, HDD-backed) has no SLOG."
        fi
        if ! $has_l2arc && (( pool_size_gb > 1000 )) && ! $is_ssd; then
            info "Pool '$pool' (${pool_size_gb} GiB, HDD-backed) has no L2ARC."
        fi
        if ! $has_special && (( pool_size_gb > 500 )); then
            info "Pool '$pool' has no special vdev."
        fi

    done < <(zpool list -H -o name 2>/dev/null)

    subheader "ZFS Module Parameters"
    txg_timeout=$(cat /sys/module/zfs/parameters/zfs_txg_timeout 2>/dev/null || echo "N/A")
    info "TXG timeout: ${txg_timeout}s (default: 5s)"

    zfs_prefetch=$(cat /sys/module/zfs/parameters/zfs_prefetch_disable 2>/dev/null || echo "N/A")
    if [[ "$zfs_prefetch" == "1" ]]; then
        rec "ZFS prefetch is disabled. Re-enable for sequential read workloads."
    fi

    subheader "ZFS Version Alignment"
    zfs_user_ver=$(zfs --version 2>/dev/null | head -1 | grep -oP '[\d.]+' | head -1)
    zfs_mod_ver=$(cat /sys/module/zfs/version 2>/dev/null | grep -oP '[\d.]+' | head -1)

    if [[ -n "$zfs_user_ver" && -n "$zfs_mod_ver" ]]; then
        if [[ "$zfs_user_ver" == "$zfs_mod_ver" ]]; then
            ok "ZFS userspace ($zfs_user_ver) matches kernel module ($zfs_mod_ver)"
        else
            crit "ZFS version mismatch: userspace=$zfs_user_ver, kernel module=$zfs_mod_ver"
            rec "  Align versions or reboot to load matching kernel module."
        fi
    fi

else
    info "ZFS not available on this node."
fi

###############################################################################
# 5. NETWORK CONFIGURATION
###############################################################################
header "5. NETWORK CONFIGURATION"

subheader "Bridge & Interface Inventory"
while IFS= read -r bridge; do
    [[ -z "$bridge" ]] && continue
    members=$(bridge link show 2>/dev/null | grep "master $bridge" | awk '{print $2}' | tr -d ':' | tr '\n' ', ' | sed 's/,$//')
    ip_addr=$(ip -4 addr show "$bridge" 2>/dev/null | grep inet | awk '{print $2}' | head -1)
    mtu=$(ip link show "$bridge" 2>/dev/null | grep mtu | grep -oP 'mtu \K[0-9]+')
    info "Bridge $bridge: IP=$ip_addr MTU=$mtu Members=[$members]"
done < <(ip -o link show type bridge 2>/dev/null | awk '{print $2}' | tr -d ':')

bonds=$(ls /proc/net/bonding/ 2>/dev/null)
if [[ -n "$bonds" ]]; then
    subheader "Bond Interfaces"
    for bond in $bonds; do
        mode=$(grep "Bonding Mode:" /proc/net/bonding/"$bond" 2>/dev/null | awk -F: '{print $2}' | xargs)
        slaves=$(grep "Slave Interface:" /proc/net/bonding/"$bond" 2>/dev/null | awk '{print $NF}' | tr '\n' ', ' | sed 's/,$//')
        info "Bond $bond: Mode=$mode Slaves=[$slaves]"
    done
else
    phy_nics=$(ls /sys/class/net/ 2>/dev/null | grep -E "^(en|eth)" | wc -l)
    if (( phy_nics >= 2 )); then
        info "No bond interfaces configured. $phy_nics physical NICs detected."
        rec "Consider bonding NICs for redundancy or throughput."
    fi
fi

vlans=$(ip -d link show 2>/dev/null | grep -B1 "vlan " | grep "^[0-9]" | awk '{print $2}' | tr -d ':')
if [[ -n "$vlans" ]]; then
    subheader "VLAN Interfaces"
    for vlan in $vlans; do
        vlan_id=$(ip -d link show "$vlan" 2>/dev/null | grep "vlan " | grep -oP 'id \K[0-9]+')
        info "VLAN: $vlan (ID: $vlan_id)"
    done
fi

subheader "MTU Analysis"
non_standard_mtu=false
while IFS= read -r iface; do
    [[ -z "$iface" ]] && continue
    mtu=$(ip link show "$iface" 2>/dev/null | grep -oP 'mtu \K[0-9]+')
    if [[ -n "$mtu" ]] && (( mtu > 1500 )); then
        info "Interface $iface has jumbo MTU: $mtu"
        non_standard_mtu=true
    fi
done < <(ls /sys/class/net/ 2>/dev/null | grep -E "^(en|eth|bond|vmbr)")

if ! $non_standard_mtu; then
    info "All interfaces using standard MTU (1500)."
fi

###############################################################################
# 6. BACKUP COVERAGE
###############################################################################
header "6. BACKUP COVERAGE AUDIT"

node="$HOSTNAME"
unprotected_vms=0
backup_included=()

if [[ -f /etc/pve/jobs.cfg ]]; then
    while IFS= read -r vmid_line; do
        vmid_list=$(echo "$vmid_line" | xargs)
        IFS=',' read -ra vmids <<< "$vmid_list"
        for v in "${vmids[@]}"; do
            v=$(echo "$v" | xargs)
            [[ "$v" =~ ^[0-9]+$ ]] && backup_included+=("$v")
        done
    done < <(grep "^\s*vmid " /etc/pve/jobs.cfg 2>/dev/null | sed 's/^\s*vmid //')
fi

if [[ -f /etc/pve/vzdump.cron ]]; then
    while IFS= read -r vmid_line; do
        vmid_list=$(echo "$vmid_line" | xargs)
        IFS=',' read -ra vmids <<< "$vmid_list"
        for v in "${vmids[@]}"; do
            v=$(echo "$v" | xargs)
            [[ "$v" =~ ^[0-9]+$ ]] && backup_included+=("$v")
        done
    done < <(grep "^\s*vmid " /etc/pve/vzdump.cron 2>/dev/null | sed 's/^\s*vmid //')
fi

pvesh_output=$(pvesh get /cluster/backup --output-format json-pretty 2>/dev/null || true)
if [[ -n "$pvesh_output" ]]; then
    while IFS= read -r vmid_csv; do
        [[ -z "$vmid_csv" ]] && continue
        IFS=',' read -ra vmids <<< "$vmid_csv"
        for v in "${vmids[@]}"; do
            v=$(echo "$v" | xargs)
            [[ "$v" =~ ^[0-9]+$ ]] && backup_included+=("$v")
        done
    done < <(echo "$pvesh_output" | grep -oP '"vmid"\s*:\s*"\K[^"]+')
fi

all_mode=0
if [[ -n "$pvesh_output" ]]; then
    all_mode=$(echo "$pvesh_output" | grep -c '"all"\s*:\s*1' || true)
fi
if grep -qP "^\s+all\s+1" /etc/pve/jobs.cfg 2>/dev/null; then
    all_mode=$((all_mode + 1))
fi

readarray -t backup_included < <(printf '%s\n' "${backup_included[@]}" | sort -un)

if (( all_mode > 0 )); then
    ok "At least one backup job covers all VMs/CTs on this node"
else
    while IFS= read -r vmid; do
        [[ -z "$vmid" ]] && continue
        vm_name=$(qm config "$vmid" 2>/dev/null | grep "^name:" | awk '{print $2}')
        found=false
        for included in "${backup_included[@]}"; do
            [[ "$included" == "$vmid" ]] && found=true && break
        done
        if ! $found; then
            rec "VM $vmid ($vm_name) is not included in any detected backup job"
            unprotected_vms=$((unprotected_vms + 1))
        fi
    done < <(qm list 2>/dev/null | awk 'NR>1 {print $1}')

    while IFS= read -r ctid; do
        [[ -z "$ctid" ]] && continue
        ct_name=$(pct config "$ctid" 2>/dev/null | grep "^hostname:" | awk '{print $2}')
        found=false
        for included in "${backup_included[@]}"; do
            [[ "$included" == "$ctid" ]] && found=true && break
        done
        if ! $found; then
            rec "CT $ctid ($ct_name) is not included in any detected backup job"
            unprotected_vms=$((unprotected_vms + 1))
        fi
    done < <(pct list 2>/dev/null | awk 'NR>1 {print $1}')

    if (( unprotected_vms == 0 )); then
        ok "All VMs/CTs appear to be included in a backup job"
    else
        rec "$unprotected_vms VM(s)/CT(s) may not be covered by any backup job. Verify manually."
    fi
fi

###############################################################################
# 7. PCIe & PASSTHROUGH
###############################################################################
header "7. PCIe & PASSTHROUGH CONFIGURATION"

subheader "GPU Inventory"
gpu_count=0
while IFS= read -r gpu_line; do
    [[ -z "$gpu_line" ]] && continue
    gpu_count=$((gpu_count + 1))
    info "GPU: $gpu_line"
done < <(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | sed 's/^[0-9a-f:.]\+ //')

if (( gpu_count == 0 )); then
    info "No discrete GPU detected."
fi

subheader "PCIe Passthrough VMs"
pt_count=0
while IFS= read -r conf; do
    [[ -f "$conf" ]] || continue
    vmid=$(basename "$conf" .conf)
    hostpci_lines=$(grep "^hostpci" "$conf" 2>/dev/null)
    if [[ -n "$hostpci_lines" ]]; then
        vm_name=$(grep "^name:" "$conf" 2>/dev/null | awk '{print $2}')
        pt_count=$((pt_count + 1))
        while IFS= read -r hpci; do
            info "VM $vmid ($vm_name): $hpci"
        done <<< "$hostpci_lines"
    fi
done < <(ls /etc/pve/qemu-server/*.conf 2>/dev/null)

if (( pt_count > 0 && !iommu_enabled )); then
    crit "$pt_count VM(s) use PCIe passthrough but IOMMU may not be properly enabled"
fi

if (( pt_count == 0 )); then
    info "No VMs configured with PCIe passthrough."
fi

if lsmod | grep -q nvidia 2>/dev/null; then
    subheader "NVIDIA Driver"
    nvidia_ver=$(cat /proc/driver/nvidia/version 2>/dev/null | grep "Module" | awk '{print $8}')
    info "NVIDIA kernel module loaded. Version: ${nvidia_ver:-unknown}"

    if command -v dkms &>/dev/null; then
        dkms_status=$(dkms status 2>/dev/null | grep nvidia)
        if echo "$dkms_status" | grep -q "installed"; then
            ok "NVIDIA DKMS module is installed for current kernel"
        else
            rec "NVIDIA DKMS may not be built for current kernel. Verify before kernel update."
        fi
    fi

    kernel_held=$(apt-mark showhold 2>/dev/null | grep -c "proxmox-kernel\|pve-kernel" || true)
    if (( kernel_held == 0 )); then
        rec "NVIDIA DKMS detected but kernel packages are not held. A kernel upgrade may break the driver."
    else
        ok "Kernel packages are held (safe for DKMS/NVIDIA)"
    fi
fi

###############################################################################
# 8. SECURITY & SUBSCRIPTION
###############################################################################
header "8. SECURITY & BEST PRACTICES"

subheader "Subscription"
sub_status=$(pvesubscription get 2>/dev/null | grep "^status:" | awk '{print $2}')
if [[ "$sub_status" == "Active" || "$sub_status" == "active" ]]; then
    ok "Proxmox subscription is active"
elif [[ "$sub_status" == "notfound" || -z "$sub_status" ]]; then
    info "No Proxmox subscription. Community/no-subscription repository in use."
    if grep -rq "enterprise.proxmox.com" /etc/apt/sources.list.d/ 2>/dev/null; then
        rec "Enterprise repository is configured without a subscription. This will cause apt update errors."
    fi
else
    info "Subscription status: $sub_status"
fi

subheader "SSH Security"
ssh_root_login=$(grep "^PermitRootLogin" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
if [[ "$ssh_root_login" == "yes" || -z "$ssh_root_login" ]]; then
    info "SSH root login is permitted. Consider restricting to key-based auth."
else
    ok "SSH root login: $ssh_root_login"
fi

ssh_password_auth=$(grep "^PasswordAuthentication" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
if [[ "$ssh_password_auth" == "yes" || -z "$ssh_password_auth" ]]; then
    info "SSH password authentication is enabled. Key-based auth is recommended."
fi

subheader "Firewall"
pve_fw_enabled=$(grep "^enable:" /etc/pve/firewall/cluster.fw 2>/dev/null | awk '{print $2}')
if [[ "$pve_fw_enabled" == "1" ]]; then
    ok "Proxmox cluster firewall is enabled"
else
    info "Proxmox cluster firewall is not enabled."
fi

subheader "Swap Configuration"
swap_total=$(free -m | awk '/^Swap:/ {print $2}')
if (( swap_total == 0 )); then
    rec "No swap configured. A small swap partition prevents hard OOM kills."
elif (( swap_total > mem_total_mb )); then
    rec "Swap (${swap_total} MiB) exceeds RAM (${mem_total_mb} MiB). Excessive swap degrades performance."
else
    ok "Swap: ${swap_total} MiB (proportionate to ${mem_total_mb} MiB RAM)"
fi

swappiness=$(cat /proc/sys/vm/swappiness 2>/dev/null || echo "N/A")
if [[ "$swappiness" != "N/A" ]] && (( swappiness > 10 )); then
    rec "vm.swappiness=$swappiness. Set to 10 or lower for Proxmox/ZFS nodes."
    rec "  Run: sysctl -w vm.swappiness=10 and persist in /etc/sysctl.d/99-proxmox.conf"
else
    ok "vm.swappiness=$swappiness (appropriate for Proxmox)"
fi

###############################################################################
# 9. HARDWARE UPGRADE RECOMMENDATIONS
###############################################################################
header "9. HARDWARE UPGRADE RECOMMENDATIONS"

hw_recs=0

if (( mem_total_gb < 16 )); then
    rec "System has only ${mem_total_gb} GiB RAM. Proxmox with ZFS benefits from 32+ GiB."
    hw_recs=$((hw_recs + 1))
elif (( mem_total_gb < 32 && autostart_vm_pct > 70 )); then
    rec "RAM is ${mem_total_gb} GiB with ${autostart_vm_pct}% VM-reserved at autostart. Additional RAM would help."
    hw_recs=$((hw_recs + 1))
fi

has_nvme=false; has_ssd=false; has_hdd=false
while IFS= read -r dev; do
    [[ -z "$dev" ]] && continue
    if echo "$dev" | grep -q "nvme"; then
        has_nvme=true
    else
        rot=$(cat "/sys/block/$(basename "$dev")/queue/rotational" 2>/dev/null)
        if [[ "$rot" == "0" ]]; then has_ssd=true; else has_hdd=true; fi
    fi
done < <(lsblk -dno NAME 2>/dev/null | grep -E "^(sd|nvme)")

if $has_hdd && ! $has_nvme && ! $has_ssd; then
    rec "System has only HDD storage. An NVMe or SSD would significantly improve I/O."
    hw_recs=$((hw_recs + 1))
elif $has_hdd && ! $has_nvme; then
    rec "Consider an NVMe drive for ZFS SLOG/L2ARC on HDD-backed pools."
    hw_recs=$((hw_recs + 1))
fi

ecc_check=$(dmidecode -t memory 2>/dev/null | grep "Error Correction Type:" | head -1 | awk -F: '{print $2}' | xargs)
if [[ -n "$ecc_check" ]]; then
    if echo "$ecc_check" | grep -qi "none\|unknown"; then
        info "Memory error correction: $ecc_check. ECC RAM is recommended for ZFS."
    else
        ok "Memory error correction: $ecc_check"
    fi
fi

ups_detected=false
if systemctl is-active --quiet apcupsd 2>/dev/null || systemctl is-active --quiet nut-server 2>/dev/null || systemctl is-active --quiet nut-monitor 2>/dev/null; then
    ups_detected=true
    ok "UPS monitoring service detected"
fi
if ! $ups_detected; then
    info "No UPS monitoring service detected. A UPS with monitoring prevents data loss."
fi

if (( hw_recs == 0 )); then
    ok "No urgent hardware upgrades recommended."
fi

###############################################################################
# SUMMARY
###############################################################################
header "SUMMARY"

log "  Total Recommendations: $RECOMMENDATIONS"
log "  Total Criticals:       $CRITICALS"
log ""

if (( CRITICALS > 0 )); then
    log "  RESULT: CRITICAL MISCONFIGURATIONS DETECTED - Address immediately"
    EXIT_CODE=2
elif (( RECOMMENDATIONS > 0 )); then
    log "  RESULT: $RECOMMENDATIONS RECOMMENDATION(S) - Review items above"
    EXIT_CODE=1
else
    log "  RESULT: WELL CONFIGURED - No issues detected"
    EXIT_CODE=0
fi

log ""
log "==============================================================================="
log "  End of audit - Generated $(date '+%Y-%m-%d %H:%M:%S')"
log "==============================================================================="

# --- Output ------------------------------------------------------------------
echo "$REPORT"

if [[ -n "$OUTPUT_FILE" ]]; then
    report_file="${OUTPUT_FILE}/pve-config-audit-${HOSTNAME}-${DATE_SHORT}.txt"
    echo "$REPORT" > "$report_file"
    echo "Report saved to: $report_file"
fi

exit $EXIT_CODE
