initial commit of file from CVS for smeserver-zabbix-server on Sat Sep 7 21:19:08 AEST 2024

This commit is contained in:
Trevor Batley 2024-09-07 21:19:08 +10:00
parent 43379e295c
commit 88f19a11b6
57 changed files with 3069 additions and 2 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.rpm
*.log
*spec-20*
*.tar.gz

21
Makefile Normal file
View File

@ -0,0 +1,21 @@
# Makefile for source rpm: smeserver-zabbix-server
# $Id: Makefile,v 1.1 2020/12/04 18:33:23 brianr Exp $
NAME := smeserver-zabbix-server
SPECFILE = $(firstword $(wildcard *.spec))
define find-makefile-common
for d in common ../common ../../common ; do if [ -f $$d/Makefile.common ] ; then if [ -f $$d/CVS/Root -a -w $$/Makefile.common ] ; then cd $$d ; cvs -Q update ; fi ; echo "$$d/Makefile.common" ; break ; fi ; done
endef
MAKEFILE_COMMON := $(shell $(find-makefile-common))
ifeq ($(MAKEFILE_COMMON),)
# attept a checkout
define checkout-makefile-common
test -f CVS/Root && { cvs -Q -d $$(cat CVS/Root) checkout common && echo "common/Makefile.common" ; } || { echo "ERROR: I can't figure out how to checkout the 'common' module." ; exit -1 ; } >&2
endef
MAKEFILE_COMMON := $(shell $(checkout-makefile-common))
endif
include $(MAKEFILE_COMMON)

View File

