Bash скрипт-отсылка смс через телефон при отсутствие ping на заданный узел

Материал из BiTel WiKi

Перейти к: навигация, поиск

Это переделанный скрипт Sets. Принцип работы: Скприпт проверяет через "ping" "живость" указанных узлов, если узел(ы) лег(ли), отправляется SMS сообщение с уведомлением о том какие узлы лежат, если упавшие узлы поднимутся, отправляется еще одно сообщение с уведомлением о том какие узлы поднялись, так же можно настроить задержку отправки смс (в секундах) настраивается

[node_tDelay]='20'

в этом поле Установка: Подключаем сотовый телефон(правда не все телефоны способны отправлять SMS с компьютера) устанавливается приложение scmxx, в Debian like есть в репозиториях можно установить через

apt-get install scmxx

Распаковываем скрипт в удобную Вам папку у меня например в "/root/scripts/smsalarm/" Естественно нужно установить флаг исполнения на нем:

chmod +x /root/scripts/smsalarm/smsalarm

Правим скрипт: прописываем нужные нам узлы и номера телефонов заинтересованных людей Добавляем в cron запуск скрипта(я установил каждые 3 минуты)

SHELL=/bin/bash
MAILTO=root
*/3 * * * * /root/scripts/smsalarm/smsalarm

Ждем печальных новостей...

#!/bin/bash
 
# v2.1.00
#   Node check:
#       [modified]:
#           - Report error, when down time is _equal_or_greater_, than delay.
#           (Before was only when _greater_).
#       [improved]:
#           - Timeout checks merged.  When host go down for the first time, we
#           check is delay equal to zero to report error immediately. This
#           check merged with regular timeout checks, when host was down for a
#           while.
# v2.0.04
#   General:
#       [improved]:
#           - Remove any number of trailing slashes from 'tmp_path'.
# v2.0.03
#   ....
 
 
# Переменные {{{
 
# Временные переменные.
declare -i i=0
declare -i j=0
declare s=""
 
# Отладка (битовое ИЛИ для включения нескольких сразу)
i=1
declare -r -i debug_input_parse=$i && ((i<<=1))
declare -r -i debug_init_node_t=$i && ((i<<=1))
declare -r -i debug_select_node=$i && ((i<<=1))
declare -r -i debug_check_node=$i  && ((i<<=1))
declare -r -i debug_all=-1
declare -r -i debug=$debug_check_node
 
# Для печати ошибок/предупреждений/инфо
declare -r PS_E="${0##*/}: Error"
declare -r PS_W="${0##*/}: Warning"
declare -r PS_I="${0##*/}: Info"
 
# Список телефонов
declare -r -a telephone=( 
    01
    02
    03
)
# Для "текущих" значений в циклах
declare tel=""
declare msg=""
 
declare tmp_path="."
# Удаляем все '/' на конце.
tmp_path="$(echo "$tmp_path" | sed -e'/^\/$/q; s/\/\+$//')"
declare -r tmp_path
 
 
# Переменные для вычисления "а не пора ли отправлять сообщение?"
declare -i cur_time=0
declare -i fail_time=0
declare -i delta_time=0
 
# Принцип хранения данных аналогичен (не совсем) такой структуре {{{
#
#   struct node_t {
#       int delay;              // задержка отправки сообщения
#       struct list_t list;     // список узлов для этой задержки
#   };
#
# (.list теперь не указатель, в отличие от первой версии). Как и в 1-ой
# версии, узлы, имеющие одинаковую задержку для отправки сообщений,
# содержаться в одном объекте типа 'struct node_t'.
#
# Как это все реализовано здесь:
#
# Все объекты последовательно записаны в массив
#
#   node_t__data[] (аналогия с памятью)
#
# и смещение до начала каждого объекта записано в массиве
#
#   node_t__off[] (аналогия с массивом указателей 'struct node_t **')
#
# Смещение элементов объекта (.delay и .list) от его начала записано в
# переменных
#
#   node_tDelay
#   node_tList
# 
# В отличие от 1-ой версии, кроме другого принципа хранения данных, здесь уже
# объекты не имеют имен (т.е к ним можно обратиться только по индексу в
# массиве - аналогия с 'struct node_t *').
#
# Вот картинка
#
#                                     node_t__data[]
#                                 ------============
#       node_t__off[]                |  I          I
#       I===========I                |  I          I
#       I           I                |  I          I
#       I  ...      I                |  I          I
#       I-----------I                v  I          I
#       I смещение  I------------> -----I----------I------------ объект А
#       I объекта А I                   I          I | | node_tDelay
#       I-----------I                   I          I | v
#       I смещение  I------+            I----------I-|---
#       I объекта Б I      |            I задержка I | node_tList
#       I-----------I      |            I          I v
#       I           I      |            I----------I---
#       I           I      |            I список   I
#       I           I      |            I узлов    I
#       I           I      |            I ...      I
#       I           I      +-----> -----I----------I------------- объект Б
#       I           I      |            I ...      I
# 
# (на самом деле, node_tDelay == 0, а не так, как нарисовано на картинке)
#
# }}}
 
