koalvi.blogspot.com - другой мой блог "сойдет на троечку"

среда, 20 августа 2025 г.

контроль атрибутов SMART диска в Linux, с передачей их изменения в телеграм

 идея контролировать атрибуты винчестера в автоматическом порядке, при этом не создавая лишней нагрузки на файловую систему, значит: запись только если поменялись критические значения, некритические есть в файле для конкретного винчестера, учитывая что производители могут быть разными как и атрибуты у моделей

помогали несколько движков ИИ, не с первого далеко раза
периоды запуска реализованы через cron
заморачиваться с передачей названия диска в строке не стал
вероятно этот велосипед умрет с этой системой
реализация дополнится отсылкой почты админу

в телеграмме ньюанс насчет перевода на новые строки, меня не раздражает, даже некий юни- шарм

=========== smart_sda.bash

DISK="/dev/sda"
NON_CRITICAL_FILE="/opt/smart/smart_non_critical_sda.txt"
# Load configuration from external file
CONFIG_FILE="/opt/smart/smart_v2.conf"
if [ ! -f "$CONFIG_FILE" ]; then
    echo "Configuration file not found at $CONFIG_FILE. Please create it." >&2
    exit 1
fi
source "$CONFIG_FILE"

# Generate file paths based on disk name
DISK_NAME=$(basename "$DISK")  # e.g., "sda" from "/dev/sda"
LOG_FILE="/dev/null"  # Override log file to /dev/null
STATE_FILE="$BASE_DATA_DIR/smart_state_${DISK_NAME}.txt"
DISK_INFO_FILE="$BASE_DATA_DIR/smart_disk_info_${DISK_NAME}.txt"

# Ensure base directories exist
mkdir -p "$BASE_DATA_DIR" 2>/dev/null

# Ensure smartctl is installed
if ! command -v "$SMARTCTL" &> /dev/null; then
    echo "smartctl not found. Please install smartmontools." >> "$LOG_FILE"
    exit 1
fi

# Ensure non-critical attributes file exists
if [ ! -f "$NON_CRITICAL_FILE" ]; then
    echo "Non-critical attributes file not found at $NON_CRITICAL_FILE" >> "$LOG_FILE"
    exit 1
fi

# Read non-critical attributes into an array, extracting only the first field (number)
mapfile -t NON_CRITICAL < <(grep -v '^\s*#' "$NON_CRITICAL_FILE" | grep -v '^\s*$' | awk '{print $1}')

# Function to check if attribute is non-critical
is_non_critical() {
    local attr_id=$1
    for nc in "${NON_CRITICAL[@]}"; do
        if [ "$nc" == "$attr_id" ]; then
            return 0
        fi
    done
    return 1
}

# Function to get SMART attributes
get_smart_attributes() {
    local output
    output=$("$SMARTCTL" -n standby -A "$DISK" 2>&1)
    if [ $? -ne 0 ]; then
        echo "Error executing smartctl -A on $DISK: $output" >> "$LOG_FILE"
        exit 1
    fi
    echo "$output" | awk '/^[ 0-9]/ { print $1, $2, $10 }'
}

# Function to get disk info (name and serial number) - handles multi-word models
get_disk_info() {
    local output
    output=$("$SMARTCTL" -i "$DISK" 2>&1)
    if [ $? -ne 0 ]; then
        echo "Error executing smartctl -i on $DISK: $output" >> "$LOG_FILE"
        exit 1
    fi
    echo "$output" | awk '
        /Device Model/ {
            out = "Device Model: ";
            for (i=3; i<=NF; i++) out = out $i (i<NF ? " " : "");
            print out
        }
        /Serial Number/ {
            out = "Serial Number: ";
            for (i=3; i<=NF; i++) out = out $i (i<NF ? " " : "");
            print out
        }
    '
}

# Save disk info if file doesn't exist
if [ ! -f "$DISK_INFO_FILE" ]; then
    get_disk_info > "$DISK_INFO_FILE"
fi

# Main logic
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] Checking SMART attributes for $DISK" >> "$LOG_FILE"

# Get current SMART data
current_data=$(get_smart_attributes)
if [ -z "$current_data" ]; then
    echo "[$timestamp] No SMART data retrieved for $DISK." >> "$LOG_FILE"
    exit 0
fi

# Flag to track if changes were found
changes_detected=false
telegram_message=""

# Check if previous data exists
if [ -f "$STATE_FILE" ]; then
    previous_data=$(cat "$STATE_FILE")

    # Compare with previous data
    while IFS= read -r prev_line; do
        prev_id=$(echo "$prev_line" | awk '{print $1}')
        prev_name=$(echo "$prev_line" | awk '{print $2}')
        prev_value=$(echo "$prev_line" | awk '{print $3}')

        # Find matching line in current data
        current_line=$(echo "$current_data" | grep "^$prev_id ")
        if [ -n "$current_line" ]; then
            current_value=$(echo "$current_line" | awk '{print $3}')

            # Check if value changed and attribute is not non-critical
            if [ "$prev_value" != "$current_value" ] && ! is_non_critical "$prev_id"; then
                echo "[$timestamp] Attribute $prev_id ($prev_name) changed: $prev_value -> $current_value" >> "$LOG_FILE"
                changes_detected=true
                telegram_message+="$prev_id ($prev_name): $prev_value -> $current_value\n"
            fi
        fi
    done <<< "$previous_data"
fi

# Save current data as new state only if changes in critical attributes were detected or no previous state exists
if [ "$changes_detected" = true ] || [ ! -f "$STATE_FILE" ]; then
    echo "$current_data" > "$STATE_FILE"
fi

# Send Telegram notification if changes were detected
if [ "$changes_detected" = true ]; then
    disk_info=$(cat "$DISK_INFO_FILE")
    full_message="SMART Changes detected on $DISK at $timestamp\n$disk_info\nChanges:\n$telegram_message"
    curl_response=$(curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
         -d chat_id="$TELEGRAM_CHAT_ID" \
         -d text="$full_message" 2>&1)
    if [ $? -ne 0 ]; then
        echo "[$timestamp] Error sending Telegram notification for $DISK: $curl_response" >> "$LOG_FILE"
    else
        echo "[$timestamp] Telegram notification sent for $DISK" >> "$LOG_FILE"
    fi
fi
======== smart_v2.conf ====
SMARTCTL="/usr/sbin/smartctl"
BASE_LOG_DIR="/opt/smart/out"
BASE_DATA_DIR="/opt/smart"
TELEGRAM_BOT_TOKEN="xxxxxx:*************************"
TELEGRAM_CHAT_ID="-xxxxxxxxxxx"
=== smart_non_critical_sda.txt=====
16
9
=====================

Комментариев нет:

Отправить комментарий