@ -1,3 +1,15 @@
# smeserver-zabbix-server # <img src="https://www.koozali.org/images/koozali/Logo/Png/Koozali_logo_2016.png" width="25%" vertical="auto" style="vertical-align:bottom"> smeserver-zabbix-server
SMEServer Koozali developed git repo for smeserver-zabbix-server smecontribs SMEServer Koozali developed git repo for smeserver-zabbix-server smecontribs
## Wiki
<br />https://wiki.koozali.org/
## Bugzilla
Show list of outstanding bugs: [here](https://bugs.koozali.org/buglist.cgi?component=smeserver-zabbix-server&product=SME%20Contribs&query_format=advanced&limit=0&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=CONFIRMED)
## Description
<br />*This description has been generated by an LLM AI system and cannot be relied on to be fully correct.*
*Once it has been checked, then this comment will be deleted*
<br />

1
contriborbase Normal file
View File

@ -0,0 +1 @@
contribs10

75
createlinks Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/perl -w
use esmith::Build::CreateLinks qw(:all);
my $event = 'zabbix-server-update';
# Templates to expand
templates2events("/etc/httpd/conf/httpd.conf", $event);
templates2events("/etc/zabbix/zabbix_server.conf", qw(zabbix-server-update bootstrap-console-save));
templates2events("/etc/zabbix/zabbix.conf.php", qw(zabbix-server-update bootstrap-console-save));
# new path with zabbix 4.4.6
templates2events("/etc/zabbix/web/zabbix.conf.php", qw(zabbix-server-update bootstrap-console-save));
templates2events("/etc/sudoers", $event);
templates2events("/var/lib/zabbix/bin/sendxmpp", $event);
templates2events("/var/lib/zabbix/.sendxmpprc", $event);
templates2events("/etc/e-smith/sql/init105/80zabbix-server", qw(zabbix-server-update bootstrap-console-save));
templates2events("/etc/rc.d/init.d/masq", $event);
# Services to restart
#safe_symlink("restart", "root/etc/e-smith/events/$event/services2adjust/mysql.init");
safe_symlink("sigusr1", "root/etc/e-smith/events/$event/services2adjust/httpd-e-smith");
safe_symlink("restart", "root/etc/e-smith/events/$event/services2adjust/zabbix-server");
safe_symlink("adjust", "root/etc/e-smith/events/$event/services2adjust/masq");
safe_symlink("/etc/e-smith/events/actions/zabbix-server","root/etc/e-smith/events/$event/50zabbix-server");
# PHP header and footer
safe_symlink("/etc/e-smith/templates-default/template-begin-php", "root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/template-begin");
safe_symlink("/etc/e-smith/templates-default/template-end-php", "root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/template-end");
# Bash header
safe_symlink("/etc/e-smith/templates-default/template-begin-shell", "root/etc/e-smith/templates/var/lib/zabbix/bin/sendxmpp/template-begin");
# Start and stop links - sysV
#service_link_enhanced("zabbix-server", "S99", "7");
#service_link_enhanced("zabbix-server", "K10", "6");
#service_link_enhanced("zabbix-server", "K10", "0");
#Systemd start stop
# rpm update action (invoked by yum on install and update
$contrib = "smeserver-zabbix-server";
safe_symlink("/etc/e-smith/events/$contrib-update", "root/etc/e-smith/events/$contrib-z50-update");
event_actions("$contrib-update", qw(
systemd-default 88
systemd-reload 89
zabbix-server 92
));
event_templates("$contrib-update", qw(
/etc/httpd/conf/httpd.conf
/etc/crontab
/etc/rc.d/init.d/masq
/etc/sudoers
/etc/e-smith/sql/init105/80zabbix-server
/etc/zabbix/zabbix.conf.php
/etc/zabbix/web/zabbix.conf.php
/var/lib/zabbix/bin/sendxmpp
/etc/zabbix/zabbix_server.conf
/usr/share/zabbix/.user.ini
/etc/systemd/system-preset/49-koozali.preset
/etc/opt/remi/php74/php-fpm.d/www.conf
/etc/backup-data.d/smeserver-zabbix-server.include
));
event_services("$contrib-update", qw(
httpd-e-smith sigusr1
php74-php-fpm reload
masq adjust
));
#zabbix server 92 will stop zabbix, restart mariadb105-mysql.init and start zabbix
#that is why those services are not listed in event_service. order matter.
#also this is at the end to be sure that mariadb105 is installed and running

View File

@ -0,0 +1 @@
zabbixdb

View File

@ -0,0 +1 @@
zabbixuser

View File

@ -0,0 +1 @@
zabbix

View File

@ -0,0 +1 @@
localhost

View File

@ -0,0 +1 @@
enabled

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1 @@
10051

View File

@ -0,0 +1 @@
local

View File

@ -0,0 +1 @@
private

View File

@ -0,0 +1 @@
enabled

View File

@ -0,0 +1 @@
service

View File

@ -0,0 +1,38 @@
{
my $rec = $DB->get('zabbix-server')
|| $DB->new_record('zabbix-server', {type => 'service'});
my $pw = $rec->prop('DbPassword');
if (not $pw or length($pw) < 57)
{
use MIME::Base64 qw(encode_base64);
$pw = "not set due to error";
if ( open( RANDOM, "/dev/urandom" ) )
{
my $buf;
# 57 bytes is a full line of Base64 coding, and contains
# 456 bits of randomness - given a perfectly random /dev/random
if ( read( RANDOM, $buf, 57 ) != 57 )
{
warn("Short read from /dev/random: $!");
}
else
{
$pw = encode_base64($buf);
chomp $pw;
}
close RANDOM;
}
else
{
warn "Could not open /dev/urandom: $!";
}
$rec->set_prop('DbPassword', $pw);
}
my $AdminPass = $rec->prop('AdminPassword') ||
$rec->set_prop('AdminPassword', `/usr/bin/openssl rand -base64 15 | /usr/bin/tr -c -d '[:graph:]'`);
}

View File

@ -0,0 +1,3 @@
systemctl daemon-reload
systemsctl preset-all
systemsctl restart zabbix-server

View File

@ -0,0 +1,7 @@
#!/bin/bash
# restart mysql.init
/usr/bin/systemctl stop zabbix-server 1>/dev/null
/usr/bin/systemctl restart mariadb105-mysql.init 1>/dev/null
/usr/bin/systemctl start zabbix-server 1>/dev/null
exit 0

View File

@ -0,0 +1 @@
PERMS=0750

View File

@ -0,0 +1,3 @@
TEMPLATE_PATH="/etc/zabbix/zabbix.conf.php"
OUTPUT_FILENAME="/etc/zabbix/web/zabbix.conf.php"

View File

@ -0,0 +1,3 @@
PERMS=0600
UID="zabbix"
GID="zabbix"

View File

@ -0,0 +1,4 @@
PERMS=0750
UID="root"
GID="zabbix"

View File

@ -0,0 +1,21 @@
#Only non rpm owned files are backupe there
{
use RPM2;
my $rpm_db = RPM2->open_rpm_db();
my @dirs = qw(
/etc/zabbix
/etc/zabbix/zabbix_agentd.conf.d/
/var/lib/zabbix/bin/
);
foreach my $some_dir (@dirs) {
next unless ( -e $some_dir );
opendir(my $dh, $some_dir) || die "Can't open $some_dir: $!";
while ( (my $file = readdir $dh) ) {
next if $file =~ /^\.{1,2}$/;
$OUT .= "$some_dir/$file\n" unless $rpm_db->find_by_file("$some_dir/$file");
}
closedir $dh;
}
}

View File

@ -0,0 +1,73 @@
{
my $db = ${'zabbix-server'}{'DbName'} || 'zabbixdb';
my $user = ${'zabbix-server'}{'DbUser'} || 'zabbixuser';
my $pass = ${'zabbix-server'}{'DbPassword'} || 'secret';
my $schema = `rpm -qd zabbix-server-mysql | grep create`;
chomp $schema;
my $curcharset= ( -d "/var/lib/mysql/$db" ) ? `echo 'show variables like "character_set_database";'|mysql $db|grep character_set_database|sed -r 's/^character_set_database\\s*([a-zA-Z0-9_-]+)/\\1/'` : "utf8";
chomp $curcharset;
my $adminpass= ${'zabbix-server'}{'AdminPassword'} || 'zabbix';
$hashpass=`/usr/bin/htpasswd -bnBC 10 '' $adminpass | tr -d ':'`;
$hashpass =~ tr/\r\n//d;
$version = `/bin/ls -d /usr/share/doc/zabbix-web*|grep -Eo '[0-9.]+\$'|cut -d. -f1 || echo 4 `;
$modpass=($version > 4)? "update users set passwd='$hashpass' where alias='Admin';": "#$version";
$OUT .= <<"END";
#! /bin/sh
if [ -d /var/opt/rh/rh-mariadb105/lib/mysql/$db ]; then
# check if utf8
if [[ "$curcharset" != "utf8" ]] ;then
echo "ALTER DATABASE $db CHARACTER SET utf8 COLLATE utf8_bin;" |/usr/bin/mysql105
echo 'ALTER TABLE `$db`.`problem_tag` DROP INDEX `problem_tag_1`, ADD INDEX `problem_tag_1` (`eventid`, `tag` (100), `value`(100));' |/usr/bin/mysql105
mysql --batch --skip-column-names --execute 'select concat("alter table ",TABLE_SCHEMA,".",TABLE_NAME," CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;") from information_schema.TABLES where TABLE_SCHEMA="$db"' | /usr/bin/mysql105
fi
for P in \$(rpm -qd zabbix-server-mysql | grep dbpatch | grep mysql); do
/usr/bin/mysql105 $db < \$P
done
else
echo "CREATE DATABASE $db CHARACTER SET utf8 COLLATE utf8_bin;" | /usr/bin/mysql105
/usr/bin/gunzip < $schema | /usr/bin/mysql105 $db
fi
/usr/bin/mysql105 <<EOF
USE $db;
update users set passwd=md5('$adminpass') where alias='Admin' and passwd=md5('zabbix');
$modpass
EOF
/usr/bin/mysql105 <<EOF
USE mysql;
grant all on $db.* to '${'zabbix-server'}{DbUser}'\@'localhost' identified by '${'zabbix-server'}{DbPassword}';
FLUSH PRIVILEGES;
USE ${'zabbix-server'}{DbName};
INSERT IGNORE INTO media_type (
mediatypeid,
type, description,
smtp_server,
smtp_helo,
smtp_email,
exec_path,
gsm_modem,
username,
passwd)
VALUES(
4,
1,
'xmpp',
'',
'',
'',
'sendxmpp',
'',
'',
'');
EOF
END
}

View File

@ -0,0 +1,45 @@
{
if (
((${'zabbix-server'}{'status'} || 'disabled') eq 'enabled') &&
((${'zabbix-server'}{'WebAccess'} || 'local') ne 'disabled')){
my $access = (${'zabbix-server'}{'WebAccess'} || 'local') eq 'public' ?
'all granted':"ip $localAccess $externalSSLAccess";
my $tz = ${'TimeZone'} || 'Europe/Paris';
$OUT .=<<"HERE";
#-------------------------------------------#
# Zabbix monitoring system php web frontend #
#-------------------------------------------#
Alias /zabbix /usr/share/zabbix
<Directory "/usr/share/zabbix">
SSLRequireSSL on
Options FollowSymLinks
AllowOverride None
#AddType application/x-httpd-php .php
<FilesMatch .php>
SetHandler "proxy:unix:/var/run/php-fpm/php74-zabbix-server.sock|fcgi://localhost"
</FilesMatch>
Require $access
</Directory>
<Directory "/usr/share/zabbix/include">
Require all denied
<files *.php>
Require all denied
</files>
</Directory>
<Directory "/usr/share/zabbix/include/classes">
Require all denied
<files *.php>
Require all denied
</files>
</Directory>
HERE
}
}

View File

@ -0,0 +1,7 @@
{
if ($port ne ${modSSL}{'TCPPort'}){
$OUT = ' RewriteRule ^/zabbix(/.*|$) https://%{HTTP_HOST}/zabbix$1 [L,R]';
}
}

View File

@ -0,0 +1,60 @@
{
if ($PHP_VERSION eq '74'){
if ((${'zabbix-server'}{status} || 'disabled') eq 'enabled'){
my $id = 'zabbix-server';
my $openbasedir = '/usr/share/zabbix:/var/cache/zabbix/:/var/run/php-fpm/:/var/lib/php/zabbix/' .
':/var/lock/zabbix/:/etc/zabbix/:/usr/share/php/:/usr/share/pear/:/opt/remi/php74/root/usr/share/pear/' .
':/opt/remi/php74/root/usr/share/php/';
$disablefunctions = 'system, show_source, symlink, exec, dl, shell_exec, passthru, phpinfo, ' .
'escapeshellarg, escapeshellcmd';
$socket = ( -d "/var/lib/mysql/zabbixdb") ? "/var/lib/mysql/mysql.sock" : "/var/lib/mysql/mariadb105.sock";
$OUT .=<<_EOF;
[php$PHP_VERSION-$id]
user = www
group = www
listen.owner = root
listen.group = www
listen.mode = 0660
listen = /var/run/php-fpm/php$PHP_VERSION-$id.sock
pm = dynamic
pm.max_children = 15
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 4
pm.max_requests = 1000
request_terminate_timeout = 30
php_admin_value[session.save_path] = /var/lib/php/zabbix/session
php_admin_value[opcache.file_cache] = /var/lib/php/zabbix/opcache
php_admin_value[upload_tmp_dir] = /var/lib/php/zabbix/tmp
php_admin_value[error_log] = /var/log/php/zabbix/error.log
slowlog = /var/log/php/zabbix/slow.log
php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f php@{ $DomainName }
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = syslog
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 600
php_admin_value[max_input_time] = 600
php_admin_value[post_max_size] = 32M
php_admin_value[upload_max_filesize] = 16M
php_admin_value[disable_functions] = $disablefunctions
php_admin_value[open_basedir] = $openbasedir
php_admin_flag[allow_url_fopen] = on
php_admin_flag[file_upload] = off
php_admin_flag[session.cookie_httponly] = on
php_admin_flag[allow_url_include] = off
php_admin_value[session.save_handler] = files
php_admin_value[always_populate_raw_post_data] = -1
php_value[mysqli.default_socket] = $socket
php_value[mysql.default_socket] = $socket
_EOF
}
else{
$OUT .= '; Zabbix Server is disabled';
}
}
}

View File

@ -0,0 +1 @@
Cmnd_Alias ZABBIX = /usr/sbin/fping,/usr/sbin/fping6

View File

@ -0,0 +1 @@
zabbix ALL=(root) NOPASSWD: ZABBIX

View File

@ -0,0 +1,19 @@
/*
** ZABBIX
** Copyright (C) 2000-2005 SIA Zabbix
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**/

View File

@ -0,0 +1,20 @@
global $DB;
$DB["TYPE"] = "MYSQL";
$DB["SERVER"] = "localhost";
$DB["PORT"] = "0";
{
my $dbname = ${'zabbix-server'}{'DbName'} || 'zabbix';
my $dbuser = ${'zabbix-server'}{'DbUser'} || 'zabbix';
my $dbpass = ${'zabbix-server'}{'DbPassword'} || 'secret';
$OUT .=<<"HERE";
\$DB["DATABASE"] = "$dbname";
\$DB["USER"] = "$dbuser";
\$DB["PASSWORD"] = "$dbpass";
HERE
}

View File

@ -0,0 +1,10 @@
{
my $port = ${'zabbix-server'}{'TCPPort'} || '10051';
$OUT .=<<"HERE";
\$ZBX_SERVER = "localhost";
\$ZBX_SERVER_PORT = "$port";
HERE
}

View File

@ -0,0 +1 @@
$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;

View File

@ -0,0 +1,4 @@
# This is config file for ZABBIX server process
# To get more information about ZABBIX,
# go http://www.zabbix.com

View File

@ -0,0 +1,11 @@
############ GENERAL PARAMETERS #################
#NodeID dropped since Zabbix 2.40
{
# This defines unique NodeID in distributed setup,
# Default value 0 (standalone server)
# This parameter must be between 0 and 999
#my $nodeID = ${'zabbix-server'}{'NodeID'} || '0';
#$OUT .= "NodeID=$nodeID\n";
}

View File

@ -0,0 +1,35 @@
# Number of pre-forked instances of pollers
# Default value is 5
# This parameter must be between 0 and 255
StartPollers=5
# Number of pre-forked instances of IPMI pollers
# Default value is 0
# This parameter must be between 0 and 255
#StartIPMIPollers=0
# Number of pre-forked instances of pollers for unreachable hosts
# Default value is 1
# This parameter must be between 0 and 255
#StartPollersUnreachable=1
# Number of pre-forked instances of trappers
# Default value is 5
# This parameter must be between 0 and 255
StartTrappers=5
# Number of pre-forked instances of ICMP pingers
# Default value is 1
# This parameter must be between 0 and 255
StartPingers=1
# Number of pre-forked instances of discoverers
# Default value is 1
# This parameter must be between 0 and 255
StartDiscoverers=1
# Number of pre-forked instances of HTTP pollers
# Default value is 1
# This parameter must be between 0 and 255
#StartHTTPPollers=1

View File

@ -0,0 +1,16 @@
# Listen port for trapper. Default port number is 10051. This parameter
# must be between 1024 and 32767
{
my $port = ${'zabbix-server'}{'TCPPort'} || '10051';
$OUT .= "ListenPort=$port\n";
}
# Source IP address for outgouing connections
#SourceIP=
# Listen interface for trapper. Trapper will listen all network interfaces
# if this parameter is missing.
#ListenIP=127.0.0.1

View File

@ -0,0 +1,15 @@
# How often ZABBIX will perform housekeeping procedure
# (in hours)
# Default value is 1 hour
# Housekeeping is removing unnecessary information from
# tables history, alert, and alarms
# This parameter must be between 1 and 24
#HousekeepingFrequency=1
# Uncomment this line to disable housekeeping procedure
#DisableHousekeeping=1
# Frequency of ICMP pings (item keys 'icmpping' and 'icmppingsec'). Defauls is 60 seconds.
#PingerFrequency=60

View File

@ -0,0 +1,9 @@
# Specifies debug level
# 0 - debug is not created
# 1 - critical information
# 2 - error information
# 3 - warnings (default)
# 4 - for debugging (produces lots of information)
DebugLevel=3

View File

@ -0,0 +1,13 @@
# Name of PID file
PidFile=/run/zabbix/zabbix_server.pid
# Name of log file
# If not set, syslog is used
LogFile=/var/log/zabbix/zabbix_server.log
# Maximum size of log file in MB. Set to 0 to disable automatic log rotation.
LogFileSize=10

View File

@ -0,0 +1,17 @@
# Location for custom alert scripts
AlertScriptsPath=/var/lib/zabbix/bin
# Location of external scripts
ExternalScripts=/var/lib/zabbix/bin
# Location of fping. Default is /usr/sbin/fping
# Make sure that fping binary has root permissions and SUID flag set
FpingLocation=/var/lib/zabbix/bin/fping
# Location of fping6. Default is /usr/sbin/fping6
# Make sure that fping binary has root permissions and SUID flag set
Fping6Location=/var/lib/zabbix/bin/fping6
# Temporary directory. Default is /tmp
TmpDir=/var/lib/zabbix/tmp

View File

@ -0,0 +1,36 @@
# Database host name
# Default is localhost
DBHost=localhost
# Database name
# SQLite3 note: path to database file must be provided. DBUser and DBPassword are ignored.
{
my $dbname = ${'zabbix-server'}{'DbName'} || 'zabbix';
my $dbuser = ${'zabbix-server'}{'DbUser'} || 'zabbix';
my $dbpass = ${'zabbix-server'}{'DbPassword'} || 'secret';
$OUT .=<<"HERE";
DBName=$dbname
# Database user
DBUser=$dbuser
# Database password
# Comment this line if no password used
DBPassword=$dbpass
HERE
}
# Connect to MySQL using Unix socket?
#DBSocket=/var/lib/mysql/mysql.sock
DBSocket=/var/lib/mysql/{$OUT = ( -d "/var/lib/mysql/zabbixdb") ? "mysql" : "mariadb105"}.sock
# Enable database cache module
StartDBSyncers=1

View File

@ -0,0 +1,12 @@
{
my $tz = ${'TimeZone'} || 'Europe/Paris';
$OUT =<<"HERE";
max_execution_time=600
max_input_time=600
memory_limit=256M
date.timezone=$tz
post_max_size=32M
always_populate_raw_post_data=-1
HERE
}

View File

@ -0,0 +1,3 @@
# Jabber Account for zabbix alerts
{${'zabbix-server'}{'JabberAccount'}}@{${'zabbix-server'}{'JabberServer'}} {${'zabbix-server'}{'JabberPassword'}}

View File

@ -0,0 +1,6 @@
echo "$3" | \
sendxmpp -r zabbix -f /var/lib/zabbix/.sendxmpprc \
{(${'zabbix-server'}{'JabberTLS'} || 'enabled') eq 'disabled' ? '':'-t \\';}
-s "$2" "$1"

View File

@ -0,0 +1,3 @@
[Install]
WantedBy=sme-server.target

View File

@ -0,0 +1,6 @@
max_execution_time=600
max_input_time=600
memory_limit=256M
date.timezone=$tz
post_max_size=32M
always_populate_raw_post_data=-1

View File

@ -0,0 +1,139 @@
#!/usr/bin/perl -w
# Check peer certificate validity for Zabbix
# Require perl module : IO::Socket, Net::SSLeay, Date::Parse
# Require unix programs : openssl, echo, sendmail
#
# Based on sslexpire from Emmanuel Lacour <elacour@home-dn.net>
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any
# later version.
#
# This file is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this file; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
use strict;
use IO::Socket;
use Net::SSLeay;
use Getopt::Long;
use Date::Parse;
Net::SSLeay::SSLeay_add_ssl_algorithms();
Net::SSLeay::randomize();
# Default values
my $opensslpath = "/usr/bin/openssl";
my $host = '127.0.0.1';
my $port = '443';
my %opts;
GetOptions (\%opts,
'host|h=s',
'port|p=s',
'help',
);
if ($opts{'host'}) {
$host = $opts{'host'};
}
if ($opts{'port'}){
$port = $opts{'port'};
}
if ($opts{'help'}) {
&usage;
}
# Print program usage
sub usage {
print "Usage: sslexpire [OPTION]...
-h, --host=HOST check this host
-p, --port=TCPPORT check this port on the previous host
--help print this help, then exit
";
exit;
}
# This will return the expiration date
sub getExpire {
my ($l_host,$l_port) = @_;
my ($l_expdate,$l_comment);
# Connect to $l_host:$l_port
my $socket = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => $l_host,
PeerPort => $l_port
);
# If we connected successfully
if ($socket) {
# Intiate ssl
my $l_ctx = Net::SSLeay::CTX_new();
my $l_ssl = Net::SSLeay::new($l_ctx);
Net::SSLeay::set_fd($l_ssl, fileno($socket));
my $res = Net::SSLeay::connect($l_ssl);
# Get peer certificate
my $l_x509 = Net::SSLeay::get_peer_certificate($l_ssl);
if ($l_x509) {
my $l_string = Net::SSLeay::PEM_get_string_X509($l_x509);
# Get the expiration date, using openssl
$l_expdate = `echo "$l_string" | $opensslpath x509 -enddate -noout 2>&1`;
$l_expdate =~ s/.*=//;
chomp($l_expdate);
}
else {
$l_expdate = 1;
}
# Close and cleanup
Net::SSLeay::free($l_ssl);
Net::SSLeay::CTX_free($l_ctx);
close $socket;
}
else {
$l_expdate = 1;
}
return $l_expdate;
}
# Print remaining days before expiration
sub report {
# Convert date into epoch using date command
my ($l_expdate) = @_;
if ($l_expdate ne "1") {
# The current date
my $l_today = time;
my $l_epochdate = str2time($l_expdate);
# Calculate diff between expiration date and today
my $l_diff = ($l_epochdate - $l_today)/(3600*24);
# Report if needed
printf "%.0f\n", $l_diff;
}
else {
print "Unable to read certificate!\n";
exit (1);
}
}
# Get expiration date
my $expdate = getExpire($host,$port);
# Report
report("$expdate");

View File