# Смещения элементов структуры от начала самой структуры.
i=0
declare -r -i node_tDelay=$((i++))  # смещение элемента .delay (размер == 1)
declare -r -i node_tList=$((i++))   # смещение элемента .list (размер
                                    # неопределен)
declare -r -i minsize_node_t=$i     # минимальный размер правильного объекта
i=0
 
# Для "текущего" объекта в циклах
declare node_t=""           # имя (указатель) текущего объекта
declare -i sizeof_node_t=0  # размер текущего объекта.
 
declare -i node_t__delay=0      # значение элемента .delay текущего объекта
 
declare -i node_t__list_start=0 # индекс в node_t__data[], где начинается
                                # элемент .list текущего объекта
 
declare -i node_t__list_end=0   # индекс в node_t__data[], где кончается
                                # элемент .list текущего объекта (последний
                                # элемент .list-а находится по предыдущему
                                # индексу)
 
declare node=""                 # текущий элемент в .list
 
# Определения объектов.
### Редактируется пользователем (начало).
# Определения не соответствующие по форме (например, без использования
# '[node_tDelay]=..' и тд) могут обойти проверки на ошибки, поэтому лучше
# писать, как в примере -)
 
declare -a node_t_A=(
    [node_tDelay]='0'
    [node_tList]='A1' 'A2'
)
declare -a node_t_B=(
    [node_tDelay]='20'
    [node_tList]='B1' 'B2' 'B3'
)
# список всех объектов (объекты, не указанные здесь, игнорируются).
declare -a node_t__obj=(
    node_t_A
    node_t_B
)
 
### Редактируется пользователем (конец)
 
# Определения внутренней структуры хранения.
if declare -p node_t__off node_t__data >/dev/null 2>&1; then
    echo "${PS_E}: one of internal variable names - 'node_t__off' or 'node_t__data' - already used"
    exit -1
fi
# Все объекты записаны сюда последовательно.
declare node_t__data=( )
# Смещения в node_t__data[] до начала соответствующего объекта.
declare -i node_t__off=( 0 )
 
 
# }}}
# Инициализация внутренней структуры. {{{
 
# Копируем (а заодно проверяем) данные из объектов, определенных
# пользователем, в node_t__data[] и записываем соответствующие смещения в
# node_t__off[]. Скопированы будут только объекты, указанные в node_t__obj[].
# Последовательность объектов в node_t__data[] будет такая же, как в
# node_t__obj[]. После завершения копирования node_t__obj[] и все объекты,
# указанные в нем, будут удалены. Т.е далее получить доступ к объектам можно
# будет _только_ через node_t__off[] и node_t__data[].
 
# FIXME: check type of user-defined variables.
# '-r' flag will be detected during `unset`. '-a' flag is implicitly checked
# through number of elements in user-defined object. Probably, that's enough.
# FIXME: check content of user-defined objects (.delay is integer, .list
# containt only not empty elements).
# We really need this?
 
if [ "x${node_t__obj[*]}" == "x" ]; then
    echo "${PS_I}: Nothing to be done, no objects defined."
    exit 0
fi
 
i=0
for node_t in "${node_t__obj[@]}"; do
    if ! declare -p ${node_t}|> >/dev/null 2>&1; then
        echo "${PS_W}: object '$node_t' is not defined, skipped."
        continue
    fi
    eval "sizeof_node_t=\${#$node_t[*]}"
    if ((sizeof_node_t < minsize_node_t)); then
        echo "${PS_W}: size of element '$node_t' is lesser, than minimal, skipped."
        continue
    fi
    node_t__off[++i]=$((node_t__off[i] + sizeof_node_t))
    ((debug & debug_init_node_t)) \
        && echo "Init: object '$node_t', sizeof = '$sizeof_node_t'" \
        && echo "  offset = '${node_t__off[i]}', index = '$i'"
    eval "
        node_t__data=(
            \"\${node_t__data[@]}\"
            \"\${$node_t[@]}\"
        )
    "
    if ! unset $node_t; then
        echo "${PS_E}: can not unset variable '$node_t'"
        exit -1
    fi
done
((debug & debug_init_node_t)) \
    && echo "Init: completed, environment" \
    && declare -p node_t__off node_t__data \
    && (
        IFS=','
        declare -p "${node_t__obj[@]}" >/dev/null 2>&1 \
        || echo "${node_t__obj[*]} unset"
    )
if ! unset node_t__obj; then
    echo "${PS_E}: can not unset variable 'node_t__obj'"
    exit -1
fi
 
# }}}
# Функции (начало) {{{
 
func_check_node() {
    # Параметры:
    #   1   - node
    # Возвращаемое значение:
    #   0   - успех,
    #   >0  - неудача.
 
    # Для отладки:
    #declare -i repl=0
    #read -p'->' -n1 -r repl
    #return $repl
 
    /bin/ping -c 5 $1 > /dev/null 2> /dev/null
    return $?
}
 
