Compare commits

..

2 Commits

Author SHA1 Message Date
76f7aaa64c Bump version to 1.2.0 and update changelog
Update version number and document all changes from the
issue fixes including new features, bug fixes, and
performance improvements.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:56:29 -05:00
ef004c621e Fix CPU MHz parsing and memory DIMM display issues
- Fix CPU MHz showing multiple lines by using head -1 and proper regex
- Add CPU max MHz display when available
- Fix memory DIMM table not showing data by improving regex patterns
- Fix Type: field matching other fields like "Type Detail:"
- Filter out bonding_masters from network interfaces list
- Add fallback message when DIMM details cannot be parsed

#17

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:55:55 -05:00
2 changed files with 90 additions and 64 deletions

View File

@@ -73,6 +73,8 @@ curl -sL "http://10.10.10.63:3000/LotusGuild/proxDoc/raw/branch/main/proxDoc.sh"
- `--vm-list`: Check VM vitals - `--vm-list`: Check VM vitals
- `--ct-list`: Check container vitals - `--ct-list`: Check container vitals
- `--backup`: Review backup health - `--backup`: Review backup health
- `--checks=LIST`: Run only specific checks (comma-separated)
- Valid checks: cpu, ram, memory, storage, disk, network, hardware, temps, services, ceph, vms, containers
## Output Information ## Output Information
@@ -101,10 +103,26 @@ The script provides detailed information about:
## Version ## Version
Current Version: 1.1.0 Current Version: 1.2.0
### Changelog ### Changelog
#### v1.2.0
- Fixed variable quoting in disk iteration loops (security)
- Added input validation with whitelist of valid options
- Added examples to help documentation
- Added timeout protection to smartctl and ceph commands
- Added `--checks=` option for selective diagnostics
- Extracted magic strings into named constants
- Added validation for potentially empty variables
- Standardized error handling with cleanup trap
- Added optional logging infrastructure (PROXDOC_LOGFILE)
- Cached disk list and unit files to reduce command overhead
- Added efficient process wait utility function
- Fixed CPU MHz parsing showing multiple values
- Fixed memory DIMM table not displaying data
- Fixed bonding_masters showing as network interface
#### v1.1.0 #### v1.1.0
- Added DriveAtlas integration (`--drives`) for physical drive bay mapping - Added DriveAtlas integration (`--drives`) for physical drive bay mapping
- Added Ceph cluster health monitoring (`--ceph`) - Added Ceph cluster health monitoring (`--ceph`)

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
VERSION="1.1.0" VERSION="1.2.0"
################### ###################
# Timeout Configuration # Timeout Configuration
@@ -210,15 +210,21 @@ get_disk_health() {
} }
get_cpu_info() { get_cpu_info() {
local cpu_info cpu_cores cpu_mhz local cpu_info cpu_cores cpu_mhz cpu_max_mhz
cpu_info=$(grep -m 1 -w 'model name' /proc/cpuinfo 2>/dev/null | awk -F: '{print $2}' | xargs) cpu_info=$(grep -m 1 -w 'model name' /proc/cpuinfo 2>/dev/null | awk -F: '{print $2}' | xargs)
cpu_cores=$(lscpu 2>/dev/null | grep '^CPU(s):' | awk '{print $2}') cpu_cores=$(lscpu 2>/dev/null | grep -E '^CPU\(s\):' | awk '{print $2}')
cpu_mhz=$(lscpu 2>/dev/null | grep 'MHz' | awk '{print $4}') # Get current CPU MHz (first match only) - handle both "CPU MHz:" and "CPU(s) MHz:" formats
cpu_mhz=$(lscpu 2>/dev/null | grep -E '^CPU( )?(max )?MHz:' | head -1 | awk -F: '{print $2}' | xargs)
cpu_max_mhz=$(lscpu 2>/dev/null | grep -E '^CPU max MHz:' | awk -F: '{print $2}' | xargs)
echo -e "${GREEN}CPU Model:${NC} ${cpu_info:-Unknown}" echo -e "${GREEN}CPU Model:${NC} ${cpu_info:-Unknown}"
echo -e "${GREEN}CPU Cores:${NC} ${cpu_cores:-Unknown}" echo -e "${GREEN}CPU Cores:${NC} ${cpu_cores:-Unknown}"
echo -e "${GREEN}CPU MHz:${NC} ${cpu_mhz:-Unknown}" if [[ -n "$cpu_max_mhz" ]]; then
echo -e "${GREEN}CPU MHz:${NC} ${cpu_mhz:-Unknown} (max: ${cpu_max_mhz})"
else
echo -e "${GREEN}CPU MHz:${NC} ${cpu_mhz:-Unknown}"
fi
} }
get_ram_info() { get_ram_info() {
@@ -292,97 +298,96 @@ get_motherboard_info() {
get_memory_details() { get_memory_details() {
echo -e "\n${GREEN}=== Memory DIMM Information ===${NC}" echo -e "\n${GREEN}=== Memory DIMM Information ===${NC}"
# Use a more robust parsing approach
local locator size type speed manufacturer
local in_device=false
# Print header # Print header
printf "%-12s %-12s %-10s %-12s %-20s\n" "Slot" "Size" "Type" "Speed" "Manufacturer" printf "%-12s %-12s %-10s %-12s %-20s\n" "Slot" "Size" "Type" "Speed" "Manufacturer"
printf "%-12s %-12s %-10s %-12s %-20s\n" "----" "----" "----" "-----" "------------" printf "%-12s %-12s %-10s %-12s %-20s\n" "----" "----" "----" "-----" "------------"
local locator="" size="" type="" speed="" manufacturer=""
local in_device=false
local dimm_count=0
while IFS= read -r line; do while IFS= read -r line; do
# Detect start of a memory device section # Detect start of a memory device section (may have leading whitespace or not)
if [[ "$line" =~ ^Memory[[:space:]]Device ]]; then if [[ "$line" =~ Memory[[:space:]]+Device$ ]] || [[ "$line" == "Memory Device" ]]; then
# If we have data from previous device, print it # Print previous device if it had valid data
if [[ -n "$locator" && -n "$size" && ! "$size" =~ (No|Not|Installed) ]]; then if [[ -n "$locator" && -n "$size" && ! "$size" =~ ^(No|Not)[[:space:]] ]]; then
printf "%-12s %-12s %-10s %-12s %-20s\n" \ printf "%-12s %-12s %-10s %-12s %-20s\n" \
"${locator:-N/A}" \ "${locator:-N/A}" \
"${size:-N/A}" \ "${size:-N/A}" \
"${type:-N/A}" \ "${type:-N/A}" \
"${speed:-N/A}" \ "${speed:-N/A}" \
"${manufacturer:-N/A}" "${manufacturer:-N/A}"
((dimm_count++))
fi fi
# Reset for new device
# Reset variables for new device locator="" size="" type="" speed="" manufacturer=""
locator=""
size=""
type=""
speed=""
manufacturer=""
in_device=true in_device=true
continue continue
fi fi
# Skip if not in a device section # Skip if not in a device section
[[ "$in_device" != true ]] && continue [[ "$in_device" != true ]] && continue
# Parse fields (case-insensitive, flexible whitespace) # Parse fields - be very specific to avoid matching wrong lines
if [[ "$line" =~ ^[[:space:]]*Locator:[[:space:]]*(.+)$ ]] && [[ ! "$line" =~ Bank ]]; then # Locator (but not Bank Locator)
if [[ "$line" =~ ^[[:space:]]*Locator:[[:space:]]*(.+)$ ]] && [[ ! "$line" =~ Bank[[:space:]]*Locator ]]; then
locator="${BASH_REMATCH[1]}" locator="${BASH_REMATCH[1]}"
locator="${locator// /_}" # Replace spaces with underscores locator="${locator// /_}"
# Size
elif [[ "$line" =~ ^[[:space:]]*Size:[[:space:]]*(.+)$ ]]; then elif [[ "$line" =~ ^[[:space:]]*Size:[[:space:]]*(.+)$ ]]; then
size="${BASH_REMATCH[1]}" size="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^[[:space:]]*Type:[[:space:]]*(.+)$ ]]; then # Type (exact match, not Type Detail or other Type fields)
elif [[ "$line" =~ ^[[:space:]]*Type:[[:space:]]*([A-Za-z0-9]+)$ ]]; then
type="${BASH_REMATCH[1]}" type="${BASH_REMATCH[1]}"
# Skip if it's an error or unknown type # Only clear if it's truly unknown
[[ "$type" =~ (Unknown|Error|Correction) ]] && type="" [[ "$type" == "Unknown" ]] && type=""
# Speed (exact field)
elif [[ "$line" =~ ^[[:space:]]*Speed:[[:space:]]*(.+)$ ]]; then elif [[ "$line" =~ ^[[:space:]]*Speed:[[:space:]]*(.+)$ ]]; then
speed="${BASH_REMATCH[1]}" speed="${BASH_REMATCH[1]}"
[[ "$speed" =~ Unknown ]] && speed="" [[ "$speed" == "Unknown" ]] && speed=""
# Manufacturer
elif [[ "$line" =~ ^[[:space:]]*Manufacturer:[[:space:]]*(.+)$ ]]; then elif [[ "$line" =~ ^[[:space:]]*Manufacturer:[[:space:]]*(.+)$ ]]; then
manufacturer="${BASH_REMATCH[1]}" manufacturer="${BASH_REMATCH[1]}"
[[ "$manufacturer" =~ (Unknown|NO DIMM) ]] && manufacturer="" # Clear common placeholder values
[[ "$manufacturer" =~ ^(Unknown|NO[[:space:]]DIMM|Not[[:space:]]Specified)$ ]] && manufacturer=""
fi fi
# Empty line marks end of device section # Empty line or new section marks end
if [[ -z "$line" ]]; then if [[ -z "$line" ]] || [[ "$line" =~ ^Handle ]]; then
in_device=false in_device=false
fi fi
done < <(dmidecode -t memory 2>/dev/null) done < <(dmidecode -t memory 2>/dev/null)
# Print last device if it has data # Print last device if valid
if [[ -n "$locator" && -n "$size" && ! "$size" =~ (No|Not|Installed) ]]; then if [[ -n "$locator" && -n "$size" && ! "$size" =~ ^(No|Not)[[:space:]] ]]; then
printf "%-12s %-12s %-10s %-12s %-20s\n" \ printf "%-12s %-12s %-10s %-12s %-20s\n" \
"${locator:-N/A}" \ "${locator:-N/A}" \
"${size:-N/A}" \ "${size:-N/A}" \
"${type:-N/A}" \ "${type:-N/A}" \
"${speed:-N/A}" \ "${speed:-N/A}" \
"${manufacturer:-N/A}" "${manufacturer:-N/A}"
((dimm_count++))
fi fi
# If no DIMMs were printed, show a message
if [[ $dimm_count -eq 0 ]]; then
echo " (Unable to parse DIMM details from dmidecode)"
fi
# Memory summary # Memory summary
echo -e "\n${GREEN}Memory Summary:${NC}" echo -e "\n${GREEN}Memory Summary:${NC}"
# Count slots more reliably # Count slots and populated using simpler grep approach
local total_slots=0 local total_slots populated
local populated=0 total_slots=$(dmidecode -t memory 2>/dev/null | grep -c "^[[:space:]]*Locator:" | head -1)
# Subtract Bank Locator lines
while IFS= read -r line; do local bank_locators
if [[ "$line" =~ ^[[:space:]]*Locator: ]] && [[ ! "$line" =~ Bank ]]; then bank_locators=$(dmidecode -t memory 2>/dev/null | grep -c "Bank Locator:")
((total_slots++)) total_slots=$((total_slots - bank_locators))
fi
done < <(dmidecode -t memory 2>/dev/null) populated=$(dmidecode -t memory 2>/dev/null | grep "^[[:space:]]*Size:" | grep -cv "No Module\|Not Installed")
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*Size:[[:space:]]*(.+)$ ]]; then
local size_val="${BASH_REMATCH[1]}"
if [[ ! "$size_val" =~ (No|Not|Installed) ]]; then
((populated++))
fi
fi
done < <(dmidecode -t memory 2>/dev/null)
echo -e " Total Slots: $total_slots" echo -e " Total Slots: $total_slots"
echo -e " Populated: $populated" echo -e " Populated: $populated"
@@ -445,16 +450,19 @@ get_physical_interfaces() {
for iface in /sys/class/net/*; do for iface in /sys/class/net/*; do
# Skip if glob didn't match anything # Skip if glob didn't match anything
[[ -e "$iface" ]] || continue [[ -e "$iface" ]] || continue
# Get just the interface name # Get just the interface name
iface=$(basename "$iface") iface=$(basename "$iface")
# Skip loopback # Skip loopback
[[ "$iface" == "lo" ]] && continue [[ "$iface" == "lo" ]] && continue
# Skip bonding_masters (virtual file, not an interface)
[[ "$iface" == "bonding_masters" ]] && continue
# Skip virtual/firewall interfaces # Skip virtual/firewall interfaces
[[ "$iface" =~ $VIRTUAL_IFACE_PATTERN ]] && continue [[ "$iface" =~ $VIRTUAL_IFACE_PATTERN ]] && continue
# This is a physical interface # This is a physical interface
echo "$iface" echo "$iface"
done done