@ -0,0 +1,109 @@
#!/usr/bin/perl
=head1 SYNOPSIS
check_ssl_certificate.pl
--url,-u URL
--sni,-s HOSTNAME SNI servername (SSL vhost) that will be requested during SSL handshake.
This tells the server which certificate to return.
Default to the host passed with --url
=cut
use strict;
use warnings;
use IO::Socket::SSL;
use LWP::UserAgent;
use URI::URL;
use DateTime::Format::ISO8601;
use Getopt::Long qw/:config auto_help/;
use Pod::Usage;
use JSON qw(to_json);
use constant TIMEOUT => 10;
my ($url, $sni, $status, @san);
sub ssl_opts {
my ($sni, $expiration_date_ref, $status_ref, $san_ref) = @_;
return (
'verify_hostname' => 0,
'SSL_ca_file' => '/etc/pki/tls/certs/ca-bundle.crt',
'SSL_hostname' => $sni,
'SSL_verifycn_name' => $sni,
'SSL_verify_scheme' => 'http',
'SSL_verify_callback' => sub {
my (undef, $ctx_store) = @_;
# Get the error message from openssl verification
$$status_ref = Net::SSLeay::X509_verify_cert_error_string(Net::SSLeay::X509_STORE_CTX_get_error($ctx_store));
# Get the raw cert, to extract the expiration
my $cert = Net::SSLeay::X509_STORE_CTX_get_current_cert($ctx_store);
$$expiration_date_ref = Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notAfter($cert));
# Get Alt names so we can check later if the hostname match
@$san_ref = Net::SSLeay::X509_get_subjectAltNames($cert);
# Keep only odd elements. Even ones contains subject types which we're not interested in
@$san_ref = @$san_ref[grep $_ % 2, 0..scalar(@$san_ref)];
# Always return success
return 1;
}
)
}
sub https_get {
my ($url, $sni, $expiration_date_ref, $status_ref, $san_ref) = @_;
my $ua = LWP::UserAgent->new();
$ua->timeout(TIMEOUT);
$ua->ssl_opts( ssl_opts($sni, $expiration_date_ref, $status_ref, $san_ref) );
my $request = HTTP::Request->new('GET', $url);
$request->header(Host => $sni);
my $response = $ua->simple_request($request);
return $response;
}
sub wildcard_match {
my ($cn, $host) = @_;
my $match = 0;
return 0 if $cn !~ m/^\*\.(.*)$/;
my $cn_dom = $1;
my $host_dom = ($sni =~ m/^[^\.]+\.(.*)$/)[0];
return ($cn_dom eq $host_dom);
}
GetOptions ("url|u=s" => \$url,
"sni|s=s" => \$sni) or pod2usage(1);
if (@ARGV) {
print "This script takes no arguments...\n";
pod2usage(1);
}
pod2usage(1) if (!$url);
my $expiration_date;
my $uri = URI->new($url);
die "Only https urls are supported\n" unless $uri->scheme eq 'https';
$sni ||= $uri->host;
my $response = https_get($url, $sni, \$expiration_date, \$status, \@san);
my $out = {
code => $response->code,
status => $response->message,
days_left => undef,
cert_cn => undef,
issuer => undef
};
if ($response->code != 500) { # Even a 404 is good enough, as far as cert validation goes...
my $now = DateTime->now;
$expiration_date = DateTime::Format::ISO8601->parse_datetime( $expiration_date );
$out->{issuer} = $response->headers->{'client-ssl-cert-issuer'};
$out->{cert_cn} = ($response->headers->{'client-ssl-cert-subject'} =~ m/CN=(.*)$/)[0];
$status = "no common name" if !$out->{cert_cn};
$out->{status} = ($status eq 'ok' and !grep { $sni eq $_ } @san and !wildcard_match($out->{cert_cn},$sni)) ?
$out->{status} = "hostname mismatch ($sni doesn't match any of " . join(" ", @san) . ")" :
$status;
$out->{days_left} = ($expiration_date < $now) ? -1 * $expiration_date->delta_days($now)->delta_days :
$expiration_date->delta_days($now)->delta_days
}
print to_json($out, { pretty => 1 });

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/bin/sudo /usr/sbin/fping "$@"

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/bin/sudo /usr/sbin/fping6 "$@"

View File

