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