SMART Monitoring System

от автора

В продолжении статьи: https://habr.com/ru/articles/962290
Пришла мне мысль что для такой задачи должны быть готовые зрелые решения. В частности в smartmontools и оказалось что всё уже давно придумано. Но не до конца …

Решил поделится опытом. Причесал моё словоблудие нейронкой.


📋 Оглавление

  1. ⚙️ Основная конфигурация smartd

  2. 🔍 Параметры DEVICESCAN

  3. 🔔 Скрипт уведомлений в Telegram

  4. 🔄 Fallback-скрипт проверка пропущенных тестов

  5. 📊 Weekly Summary еженедельная сводка

  6. 🧩 Архитектура системы

  7. ✅ Чеклист проверки


⚙️ Основная конфигурация smartd

Файл: /etc/smartmontools/smartd.conf

DEVICESCAN -a -o on -S on \  -s (S/../../7/10|L/../../01/12) \  -m root \  -M exec /path/to/script/smartd-tg-alert.sh \  -n standby,10,q

🔍 Параметры DEVICESCAN

Параметр

Описание

DEVICESCAN

Автоматическое обнаружение всех дисков: SATA, NVMe, USB (с поддержкой SMART)

-a

Мониторить все SMART-атрибуты, ошибки, health-статус

-o on

Включить автоматический offline-сбор данных (если поддерживается)

-S on

Включить автосохранение атрибутов

-s (S/../../7/10\|L/../../01/12)

Расписание тестов (см. ниже)

-m root

Получатель email-уведомлений (резервный канал)

-M exec /path/to/script.sh

Скрипт для кастомных уведомлений

-n standby,10,q

Не будить диск в спящем режиме (до 10 пропусков, q = quiet)

📅 Формат расписания -s

Синтаксис: T/MM/DD/d/HH

Поле

Значение

Пример

T

Тип теста: S = Short, L = Long

S, L

MM

Месяц (01–12 или .. = любой)

..

DD

День месяца (01–31 или .. = любой)

01, ..

d

День недели (1–7, где 7 = воскресенье)

7, .

HH

Час запуска (00–23)

10, 12

Моя конфигурация:

(S/../../7/10|L/../../01/12)

Тест

Расписание

🔹 Короткий

Каждое воскресенье в 10:00

🔹 Длинный

1-го числа каждого месяца в 12:00

💡 Важно: Если в момент запуска диск в режиме standby и активна опция -n, тест будет пропущен. Для критичных систем можно убрать эту опцию или использовать -n standby,10,m (отправить письмо при пропуске).


🔔 Скрипт уведомлений в Telegram

Файл: /path/to/script/smartd-tg-alert.sh

#!/bin/bashBOT_TOKEN="bot-token"CHAT_ID="chat-id"HOST=$(hostname)MESSAGE="$SMARTD_MESSAGE"curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \    -d "chat_id=$CHAT_ID" \    -d "text=🖥 *$HOST*\n\n$MESSAGE" \    -d "parse_mode=Markdown" >/dev/null 2>&1# Всегда возвращаем 0, чтобы smartd не считал ошибкуexit 0

🔧 Настройка прав:

sudo chmod +x /path/to/script/smartd-tg-alert.shsudo chown root:root /path/to/script/smartd-tg-alert.sh

⚠️ Важно: Бот должен быть запущен, а чат с ним — начат пользователем. Бот не может писать первым.


🔄 Fallback-скрипт: проверка пропущенных тестов

Зачем: Если ПК был выключен в момент планового теста — этот скрипт запустит его при первой возможности.

Файл: /path/to/script/smart-fallback.sh