func_send_msg() {
    # Параметры:
    # 1 - telephone
    # 2 - message
    # Возвращаемое значение:
    #   0   - успех,
    #   >0  - неудача.
 
    # Для отладки:
    #echo - $1 - $2 
    #return $?
 
    /usr/bin/scmxx --device=/dev/ttyACM0 --send --sms --direct --number=$1 --text="$2"
    return $?
}
 
# Функции (конец) }}}
 
# Последний элемент в node_t__off[] - это смещение на начало пустой области в
# node_t__data[], поэтому его пропускаем.
 
for ((i = 0; i < (${#node_t__off[*]} - 1); i++)); do
    node_t__delay="${node_t__data[node_t__off[i] + node_tDelay]}";
    node_t__list_start=$((node_t__off[i] + node_tList));
    node_t__list_end=$((node_t__off[i + 1]));
    ((debug & debug_select_node)) \
        && echo "Select: object [$i] with offset '${node_t__off[i]}'" \
        && echo "  delay = '$node_t__delay', list_start = '$node_t__list_start', list_end = '$node_t__list_end'"
    for ((j = node_t__list_start; j < node_t__list_end; j++)); do
        node=${node_t__data[j]};
        node_file="$tmp_path/$node"
        ((debug & debug_select_node)) \
            && echo "  Select node from .list: '$node' with path '$node_file'"
        func_check_node "$node"
        ret=$?
        if ((ret)); then
            ((debug & debug_check_node)) \
                && echo "  Node '$node' down"
 
            cur_time="$(date +%s)"
            if [ -e "$node_file" ]; then
                # Узел уже "лежал" в предыдущую проверку.
                fail_time="$(<"$node_file")"
            else
                # Узел упал в первый раз.
                echo "$cur_time" >"$node_file"
                fail_time=$cur_time
            fi
            delta_time=$((cur_time - fail_time))
            ((debug & debug_check_node)) \
                && echo "    over $delta_time seconds ($cur_time, $fail_time)"
 
            if ((delta_time >= node_t__delay)); then
                # Узел лежит дольше, чем ему можно (>= элемента .delay). Время
                # падения сбрасываем в текущее, чтобы сообщения не
                # отправлялись каждую следующую проверку.
                [ -z "$errors" ] \
                    && errors="Down: $node" \
                    || errors="$errors, $node"
                echo "$cur_time" >"$node_file"
                # Сохраняем _предыдущее_ время модификации, - чтобы потом
                # определить было ли отправлено хотя бы одно сообщение.
                touch -m --date="@$fail_time" "$node_file"
                ((debug & debug_check_node)) \
                    && echo "  Timeout ('$node_t__delay') reached, mark as error: '$errors'" \
                    && echo "  New fail time: '$(<"$node_file")'" \
                    && echo "  Previous fail time: '$(stat -t -c "%Y" "$node_file")'"
            fi
        else
            ((debug & debug_check_node)) \
                && echo "  Node '$node' up"
            if [ -e "$node_file" ]; then
                # fail_time - время отправки сообщения _перед_ предыдущим,
                # если оно было, либо время первого падения интерфейса.
                cur_time="$(date +%s)"
                fail_time="$(stat -t --format="%Y" "$node_file")"
                delta_time=$((cur_time - fail_time))
                ((debug & debug_check_node)) \
                    && echo "    but was down over $delta_time seconds"
 
                if ((delta_time >= node_t__delay)); then
                    # Если разница между текущем временем и временем
                    # модификации больше допустимой, сообщения (хотя бы одно)
                    # отправлено было.
                    [ -z "$fixed" ] \
                        && fixed="Fixed: $node" \
                        || fixed="$fixed, $node"
                    ((debug & debug_check_node)) \
                        && echo "  Error message was sent, mark as fixed: '$fixed'"
                fi
            fi
            /bin/rm -f "$node_file"
        fi
    done
done
 
for msg in "$errors" "$fixed"; do
    [ -z "$msg" ] && continue
    for tel in "${telephone[@]}"; do
        func_send_msg "$tel" "$msg"
    done
done
 
exit 0

Можно настроить логирование если нужно...просто перенаправить вывод программы в фаил в crontab изменить строчку на

*/3 * * * * /root/scripts/smsalarm/smsalarm >> smsalarm.log

И в нужных местах изменить на:

&& echo `date +%F,%X` "  Node '$node' up"
&& echo `date +%F,%X` "  Node '$node' down"

формат даты каждый выбирает по вкусу :)

Так же можно использовать утилиту logrotate и настроить как системные логи :) В приведенном ниже примере идет сжатие логов, размер 1 файла лога = 50000кб всего 4 файла

/var/log/smsalarm.log { #путь до файла логов
rotate 4
size 50000k
compress
}

--skyb

Личные инструменты