diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cbb3a13
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.rpm
+*.log
+*spec-20*
+*.tar.gz
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..18330f8
--- /dev/null
+++ b/Makefile
@@ -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)
diff --git a/README.md b/README.md
index d8bc1b8..4670027 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,15 @@
-# smeserver-zabbix-server
+# smeserver-zabbix-server
-SMEServer Koozali developed git repo for smeserver-zabbix-server smecontribs
\ No newline at end of file
+SMEServer Koozali developed git repo for smeserver-zabbix-server smecontribs
+
+## Wiki
+
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
+
+
*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*
+
diff --git a/contriborbase b/contriborbase
new file mode 100644
index 0000000..9b7fd51
--- /dev/null
+++ b/contriborbase
@@ -0,0 +1 @@
+contribs10
diff --git a/createlinks b/createlinks
new file mode 100644
index 0000000..c3b7887
--- /dev/null
+++ b/createlinks
@@ -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
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/DbName b/root/etc/e-smith/db/configuration/defaults/zabbix-server/DbName
new file mode 100644
index 0000000..a2fe07f
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/DbName
@@ -0,0 +1 @@
+zabbixdb
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/DbUser b/root/etc/e-smith/db/configuration/defaults/zabbix-server/DbUser
new file mode 100644
index 0000000..c147932
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/DbUser
@@ -0,0 +1 @@
+zabbixuser
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberAccount b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberAccount
new file mode 100644
index 0000000..1f5a82f
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberAccount
@@ -0,0 +1 @@
+zabbix
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberPassword b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberPassword
new file mode 100644
index 0000000..d97c5ea
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberPassword
@@ -0,0 +1 @@
+secret
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberServer b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberServer
new file mode 100644
index 0000000..2fbb50c
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberServer
@@ -0,0 +1 @@
+localhost
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberTLS b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberTLS
new file mode 100644
index 0000000..86981e6
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/JabberTLS
@@ -0,0 +1 @@
+enabled
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/NodeID b/root/etc/e-smith/db/configuration/defaults/zabbix-server/NodeID
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/NodeID
@@ -0,0 +1 @@
+0
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/TCPPort b/root/etc/e-smith/db/configuration/defaults/zabbix-server/TCPPort
new file mode 100644
index 0000000..f02a806
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/TCPPort
@@ -0,0 +1 @@
+10051
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/WebAccess b/root/etc/e-smith/db/configuration/defaults/zabbix-server/WebAccess
new file mode 100644
index 0000000..4083037
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/WebAccess
@@ -0,0 +1 @@
+local
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/access b/root/etc/e-smith/db/configuration/defaults/zabbix-server/access
new file mode 100644
index 0000000..3e18ebf
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/access
@@ -0,0 +1 @@
+private
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/status b/root/etc/e-smith/db/configuration/defaults/zabbix-server/status
new file mode 100644
index 0000000..86981e6
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/status
@@ -0,0 +1 @@
+enabled
diff --git a/root/etc/e-smith/db/configuration/defaults/zabbix-server/type b/root/etc/e-smith/db/configuration/defaults/zabbix-server/type
new file mode 100644
index 0000000..24e1098
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/defaults/zabbix-server/type
@@ -0,0 +1 @@
+service
diff --git a/root/etc/e-smith/db/configuration/migrate/zabbix-server-database b/root/etc/e-smith/db/configuration/migrate/zabbix-server-database
new file mode 100644
index 0000000..9161481
--- /dev/null
+++ b/root/etc/e-smith/db/configuration/migrate/zabbix-server-database
@@ -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:]'`);
+
+
+}
+
diff --git a/root/etc/e-smith/events/actions/smeserver-zabbix-server b/root/etc/e-smith/events/actions/smeserver-zabbix-server
new file mode 100644
index 0000000..583acc3
--- /dev/null
+++ b/root/etc/e-smith/events/actions/smeserver-zabbix-server
@@ -0,0 +1,3 @@
+systemctl daemon-reload
+systemsctl preset-all
+systemsctl restart zabbix-server
diff --git a/root/etc/e-smith/events/actions/zabbix-server b/root/etc/e-smith/events/actions/zabbix-server
new file mode 100644
index 0000000..dee37e5
--- /dev/null
+++ b/root/etc/e-smith/events/actions/zabbix-server
@@ -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
+
diff --git a/root/etc/e-smith/templates.metadata/etc/e-smith/sql/init105/80zabbix-server b/root/etc/e-smith/templates.metadata/etc/e-smith/sql/init105/80zabbix-server
new file mode 100644
index 0000000..940dcf3
--- /dev/null
+++ b/root/etc/e-smith/templates.metadata/etc/e-smith/sql/init105/80zabbix-server
@@ -0,0 +1 @@
+PERMS=0750
diff --git a/root/etc/e-smith/templates.metadata/etc/zabbix/web/zabbix.conf.php b/root/etc/e-smith/templates.metadata/etc/zabbix/web/zabbix.conf.php
new file mode 100644
index 0000000..fa539ea
--- /dev/null
+++ b/root/etc/e-smith/templates.metadata/etc/zabbix/web/zabbix.conf.php
@@ -0,0 +1,3 @@
+TEMPLATE_PATH="/etc/zabbix/zabbix.conf.php"
+OUTPUT_FILENAME="/etc/zabbix/web/zabbix.conf.php"
+
diff --git a/root/etc/e-smith/templates.metadata/var/lib/zabbix/.sendxmpprc b/root/etc/e-smith/templates.metadata/var/lib/zabbix/.sendxmpprc
new file mode 100644
index 0000000..0d67773
--- /dev/null
+++ b/root/etc/e-smith/templates.metadata/var/lib/zabbix/.sendxmpprc
@@ -0,0 +1,3 @@
+PERMS=0600
+UID="zabbix"
+GID="zabbix"
diff --git a/root/etc/e-smith/templates.metadata/var/lib/zabbix/bin/sendxmpp b/root/etc/e-smith/templates.metadata/var/lib/zabbix/bin/sendxmpp
new file mode 100644
index 0000000..a44f56f
--- /dev/null
+++ b/root/etc/e-smith/templates.metadata/var/lib/zabbix/bin/sendxmpp
@@ -0,0 +1,4 @@
+PERMS=0750
+UID="root"
+GID="zabbix"
+
diff --git a/root/etc/e-smith/templates/etc/backup-data.d/smeserver-zabbix-server.include/template-begin b/root/etc/e-smith/templates/etc/backup-data.d/smeserver-zabbix-server.include/template-begin
new file mode 100644
index 0000000..f74560e
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/backup-data.d/smeserver-zabbix-server.include/template-begin
@@ -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;
+}
+}
diff --git a/root/etc/e-smith/templates/etc/e-smith/sql/init105/80zabbix-server b/root/etc/e-smith/templates/etc/e-smith/sql/init105/80zabbix-server
new file mode 100644
index 0000000..878a84b
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/e-smith/sql/init105/80zabbix-server
@@ -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 <
+ SSLRequireSSL on
+ Options FollowSymLinks
+ AllowOverride None
+ #AddType application/x-httpd-php .php
+
+ SetHandler "proxy:unix:/var/run/php-fpm/php74-zabbix-server.sock|fcgi://localhost"
+
+ Require $access
+
+
+
+ Require all denied
+
+ Require all denied
+
+
+
+
+ Require all denied
+
+ Require all denied
+
+
+
+HERE
+}
+}
diff --git a/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/60ZabbixSSL b/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/60ZabbixSSL
new file mode 100644
index 0000000..1139ed2
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/60ZabbixSSL
@@ -0,0 +1,7 @@
+{
+
+ if ($port ne ${modSSL}{'TCPPort'}){
+ $OUT = ' RewriteRule ^/zabbix(/.*|$) https://%{HTTP_HOST}/zabbix$1 [L,R]';
+ }
+}
+
diff --git a/root/etc/e-smith/templates/etc/php-fpm.d/www.conf/20zabbix-server b/root/etc/e-smith/templates/etc/php-fpm.d/www.conf/20zabbix-server
new file mode 100644
index 0000000..6b0cc7d
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/php-fpm.d/www.conf/20zabbix-server
@@ -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';
+ }
+}
+}
diff --git a/root/etc/e-smith/templates/etc/sudoers/00zabbixAlias b/root/etc/e-smith/templates/etc/sudoers/00zabbixAlias
new file mode 100644
index 0000000..049fba3
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/sudoers/00zabbixAlias
@@ -0,0 +1 @@
+Cmnd_Alias ZABBIX = /usr/sbin/fping,/usr/sbin/fping6
diff --git a/root/etc/e-smith/templates/etc/sudoers/30zabbix b/root/etc/e-smith/templates/etc/sudoers/30zabbix
new file mode 100644
index 0000000..717991f
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/sudoers/30zabbix
@@ -0,0 +1 @@
+zabbix ALL=(root) NOPASSWD: ZABBIX
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/00header b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/00header
new file mode 100644
index 0000000..a352e59
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/00header
@@ -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.
+**/
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/10database b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/10database
new file mode 100644
index 0000000..1bbda93
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/10database
@@ -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
+}
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/20zbxserver b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/20zbxserver
new file mode 100644
index 0000000..0829a7e
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/20zbxserver
@@ -0,0 +1,10 @@
+{
+my $port = ${'zabbix-server'}{'TCPPort'} || '10051';
+
+$OUT .=<<"HERE";
+
+\$ZBX_SERVER = "localhost";
+\$ZBX_SERVER_PORT = "$port";
+HERE
+}
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/30images b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/30images
new file mode 100644
index 0000000..701b458
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix.conf.php/30images
@@ -0,0 +1 @@
+$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/00header b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/00header
new file mode 100644
index 0000000..916507e
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/00header
@@ -0,0 +1,4 @@
+# This is config file for ZABBIX server process
+# To get more information about ZABBIX,
+# go http://www.zabbix.com
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/10NodeID b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/10NodeID
new file mode 100644
index 0000000..b7740ed
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/10NodeID
@@ -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";
+}
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/20instances b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/20instances
new file mode 100644
index 0000000..8f0f6ba
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/20instances
@@ -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
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/30listen b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/30listen
new file mode 100644
index 0000000..d580160
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/30listen
@@ -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
+
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/40freq b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/40freq
new file mode 100644
index 0000000..5ae7d8d
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/40freq
@@ -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
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/50debug b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/50debug
new file mode 100644
index 0000000..32f45d2
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/50debug
@@ -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
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/60pidAndLog b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/60pidAndLog
new file mode 100644
index 0000000..3c88d14
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/60pidAndLog
@@ -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
+
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/70location b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/70location
new file mode 100644
index 0000000..5984a7a
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/70location
@@ -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
+
diff --git a/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/80database b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/80database
new file mode 100644
index 0000000..1e33f3d
--- /dev/null
+++ b/root/etc/e-smith/templates/etc/zabbix/zabbix_server.conf/80database
@@ -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
+
diff --git a/root/etc/e-smith/templates/usr/share/zabbix/.user.ini/20zabbix-server b/root/etc/e-smith/templates/usr/share/zabbix/.user.ini/20zabbix-server
new file mode 100644
index 0000000..fc5f22b
--- /dev/null
+++ b/root/etc/e-smith/templates/usr/share/zabbix/.user.ini/20zabbix-server
@@ -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
+}
+
diff --git a/root/etc/e-smith/templates/var/lib/zabbix/.sendxmpprc/all b/root/etc/e-smith/templates/var/lib/zabbix/.sendxmpprc/all
new file mode 100644
index 0000000..c72a3d3
--- /dev/null
+++ b/root/etc/e-smith/templates/var/lib/zabbix/.sendxmpprc/all
@@ -0,0 +1,3 @@
+# Jabber Account for zabbix alerts
+{${'zabbix-server'}{'JabberAccount'}}@{${'zabbix-server'}{'JabberServer'}} {${'zabbix-server'}{'JabberPassword'}}
+
diff --git a/root/etc/e-smith/templates/var/lib/zabbix/bin/sendxmpp/all b/root/etc/e-smith/templates/var/lib/zabbix/bin/sendxmpp/all
new file mode 100644
index 0000000..e5cfbca
--- /dev/null
+++ b/root/etc/e-smith/templates/var/lib/zabbix/bin/sendxmpp/all
@@ -0,0 +1,6 @@
+
+echo "$3" | \
+ sendxmpp -r zabbix -f /var/lib/zabbix/.sendxmpprc \
+ {(${'zabbix-server'}{'JabberTLS'} || 'enabled') eq 'disabled' ? '':'-t \\';}
+ -s "$2" "$1"
+
diff --git a/root/usr/lib/systemd/system/zabbix-server.service.d/50koozali.conf b/root/usr/lib/systemd/system/zabbix-server.service.d/50koozali.conf
new file mode 100644
index 0000000..87a9404
--- /dev/null
+++ b/root/usr/lib/systemd/system/zabbix-server.service.d/50koozali.conf
@@ -0,0 +1,3 @@
+[Install]
+ WantedBy=sme-server.target
+
diff --git a/root/usr/share/zabbix/.user.ini b/root/usr/share/zabbix/.user.ini
new file mode 100644
index 0000000..b0a6b90
--- /dev/null
+++ b/root/usr/share/zabbix/.user.ini
@@ -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
diff --git a/root/var/lib/zabbix/bin/cert_expire.pl b/root/var/lib/zabbix/bin/cert_expire.pl
new file mode 100644
index 0000000..443ced6
--- /dev/null
+++ b/root/var/lib/zabbix/bin/cert_expire.pl
@@ -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
+#
+# 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");
diff --git a/root/var/lib/zabbix/bin/check_cert.pl b/root/var/lib/zabbix/bin/check_cert.pl
new file mode 100644
index 0000000..4592adf
--- /dev/null
+++ b/root/var/lib/zabbix/bin/check_cert.pl
@@ -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 });
diff --git a/root/var/lib/zabbix/bin/fping b/root/var/lib/zabbix/bin/fping
new file mode 100644
index 0000000..2173537
--- /dev/null
+++ b/root/var/lib/zabbix/bin/fping
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/sudo /usr/sbin/fping "$@"
diff --git a/root/var/lib/zabbix/bin/fping6 b/root/var/lib/zabbix/bin/fping6
new file mode 100644
index 0000000..38b1d6d
--- /dev/null
+++ b/root/var/lib/zabbix/bin/fping6
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/sudo /usr/sbin/fping6 "$@"
diff --git a/root/var/lib/zabbix/bin/zbxtg.py b/root/var/lib/zabbix/bin/zbxtg.py
new file mode 100644
index 0000000..cd5626c
--- /dev/null
+++ b/root/var/lib/zabbix/bin/zbxtg.py
@@ -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()
diff --git a/root/var/lib/zabbix/bin/zbxtg_group.py b/root/var/lib/zabbix/bin/zbxtg_group.py
new file mode 100644
index 0000000..cd5626c
--- /dev/null
+++ b/root/var/lib/zabbix/bin/zbxtg_group.py
@@ -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()
diff --git a/root/var/lib/zabbix/bin/zbxtg_settings.example.py b/root/var/lib/zabbix/bin/zbxtg_settings.example.py
new file mode 100644
index 0000000..4b9e656
--- /dev/null
+++ b/root/var/lib/zabbix/bin/zbxtg_settings.example.py
@@ -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": "đŠ",
+}
diff --git a/smeserver-zabbix-server.spec b/smeserver-zabbix-server.spec
new file mode 100644
index 0000000..baf2fad
--- /dev/null
+++ b/smeserver-zabbix-server.spec
@@ -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 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 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 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 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 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 0.1-27.sme
+- fix init sql, typo and reload deamon [SME: 11232]
+
+* Sun Nov 07 2021 Jean-Philippe Pialasse 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 0.1-25.sme
+- Switch-to-specific-php-fpm [SME: 11232]
+
+* Mon Nov 01 2021 Brian Read 0.1-24.sme
+- Remove post and postun command in spec file [SME: 11232]
+
+* Thu Dec 10 2020 Brian Read 0.1-23.sme
+- Add in post instructions to spec for SQL db creation [SME: 11232]
+
+* Tue Dec 08 2020 Brian Read 0.1-22.sme
+- Add expand zabbix-server.conf and .user.ini to creatlinks update event [SME:11232]
+
+* Tue Dec 08 2020 Brian Read 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 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 0.1-19.sme
+- adapt for zabbix 4.4.6 [SME: 10944]
+
+* Thu Sep 05 2019 Jean-Philipe Pialasse 0.1-17.sme
+- remove deprecated option preventing from starting service [SME: 10458]
+
+* Wed Mar 29 2017 Jean-Philipe Pialasse 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 0.1-15.sme
+- finitial import [SME: 9569]
+- fix php requirement
+- fix add path to new db
+
+* Sun Feb 07 2016 stephane de Labrusse 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. 0.1-13
+- Increase PHP mem limit to 128M
+
+* Mon Mar 8 2010 Daniel B. 0.1-12
+- Use global TimeZone
+
+* Wed Dec 2 2009 Daniel B. 0.1-11
+- Support several mysql DB patches
+
+* Tue Mar 03 2009 Daniel B. 0.1-10
+- Add smeserver-remoteuseraccess as a dependencie (sudoers template problem)
+
+* Mon Mar 02 2009 Daniel B. 0.1-9
+- specify path to .sendxmpprc file in the script sendxmpp
+
+* Mon Mar 02 2009 Daniel B. 0.1-8
+- move .sendxmpprc template to the correct directory
+
+* Mon Mar 02 2009 Daniel B. 0.1-7
+- Move jabber account informations to xmpprc
+
+* Mon Mar 02 2009 Daniel B. 0.1-6
+- Adjust service masq during zabbix-server-update event
+- Enable DB cache module with StartDBSyncers directive
+
+* Sun Mar 01 2009 Daniel B. 0.1-5
+- Fix permissions on /var/lib/zabbix/tmp
+
+* Tue Feb 17 2009 Daniel B. 0.1-4
+- rename event zabbix-update to zabbix-server-update
+
+* Wed Feb 11 2009 Daniel B. 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. 0.1-2
+- Link template-begin-shell to template-begin for sendxmpp script
+
+* Fri Feb 06 2009 Daniel B. 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. 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
+