@ -0,0 +1,939 @@
#!/usr/bin/env python
# coding: utf-8
import sys
import os
import time
import random
import string
import requests
import json
import re
import stat
import hashlib
import subprocess
#import sqlite3
from os.path import dirname
import zbxtg_settings
class Cache:
def __init__(self, database):
self.database = database
def create_db(self, database):
pass
class TelegramAPI:
tg_url_bot_general = "https://api.telegram.org/bot"
def http_get(self, url):
answer = requests.get(url, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def __init__(self, key):
self.debug = False
self.key = key
self.proxies = {}
self.type = "private" # 'private' for private chats or 'group' for group chats
self.markdown = False
self.html = False
self.disable_web_page_preview = False
self.disable_notification = False
self.reply_to_message_id = 0
self.tmp_dir = None
self.tmp_uids = None
self.location = {"latitude": None, "longitude": None}
self.update_offset = 0
self.image_buttons = False
self.result = None
self.ok = None
self.error = None
self.get_updates_from_file = False
def get_me(self):
url = self.tg_url_bot_general + self.key + "/getMe"
me = self.http_get(url)
return me
def get_updates(self):
url = self.tg_url_bot_general + self.key + "/getUpdates"
params = {"offset": self.update_offset}
if self.debug:
print_message(url)
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
if self.get_updates_from_file:
print_message("Getting updated from file getUpdates.txt")
self.result = json.loads("".join(file_read("getUpdates.txt")))
if self.debug:
print_message("Content of /getUpdates:")
print_message(json.dumps(self.result))
self.ok_update()
return self.result
def send_message(self, to, message):
url = self.tg_url_bot_general + self.key + "/sendMessage"
message = "\n".join(message)
params = {"chat_id": to, "text": message, "disable_web_page_preview": self.disable_web_page_preview,
"disable_notification": self.disable_notification}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
if self.markdown or self.html:
parse_mode = "HTML"
if self.markdown:
parse_mode = "Markdown"
params["parse_mode"] = parse_mode
if self.debug:
print_message("Trying to /sendMessage:")
print_message(url)
print_message("post params: " + str(params))
answer = requests.post(url, params=params, proxies=self.proxies)
if answer.status_code == 414:
self.result = {"ok": False, "description": "414 URI Too Long"}
else:
self.result = answer.json()
self.ok_update()
return self.result
def update_message(self, to, message_id, message):
url = self.tg_url_bot_general + self.key + "/editMessageText"
message = "\n".join(message)
params = {"chat_id": to, "message_id": message_id, "text": message,
"disable_web_page_preview": self.disable_web_page_preview,
"disable_notification": self.disable_notification}
if self.markdown or self.html:
parse_mode = "HTML"
if self.markdown:
parse_mode = "Markdown"
params["parse_mode"] = parse_mode
if self.debug:
print_message("Trying to /editMessageText:")
print_message(url)
print_message("post params: " + str(params))
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def send_photo(self, to, message, path):
url = self.tg_url_bot_general + self.key + "/sendPhoto"
message = "\n".join(message)
if self.image_buttons:
reply_markup = json.dumps({"inline_keyboard": [[
{"text": "R", "callback_data": "graph_refresh"},
{"text": "1h", "callback_data": "graph_period_3600"},
{"text": "3h", "callback_data": "graph_period_10800"},
{"text": "6h", "callback_data": "graph_period_21600"},
{"text": "12h", "callback_data": "graph_period_43200"},
{"text": "24h", "callback_data": "graph_period_86400"},
], ]})
else:
reply_markup = json.dumps({})
params = {"chat_id": to, "caption": message, "disable_notification": self.disable_notification,
"reply_markup": reply_markup}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
files = {"photo": open(path, 'rb')}
if self.debug:
print_message("Trying to /sendPhoto:")
print_message(url)
print_message(params)
print_message("files: " + str(files))
answer = requests.post(url, params=params, files=files, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def send_txt(self, to, text, text_name=None):
path = self.tmp_dir + "/" + "zbxtg_txt_"
url = self.tg_url_bot_general + self.key + "/sendDocument"
text = "\n".join(text)
if not text_name:
path += "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
else:
path += text_name
path += ".txt"
file_write(path, text)
params = {"chat_id": to, "caption": path.split("/")[-1], "disable_notification": self.disable_notification}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
files = {"document": open(path, 'rb')}
if self.debug:
print_message("Trying to /sendDocument:")
print_message(url)
print_message(params)
print_message("files: " + str(files))
answer = requests.post(url, params=params, files=files, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def get_uid(self, name):
uid = 0
if self.debug:
print_message("Getting uid from /getUpdates...")
updates = self.get_updates()
for m in updates["result"]:
if "message" in m:
chat = m["message"]["chat"]
elif "edited_message" in m:
chat = m["edited_message"]["chat"]
else:
continue
if chat["type"] == self.type == "private":
if "username" in chat:
if chat["username"] == name:
uid = chat["id"]
if (chat["type"] == "group" or chat["type"] == "supergroup") and self.type == "group":
if "title" in chat:
if sys.version_info[0] < 3:
if chat["title"] == name.decode("utf-8"):
uid = chat["id"]
else:
if chat["title"] == name:
uid = chat["id"]
return uid
def error_need_to_contact(self, to):
if self.type == "private":
print_message("User '{0}' needs to send some text bot in private".format(to))
if self.type == "group":
print_message("You need start a conversation with your bot first in '{0}' group chat, type '/start@{1}'"
.format(to, self.get_me()["result"]["username"]))
def update_cache_uid(self, name, uid, message="Add new string to cache file"):
cache_string = "{0};{1};{2}\n".format(name, self.type, str(uid).rstrip())
# FIXME
if self.debug:
print_message("{0}: {1}".format(message, cache_string))
with open(self.tmp_uids, "a") as cache_file_uids:
cache_file_uids.write(cache_string)
return True
def get_uid_from_cache(self, name):
if self.debug:
print_message("Trying to read cached uid for {0}, {1}, from {2}".format(name, self.type, self.tmp_uids))
uid = 0
if os.path.isfile(self.tmp_uids):
with open(self.tmp_uids, 'r') as cache_file_uids:
cache_uids_old = cache_file_uids.readlines()
for u in cache_uids_old:
u_splitted = u.split(";")
if name == u_splitted[0] and self.type == u_splitted[1]:
uid = u_splitted[2]
return uid
def send_location(self, to, coordinates):
url = self.tg_url_bot_general + self.key + "/sendLocation"
params = {"chat_id": to, "disable_notification": self.disable_notification,
"latitude": coordinates["latitude"], "longitude": coordinates["longitude"]}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
if self.debug:
print_message("Trying to /sendLocation:")
print_message(url)
print_message("post params: " + str(params))
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def answer_callback_query(self, callback_query_id, text=None):
url = self.tg_url_bot_general + self.key + "/answerCallbackQuery"
if not text:
params = {"callback_query_id": callback_query_id}
else:
params = {"callback_query_id": callback_query_id, "text": text}
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def ok_update(self):
self.ok = self.result["ok"]
if self.ok:
self.error = None
else:
self.error = self.result["description"]
print_message(self.error)
return True
def markdown_fix(message, offset, emoji=False):
offset = int(offset)
if emoji: # https://github.com/ableev/Zabbix-in-Telegram/issues/152
offset -= 2
message = "\n".join(message)
message = message[:offset] + message[offset+1:]
message = message.split("\n")
return message
class ZabbixWeb:
def __init__(self, server, username, password):
self.debug = False
self.server = server
self.username = username
self.password = password
self.proxies = {}
self.verify = True
self.cookie = None
self.basic_auth_user = None
self.basic_auth_pass = None
self.tmp_dir = None
def login(self):
if not self.verify:
requests.packages.urllib3.disable_warnings()
data_api = {"name": self.username, "password": self.password, "enter": "Sign in"}
answer = requests.post(self.server + "/", data=data_api, proxies=self.proxies, verify=self.verify,
auth=requests.auth.HTTPBasicAuth(self.basic_auth_user, self.basic_auth_pass))
cookie = answer.cookies
if len(answer.history) > 1 and answer.history[0].status_code == 302:
print_message("probably the server in your config file has not full URL (for example "
"'{0}' instead of '{1}')".format(self.server, self.server + "/zabbix"))
if not cookie:
print_message("authorization has failed, url: {0}".format(self.server + "/"))
cookie = None
self.cookie = cookie
def graph_get(self, itemid, period, title, width, height, version=3):
file_img = self.tmp_dir + "/{0}.png".format("".join(itemid))
title = requests.utils.quote(title)
colors = {
0: "00CC00",
1: "CC0000",
2: "0000CC",
3: "CCCC00",
4: "00CCCC",
5: "CC00CC",
}
drawtype = 5
if len(itemid) > 1:
drawtype = 2
zbx_img_url_itemids = []
for i in range(0, len(itemid)):
itemid_url = "&items[{0}][itemid]={1}&items[{0}][sortorder]={0}&" \
"items[{0}][drawtype]={3}&items[{0}][color]={2}".format(i, itemid[i], colors[i], drawtype)
zbx_img_url_itemids.append(itemid_url)
zbx_img_url = self.server + "/chart3.php?"
if version < 4:
zbx_img_url += "period={0}".format(period)
else:
zbx_img_url += "from=now-{0}&to=now".format(period)
zbx_img_url += "&name={0}&width={1}&height={2}&graphtype=0&legend=1".format(title, width, height)
zbx_img_url += "".join(zbx_img_url_itemids)
if self.debug:
print_message(zbx_img_url)
answer = requests.get(zbx_img_url, cookies=self.cookie, proxies=self.proxies, verify=self.verify,
auth=requests.auth.HTTPBasicAuth(self.basic_auth_user, self.basic_auth_pass))
status_code = answer.status_code
if status_code == 404:
print_message("can't get image from '{0}'".format(zbx_img_url))
return False
res_img = answer.content
file_bwrite(file_img, res_img)
return file_img
def api_test(self):
headers = {'Content-type': 'application/json'}
api_data = json.dumps({"jsonrpc": "2.0", "method": "user.login", "params":
{"user": self.username, "password": self.password}, "id": 1})
api_url = self.server + "/api_jsonrpc.php"
api = requests.post(api_url, data=api_data, proxies=self.proxies, headers=headers)
return api.text
def print_message(message):
message = str(message) + "\n"
filename = sys.argv[0].split("/")[-1]
sys.stderr.write(filename + ": " + message)
def list_cut(elements, symbols_limit):
symbols_count = symbols_count_now = 0
elements_new = []
element_last_list = []
for e in elements:
symbols_count_now = symbols_count + len(e)
if symbols_count_now > symbols_limit:
limit_idx = symbols_limit - symbols_count
e_list = list(e)
for idx, ee in enumerate(e_list):
if idx < limit_idx:
element_last_list.append(ee)
else:
break
break
else:
symbols_count = symbols_count_now + 1
elements_new.append(e)
if symbols_count_now < symbols_limit:
return elements, False
else:
element_last = "".join(element_last_list)
elements_new.append(element_last)
return elements_new, True
class Maps:
# https://developers.google.com/maps/documentation/geocoding/intro
def __init__(self):
self.key = None
self.proxies = {}
def get_coordinates_by_address(self, address):
coordinates = {"latitude": 0, "longitude": 0}
url_api = "https://maps.googleapis.com/maps/api/geocode/json?key={0}&address={1}".format(self.key, address)
url = url_api
answer = requests.get(url, proxies=self.proxies)
result = answer.json()
try:
coordinates_dict = result["results"][0]["geometry"]["location"]
except:
if "error_message" in result:
print_message("[" + result["status"] + "]: " + result["error_message"])
return coordinates
coordinates = {"latitude": coordinates_dict["lat"], "longitude": coordinates_dict["lng"]}
return coordinates
def file_write(filename, text):
with open(filename, "w") as fd:
fd.write(str(text))
return True
def file_bwrite(filename, data):
with open(filename, "wb") as fd:
fd.write(data)
return True
def file_read(filename):
with open(filename, "r") as fd:
text = fd.readlines()
return text
def file_append(filename, text):
with open(filename, "a") as fd:
fd.write(str(text))
return True
def external_image_get(url, tmp_dir, timeout=6):
image_hash = hashlib.md5()
image_hash.update(url.encode())
file_img = tmp_dir + "/external_{0}.png".format(image_hash.hexdigest())
try:
answer = requests.get(url, timeout=timeout, allow_redirects=True)
except requests.exceptions.ReadTimeout as ex:
print_message("Can't get external image from '{0}': timeout".format(url))
return False
status_code = answer.status_code
if status_code == 404:
print_message("Can't get external image from '{0}': HTTP 404 error".format(url))
return False
answer_image = answer.content
file_bwrite(file_img, answer_image)
return file_img
def age2sec(age_str):
age_sec = 0
age_regex = "([0-9]+d)?\s?([0-9]+h)?\s?([0-9]+m)?"
age_pattern = re.compile(age_regex)
intervals = age_pattern.match(age_str).groups()
for i in intervals:
if i:
metric = i[-1]
if metric == "d":
age_sec += int(i[0:-1])*86400
if metric == "h":
age_sec += int(i[0:-1])*3600
if metric == "m":
age_sec += int(i[0:-1])*60
return age_sec
def main():
tmp_dir = zbxtg_settings.zbx_tg_tmp_dir
if tmp_dir == "/tmp/" + zbxtg_settings.zbx_tg_prefix:
print_message("WARNING: it is strongly recommended to change `zbx_tg_tmp_dir` variable in config!!!")
print_message("https://github.com/ableev/Zabbix-in-Telegram/wiki/Change-zbx_tg_tmp_dir-in-settings")
tmp_cookie = tmp_dir + "/cookie.py.txt"
tmp_uids = tmp_dir + "/uids.txt"
tmp_need_update = False # do we need to update cache file with uids or not
rnd = random.randint(0, 999)
ts = time.time()
hash_ts = str(ts) + "." + str(rnd)
log_file = "/dev/null"
args = sys.argv
settings = {
"zbxtg_itemid": "0", # itemid for graph
"zbxtg_title": None, # title for graph
"zbxtg_image_period": None,
"zbxtg_image_age": "3600",
"zbxtg_image_width": "900",
"zbxtg_image_height": "200",
"tg_method_image": False, # if True - default send images, False - send text
"tg_chat": False, # send message to chat or in private
"tg_group": False, # send message to chat or in private
"is_debug": False,
"is_channel": False,
"disable_web_page_preview": False,
"location": None, # address
"lat": 0, # latitude
"lon": 0, # longitude
"is_single_message": False,
"markdown": False,
"html": False,
"signature": None,
"signature_disable": False,
"graph_buttons": False,
"extimg": None,
"to": None,
"to_group": None,
"forked": False,
}
url_github = "https://github.com/ableev/Zabbix-in-Telegram"
url_wiki_base = "https://github.com/ableev/Zabbix-in-Telegram/wiki"
url_tg_group = "https://t.me/ZbxTg"
url_tg_channel = "https://t.me/Zabbix_in_Telegram"
settings_description = {
"itemid": {"name": "zbxtg_itemid", "type": "list",
"help": "script will attach a graph with that itemid (could be multiple)", "url": "Graphs"},
"title": {"name": "zbxtg_title", "type": "str", "help": "title for attached graph", "url": "Graphs"},
"graphs_period": {"name": "zbxtg_image_period", "type": "int", "help": "graph period", "url": "Graphs"},
"graphs_age": {"name": "zbxtg_image_age", "type": "str", "help": "graph period as age", "url": "Graphs"},
"graphs_width": {"name": "zbxtg_image_width", "type": "int", "help": "graph width", "url": "Graphs"},
"graphs_height": {"name": "zbxtg_image_height", "type": "int", "help": "graph height", "url": "Graphs"},
"graphs": {"name": "tg_method_image", "type": "bool", "help": "enables graph sending", "url": "Graphs"},
"chat": {"name": "tg_chat", "type": "bool", "help": "deprecated, don't use it, see 'group'",
"url": "How-to-send-message-to-the-group-chat"},
"group": {"name": "tg_group", "type": "bool", "help": "sends message to a group",
"url": "How-to-send-message-to-the-group-chat"},
"debug": {"name": "is_debug", "type": "bool", "help": "enables 'debug'",
"url": "How-to-test-script-in-command-line"},
"channel": {"name": "is_channel", "type": "bool", "help": "sends message to a channel",
"url": "Channel-support"},
"disable_web_page_preview": {"name": "disable_web_page_preview", "type": "bool",
"help": "disable web page preview", "url": "Disable-web-page-preview"},
"location": {"name": "location", "type": "str", "help": "address of location", "url": "Location"},
"lat": {"name": "lat", "type": "str", "help": "specify latitude (and lon too!)", "url": "Location"},
"lon": {"name": "lon", "type": "str", "help": "specify longitude (and lat too!)", "url": "Location"},
"single_message": {"name": "is_single_message", "type": "bool", "help": "do not split message and graph",
"url": "Why-am-I-getting-two-messages-instead-of-one"},
"markdown": {"name": "markdown", "type": "bool", "help": "markdown support", "url": "Markdown-and-HTML"},
"html": {"name": "html", "type": "bool", "help": "markdown support", "url": "Markdown-and-HTML"},
"signature": {"name": "signature", "type": "str",
"help": "bot's signature", "url": "Bot-signature"},
"signature_disable": {"name": "signature_disable", "type": "bool",
"help": "enables/disables bot's signature", "url": "Bot-signature"},
"graph_buttons": {"name": "graph_buttons", "type": "bool",
"help": "activates buttons under graph, could be using in ZbxTgDaemon",
"url": "Interactive-bot"},
"external_image": {"name": "extimg", "type": "str",
"help": "should be url; attaches external image from different source",
"url": "External-image-as-graph"},
"to": {"name": "to", "type": "str", "help": "rewrite zabbix username, use that instead of arguments",
"url": "Custom-to-and-to_group"},
"to_group": {"name": "to_group", "type": "str",
"help": "rewrite zabbix username, use that instead of arguments", "url": "Custom-to-and-to_group"},
"forked": {"name": "forked", "type": "bool", "help": "internal variable, do not use it. Ever.", "url": ""},
}
if len(args) < 4:
do_not_exit = False
if "--features" in args:
print(("List of available settings, see {0}/Settings\n---".format(url_wiki_base)))
for sett, proprt in list(settings_description.items()):
print(("{0}: {1}\ndoc: {2}/{3}\n--".format(sett, proprt["help"], url_wiki_base, proprt["url"])))
elif "--show-settings" in args:
do_not_exit = True
print_message("Settings: " + str(json.dumps(settings, indent=2)))
else:
print(("Hi. You should provide at least three arguments.\n"
"zbxtg.py [TO] [SUBJECT] [BODY]\n\n"
"1. Read main page and/or wiki: {0} + {1}\n"
"2. Public Telegram group (discussion): {2}\n"
"3. Public Telegram channel: {3}\n"
"4. Try dev branch for test purposes (new features, etc): {0}/tree/dev"
.format(url_github, url_wiki_base, url_tg_group, url_tg_channel)))
if not do_not_exit:
sys.exit(0)
zbx_to = args[1]
zbx_subject = args[2]
zbx_body = args[3]
tg = TelegramAPI(key=zbxtg_settings.tg_key)
tg.tmp_dir = tmp_dir
tg.tmp_uids = tmp_uids
if zbxtg_settings.proxy_to_tg:
proxy_to_tg = zbxtg_settings.proxy_to_tg
if not proxy_to_tg.find("http") and not proxy_to_tg.find("socks"):
proxy_to_tg = "https://" + proxy_to_tg
tg.proxies = {
"https": "{0}".format(proxy_to_tg),
}
zbx = ZabbixWeb(server=zbxtg_settings.zbx_server, username=zbxtg_settings.zbx_api_user,
password=zbxtg_settings.zbx_api_pass)
zbx.tmp_dir = tmp_dir
# workaround for Zabbix 4.x
zbx_version = 3
try:
zbx_version = zbxtg_settings.zbx_server_version
except:
pass
if zbxtg_settings.proxy_to_zbx:
zbx.proxies = {
"http": "http://{0}/".format(zbxtg_settings.proxy_to_zbx),
"https": "https://{0}/".format(zbxtg_settings.proxy_to_zbx)
}
# https://github.com/ableev/Zabbix-in-Telegram/issues/55
try:
if zbxtg_settings.zbx_basic_auth:
zbx.basic_auth_user = zbxtg_settings.zbx_basic_auth_user
zbx.basic_auth_pass = zbxtg_settings.zbx_basic_auth_pass
except:
pass
try:
zbx_api_verify = zbxtg_settings.zbx_api_verify
zbx.verify = zbx_api_verify
except:
pass
map = Maps()
# api key to resolve address to coordinates via google api
try:
if zbxtg_settings.google_maps_api_key:
map.key = zbxtg_settings.google_maps_api_key
if zbxtg_settings.proxy_to_tg:
map.proxies = {
"http": "http://{0}/".format(zbxtg_settings.proxy_to_tg),
"https": "https://{0}/".format(zbxtg_settings.proxy_to_tg)
}
except:
pass
zbxtg_body = (zbx_subject + "\n" + zbx_body).splitlines()
zbxtg_body_text = []
for line in zbxtg_body:
if line.find(zbxtg_settings.zbx_tg_prefix) > -1:
setting = re.split("[\s:=]+", line, maxsplit=1)
key = setting[0].replace(zbxtg_settings.zbx_tg_prefix + ";", "")
if key not in settings_description:
if "--debug" in args:
print_message("[ERROR] There is no '{0}' method, use --features to get help".format(key))
continue
if settings_description[key]["type"] == "list":
value = setting[1].split(",")
elif len(setting) > 1 and len(setting[1]) > 0:
value = setting[1]
elif settings_description[key]["type"] == "bool":
value = True
else:
value = settings[settings_description[key]["name"]]
if key in settings_description:
settings[settings_description[key]["name"]] = value
else:
zbxtg_body_text.append(line)
tg_method_image = bool(settings["tg_method_image"])
tg_chat = bool(settings["tg_chat"])
tg_group = bool(settings["tg_group"])
is_debug = bool(settings["is_debug"])
is_channel = bool(settings["is_channel"])
disable_web_page_preview = bool(settings["disable_web_page_preview"])
is_single_message = bool(settings["is_single_message"])
# experimental way to send message to the group https://github.com/ableev/Zabbix-in-Telegram/issues/15
if args[0].split("/")[-1] == "zbxtg_group.py" or "--group" in args or tg_chat or tg_group:
tg_chat = True
tg_group = True
tg.type = "group"
if "--debug" in args or is_debug:
is_debug = True
tg.debug = True
zbx.debug = True
print_message(tg.get_me())
print_message("Cache file with uids: " + tg.tmp_uids)
log_file = tmp_dir + ".debug." + hash_ts + ".log"
#print_message(log_file)
if "--markdown" in args or settings["markdown"]:
tg.markdown = True
if "--html" in args or settings["html"]:
tg.html = True
if "--channel" in args or is_channel:
tg.type = "channel"
if "--disable_web_page_preview" in args or disable_web_page_preview:
if is_debug:
print_message("'disable_web_page_preview' option has been enabled")
tg.disable_web_page_preview = True
if "--graph_buttons" in args or settings["graph_buttons"]:
tg.image_buttons = True
if "--forked" in args:
settings["forked"] = True
if "--tg-key" in args:
tg.key = args[args.index("--tg-key") + 1]
location_coordinates = {"latitude": None, "longitude": None}
if settings["lat"] > 0 and settings["lat"] > 0:
location_coordinates = {"latitude": settings["lat"], "longitude": settings["lon"]}
tg.location = location_coordinates
else:
if settings["location"]:
location_coordinates = map.get_coordinates_by_address(settings["location"])
if location_coordinates:
settings["lat"] = location_coordinates["latitude"]
settings["lon"] = location_coordinates["longitude"]
tg.location = location_coordinates
if not os.path.isdir(tmp_dir):
if is_debug:
print_message("Tmp dir doesn't exist, creating new one...")
try:
os.makedirs(tmp_dir)
open(tg.tmp_uids, "a").close()
os.chmod(tmp_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
os.chmod(tg.tmp_uids, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
except:
tmp_dir = "/tmp"
if is_debug:
print_message("Using {0} as a temporary dir".format(tmp_dir))
done_all_work_in_the_fork = False
# issue75
to_types = ["to", "to_group", "to_channel"]
to_types_to_telegram = {"to": "private", "to_group": "group", "to_channel": "channel"}
multiple_to = {}
for i in to_types:
multiple_to[i]=[]
for t in to_types:
try:
if settings[t] and not settings["forked"]:
# zbx_to = settings["to"]
multiple_to[t] = re.split(",", settings[t])
except KeyError:
pass
# example:
# {'to_channel': [], 'to': ['usr1', 'usr2', 'usr3'], 'to_group': []}
if (sum([len(v) for k, v in list(multiple_to.items())])) == 1:
# if we have only one recipient, we don't need fork to send message, just re-write "to" vaiable
tmp_max = 0
for t in to_types:
if len(multiple_to[t]) > tmp_max:
tmp_max = len(multiple_to[t])
tg.type = to_types_to_telegram[t]
zbx_to = multiple_to[t][0]
else:
for t in to_types:
for i in multiple_to[t]:
args_new = list(args)
args_new[1] = i
if t == "to_group":
args_new.append("--group")
args_new.append("--forked")
args_new.insert(0, sys.executable)
if is_debug:
print_message("Fork for custom recipient ({1}), new args: {0}".format(args_new,
to_types_to_telegram[t]))
subprocess.call(args_new)
done_all_work_in_the_fork = True
if done_all_work_in_the_fork:
sys.exit(0)
uid = None
if tg.type == "channel":
uid = zbx_to
if tg.type == "private":
zbx_to = zbx_to.replace("@", "")
if zbx_to.isdigit():
uid = zbx_to
if not uid:
uid = tg.get_uid_from_cache(zbx_to)
if not uid:
uid = tg.get_uid(zbx_to)
if uid:
tmp_need_update = True
if not uid:
tg.error_need_to_contact(zbx_to)
sys.exit(1)
if tmp_need_update:
tg.update_cache_uid(zbx_to, str(uid).rstrip())
if is_debug:
print_message("Telegram uid of {0} '{1}': {2}".format(tg.type, zbx_to, uid))
# add signature, turned off by default, you can turn it on in config
try:
if "--signature" in args or settings["signature"] or zbxtg_settings.zbx_tg_signature\
and not "--signature_disable" in args and not settings["signature_disable"]:
if "--signature" in args:
settings["signature"] = args[args.index("--signature") + 1]
if not settings["signature"]:
settings["signature"] = zbxtg_settings.zbx_server
zbxtg_body_text.append("--")
zbxtg_body_text.append(settings["signature"])
except:
pass
# replace text with emojis
internal_using_emoji = False # I hate that, but... https://github.com/ableev/Zabbix-in-Telegram/issues/152
if hasattr(zbxtg_settings, "emoji_map"):
zbxtg_body_text_emoji_support = []
for l in zbxtg_body_text:
l_new = l
for k, v in list(zbxtg_settings.emoji_map.items()):
l_new = l_new.replace("{{" + k + "}}", v)
zbxtg_body_text_emoji_support.append(l_new)
if len("".join(zbxtg_body_text)) - len("".join(zbxtg_body_text_emoji_support)):
internal_using_emoji = True
zbxtg_body_text = zbxtg_body_text_emoji_support
if not is_single_message:
tg.send_message(uid, zbxtg_body_text)
if not tg.ok:
# first case if group has been migrated to a supergroup, we need to update chat_id of that group
if tg.error.find("migrated") > -1 and tg.error.find("supergroup") > -1:
migrate_to_chat_id = tg.result["parameters"]["migrate_to_chat_id"]
tg.update_cache_uid(zbx_to, migrate_to_chat_id, message="Group chat is migrated to supergroup, "
"updating cache file")
uid = migrate_to_chat_id
tg.send_message(uid, zbxtg_body_text)
# another case if markdown is enabled and we got parse error, try to remove "bad" symbols from message
if tg.markdown and tg.error.find("Can't find end of the entity starting at byte offset") > -1:
markdown_warning = "Original message has been fixed due to {0}. " \
"Please, fix the markdown, it's slowing down messages sending."\
.format(url_wiki_base + "/" + settings_description["markdown"]["url"])
markdown_fix_attempts = 0
while not tg.ok and markdown_fix_attempts != 3:
offset = re.search("Can't find end of the entity starting at byte offset ([0-9]+)", tg.error).group(1)
zbxtg_body_text = markdown_fix(zbxtg_body_text, offset, emoji=internal_using_emoji) + \
["\n"] + [markdown_warning]
tg.disable_web_page_preview = True
tg.send_message(uid, zbxtg_body_text)
markdown_fix_attempts += 1
if tg.ok:
print_message(markdown_warning)
if is_debug:
print((tg.result))
if settings["zbxtg_image_age"]:
age_sec = age2sec(settings["zbxtg_image_age"])
if age_sec > 0 and age_sec > 3600:
settings["zbxtg_image_period"] = age_sec
message_id = 0
if tg_method_image:
zbx.login()
if not zbx.cookie:
text_warn = "Login to Zabbix web UI has failed (web url, user or password are incorrect), "\
"unable to send graphs check manually"
tg.send_message(uid, [text_warn])
print_message(text_warn)
else:
if not settings["extimg"]:
zbxtg_file_img = zbx.graph_get(settings["zbxtg_itemid"], settings["zbxtg_image_period"],
settings["zbxtg_title"], settings["zbxtg_image_width"],
settings["zbxtg_image_height"], version=zbx_version)
else:
zbxtg_file_img = external_image_get(settings["extimg"], tmp_dir=zbx.tmp_dir)
zbxtg_body_text, is_modified = list_cut(zbxtg_body_text, 200)
if tg.ok:
message_id = tg.result["result"]["message_id"]
tg.reply_to_message_id = message_id
if not zbxtg_file_img:
text_warn = "Can't get graph image, check script manually, see logs, or disable graphs"
tg.send_message(uid, [text_warn])
print_message(text_warn)
else:
if not is_single_message:
zbxtg_body_text = ""
else:
if is_modified:
text_warn = "probably you will see MEDIA_CAPTION_TOO_LONG error, "\
"the message has been cut to 200 symbols, "\
"https://github.com/ableev/Zabbix-in-Telegram/issues/9"\
"#issuecomment-166895044"
print_message(text_warn)
if not is_single_message:
tg.disable_notification = True
tg.send_photo(uid, zbxtg_body_text, zbxtg_file_img)
if tg.ok:
settings["zbxtg_body_text"] = zbxtg_body_text
os.remove(zbxtg_file_img)
else:
if tg.error.find("PHOTO_INVALID_DIMENSIONS") > -1:
if not tg.disable_web_page_preview:
tg.disable_web_page_preview = True
text_warn = "Zabbix user couldn't get graph (probably has no rights to get data from host), " \
"check script manually, see {0}".format(url_wiki_base + "/" +
settings_description["graphs"]["url"])
tg.send_message(uid, [text_warn])
print_message(text_warn)
if tg.location and location_coordinates["latitude"] and location_coordinates["longitude"]:
tg.reply_to_message_id = message_id
tg.disable_notification = True
tg.send_location(to=uid, coordinates=location_coordinates)
if "--show-settings" in args:
print_message("Settings: " + str(json.dumps(settings, indent=2)))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,939 @@
#!/usr/bin/env python
# coding: utf-8
import sys
import os
import time
import random
import string
import requests
import json
import re
import stat
import hashlib
import subprocess
#import sqlite3
from os.path import dirname
import zbxtg_settings
class Cache:
def __init__(self, database):
self.database = database
def create_db(self, database):
pass
class TelegramAPI:
tg_url_bot_general = "https://api.telegram.org/bot"
def http_get(self, url):
answer = requests.get(url, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def __init__(self, key):
self.debug = False
self.key = key
self.proxies = {}
self.type = "private" # 'private' for private chats or 'group' for group chats
self.markdown = False
self.html = False
self.disable_web_page_preview = False
self.disable_notification = False
self.reply_to_message_id = 0
self.tmp_dir = None
self.tmp_uids = None
self.location = {"latitude": None, "longitude": None}
self.update_offset = 0
self.image_buttons = False
self.result = None
self.ok = None
self.error = None
self.get_updates_from_file = False
def get_me(self):
url = self.tg_url_bot_general + self.key + "/getMe"
me = self.http_get(url)
return me
def get_updates(self):
url = self.tg_url_bot_general + self.key + "/getUpdates"
params = {"offset": self.update_offset}
if self.debug:
print_message(url)
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
if self.get_updates_from_file:
print_message("Getting updated from file getUpdates.txt")
self.result = json.loads("".join(file_read("getUpdates.txt")))
if self.debug:
print_message("Content of /getUpdates:")
print_message(json.dumps(self.result))
self.ok_update()
return self.result
def send_message(self, to, message):
url = self.tg_url_bot_general + self.key + "/sendMessage"
message = "\n".join(message)
params = {"chat_id": to, "text": message, "disable_web_page_preview": self.disable_web_page_preview,
"disable_notification": self.disable_notification}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
if self.markdown or self.html:
parse_mode = "HTML"
if self.markdown:
parse_mode = "Markdown"
params["parse_mode"] = parse_mode
if self.debug:
print_message("Trying to /sendMessage:")
print_message(url)
print_message("post params: " + str(params))
answer = requests.post(url, params=params, proxies=self.proxies)
if answer.status_code == 414:
self.result = {"ok": False, "description": "414 URI Too Long"}
else:
self.result = answer.json()
self.ok_update()
return self.result
def update_message(self, to, message_id, message):
url = self.tg_url_bot_general + self.key + "/editMessageText"
message = "\n".join(message)
params = {"chat_id": to, "message_id": message_id, "text": message,
"disable_web_page_preview": self.disable_web_page_preview,
"disable_notification": self.disable_notification}
if self.markdown or self.html:
parse_mode = "HTML"
if self.markdown:
parse_mode = "Markdown"
params["parse_mode"] = parse_mode
if self.debug:
print_message("Trying to /editMessageText:")
print_message(url)
print_message("post params: " + str(params))
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def send_photo(self, to, message, path):
url = self.tg_url_bot_general + self.key + "/sendPhoto"
message = "\n".join(message)
if self.image_buttons:
reply_markup = json.dumps({"inline_keyboard": [[
{"text": "R", "callback_data": "graph_refresh"},
{"text": "1h", "callback_data": "graph_period_3600"},
{"text": "3h", "callback_data": "graph_period_10800"},
{"text": "6h", "callback_data": "graph_period_21600"},
{"text": "12h", "callback_data": "graph_period_43200"},
{"text": "24h", "callback_data": "graph_period_86400"},
], ]})
else:
reply_markup = json.dumps({})
params = {"chat_id": to, "caption": message, "disable_notification": self.disable_notification,
"reply_markup": reply_markup}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
files = {"photo": open(path, 'rb')}
if self.debug:
print_message("Trying to /sendPhoto:")
print_message(url)
print_message(params)
print_message("files: " + str(files))
answer = requests.post(url, params=params, files=files, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def send_txt(self, to, text, text_name=None):
path = self.tmp_dir + "/" + "zbxtg_txt_"
url = self.tg_url_bot_general + self.key + "/sendDocument"
text = "\n".join(text)
if not text_name:
path += "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
else:
path += text_name
path += ".txt"
file_write(path, text)
params = {"chat_id": to, "caption": path.split("/")[-1], "disable_notification": self.disable_notification}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
files = {"document": open(path, 'rb')}
if self.debug:
print_message("Trying to /sendDocument:")
print_message(url)
print_message(params)
print_message("files: " + str(files))
answer = requests.post(url, params=params, files=files, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def get_uid(self, name):
uid = 0
if self.debug:
print_message("Getting uid from /getUpdates...")
updates = self.get_updates()
for m in updates["result"]:
if "message" in m:
chat = m["message"]["chat"]
elif "edited_message" in m:
chat = m["edited_message"]["chat"]
else:
continue
if chat["type"] == self.type == "private":
if "username" in chat:
if chat["username"] == name:
uid = chat["id"]
if (chat["type"] == "group" or chat["type"] == "supergroup") and self.type == "group":
if "title" in chat:
if sys.version_info[0] < 3:
if chat["title"] == name.decode("utf-8"):
uid = chat["id"]
else:
if chat["title"] == name:
uid = chat["id"]
return uid
def error_need_to_contact(self, to):
if self.type == "private":
print_message("User '{0}' needs to send some text bot in private".format(to))
if self.type == "group":
print_message("You need start a conversation with your bot first in '{0}' group chat, type '/start@{1}'"
.format(to, self.get_me()["result"]["username"]))
def update_cache_uid(self, name, uid, message="Add new string to cache file"):
cache_string = "{0};{1};{2}\n".format(name, self.type, str(uid).rstrip())
# FIXME
if self.debug:
print_message("{0}: {1}".format(message, cache_string))
with open(self.tmp_uids, "a") as cache_file_uids:
cache_file_uids.write(cache_string)
return True
def get_uid_from_cache(self, name):
if self.debug:
print_message("Trying to read cached uid for {0}, {1}, from {2}".format(name, self.type, self.tmp_uids))
uid = 0
if os.path.isfile(self.tmp_uids):
with open(self.tmp_uids, 'r') as cache_file_uids:
cache_uids_old = cache_file_uids.readlines()
for u in cache_uids_old:
u_splitted = u.split(";")
if name == u_splitted[0] and self.type == u_splitted[1]:
uid = u_splitted[2]
return uid
def send_location(self, to, coordinates):
url = self.tg_url_bot_general + self.key + "/sendLocation"
params = {"chat_id": to, "disable_notification": self.disable_notification,
"latitude": coordinates["latitude"], "longitude": coordinates["longitude"]}
if self.reply_to_message_id:
params["reply_to_message_id"] = self.reply_to_message_id
if self.debug:
print_message("Trying to /sendLocation:")
print_message(url)
print_message("post params: " + str(params))
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def answer_callback_query(self, callback_query_id, text=None):
url = self.tg_url_bot_general + self.key + "/answerCallbackQuery"
if not text:
params = {"callback_query_id": callback_query_id}
else:
params = {"callback_query_id": callback_query_id, "text": text}
answer = requests.post(url, params=params, proxies=self.proxies)
self.result = answer.json()
self.ok_update()
return self.result
def ok_update(self):
self.ok = self.result["ok"]
if self.ok:
self.error = None
else:
self.error = self.result["description"]
print_message(self.error)
return True
def markdown_fix(message, offset, emoji=False):
offset = int(offset)
if emoji: # https://github.com/ableev/Zabbix-in-Telegram/issues/152
offset -= 2
message = "\n".join(message)
message = message[:offset] + message[offset+1:]
message = message.split("\n")
return message
class ZabbixWeb:
def __init__(self, server, username, password):
self.debug = False
self.server = server
self.username = username
self.password = password
self.proxies = {}
self.verify = True
self.cookie = None
self.basic_auth_user = None
self.basic_auth_pass = None
self.tmp_dir = None
def login(self):
if not self.verify:
requests.packages.urllib3.disable_warnings()
data_api = {"name": self.username, "password": self.password, "enter": "Sign in"}
answer = requests.post(self.server + "/", data=data_api, proxies=self.proxies, verify=self.verify,
auth=requests.auth.HTTPBasicAuth(self.basic_auth_user, self.basic_auth_pass))
cookie = answer.cookies
if len(answer.history) > 1 and answer.history[0].status_code == 302:
print_message("probably the server in your config file has not full URL (for example "
"'{0}' instead of '{1}')".format(self.server, self.server + "/zabbix"))
if not cookie:
print_message("authorization has failed, url: {0}".format(self.server + "/"))
cookie = None
self.cookie = cookie
def graph_get(self, itemid, period, title, width, height, version=3):
file_img = self.tmp_dir + "/{0}.png".format("".join(itemid))
title = requests.utils.quote(title)
colors = {
0: "00CC00",
1: "CC0000",
2: "0000CC",
3: "CCCC00",
4: "00CCCC",
5: "CC00CC",
}
drawtype = 5
if len(itemid) > 1:
drawtype = 2
zbx_img_url_itemids = []
for i in range(0, len(itemid)):
itemid_url = "&items[{0}][itemid]={1}&items[{0}][sortorder]={0}&" \
"items[{0}][drawtype]={3}&items[{0}][color]={2}".format(i, itemid[i], colors[i], drawtype)
zbx_img_url_itemids.append(itemid_url)
zbx_img_url = self.server + "/chart3.php?"
if version < 4:
zbx_img_url += "period={0}".format(period)
else:
zbx_img_url += "from=now-{0}&to=now".format(period)
zbx_img_url += "&name={0}&width={1}&height={2}&graphtype=0&legend=1".format(title, width, height)
zbx_img_url += "".join(zbx_img_url_itemids)
if self.debug:
print_message(zbx_img_url)
answer = requests.get(zbx_img_url, cookies=self.cookie, proxies=self.proxies, verify=self.verify,
auth=requests.auth.HTTPBasicAuth(self.basic_auth_user, self.basic_auth_pass))
status_code = answer.status_code
if status_code == 404:
print_message("can't get image from '{0}'".format(zbx_img_url))
return False
res_img = answer.content
file_bwrite(file_img, res_img)
return file_img
def api_test(self):
headers = {'Content-type': 'application/json'}
api_data = json.dumps({"jsonrpc": "2.0", "method": "user.login", "params":
{"user": self.username, "password": self.password}, "id": 1})
api_url = self.server + "/api_jsonrpc.php"
api = requests.post(api_url, data=api_data, proxies=self.proxies, headers=headers)
return api.text
def print_message(message):
message = str(message) + "\n"
filename = sys.argv[0].split("/")[-1]
sys.stderr.write(filename + ": " + message)
def list_cut(elements, symbols_limit):
symbols_count = symbols_count_now = 0
elements_new = []
element_last_list = []
for e in elements:
symbols_count_now = symbols_count + len(e)
if symbols_count_now > symbols_limit:
limit_idx = symbols_limit - symbols_count
e_list = list(e)
for idx, ee in enumerate(e_list):
if idx < limit_idx:
element_last_list.append(ee)
else:
break
break
else:
symbols_count = symbols_count_now + 1
elements_new.append(e)
if symbols_count_now < symbols_limit:
return elements, False
else:
element_last = "".join(element_last_list)
elements_new.append(element_last)
return elements_new, True
class Maps:
# https://developers.google.com/maps/documentation/geocoding/intro
def __init__(self):
self.key = None
self.proxies = {}
def get_coordinates_by_address(self, address):
coordinates = {"latitude": 0, "longitude": 0}
url_api = "https://maps.googleapis.com/maps/api/geocode/json?key={0}&address={1}".format(self.key, address)
url = url_api
answer = requests.get(url, proxies=self.proxies)
result = answer.json()
try:
coordinates_dict = result["results"][0]["geometry"]["location"]
except:
if "error_message" in result:
print_message("[" + result["status"] + "]: " + result["error_message"])
return coordinates
coordinates = {"latitude": coordinates_dict["lat"], "longitude": coordinates_dict["lng"]}
return coordinates
def file_write(filename, text):
with open(filename, "w") as fd:
fd.write(str(text))
return True
def file_bwrite(filename, data):
with open(filename, "wb") as fd:
fd.write(data)
return True
def file_read(filename):
with open(filename, "r") as fd:
text = fd.readlines()
return text
def file_append(filename, text):
with open(filename, "a") as fd:
fd.write(str(text))
return True
def external_image_get(url, tmp_dir, timeout=6):
image_hash = hashlib.md5()
image_hash.update(url.encode())
file_img = tmp_dir + "/external_{0}.png".format(image_hash.hexdigest())
try:
answer = requests.get(url, timeout=timeout, allow_redirects=True)
except requests.exceptions.ReadTimeout as ex:
print_message("Can't get external image from '{0}': timeout".format(url))
return False
status_code = answer.status_code
if status_code == 404:
print_message("Can't get external image from '{0}': HTTP 404 error".format(url))
return False
answer_image = answer.content
file_bwrite(file_img, answer_image)
return file_img
def age2sec(age_str):
age_sec = 0
age_regex = "([0-9]+d)?\s?([0-9]+h)?\s?([0-9]+m)?"
age_pattern = re.compile(age_regex)
intervals = age_pattern.match(age_str).groups()
for i in intervals:
if i:
metric = i[-1]
if metric == "d":
age_sec += int(i[0:-1])*86400
if metric == "h":
age_sec += int(i[0:-1])*3600
if metric == "m":
age_sec += int(i[0:-1])*60
return age_sec
def main():
tmp_dir = zbxtg_settings.zbx_tg_tmp_dir
if tmp_dir == "/tmp/" + zbxtg_settings.zbx_tg_prefix:
print_message("WARNING: it is strongly recommended to change `zbx_tg_tmp_dir` variable in config!!!")
print_message("https://github.com/ableev/Zabbix-in-Telegram/wiki/Change-zbx_tg_tmp_dir-in-settings")
tmp_cookie = tmp_dir + "/cookie.py.txt"
tmp_uids = tmp_dir + "/uids.txt"
tmp_need_update = False # do we need to update cache file with uids or not
rnd = random.randint(0, 999)
ts = time.time()
hash_ts = str(ts) + "." + str(rnd)
log_file = "/dev/null"
args = sys.argv
settings = {
"zbxtg_itemid": "0", # itemid for graph
"zbxtg_title": None, # title for graph
"zbxtg_image_period": None,
"zbxtg_image_age": "3600",
"zbxtg_image_width": "900",
"zbxtg_image_height": "200",
"tg_method_image": False, # if True - default send images, False - send text
"tg_chat": False, # send message to chat or in private
"tg_group": False, # send message to chat or in private
"is_debug": False,
"is_channel": False,
"disable_web_page_preview": False,
"location": None, # address
"lat": 0, # latitude
"lon": 0, # longitude
"is_single_message": False,
"markdown": False,
"html": False,
"signature": None,
"signature_disable": False,
"graph_buttons": False,
"extimg": None,
"to": None,
"to_group": None,
"forked": False,
}
url_github = "https://github.com/ableev/Zabbix-in-Telegram"
url_wiki_base = "https://github.com/ableev/Zabbix-in-Telegram/wiki"
url_tg_group = "https://t.me/ZbxTg"
url_tg_channel = "https://t.me/Zabbix_in_Telegram"
settings_description = {
"itemid": {"name": "zbxtg_itemid", "type": "list",
"help": "script will attach a graph with that itemid (could be multiple)", "url": "Graphs"},
"title": {"name": "zbxtg_title", "type": "str", "help": "title for attached graph", "url": "Graphs"},
"graphs_period": {"name": "zbxtg_image_period", "type": "int", "help": "graph period", "url": "Graphs"},
"graphs_age": {"name": "zbxtg_image_age", "type": "str", "help": "graph period as age", "url": "Graphs"},
"graphs_width": {"name": "zbxtg_image_width", "type": "int", "help": "graph width", "url": "Graphs"},
"graphs_height": {"name": "zbxtg_image_height", "type": "int", "help": "graph height", "url": "Graphs"},
"graphs": {"name": "tg_method_image", "type": "bool", "help": "enables graph sending", "url": "Graphs"},
"chat": {"name": "tg_chat", "type": "bool", "help": "deprecated, don't use it, see 'group'",
"url": "How-to-send-message-to-the-group-chat"},
"group": {"name": "tg_group", "type": "bool", "help": "sends message to a group",
"url": "How-to-send-message-to-the-group-chat"},
"debug": {"name": "is_debug", "type": "bool", "help": "enables 'debug'",
"url": "How-to-test-script-in-command-line"},
"channel": {"name": "is_channel", "type": "bool", "help": "sends message to a channel",
"url": "Channel-support"},
"disable_web_page_preview": {"name": "disable_web_page_preview", "type": "bool",
"help": "disable web page preview", "url": "Disable-web-page-preview"},
"location": {"name": "location", "type": "str", "help": "address of location", "url": "Location"},
"lat": {"name": "lat", "type": "str", "help": "specify latitude (and lon too!)", "url": "Location"},
"lon": {"name": "lon", "type": "str", "help": "specify longitude (and lat too!)", "url": "Location"},
"single_message": {"name": "is_single_message", "type": "bool", "help": "do not split message and graph",
"url": "Why-am-I-getting-two-messages-instead-of-one"},
"markdown": {"name": "markdown", "type": "bool", "help": "markdown support", "url": "Markdown-and-HTML"},
"html": {"name": "html", "type": "bool", "help": "markdown support", "url": "Markdown-and-HTML"},
"signature": {"name": "signature", "type": "str",
"help": "bot's signature", "url": "Bot-signature"},
"signature_disable": {"name": "signature_disable", "type": "bool",
"help": "enables/disables bot's signature", "url": "Bot-signature"},
"graph_buttons": {"name": "graph_buttons", "type": "bool",
"help": "activates buttons under graph, could be using in ZbxTgDaemon",
"url": "Interactive-bot"},
"external_image": {"name": "extimg", "type": "str",
"help": "should be url; attaches external image from different source",
"url": "External-image-as-graph"},
"to": {"name": "to", "type": "str", "help": "rewrite zabbix username, use that instead of arguments",
"url": "Custom-to-and-to_group"},
"to_group": {"name": "to_group", "type": "str",
"help": "rewrite zabbix username, use that instead of arguments", "url": "Custom-to-and-to_group"},
"forked": {"name": "forked", "type": "bool", "help": "internal variable, do not use it. Ever.", "url": ""},
}
if len(args) < 4:
do_not_exit = False
if "--features" in args:
print(("List of available settings, see {0}/Settings\n---".format(url_wiki_base)))
for sett, proprt in list(settings_description.items()):
print(("{0}: {1}\ndoc: {2}/{3}\n--".format(sett, proprt["help"], url_wiki_base, proprt["url"])))
elif "--show-settings" in args:
do_not_exit = True
print_message("Settings: " + str(json.dumps(settings, indent=2)))
else:
print(("Hi. You should provide at least three arguments.\n"
"zbxtg.py [TO] [SUBJECT] [BODY]\n\n"
"1. Read main page and/or wiki: {0} + {1}\n"
"2. Public Telegram group (discussion): {2}\n"
"3. Public Telegram channel: {3}\n"
"4. Try dev branch for test purposes (new features, etc): {0}/tree/dev"
.format(url_github, url_wiki_base, url_tg_group, url_tg_channel)))
if not do_not_exit:
sys.exit(0)
zbx_to = args[1]
zbx_subject = args[2]
zbx_body = args[3]
tg = TelegramAPI(key=zbxtg_settings.tg_key)
tg.tmp_dir = tmp_dir
tg.tmp_uids = tmp_uids
if zbxtg_settings.proxy_to_tg:
proxy_to_tg = zbxtg_settings.proxy_to_tg
if not proxy_to_tg.find("http") and not proxy_to_tg.find("socks"):
proxy_to_tg = "https://" + proxy_to_tg
tg.proxies = {
"https": "{0}".format(proxy_to_tg),
}
zbx = ZabbixWeb(server=zbxtg_settings.zbx_server, username=zbxtg_settings.zbx_api_user,
password=zbxtg_settings.zbx_api_pass)
zbx.tmp_dir = tmp_dir
# workaround for Zabbix 4.x
zbx_version = 3
try:
zbx_version = zbxtg_settings.zbx_server_version
except:
pass
if zbxtg_settings.proxy_to_zbx:
zbx.proxies = {
"http": "http://{0}/".format(zbxtg_settings.proxy_to_zbx),
"https": "https://{0}/".format(zbxtg_settings.proxy_to_zbx)
}
# https://github.com/ableev/Zabbix-in-Telegram/issues/55
try:
if zbxtg_settings.zbx_basic_auth:
zbx.basic_auth_user = zbxtg_settings.zbx_basic_auth_user
zbx.basic_auth_pass = zbxtg_settings.zbx_basic_auth_pass
except:
pass
try:
zbx_api_verify = zbxtg_settings.zbx_api_verify
zbx.verify = zbx_api_verify
except:
pass
map = Maps()
# api key to resolve address to coordinates via google api
try:
if zbxtg_settings.google_maps_api_key:
map.key = zbxtg_settings.google_maps_api_key
if zbxtg_settings.proxy_to_tg:
map.proxies = {
"http": "http://{0}/".format(zbxtg_settings.proxy_to_tg),
"https": "https://{0}/".format(zbxtg_settings.proxy_to_tg)
}
except:
pass
zbxtg_body = (zbx_subject + "\n" + zbx_body).splitlines()
zbxtg_body_text = []
for line in zbxtg_body:
if line.find(zbxtg_settings.zbx_tg_prefix) > -1:
setting = re.split("[\s:=]+", line, maxsplit=1)
key = setting[0].replace(zbxtg_settings.zbx_tg_prefix + ";", "")
if key not in settings_description:
if "--debug" in args:
print_message("[ERROR] There is no '{0}' method, use --features to get help".format(key))
continue
if settings_description[key]["type"] == "list":
value = setting[1].split(",")
elif len(setting) > 1 and len(setting[1]) > 0:
value = setting[1]
elif settings_description[key]["type"] == "bool":
value = True
else:
value = settings[settings_description[key]["name"]]
if key in settings_description:
settings[settings_description[key]["name"]] = value
else:
zbxtg_body_text.append(line)
tg_method_image = bool(settings["tg_method_image"])
tg_chat = bool(settings["tg_chat"])
tg_group = bool(settings["tg_group"])
is_debug = bool(settings["is_debug"])
is_channel = bool(settings["is_channel"])
disable_web_page_preview = bool(settings["disable_web_page_preview"])
is_single_message = bool(settings["is_single_message"])
# experimental way to send message to the group https://github.com/ableev/Zabbix-in-Telegram/issues/15
if args[0].split("/")[-1] == "zbxtg_group.py" or "--group" in args or tg_chat or tg_group:
tg_chat = True
tg_group = True
tg.type = "group"
if "--debug" in args or is_debug:
is_debug = True
tg.debug = True
zbx.debug = True
print_message(tg.get_me())
print_message("Cache file with uids: " + tg.tmp_uids)
log_file = tmp_dir + ".debug." + hash_ts + ".log"
#print_message(log_file)
if "--markdown" in args or settings["markdown"]:
tg.markdown = True
if "--html" in args or settings["html"]:
tg.html = True
if "--channel" in args or is_channel:
tg.type = "channel"
if "--disable_web_page_preview" in args or disable_web_page_preview:
if is_debug:
print_message("'disable_web_page_preview' option has been enabled")
tg.disable_web_page_preview = True
if "--graph_buttons" in args or settings["graph_buttons"]:
tg.image_buttons = True
if "--forked" in args:
settings["forked"] = True
if "--tg-key" in args:
tg.key = args[args.index("--tg-key") + 1]
location_coordinates = {"latitude": None, "longitude": None}
if settings["lat"] > 0 and settings["lat"] > 0:
location_coordinates = {"latitude": settings["lat"], "longitude": settings["lon"]}
tg.location = location_coordinates
else:
if settings["location"]:
location_coordinates = map.get_coordinates_by_address(settings["location"])
if location_coordinates:
settings["lat"] = location_coordinates["latitude"]
settings["lon"] = location_coordinates["longitude"]
tg.location = location_coordinates
if not os.path.isdir(tmp_dir):
if is_debug:
print_message("Tmp dir doesn't exist, creating new one...")
try:
os.makedirs(tmp_dir)
open(tg.tmp_uids, "a").close()
os.chmod(tmp_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
os.chmod(tg.tmp_uids, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
except:
tmp_dir = "/tmp"
if is_debug:
print_message("Using {0} as a temporary dir".format(tmp_dir))
done_all_work_in_the_fork = False
# issue75
to_types = ["to", "to_group", "to_channel"]
to_types_to_telegram = {"to": "private", "to_group": "group", "to_channel": "channel"}
multiple_to = {}
for i in to_types:
multiple_to[i]=[]
for t in to_types:
try:
if settings[t] and not settings["forked"]:
# zbx_to = settings["to"]
multiple_to[t] = re.split(",", settings[t])
except KeyError:
pass
# example:
# {'to_channel': [], 'to': ['usr1', 'usr2', 'usr3'], 'to_group': []}
if (sum([len(v) for k, v in list(multiple_to.items())])) == 1:
# if we have only one recipient, we don't need fork to send message, just re-write "to" vaiable
tmp_max = 0
for t in to_types:
if len(multiple_to[t]) > tmp_max:
tmp_max = len(multiple_to[t])
tg.type = to_types_to_telegram[t]
zbx_to = multiple_to[t][0]
else:
for t in to_types:
for i in multiple_to[t]:
args_new = list(args)
args_new[1] = i
if t == "to_group":
args_new.append("--group")
args_new.append("--forked")
args_new.insert(0, sys.executable)
if is_debug:
print_message("Fork for custom recipient ({1}), new args: {0}".format(args_new,
to_types_to_telegram[t]))
subprocess.call(args_new)
done_all_work_in_the_fork = True
if done_all_work_in_the_fork:
sys.exit(0)
uid = None
if tg.type == "channel":
uid = zbx_to
if tg.type == "private":
zbx_to = zbx_to.replace("@", "")
if zbx_to.isdigit():
uid = zbx_to
if not uid:
uid = tg.get_uid_from_cache(zbx_to)
if not uid:
uid = tg.get_uid(zbx_to)
if uid:
tmp_need_update = True
if not uid:
tg.error_need_to_contact(zbx_to)
sys.exit(1)
if tmp_need_update:
tg.update_cache_uid(zbx_to, str(uid).rstrip())
if is_debug:
print_message("Telegram uid of {0} '{1}': {2}".format(tg.type, zbx_to, uid))
# add signature, turned off by default, you can turn it on in config
try:
if "--signature" in args or settings["signature"] or zbxtg_settings.zbx_tg_signature\
and not "--signature_disable" in args and not settings["signature_disable"]:
if "--signature" in args:
settings["signature"] = args[args.index("--signature") + 1]
if not settings["signature"]:
settings["signature"] = zbxtg_settings.zbx_server
zbxtg_body_text.append("--")
zbxtg_body_text.append(settings["signature"])
except:
pass
# replace text with emojis
internal_using_emoji = False # I hate that, but... https://github.com/ableev/Zabbix-in-Telegram/issues/152
if hasattr(zbxtg_settings, "emoji_map"):
zbxtg_body_text_emoji_support = []
for l in zbxtg_body_text:
l_new = l
for k, v in list(zbxtg_settings.emoji_map.items()):
l_new = l_new.replace("{{" + k + "}}", v)
zbxtg_body_text_emoji_support.append(l_new)
if len("".join(zbxtg_body_text)) - len("".join(zbxtg_body_text_emoji_support)):
internal_using_emoji = True
zbxtg_body_text = zbxtg_body_text_emoji_support
if not is_single_message:
tg.send_message(uid, zbxtg_body_text)
if not tg.ok:
# first case if group has been migrated to a supergroup, we need to update chat_id of that group
if tg.error.find("migrated") > -1 and tg.error.find("supergroup") > -1:
migrate_to_chat_id = tg.result["parameters"]["migrate_to_chat_id"]
tg.update_cache_uid(zbx_to, migrate_to_chat_id, message="Group chat is migrated to supergroup, "
"updating cache file")
uid = migrate_to_chat_id
tg.send_message(uid, zbxtg_body_text)
# another case if markdown is enabled and we got parse error, try to remove "bad" symbols from message
if tg.markdown and tg.error.find("Can't find end of the entity starting at byte offset") > -1:
markdown_warning = "Original message has been fixed due to {0}. " \
"Please, fix the markdown, it's slowing down messages sending."\
.format(url_wiki_base + "/" + settings_description["markdown"]["url"])
markdown_fix_attempts = 0
while not tg.ok and markdown_fix_attempts != 3:
offset = re.search("Can't find end of the entity starting at byte offset ([0-9]+)", tg.error).group(1)
zbxtg_body_text = markdown_fix(zbxtg_body_text, offset, emoji=internal_using_emoji) + \
["\n"] + [markdown_warning]
tg.disable_web_page_preview = True
tg.send_message(uid, zbxtg_body_text)
markdown_fix_attempts += 1
if tg.ok:
print_message(markdown_warning)
if is_debug:
print((tg.result))
if settings["zbxtg_image_age"]:
age_sec = age2sec(settings["zbxtg_image_age"])
if age_sec > 0 and age_sec > 3600:
settings["zbxtg_image_period"] = age_sec
message_id = 0
if tg_method_image:
zbx.login()
if not zbx.cookie:
text_warn = "Login to Zabbix web UI has failed (web url, user or password are incorrect), "\
"unable to send graphs check manually"
tg.send_message(uid, [text_warn])
print_message(text_warn)
else:
if not settings["extimg"]:
zbxtg_file_img = zbx.graph_get(settings["zbxtg_itemid"], settings["zbxtg_image_period"],
settings["zbxtg_title"], settings["zbxtg_image_width"],
settings["zbxtg_image_height"], version=zbx_version)
else:
zbxtg_file_img = external_image_get(settings["extimg"], tmp_dir=zbx.tmp_dir)
zbxtg_body_text, is_modified = list_cut(zbxtg_body_text, 200)
if tg.ok:
message_id = tg.result["result"]["message_id"]
tg.reply_to_message_id = message_id
if not zbxtg_file_img:
text_warn = "Can't get graph image, check script manually, see logs, or disable graphs"
tg.send_message(uid, [text_warn])
print_message(text_warn)
else:
if not is_single_message:
zbxtg_body_text = ""
else:
if is_modified:
text_warn = "probably you will see MEDIA_CAPTION_TOO_LONG error, "\
"the message has been cut to 200 symbols, "\
"https://github.com/ableev/Zabbix-in-Telegram/issues/9"\
"#issuecomment-166895044"
print_message(text_warn)
if not is_single_message:
tg.disable_notification = True
tg.send_photo(uid, zbxtg_body_text, zbxtg_file_img)
if tg.ok:
settings["zbxtg_body_text"] = zbxtg_body_text
os.remove(zbxtg_file_img)
else:
if tg.error.find("PHOTO_INVALID_DIMENSIONS") > -1:
if not tg.disable_web_page_preview:
tg.disable_web_page_preview = True
text_warn = "Zabbix user couldn't get graph (probably has no rights to get data from host), " \
"check script manually, see {0}".format(url_wiki_base + "/" +
settings_description["graphs"]["url"])
tg.send_message(uid, [text_warn])
print_message(text_warn)
if tg.location and location_coordinates["latitude"] and location_coordinates["longitude"]:
tg.reply_to_message_id = message_id
tg.disable_notification = True
tg.send_location(to=uid, coordinates=location_coordinates)
if "--show-settings" in args:
print_message("Settings: " + str(json.dumps(settings, indent=2)))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
tg_key = "XYZ" # telegram bot api key
zbx_tg_prefix = "zbxtg" # variable for separating text from script info
zbx_tg_tmp_dir = "/var/tmp/" + zbx_tg_prefix # directory for saving caches, uids, cookies, etc.
zbx_tg_signature = False
zbx_tg_update_messages = True
zbx_tg_matches = {
"problem": "PROBLEM: ",
"ok": "OK: "
}
zbx_server = "http://127.0.0.1/zabbix/" # zabbix server full url
zbx_api_user = "api"
zbx_api_pass = "api"
zbx_api_verify = True # True - do not ignore self signed certificates, False - ignore
#zbx_server_version = 2 # for Zabbix 2.x version
zbx_server_version = 3 # for Zabbix 3.x version, by default, not everyone updated to 4.x yet
#zbx_server_version = 4 # for Zabbix 4.x version, default will be changed in the future with this
zbx_basic_auth = False
zbx_basic_auth_user = "zabbix"
zbx_basic_auth_pass = "zabbix"
proxy_to_zbx = None
proxy_to_tg = None
# proxy_to_zbx = "http://proxy.local:3128"
# proxy_to_tg = "https://proxy.local:3128"
# proxy_to_tg = "socks5://user1:password2@hostname:port" # socks5 with username and password
# proxy_to_tg = "socks5://hostname:port" # socks5 without username and password
# proxy_to_tg = "socks5h://hostname:port" # hostname resolution on SOCKS proxy.
# This helps when internet provider alter DNS queries.
# Found here: https://stackoverflow.com/a/43266186/957508
google_maps_api_key = None # get your key, see https://developers.google.com/maps/documentation/geocoding/intro
zbx_tg_daemon_enabled = False
zbx_tg_daemon_enabled_ids = [6931850, ]
zbx_tg_daemon_enabled_users = ["ableev", ]
zbx_tg_daemon_enabled_chats = ["Zabbix in Telegram Script", ]
zbx_db_host = "localhost"
zbx_db_database = "zabbix"
zbx_db_user = "zbxtg"
zbx_db_password = "zbxtg"
emoji_map = {
"Disaster": "🔥",
"High": "🛑",
"Average": "",
"Warning": "⚠️",
"Information": "",
"Not classified": "🔘",
"OK": "",
"PROBLEM": "",
"info": "",
"WARNING": "⚠️",
"DISASTER": "",
"bomb": "💣",
"fire": "🔥",
"hankey": "💩",
}

View File

@ -0,0 +1,241 @@
# $Id: smeserver-zabbix-server.spec,v 1.20 2022/12/11 07:12:36 jpp Exp $
# Authority: vip-ire
# Name: Daniel Berteaud
%define name smeserver-zabbix-server
%define version 0.1
%define release 32
Summary: sme server integration of zabbix server and web front-end
Name: %{name}
Version: %{version}
Release: %{release}%{?dist}
License: GNU GPL version 2
URL: http://www.zabbix.com/
Group: SMEserver/addon
Source: %{name}-%{version}.tar.xz
BuildArchitectures: noarch
BuildRequires: e-smith-devtools
BuildRoot: /var/tmp/%{name}-%{version}
Requires: e-smith-release >= 10.0
Requires: e-smith-apache >= 2.6.0-19
Requires: smeserver-php >= 3.0.0-43
Requires: fping
Requires: zabbix-server-mysql >= 4.4.6
Requires: zabbix-web-mysql >= 4.4.6
Requires: zabbix-web >= 4.4.6
Requires: sendxmpp
Requires: smeserver-remoteuseraccess
Requires: smeserver-mariadb105
# for telegram bot
Requires: python2-pysocks python-requests python2-requests-oauthlib
Obsoletes: zabbix-server
Conflicts: smeserver-zabbix-proxy
AutoReqProv: no
%description
smserver integration of zabbix server and web front-end.
Zabbix is an entreprise-class open source distributed monitoring
solution
%package z50
%define provscl 5.0.30-1.el7
#5.0.30-1
Summary: SME Server integration of zabbix server 5.0 and web front-end using Remi SCLO
Group: Applications/Internet
#common
BuildArchitectures: noarch
BuildRequires: e-smith-devtools
Requires: e-smith-release >= 10.0
Requires: e-smith-apache >= 2.6.0-19
Requires: smeserver-php >= 3.0.0-43
Requires: fping
Requires: zabbix-server-mysql >= 5.0.0
#Requires: zabbix-web-mysql-scl >= 5.0.0
#Requires: zabbix-web >= 5.0.0
Requires: sendxmpp
Requires: smeserver-remoteuseraccess
Requires: smeserver-mariadb105
# specific
#Provides: rh-php72-php-fpm rh-php72-php-mbstring rh-php72 rh-php72-php-mysqlnd rh-php72-php-gd rh-php72-php-xml rh-php72-php-ldap rh-php72-php-bcmath
Provides: zabbix-web-database-scl-php74 = %{provscl}
Provides: zabbix-web-deps-scl = %{provscl}
Provides: zabbix-web-deps-scl-php73 = %{provscl}
Requires: zabbix-web = %{provscl}
Requires: php74-php-mysqlnd php74 php74-php-gd php74-php-bcmath php74-php-mbstring php74-php-xml php74-php-ldap php74-php-fpm php74-php-mysqlnd
%description z50
SME Server integration of zabbix server 5.0 and web front-end using Remi SCLO.
Zabbix is an entreprise-class open source distributed monitoring
solution
%changelog
* Sat Sep 07 2024 cvs2git.sh aka Brian Read <brianr@koozali.org> 0.1-32.sme
- Roll up patches and move to git repo [SME: 12338]
* Sat Sep 07 2024 BogusDateBot
- Eliminated rpmbuild "bogus date" warnings due to inconsistent weekday,
by assuming the date is correct and changing the weekday.
* Sat Dec 10 2022 Jean-Philippe Pialasse <tests@pialasse.com> 0.1-31.sme
- original package build for Zabbix 5.0 using Remi SCLO [SME: 11748]
support for LTS 5.0 EOL May 31, 2025
needs mariadb105.
manual migration from mariadb55 to mariadb105 needed for existing installs
* Mon Aug 01 2022 Jean-Philippe Pialasse <tests@pialasse.com> 0.1-30.sme
- update to httpd 2.4 access syntax [SME: 12068]
thanks to Vasarhelyi Zsolt
- add to core backup [SME: 12031]
non rpm owned files in /etc/zabbix, /etc/zabbix/zabbix_agentd.conf.d/
and /var/lib/zabbix/bin/
* Tue Nov 09 2021 Jean-Philippe Pialasse <tests@pialasse.com> 0.1-29.sme
- set random password to Admin i fuser exists and password is zabbix [SME: 11749]
* Mon Nov 08 2021 Jean-Philippe Pialasse <tests@pialasse.com> 0.1-28.sme
- add check cert scripts and telegram [SME: 10802]
no template provided to use with
* Sun Nov 07 2021 Jean-Philippe Pialasse <tests@pialasse.com> 0.1-27.sme
- fix init sql, typo and reload deamon [SME: 11232]
* Sun Nov 07 2021 Jean-Philippe Pialasse <tests@pialasse.com> 0.1-26.sme
- fix session and log issue [SME: 11232]
fix mysql-init not launched
* Mon Nov 01 2021 BogusDateBot
- Eliminated rpmbuild "bogus date" warnings due to inconsistent weekday,
by assuming the date is correct and changing the weekday.
Tue Mar 02 2009 --> Tue Feb 24 2009 or Mon Mar 02 2009 or Tue Mar 03 2009 or ....
Wed Feb 07 2016 --> Wed Feb 03 2016 or Sun Feb 07 2016 or Wed Feb 10 2016 or ....
* Mon Nov 01 2021 Brian Read <brianr@bjsystems.co.uk> 0.1-25.sme
- Switch-to-specific-php-fpm [SME: 11232]
* Mon Nov 01 2021 Brian Read <brianr@bjsystems.co.uk> 0.1-24.sme
- Remove post and postun command in spec file [SME: 11232]
* Thu Dec 10 2020 Brian Read <brianr@bjsystems.co.uk> 0.1-23.sme
- Add in post instructions to spec for SQL db creation [SME: 11232]
* Tue Dec 08 2020 Brian Read <brianr@bjsystems.co.uk> 0.1-22.sme
- Add expand zabbix-server.conf and .user.ini to creatlinks update event [SME:11232]
* Tue Dec 08 2020 Brian Read <brianr@bjsystems.co.uk> 0.1-21.sme
- Template-user-dot-ini-and-override-service-file-change-pid-in-conf-file [SME: 11232]
* Mon Dec 07 2020 Brian Read <brianr@bjsystems.co.uk> 0.1-20.sme
- Import to SME10 tree [SME: 11232]
- Update createlinks for systemd
- Update httpd.conf for php-fpw
- Add .user.ini for php-admin-flags that where in httpd.conf
* Sun May 10 2020 Jean-Philipe Pialasse <tests@pialasse.com> 0.1-19.sme
- adapt for zabbix 4.4.6 [SME: 10944]
* Thu Sep 05 2019 Jean-Philipe Pialasse <tests@pialasse.com> 0.1-17.sme
- remove deprecated option preventing from starting service [SME: 10458]
* Wed Mar 29 2017 Jean-Philipe Pialasse <tests@pialasse.com> 0.1-16.sme
- fix sql init not finding default sql dump to import [SME: 9569]
- NodeID support droped as per zabbix 2.4.0 and higher
- requires smeserver-php-scl as Zabbix-server 3.2.4 requires php 5.4 or higher
* Mon Jun 13 2016 Jean-Philipe Pialasse <tests@pialasse.com> 0.1-15.sme
- finitial import [SME: 9569]
- fix php requirement
- fix add path to new db
* Sun Feb 07 2016 stephane de Labrusse <stephdl@de-labrusse.fr> 0.1-14.sme
Wed Feb 07 2016 --> Wed Feb 03 2016 or Sun Feb 07 2016 or Wed Feb 10 2016 or ....
- New roll for sme9
* Mon Apr 29 2013 Daniel B. <daniel@firewall-services.com> 0.1-13
- Increase PHP mem limit to 128M
* Mon Mar 8 2010 Daniel B. <daniel@firewall-services.com> 0.1-12
- Use global TimeZone
* Wed Dec 2 2009 Daniel B. <daniel@firewall-services.com> 0.1-11
- Support several mysql DB patches
* Tue Mar 03 2009 Daniel B. <daniel@firewall-services.com> 0.1-10
- Add smeserver-remoteuseraccess as a dependencie (sudoers template problem)
* Mon Mar 02 2009 Daniel B. <daniel@firewall-services.com> 0.1-9
- specify path to .sendxmpprc file in the script sendxmpp
* Mon Mar 02 2009 Daniel B. <daniel@firewall-services.com> 0.1-8
- move .sendxmpprc template to the correct directory
* Mon Mar 02 2009 Daniel B. <daniel@firewall-services.com> 0.1-7
- Move jabber account informations to xmpprc
* Mon Mar 02 2009 Daniel B. <daniel@firewall-services.com> 0.1-6
- Adjust service masq during zabbix-server-update event
- Enable DB cache module with StartDBSyncers directive
* Sun Mar 01 2009 Daniel B. <daniel@firewall-services.com> 0.1-5
- Fix permissions on /var/lib/zabbix/tmp
* Tue Feb 17 2009 Daniel B. <daniel@firewall-services.com> 0.1-4
- rename event zabbix-update to zabbix-server-update
* Wed Feb 11 2009 Daniel B. <daniel@firewall-services.com> 0.1-3
- disable web access (usefull for distributed monitoring)
- Use stronger password for mysql database
- Use /var/lib/zabbix/bin as default location for scripts
- Use /var/lib/zabbix/tmp for temp dir
* Fri Feb 06 2009 Daniel B. <daniel@firewall-services.com> 0.1-2
- Link template-begin-shell to template-begin for sendxmpp script
* Fri Feb 06 2009 Daniel B. <daniel@firewall-services.com> 0.1-1
- templatize sendxmpp as zabbix user doesn't have access to SME db
- Add JabberTLS option in the db
* Mon Feb 02 2009 Daniel B. <daniel@firewall-services.com> 0.1-0
- initial release
%prep
%setup
%build
perl ./createlinks
%{__mkdir_p} root/var/lib/zabbix/tmp
#%{__mkdir_p} root/var/log/zabbix - conflict with zabbix-server-mysql
%{__mkdir_p} root/var/log/php/zabbix
%{__mkdir_p} root/var/lib/php/zabbix/{tmp,wsdlcache,opcache,session}
%install
rm -rf $RPM_BUILD_ROOT
(cd root ; find . -depth -print | cpio -dump $RPM_BUILD_ROOT)
rm -f %{name}-%{version}-filelist
/sbin/e-smith/genfilelist $RPM_BUILD_ROOT \
--file /var/lib/zabbix/bin/fping 'attr(0750,root,zabbix)' \
--file /var/lib/zabbix/bin/fping6 'attr(0750,root,zabbix)' \
--dir /var/lib/zabbix/tmp 'attr(0750,zabbix,zabbix)' \
--dir /var/log/php/zabbix 'attr(0770,www,www)' \
--dir /var/lib/php/zabbix 'attr(0755,root,www)' \
--dir /var/lib/php/zabbix/tmp 'attr(0770,root,www)' \
--dir /var/lib/php/zabbix/opcache 'attr(0770,root,www)' \
--dir /var/lib/php/zabbix/session 'attr(0770,root,www)' \
|grep -v '.pyc'|grep -v '.pyo' \
> %{name}-%{version}-filelist
%files -f %{name}-%{version}-filelist
%defattr(-,root,root)
%files z50 -f %{name}-%{version}-filelist
%defattr(-,root,root)
%clean
rm -rf $RPM_BUILD_ROOT
%post
%postun