#!/bin/bash# === CONFIG ===SHORT_MAX_DAYS=8     # если short тест старше 8 дней → запускаемLONG_MAX_DAYS=35     # если long тест старше 35 дней → запускаемLOG_TAG="smart-fallback"log() {    logger -t "$LOG_TAG" "$1"}get_power_on_hours() {    # Возвращает Power-On Hours (целое) или пусто.    # Поддержка ATA и NVMe.    local disk="$1"    local a out    out=$(smartctl -a "$disk" 2>/dev/null) || return 0    # NVMe: "Power On Hours:  1234"    a=$(echo "$out" | awk -F: '/^[[:space:]]*Power On Hours[[:space:]]*:/ {gsub(/[^0-9]/,"",$2); print $2; exit}')    if [[ -n "$a" ]]; then        printf '%s' "$a"        return 0    fi    # ATA: таблица атрибутов, строка "Power_On_Hours"    a=$(echo "$out" | awk '$2=="Power_On_Hours" {print $10; exit}')    if [[ "$a" =~ ^[0-9]+$ ]]; then        printf '%s' "$a"        return 0    fi}get_last_selftest_poh() {    # Возвращает Lifetime (hours) из самого свежего self-test указанного типа.    # type: "Short" или "Extended"    local output="$1"    local type="$2"    # В selftest-логе smartctl колонка обычно называется "LifeTime(hours)".    # Для строк вида:    # "# 1  Short offline  Completed ...  00%   12345  -"    # или:    # "# 1  Extended offline Completed ... 00%  12345  -"    # NVMe формат другой, например:    # "0   Short   Completed ...  1353  -"    #    # Берем первое (самое свежее) совпадение и вынимаем число из колонки lifetime.    echo "$output" | awk -v t="$type" '        $0 ~ t && $0 !~ /^[[:space:]]*Num[[:space:]]/ {            for (i=1; i<=NF; i++) {                if ($i ~ /^[0-9]+$/) n = $i            }            if (n ~ /^[0-9]+$/) { print n; exit }        }    '}check_and_run_test() {    local disk="$1"    # получаем лог тестов    local output    output=$(smartctl -l selftest "$disk" 2>/dev/null)    local poh    poh=$(get_power_on_hours "$disk")    # --- SHORT TEST ---    local last_short_poh short_age_days    last_short_poh=$(get_last_selftest_poh "$output" "Short")    short_age_days=""    if [[ -n "$poh" && -n "$last_short_poh" && "$poh" =~ ^[0-9]+$ && "$last_short_poh" =~ ^[0-9]+$ && "$poh" -ge "$last_short_poh" ]]; then        short_age_days=$(( (poh - last_short_poh) / 24 ))    fi    if [[ -z "$last_short_poh" || -z "$poh" || -z "$short_age_days" || "$short_age_days" -gt "$SHORT_MAX_DAYS" ]]; then        log "Running SHORT test on $disk (age_days: ${short_age_days:-unknown})"        smartctl -t short "$disk" >/dev/null 2>&1    fi    # --- LONG TEST ---    local last_long_poh long_age_days    last_long_poh=$(get_last_selftest_poh "$output" "Extended")    long_age_days=""    if [[ -n "$poh" && -n "$last_long_poh" && "$poh" =~ ^[0-9]+$ && "$last_long_poh" =~ ^[0-9]+$ && "$poh" -ge "$last_long_poh" ]]; then        long_age_days=$(( (poh - last_long_poh) / 24 ))    fi    if [[ -z "$last_long_poh" || -z "$poh" || -z "$long_age_days" || "$long_age_days" -gt "$LONG_MAX_DAYS" ]]; then        log "Running LONG test on $disk (age_days: ${long_age_days:-unknown})"        smartctl -t long "$disk" >/dev/null 2>&1    fi}# === MAIN ===smartctl --scan-open 2>/dev/null | awk '{print $1}' | while IFS= read -r disk; do    [[ -z "$disk" ]] && continue    if smartctl -c "$disk" &>/dev/null; then        check_and_run_test "$disk"    fidone

🔧 systemd: сервис + таймер

/etc/systemd/system/smart-fallback.service

[Unit]Description=SMART fallback test runner[Service]Type=oneshotExecStart=/path/to/script/smart-fallback.sh

/etc/systemd/system/smart-fallback.timer

[Unit]Description=Run SMART fallback daily[Timer]OnCalendar=dailyPersistent=true[Install]WantedBy=timers.target

▶️ Активация:

sudo chmod +x /path/to/script/smart-fallback.shsudo systemctl daemon-reloadsudo systemctl enable --now smart-fallback.timer

📊 Weekly Summary: еженедельная сводка

Зачем: Не просто алерты «что-то сломалось», а регулярный обзор состояния всех дисков.

Файл: /path/to/script/smart-summary.sh

#!/bin/bashBOT_TOKEN="bot-token"CHAT_ID="chat-id"HOST=$(hostname)DATE=$(date '+%Y-%m-%d')LOG_TAG="smart-summary"log() {    logger -t "$LOG_TAG" -p user.info "$1"}# Используем <b>...</b> вместо *...* для жирногоMESSAGE=$(printf '📊 <b>SMART Summary (%s)</b>\nДата: %s' "$HOST" "$DATE")$'\n\n'for disk in $(smartctl --scan-open | awk '{print $1}'); do    if smartctl -c "$disk" &>/dev/null; then        # Получаем health (работает и для ATA, и для NVMe)        health=$(smartctl -H "$disk" 2>/dev/null | grep -E "SMART overall-health|SMART Health Status" | sed 's/.*: //')        # Температура        temp=$(smartctl -A "$disk" 2>/dev/null | grep -i temperature | head -n1 | awk -F': ' '{print $2}' | awk '{print $1, $2}')        MESSAGE+=$(printf '💽 <b>%s</b> - %s' "$disk" "$health")$'\n\n'        if [[ -n "$temp" && "$temp" != *" "* ]]; then            # Важно: $(...) срезает завершающие переводы строк, поэтому добавляем их снаружи.            MESSAGE+=$(printf '%s' "$temp")$'\n'        fi        # Ошибки: Reallocated, Pending, Uncorrectable        errors=$(smartctl -A "$disk" 2>/dev/null | grep -E "(Reallocated_Sector|Current_Pending|Offline_Uncorrectable|Media_Error)" | awk '$10 > 0 {print "  • " $2 ": " $10}')        if [[ -n "$errors" ]]; then            MESSAGE+=$(printf '❗ Ошибки:\n%s' "$errors")$'\n'        fi        # Износ SSD        wear=$(smartctl -A "$disk" 2>/dev/null | grep -E "(Wear_Leveling|Percent_Lifetime|Media_Wearout)" | head -n1 | awk -F': ' '{print $2}' | awk '{print $1}')        if [[ -n "$wear" ]]; then            MESSAGE+=$(printf '🧠 Износ: %s' "$wear")$'\n'        fi        MESSAGE+=$'\n'    fidone# Отправляем с parse_mode=HTML и получаем код ответаHTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \    "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \    --data-urlencode "chat_id=$CHAT_ID" \    --data-urlencode "text=$MESSAGE" \    --data-urlencode "parse_mode=HTML")# Опционально: логирование ошибокif [[ "$HTTP_CODE" != "200" ]]; then    log "[$(date)] SMART summary failed: HTTP $HTTP_CODE"fiexit 0

