В системе отправки SMS у Zabbix есть два недочёта. Первый относится ко всем типам уведомлений. Это невозможность задания "периода тишины" без потери уведомлений. Второй -- кодировка отправляемой SMS.
Мой коллега достаточно детально разобрал то, как Zabbix работает с GSM модемом. Как оказалось, никакого кодирования он не производит, пишет отправляемые символы напрямую. Используется семибитная кодировка, а по хорошему надо использовать восьмибитную или шестнадцатибитную UCS-2.
Поэтому в приходящих сообщениях иногда возникают трудности с кодировкой.
Традиционным способом обхода проблем в подсистеме отправки SMS у Zabbix было написание внешних Alert-скриптов, поэтому и я предлагаю очередной велосипед.
К требованиям относятся:
-
поддержка работы с gammu (бывший gnokii);
-
поддержка "периода тишины" -- когда сообщения не отправляются, а только ставятся в очередь;
-
работа с Zabbix 3.0.
Рассмотрим предлагаемый скрипт:
#!/usr/local/bin/perl
# made by: KorG
# USAGE: $0 [0] [12] <user> <cmd>
use strict;
use v5.18;
use utf8;
use warnings;
no warnings 'experimental';
use Storable;
use IPC::SysV qw(IPC_CREAT SEM_UNDO IPC_EXCL);
use IPC::Semaphore;
# user defined variables
my $db = "/home/korg/sms.db";
my $key = 20160626;
# synchronize
my $sem = IPC::Semaphore->new($key, 1, 0700 | IPC_CREAT | IPC_EXCL) ||
IPC::Semaphore->new($key, 1, 0600) || die "semaphore: $!";
$sem->setall(1);
$sem->op(0, -1, SEM_UNDO);
# DB operations
store {}, $db unless -r $db;
my %db = %{retrieve($db)};
# atexit operations
END {
for (keys %db) {
delete $db{$_} unless exists $db{$_}->{time}->{time_start};
}
store \%db, $db or warn "store: $!";
$sem->remove;
}
# actual commands execution
sub execute_user_cmds($) {
my $user = shift // return;
my $aggregation = "";
while ($_ = shift @{ $db{$user}->{commands} }) {
$aggregation .= "@{$_}\n";
}
# gammu specific part
open my $gammu, "| /usr/local/bin/gammu sendsms TEXT '$user' -autolen 160" or
die "run gammu: $!";
print $gammu $aggregation;
}
# execute dispatcher
sub run_user_queue($) {
my $user = shift // return;
my $time = (localtime)[2];
my $start = $db{$user}->{time}->{time_start};
my $end = $db{$user}->{time}->{time_end};
unless (defined $start && defined $end) {
return execute_user_cmds $user;
}
return if (
( $start > $end && ($time >= $start || $time < $end) ) ||
( $start <= $end && ($time >= $start && $time < $end) )
);
return execute_user_cmds $user;
}
# parse arguments
given ($#ARGV) {
when (-1) {
run_user_queue $_ for keys %db;
}
when (0) {
run_user_queue $ARGV[0];
}
default {
my $user = shift;
my ($t_start, $t_end) = (undef, undef);
if ($user =~ /^\d\d?$/) {
$t_start = $&;
shift =~ /^\d\d?$/;
$t_end = $&;
$user = shift;
}
# delay execution
push @{ $db{$user}->{commands} }, [@ARGV];
$db{$user}->{time}->{time_start} = $t_start if defined $t_start;
$db{$user}->{time}->{time_end} = $t_end if defined $t_end;
# force commands dispatch
run_user_queue $user;
}
}
Видно, что он идеально подходит для более глобальной цели -- планирование времени выполнения команд так, чтобы в заданный промежуток команды не выполнялись, а ставились в очередь.
Чтобы переделать скрипт для работы с любыми командами достаточно поменять этот участок кода:
sub execute_user_cmds($) {
my $user = shift // return;
system @{ $_ } while ($_ = shift @{ $db{$user}->{commands} });
}
Синтаксис вызова скрипта следующий:
sms.pl [ prohibited_hours_from prohibited_hour_till ] [[ user ] | [ user message ]]
При запуске без аргументов скрипт просто проверяет очереди команд и выполняет те команды, которые можно выполнить.
При запуске с одним аргументом -- user -- проверяется только очередь для конкретного пользователя.
При запуске с тремя или более аргументами, первые два из которых числа, эти числа интерпретируются как часы начала и завершения периода тишины, следующий за ними аргумент -- имя пользователя и все остальные -- текст сообщения. Если первые два аргумента не числа, первый считается именем пользователя, а остальные -- текстом.
При этом, время начала и завершения периода тишины включает час начала и не включает час завершения.
Например, 3-8 означает диапазон с 3:00:00 до 7:59:59.
Важно то, что если указан временной промежуток, он автоматически сохраняется в файловой базе данных и используется в дальнейшем.
Текущее содержимое базы, очевидно, можно посмотреть простой командой:
perl -MStorable -MData::Dumper -e 'print Dumper(retrieve "sms.db")'
Для обеспечения периодичности можно добавить вот такую строку в crontab:
1 * * * * /usr/local/bin/sms.pl
Кроме того, необходимо настроить пути к файлам:
# необходимо задать путь к базе данных
my $db = "/home/korg/sms.db";
# необходимо задать путь к gammu
open my $gammu, "| /usr/local/bin/gammu ..." or die "run gammu: $!";
Для настройки уведомлений в Zabbix 3.0 нужно расположить скрипт в переменной, указанной в AlertScriptsPath, создать свой Media type (в Administration), в котором указать имя скрипта и передаваемые параметры. Рекомендуется в качестве последних указывать "{ALERT.SENDTO}", "{ALERT.SUBJECT}" и "{ALERT.MESSAGE}".
Настройка типа уведомлений завершена. Осталось лишь добавить нужным пользователям привязку к этому типу (в свойствах пользователей) и уведомления начнут приходить.
На этом всё, теперь ночные SMS будут приходить в 8 утра.
P.S. к слову сказать, если вас напрягает popen или экранирование номера телефона в shell, можно обойтись и без него:
system("/usr/bin/gammu", "sendsms", "TEXT", "$user", "-autolen", "160", "-text", "$aggregation");