🔧 systemd: сервис + таймер

/etc/systemd/system/smart-summary.service

[Unit]Description=SMART weekly summary[Service]Type=oneshotExecStart=/path/to/script/smart-summary.sh

/etc/systemd/system/smart-summary.timer

[Unit]Description=Run SMART summary weekly[Timer]OnCalendar=Mon 12:00Persistent=true[Install]WantedBy=timers.target

▶️ Активация:

sudo chmod +x /path/to/script/smart-summary.shsudo systemctl daemon-reloadsudo systemctl enable --now smart-summary.timer

🧩 Архитектура системы

┌─────────────────────────────────────┐│           smartd (демон)            ││  • Мониторит диски в реальном времени││  • Запускает тесты по расписанию    ││  • Вызывает скрипт при событиях     │└────────────┬────────────────────────┘             │ событие (алерт)             ▼┌─────────────────────────────────────┐│   📢 smartd-tg-alert.sh             ││   • Мгновенное уведомление в Telegram│└─────────────────────────────────────┘┌─────────────────────────────────────┐│   🔄 smart-fallback.timer (daily)   ││   • Проверяет, не пропущены ли тесты││   • Запускает их при необходимости  │└─────────────────────────────────────┘┌─────────────────────────────────────┐│   📊 smart-summary.timer (weekly)   ││   • Формирует сводку по всем дискам ││   • Отправляет отчёт в Telegram     │└─────────────────────────────────────┘

🎯 Принципы работы:

Компонент

Тип

Частота

Цель

smartd

Демон

Постоянно

Реактивный мониторинг

smartd-tg-alert.sh

Скрипт-хук

По событию

Мгновенные алерты

smart-fallback.sh

Скрипт + timer

Ежедневно

Надёжность (пропущенные тесты)

smart-summary.sh

Скрипт + timer

Еженедельно

Проактивный обзор


✅ Чеклист проверки

🔧 Конфигурация

  • sudo smartd -d -c /etc/smartmontools/smartd.conf — нет ошибок парсинга

  • sudo smartctl --scan — все ожидаемые диски отображаются

  • sudo systemctl enable --now smartd — служба активна

🔔 Уведомления

  • Скрипт smartd-tg-alert.sh имеет права +x и владельца root

  • Бот активен, чат начат, токен верный

  • Проверка отправки в телеграм напрямую: SMARTD_MESSAGE=$'TEST from smartd\nsecond line' /path/to/script/smartd-tg-alert.sh

  • Проверка отправки через smartd: добавить в /etc/smartmontools/smartd.conf временно строку -M test \ И запустить sudo smartd -c /etc/smartmontools/smartd.conf -q onecheck

🔄 Fallback

  • systemctl list-timers | grep smart-fallback — таймер активен, следующее срабатывание указано

  • sudo systemctl start smart-fallback.service — выполняется без ошибок

  • В journalctl -t smart-fallback появляются записи при запуске

📊 Summary

  • systemctl list-timers | grep smart-summary — таймер настроен на воскресенье 12:00

  • sudo systemctl start smart-summary.service — сводка приходит в Telegram

  • В сообщении отображаются: здоровье, температура, ошибки, износ (для SSD)

📋 Полезные команды

# Статус службы smartdsudo systemctl status smartd# Логи в реальном времениjournalctl -u smartd -f# Проверка расписания тестов для дискаsudo smartctl -l selftest /dev/sdb# Ручной запуск тестаsudo smartctl -t short /dev/sdbsudo smartctl -t long /dev/sdb# Просмотр результатов тестовsudo smartctl -l selftest /dev/sdb# Проверка таймеровsystemctl list-timers --all | grep smart

ссылка на оригинал статьи https://habr.com/ru/articles/1028462/