diff --git a/.gitignore b/.gitignore index 20708de3..102ea9f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .kitchen/ .kateproject.d .vagrant/ +*.swp diff --git a/CHANGELOG.md b/CHANGELOG.md index 9472e665..bd045167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,120 @@ The **patch** part changes incrementally at each release. ### Fixed +### Removed + ### Security +## [10.0.0] - 2020-05-13 + +### Added +* apache: the default VHost doesn't redirect to https for ".well-known" paths +* apt: added buster backports prerferences +* apt: check if cron is installed before adding a cron job +* apt: remove jessie/buster sources from Gandi servers +* apt: verify that /etc/evolinux is present +* certbot : new role to install and configure certbot +* etc-git: add versioning for /usr/share/scripts on Debian 10+ +* evoacme: upstream version 19.11 +* evolinux-base: default value for "evolinux_ssh_group" +* evolinux-base: install /sbin/deny +* evolinux-base: install Evocheck (default: `True`) +* evolinux-base: on debian 10 and later, add noexec on /dev/shm +* evolinux-base: on debian 10 and later, add /usr/share/scripts in root's PATH +* evolinux-base: remove the chrony package +* evomaintenance: don't configure firewall for database if not necessary +* generate-ldif: support MariaDB 10.3 +* haproxy: add a variable to keep the existing configuration +* java: add Java 11 as possible version to install +* listupgrade: install old-kernel-autoremoval script +* minifirewall: add a variable to force the check scripts update +* mongodb: mongodb: compatibility with Debian 10 +* mysql-oracle: backport tasks from mysql role +* mysql: activate binary logs by specifying log_bin path +* mysql: specify a custom server_id +* networkd-to-ifconfig: add variables for configuration by variables +* packweb-apache: Deploy opcache.php to give some insights on PHP's opcache status +* php: variable to install the mysqlnd module instead of the default mysql module +* postgresql : variable to install PostGIS (default: `False`) +* redis: rewrite of the role (separate instances, better systemd units…) +* webapps/evoadmin-web Add an htpasswd to evoadmin if you cant use an apache IP whitelist +* webapps/evoadmin-web Overload templates if needed +* evolinux-base: install ssacli for HP Smart Array +* evobackup-client role to configure a machine for backups with bkctld(8) +* bind: enable query logging for recursive resolvers +* bind: enable logrotate for recursive resolvers +* bind: enable bind9 munin plugin for recursive resolvers + +### Changed +* replace version_compare() with version()s +* removed some deprecations for Ansible 2.7 +* apache: improve permissions in save_apache_status script +* apt: hold packages only if package is installed +* bind: the munin task was present, but not included +* bind: change name of logrotate file to bind9 +* certbot: commit hook must be executed at the end +* elasticsearch: listen on local interface only by default +* evocheck: upstream version 20.04.4 +* evocheck: cron jobs execute in verbose +* evolinux-base: use "evolinux_internal_group" for SSH authentication +* evolinux-base: Don't customize the logcheck recipient by default. +* evolinux-base: configure cciss-vol-statusd in the proper file +* evomaintenance: upstream release 0.6.3 +* evomaintenance: Turn on API by default (instead of DB) +* evomaintenance: install PG dependencies only when needed +* listupgrade: update from upstream +* lxc: rely on lxc_container module instead of command module +* lxc: remove useless loop in apt execution +* lxc: update our default template to be compatible with Debian 10 +* lxc-php: refactor tasks for better maintainability +* lxc-php: Use OpenSMTPD for Stretch/Buster containers, and ssmtp for Jessie containers +* lxc-solr: changed default Solr version to 8.4.1 +* minifirewall: better alert5 activation +* minifirewall: no http filtering by default +* minifirewall: /bin/true command doesn't report "changed" anymore +* nagios-nrpe: update check_redis_instances (same as redis role) +* nagios-nrpe: change default haproxy socket path +* nagios-nrpe: check_mode per cpu dynamically +* nodejs: change default version to 12 (new LTS) +* packweb-apache: Do the install & conffigure phpContainer script (instead of evoadmin-web role) +* php: By default, allow 128M for OpCache (instead of 64M) +* php: Don't set a chroot for the default fpm pool +* php: Make sure the default pool we define can be fully functionnal witout debian's default pool file +* php: Change the default pool names to something more explicit (and same for the variables names) +* php: Add a task to remove Debian's default FPM pool file (off by default) +* php: Cleanup CLI Settings. Also, allow url fopen and don't disable functions (in CLI only) +* postgresql : changed logrotate config to 10 days (and fixed permissions) +* rbenv: changed default Ruby version to 2.7.0 +* squid: Remove wait time when we turn off squid +* squid: compatibility wit Debian 10 +* tomcat: package version derived from Debian version if missing +* varnish: remove custom ExecReload= script for Debian 10+ + +### Fixed +* etc-git: fix warnings ansible-lint +* evoadmin-web: Put the php config at the right place for Buster +* lxc: Don't stop the container if it already exists +* lxc: Fix container existance check to be able to run in check_mode +* lxc-php: Don't remove the default pool +* minifirewall: fix warnings ansible-lint +* nginx: fix munin fcgi not working (missing chmod 660 on logs) +* php: add missing handler for php7.3-fpm +* roundcube: fix typo for roundcube vhost +* tomcat: fix typo for default tomcat_version +* evolinux-base: Fix our zsyslog rotate config that doesn't work on Debian 10 +* certbot: Properly evaluate when apache is installed +* evolinux-base: Don't make alert5.service executable as systemd will complain +* webapps/evoadmin-web: Set default evoadmin_mail_tpl_force to True to fix a regression where the mail template would not get updated because the file is created before the role is first run. +* minifirewall: Backport changes from minifirewall (properly open outgoing smtp(s)) +* minifirewall: Properly detect alert5.sh to turn on firewall at boot +* packweb-apache: Add missing dependency to evoacme role +* php: Chose the debian version repo archive for packages.sury.org +* php: update surry_post.yml to match current latest PHP release +* packweb-apache: Don't try to install PHPMyAdmin on Buster as it's not available + +### Removed +* clamav : do not install the zoo package anymore + ## [9.10.1] - 2019-06-21 ### Changed diff --git a/amavis/tasks/main.yml b/amavis/tasks/main.yml index 6b8bcc63..1b0932d5 100644 --- a/amavis/tasks/main.yml +++ b/amavis/tasks/main.yml @@ -1,11 +1,10 @@ --- - name: install Amavis apt: - name: "{{ item }}" + name: + - postgrey + - amavisd-new state: present - with_items: - - postgrey - - amavisd-new tags: - amavis diff --git a/amazon-ec2/README b/amazon-ec2/README index 15f8b129..30b5891f 100644 --- a/amazon-ec2/README +++ b/amazon-ec2/README @@ -52,7 +52,7 @@ In your main evolinux playbook put this play before Evolinux one: tasks: - include_role: - name: amazon-ec2 + name: evolix/amazon-ec2 tasks_from: create-instance.yml ``` diff --git a/amazon-ec2/amazon-ec2-evolinux.yml b/amazon-ec2/amazon-ec2-evolinux.yml index d9d79347..d4e125a7 100644 --- a/amazon-ec2/amazon-ec2-evolinux.yml +++ b/amazon-ec2/amazon-ec2-evolinux.yml @@ -10,10 +10,10 @@ tasks: - include_role: - name: amazon-ec2 + name: evolix/amazon-ec2 tasks_from: setup.yml - include_role: - name: amazon-ec2 + name: evolix/amazon-ec2 tasks_from: create-instance.yml - name: Install Evolinux @@ -52,11 +52,11 @@ post_tasks: - include_role: - name: etc-git + name: evolix/etc-git tasks_from: commit.yml vars: commit_message: "Ansible post-run Evolinux playbook" - include_role: - name: evocheck + name: evolix/evocheck tasks_from: exec.yml diff --git a/apache/files/save_apache_status.sh b/apache/files/save_apache_status.sh index d65b2e08..8ca29f15 100644 --- a/apache/files/save_apache_status.sh +++ b/apache/files/save_apache_status.sh @@ -7,11 +7,15 @@ URL="http://127.0.0.1/server-status" TS=`date +%Y%m%d%H%M%S` FILE="${DIR}/${TS}.html" -mkdir -p "${DIR}" - -wget -q -O "${FILE}" "${URL}" +if [ ! -d "${DIR}" ]; then + mkdir -p "${DIR}" + chown root:adm "${DIR}" + chmod 750 "${DIR}" +fi +wget -q -U "save_apache_status" -O "${FILE}" "${URL}" chmod 640 "${FILE}" +chown root:adm "${FILE}" find "${DIR}" -type f -mtime +1 -delete diff --git a/apache/tasks/main.yml b/apache/tasks/main.yml index a461a8d4..b7611cac 100644 --- a/apache/tasks/main.yml +++ b/apache/tasks/main.yml @@ -2,28 +2,26 @@ - name: packages are installed (Debian 9 or later) apt: - name: '{{ item }}' + name: + - apache2 + - libapache2-mpm-itk + - libapache2-mod-evasive + - apachetop + - libwww-perl state: present - with_items: - - apache2 - - libapache2-mpm-itk - - libapache2-mod-evasive - - apachetop - - libwww-perl tags: - apache - packages - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: packages are installed (jessie) apt: - name: '{{ item }}' + name: + - apache2-mpm-itk + - libapache2-mod-evasive + - apachetop + - libwww-perl state: present - with_items: - - apache2-mpm-itk - - libapache2-mod-evasive - - apachetop - - libwww-perl tags: - apache - packages @@ -140,7 +138,7 @@ - apache - include_role: - name: remount-usr + name: evolix/remount-usr tags: - apache diff --git a/apache/tasks/munin.yml b/apache/tasks/munin.yml index 27aa7ad8..144ae0f8 100644 --- a/apache/tasks/munin.yml +++ b/apache/tasks/munin.yml @@ -2,11 +2,10 @@ - name: "Install munin-node and core plugins packages" apt: - name: "{{ item }}" + name: + - munin-node + - munin-plugins-core state: present - with_items: - - munin-node - - munin-plugins-core tags: - apache - munin @@ -27,11 +26,10 @@ - name: "Install fcgi packages for Munin graphs" apt: - name: "{{ item }}" + name: + - libapache2-mod-fcgid + - libcgi-fast-perl state: present - with_items: - - libapache2-mod-fcgid - - libcgi-fast-perl notify: reload apache tags: - apache diff --git a/apache/templates/evolinux-default.conf.j2 b/apache/templates/evolinux-default.conf.j2 index 3f058803..68cdcf84 100644 --- a/apache/templates/evolinux-default.conf.j2 +++ b/apache/templates/evolinux-default.conf.j2 @@ -43,6 +43,7 @@ RewriteEngine on # Redirect to HTTPS, execpt for munin, because some plugins # can't handle HTTPS! :( + RewriteCond %{REQUEST_URI} !^/.well-known.*$ [NC] [OR] RewriteCond %{REQUEST_URI} !^/server-status.*$ [NC] [OR] RewriteCond %{REQUEST_URI} !^/munin_opcache.php$ [NC] RewriteRule ^/(.*) https://{{ ansible_fqdn }}/$1 [L,R=permanent] @@ -107,6 +108,15 @@ Require all denied Include /etc/apache2/ipaddr_whitelist.conf + ScriptAlias /munin-cgi/munin-cgi-graph /usr/lib/munin/cgi/munin-cgi-graph + + Options +ExecCGI + + SetHandler fcgid-script + + Require all denied + Include /etc/apache2/ipaddr_whitelist.conf + # BEGIN phpMyAdmin section # END phpMyAdmin section diff --git a/apt/files/buster_backports_preferences b/apt/files/buster_backports_preferences new file mode 100644 index 00000000..30fef48d --- /dev/null +++ b/apt/files/buster_backports_preferences @@ -0,0 +1,3 @@ +Package: * +Pin: release a=buster-backports +Pin-Priority: 50 diff --git a/apt/tasks/backports.yml b/apt/tasks/backports.yml index 3f95300c..aecf6194 100644 --- a/apt/tasks/backports.yml +++ b/apt/tasks/backports.yml @@ -40,6 +40,6 @@ - name: Apt update apt: update_cache: yes - when: apt_backports_list | changed or apt_backports_config | changed + when: apt_backports_list is changed or apt_backports_config is changed tags: - apt diff --git a/apt/tasks/basics.yml b/apt/tasks/basics.yml index 2c736aa9..3b9aadd6 100644 --- a/apt/tasks/basics.yml +++ b/apt/tasks/basics.yml @@ -16,7 +16,9 @@ state: absent with_items: - /etc/apt/sources.list.d/debian-security.list + - /etc/apt/sources.list.d/debian-jessie.list - /etc/apt/sources.list.d/debian-stretch.list + - /etc/apt/sources.list.d/debian-buster.list - /etc/apt/sources.list.d/debian-update.list when: apt_clean_gandi_sourceslist tags: @@ -25,6 +27,6 @@ - name: Apt update apt: update_cache: yes - when: apt_basic_list | changed + when: apt_basic_list is changed tags: - apt diff --git a/apt/tasks/evolix_public.yml b/apt/tasks/evolix_public.yml index 3e00a602..0edb2ec8 100644 --- a/apt/tasks/evolix_public.yml +++ b/apt/tasks/evolix_public.yml @@ -28,6 +28,6 @@ - name: Apt update apt: update_cache: yes - when: apt_evolix_public | changed + when: apt_evolix_public is changed tags: - apt diff --git a/apt/tasks/hold_packages.yml b/apt/tasks/hold_packages.yml index b44a1581..f93c34a7 100644 --- a/apt/tasks/hold_packages.yml +++ b/apt/tasks/hold_packages.yml @@ -1,10 +1,19 @@ --- - name: "hold packages (apt)" - shell: "(apt-mark showhold | grep --quiet {{ item }}) || apt-mark hold {{ item }}" + shell: "(dpkg -l {{ item }} 2>/dev/null | grep -q -E '^(i|h)i') && ((apt-mark showhold | grep --quiet {{ item }}) || apt-mark hold {{ item }})" register: apt_mark - changed_when: "'{{ item }} set on hold.' in apt_mark.stdout" - with_items: "{{ apt_hold_packages }}" + changed_when: "item + ' set on hold.' in apt_mark.stdout" + failed_when: apt_mark.rc != 0 and not apt_mark.stdout == '' + loop: "{{ apt_hold_packages }}" + tags: + - apt + +- name: "/etc/evolinux is present" + file: + dest: /etc/evolinux + mode: "0700" + state: directory tags: - apt @@ -14,15 +23,16 @@ line: "{{ item }}" create: True state: present - with_items: "{{ apt_hold_packages }}" + loop: "{{ apt_hold_packages }}" tags: - apt - name: "unhold packages (apt)" - shell: "(apt-mark showhold | grep --quiet {{ item }}) && apt-mark unhold {{ item }}" + shell: "(dpkg -l {{ item }} 2>/dev/null | grep -q -E '^(i|h)i') && ((apt-mark showhold | grep --quiet {{ item }}) && apt-mark unhold {{ item }})" register: apt_mark - changed_when: "'Canceled hold on {{ item }}.' in apt_mark.stdout" - with_items: "{{ apt_unhold_packages }}" + changed_when: "'Canceled hold on' + item in apt_mark.stdout" + failed_when: apt_mark.rc != 0 and not apt_mark.stdout = '' + loop: "{{ apt_unhold_packages }}" tags: - apt @@ -32,7 +42,7 @@ line: "{{ item }}" create: True state: absent - with_items: "{{ apt_unhold_packages }}" + loop: "{{ apt_unhold_packages }}" tags: - apt @@ -55,6 +65,15 @@ tags: - apt +- name: Check if Cron is installed + shell: "dpkg --list 'cron' 2>/dev/null | grep -q -E '^(i|h)i'" + register: is_cron + changed_when: false + failed_when: false + check_mode: no + tags: + - apt + - name: Check for held packages (script) cron: cron_file: apt-hold-packages @@ -67,5 +86,6 @@ day: "{{ apt_check_hold_cron_day }}" month: "{{ apt_check_hold_cron_month }}" state: "present" + when: is_cron.rc == 0 tags: - apt diff --git a/apt/tasks/main.yml b/apt/tasks/main.yml index bb531d4e..92f06856 100644 --- a/apt/tasks/main.yml +++ b/apt/tasks/main.yml @@ -4,7 +4,7 @@ fail: msg: only compatible with Debian >= 8 when: - - ansible_distribution != "Debian" or ansible_distribution_major_version | version_compare('8', '<') + - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') tags: - apt diff --git a/bind/defaults/main.yml b/bind/defaults/main.yml index b7bc2090..99b33e13 100644 --- a/bind/defaults/main.yml +++ b/bind/defaults/main.yml @@ -2,8 +2,10 @@ bind_recursive_server: False bind_authoritative_server: True bind_chroot_set: True -bind_chroot_path: /var/chroot-bind +# Until chroot-bind.sh is migrated to ansible, we hardcode the chroot paths. +#bind_chroot_path: /var/chroot-bind bind_systemd_service_path: /etc/systemd/system/bind9.service bind_statistics_file: /var/run/named.stats bind_log_file: /var/log/bind.log bind_query_file: /var/log/bind_queries.log +bind_cache_dir: /var/cache/bind diff --git a/bind/tasks/main.yml b/bind/tasks/main.yml index 8fdf1692..3ae02f24 100644 --- a/bind/tasks/main.yml +++ b/bind/tasks/main.yml @@ -1,10 +1,19 @@ +# Until chroot-bind.sh is migrated to ansible, we hardcode the chroot paths. +- name: set chroot variables + set_fact: + bind_log_file: /var/log/bind.log + bind_query_file: /var/log/bind_queries.log + bind_cache_dir: /var/cache/bind + bind_statistics_file: /var/run/named.stats + bind_chroot_path: /var/chroot-bind + when: bind_chroot_set + - name: package are installed apt: - name: '{{ item }}' + name: + - bind9 + - dnstop state: present - with_items: - - bind9 - - dnstop - name: Set bind configuration for recursive server template: @@ -49,23 +58,23 @@ - restart bind when: ansible_distribution_release == "jessie" -- name: touch /var/log/bind.log if non chroot +- name: "touch {{ bind_log_file }} if non chroot" file: - path: /var/log/bind.log + path: "{{ bind_log_file }}" owner: bind group: adm mode: "0640" state: touch - when: bind_chroot_set == False + when: not bind_chroot_set -- name: touch /var/log/bind_queries.log if non chroot +- name: "touch {{ bind_query_file }} if non chroot" file: - path: /var/log/bind_queries.log + path: "{{ bind_query_file }}" owner: bind group: adm mode: "0640" state: touch - when: bind_authoritative_server and bind_chroot_set == False + when: not bind_chroot_set - name: send chroot-bind.sh in /root copy: @@ -95,24 +104,14 @@ notify: restart bind when: bind_chroot_set -- name: logrotate for non chroot bind +- name: logrotate for bind template: - src: logrotate_bind - dest: /etc/logrotate.d/bind + src: logrotate_bind.j2 + dest: /etc/logrotate.d/bind9 owner: root group: root mode: "0644" force: yes notify: restart bind - when: bind_chroot_set == False -- name: logrotate for chroot bind - template: - src: logrotate_bind_chroot.j2 - dest: /etc/logrotate.d/bind - owner: root - group: root - mode: "0644" - force: yes - notify: restart bind - when: bind_chroot_set +- include: munin.yml diff --git a/bind/tasks/munin.yml b/bind/tasks/munin.yml index a31e6b06..5f9da280 100644 --- a/bind/tasks/munin.yml +++ b/bind/tasks/munin.yml @@ -8,9 +8,8 @@ tags: - bind - munin - when: bind_authoritative_server -- name: Enable munin plugins +- name: Enable munin plugins for authoritative server file: src: "/usr/share/munin/plugins/{{ item }}" dest: "/etc/munin/plugins/{{ item }}" @@ -19,7 +18,25 @@ - bind9 - bind9_rndc notify: restart munin-node - when: bind_authoritative_server and munin_node_plugins_config.stat.exists + when: + - bind_authoritative_server + - munin_node_plugins_config.stat.exists + tags: + - bind + - munin + +- name: Enable munin plugins for recursive server + file: + src: "/usr/share/munin/plugins/{{ item }}" + dest: "/etc/munin/plugins/{{ item }}" + state: link + with_items: + - bind9 + - bind9_rndc + notify: restart munin-node + when: + - bind_recursive_server + - munin_node_plugins_config.stat.exists tags: - bind - munin @@ -33,7 +50,7 @@ mode: "0644" force: yes notify: restart munin-node - when: bind_authoritative_server and munin_node_plugins_config.stat.exists + when: munin_node_plugins_config.stat.exists tags: - bind - munin diff --git a/bind/templates/logrotate_bind b/bind/templates/logrotate_bind.j2 similarity index 56% rename from bind/templates/logrotate_bind rename to bind/templates/logrotate_bind.j2 index ee53df95..3fe1589e 100644 --- a/bind/templates/logrotate_bind +++ b/bind/templates/logrotate_bind.j2 @@ -1,4 +1,8 @@ -/var/log/bind.log { +{% if bind_chroot_set %} +{{ bind_chroot_path }}{{bind_log_file}} { +{% else %} +{{bind_log_file}} { +{% endif %} weekly missingok rotate 52 diff --git a/bind/templates/logrotate_bind_chroot.j2 b/bind/templates/logrotate_bind_chroot.j2 deleted file mode 100644 index 5db5d494..00000000 --- a/bind/templates/logrotate_bind_chroot.j2 +++ /dev/null @@ -1,10 +0,0 @@ -{{ bind_chroot_path }}/var/log/bind.log { - weekly - missingok - rotate 52 - create 640 bind bind - sharedscripts - postrotate - rndc reload > /dev/null - endscript -} diff --git a/bind/templates/munin-env_bind9.j2 b/bind/templates/munin-env_bind9.j2 index f1d4b41e..de88b27a 100644 --- a/bind/templates/munin-env_bind9.j2 +++ b/bind/templates/munin-env_bind9.j2 @@ -1,6 +1,9 @@ [bind*] user root -env.logfile {{ bind_query_file }} -env.querystats {{ bind_chroot_path }}{{ bind_statistics_file }} + +env.logfile {% if bind_chroot_set %}{{ bind_chroot_path }}{% endif %}{{ bind_query_file }} +{% if bind_authoritative_server %} +env.querystats {% if bind_chroot_set %}{{ bind_chroot_path }}{% endif %}{{ bind_statistics_file }} +{% endif %} env.MUNIN_PLUGSTATE /var/lib/munin timeout 120 diff --git a/bind/templates/named.conf.options_authoritative.j2 b/bind/templates/named.conf.options_authoritative.j2 index 04ab2551..8f48cbb5 100644 --- a/bind/templates/named.conf.options_authoritative.j2 +++ b/bind/templates/named.conf.options_authoritative.j2 @@ -4,11 +4,11 @@ acl "foo" { }; options { - directory "/var/cache/bind"; + directory "{{ bind_cache_dir }}"; version "Bingo"; auth-nxdomain no; masterfile-format text; - statistics-file "/var/run/named.stats"; + statistics-file "{{ bind_statistics_file }}"; listen-on-v6 { any; }; listen-on { any; }; @@ -23,11 +23,11 @@ logging { category queries { query_logging; }; channel default_file { - file "/var/log/bind.log"; + file "{{ bind_log_file }}"; severity info; }; channel query_logging { - file "/var/log/bind_queries.log" versions 2 size 128M; + file "{{ bind_query_file }}" versions 2 size 128M; print-category yes; print-severity yes; print-time yes; diff --git a/bind/templates/named.conf.options_recursive.j2 b/bind/templates/named.conf.options_recursive.j2 index 555230d0..27246d13 100644 --- a/bind/templates/named.conf.options_recursive.j2 +++ b/bind/templates/named.conf.options_recursive.j2 @@ -1,5 +1,5 @@ options { - directory "/var/cache/bind"; + directory "{{ bind_cache_dir }}"; version "Bingo"; auth-nxdomain no; listen-on-v6 { ::1; }; @@ -8,9 +8,17 @@ options { }; logging { - category default { default_file; }; - channel default_file { - file "/var/log/bind.log"; - severity info; - }; + category default { default_file; }; + category queries { query_logging; }; + + channel default_file { + file "{{ bind_log_file }}"; + severity info; + }; + channel query_logging { + file "{{ bind_query_file }}" versions 2 size 128M; + print-category yes; + print-severity yes; + print-time yes; + }; }; diff --git a/certbot/defaults/main.yml b/certbot/defaults/main.yml new file mode 100644 index 00000000..876be14e --- /dev/null +++ b/certbot/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +certbot_work_dir: /var/lib/letsencrypt diff --git a/certbot/files/cron_jessie b/certbot/files/cron_jessie new file mode 100644 index 00000000..0ed929fc --- /dev/null +++ b/certbot/files/cron_jessie @@ -0,0 +1,11 @@ +# /etc/cron.d/certbot: crontab entries for the certbot package +# +# Upstream recommends attempting renewal twice a day +# +# Eventually, this will be an opportunity to validate certificates +# haven't been revoked, etc. Renewal will only occur if expiration +# is within 30 days. +SHELL=/bin/sh +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +0 */12 * * * root test -x /usr/local/bin/certbot && perl -e 'sleep int(rand(3600))' && /usr/local/bin/certbot --no-self-update -q renew diff --git a/certbot/files/hooks/apache.sh b/certbot/files/hooks/apache.sh new file mode 100644 index 00000000..1235a23e --- /dev/null +++ b/certbot/files/hooks/apache.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +daemon_found_and_running() { + test -n "$(pidof apache2)" && test -n "${apache2ctl_bin}" +} +config_check() { + ${apache2ctl_bin} configtest > /dev/null 2>&1 +} +letsencrypt_used() { + grep -q -r -E "letsencrypt" /etc/apache2/ +} +main() { + if daemon_found_and_running; then + if letsencrypt_used; then + if config_check; then + debug "Apache detected... reloading" + systemctl reload apache2 + else + error "Apache config is broken, you must fix it !" + fi + else + debug "Apache doesn't use Let's Encrypt certificate. Skip." + fi + else + debug "Apache is not running or missing. Skip." + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly apache2ctl_bin=$(command -v apache2ctl) + +main diff --git a/certbot/files/hooks/dovecot.sh b/certbot/files/hooks/dovecot.sh new file mode 100644 index 00000000..56e5b5ae --- /dev/null +++ b/certbot/files/hooks/dovecot.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +daemon_found_and_running() { + test -n "$(pidof dovecot)" && test -n "${doveconf_bin}" +} +config_check() { + ${doveconf_bin} > /dev/null 2>&1 +} +letsencrypt_used() { + ${doveconf_bin} | grep -E "^ssl_cert[^_]" | grep -q "letsencrypt" +} +main() { + if daemon_found_and_running; then + if letsencrypt_used; then + if config_check; then + debug "Dovecot detected... reloading" + systemctl reload dovecot + else + error "Dovecot config is broken, you must fix it !" + fi + else + debug "Dovecot doesn't use Let's Encrypt certificate. Skip." + fi + else + debug "Dovecot is not running or missing. Skip." + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly doveconf_bin=$(command -v doveconf) + +main diff --git a/certbot/files/hooks/haproxy.sh b/certbot/files/hooks/haproxy.sh new file mode 100644 index 00000000..8bb66e2c --- /dev/null +++ b/certbot/files/hooks/haproxy.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +daemon_found_and_running() { + test -n "$(pidof haproxy)" && test -n "${haproxy_bin}" +} +found_renewed_lineage() { + test -f "${RENEWED_LINEAGE}/fullchain.pem" && test -f "${RENEWED_LINEAGE}/privkey.pem" +} +config_check() { + ${haproxy_bin} -c -f /etc/haproxy/haproxy.cfg > /dev/null 2>&1 +} +concat_files() { + # shellcheck disable=SC2174 + mkdir --mode=700 --parents "${haproxy_cert_dir}" + chown root: "${haproxy_cert_dir}" + + debug "Concatenating certificate files to ${haproxy_cert_file}" + cat "${RENEWED_LINEAGE}/fullchain.pem" "${RENEWED_LINEAGE}/privkey.pem" > "${haproxy_cert_file}" + chmod 600 "${haproxy_cert_file}" + chown root: "${haproxy_cert_file}" +} +cert_and_key_mismatch() { + haproxy_cert_md5=$(openssl x509 -noout -modulus -in "${haproxy_cert_file}" | openssl md5) + haproxy_key_md5=$(openssl rsa -noout -modulus -in "${haproxy_cert_file}" | openssl md5) + + test "${haproxy_cert_md5}" != "${haproxy_key_md5}" +} +main() { + if [ -z "${RENEWED_LINEAGE}" ]; then + error "This script must be called only by certbot!" + fi + + if daemon_found_and_running; then + if found_renewed_lineage; then + haproxy_cert_file="${haproxy_cert_dir}/$(basename "${RENEWED_LINEAGE}").pem" + failed_cert_file="/root/$(basename "${RENEWED_LINEAGE}").failed.pem" + + concat_files + + if cert_and_key_mismatch; then + mv "${haproxy_cert_file}" "${failed_cert_file}" + error "Key and cert don't match, we moved the file to ${failed_cert_file} for inspection" + fi + + if config_check; then + debug "HAProxy detected... reloading" + systemctl reload haproxy + else + error "HAProxy config is broken, you must fix it !" + fi + else + error "Couldn't find ${RENEWED_LINEAGE}/fullchain.pem or ${RENEWED_LINEAGE}/privkey.pem" + fi + else + debug "HAProxy is not running or missing. Skip." + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly haproxy_bin=$(command -v haproxy) +readonly haproxy_cert_dir="/etc/ssl/haproxy" + +main diff --git a/certbot/files/hooks/nginx.sh b/certbot/files/hooks/nginx.sh new file mode 100644 index 00000000..2a1d27e9 --- /dev/null +++ b/certbot/files/hooks/nginx.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +daemon_found_and_running() { + test -n "$(pidof nginx)" && test -n "${nginx_bin}" +} +config_check() { + ${nginx_bin} -t > /dev/null 2>&1 +} +letsencrypt_used() { + grep -q --dereference-recursive -E "letsencrypt" /etc/nginx/sites-enabled +} +main() { + if daemon_found_and_running; then + if letsencrypt_used; then + if config_check; then + debug "Nginx detected... reloading" + systemctl reload nginx + else + error "Nginx config is broken, you must fix it !" + fi + else + debug "Nginx doesn't use Let's Encrypt certificate. Skip." + fi + else + debug "Nginx is not running or missing. Skip." + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly nginx_bin=$(command -v nginx) + +main diff --git a/certbot/files/hooks/postfix.sh b/certbot/files/hooks/postfix.sh new file mode 100644 index 00000000..de6a4b7c --- /dev/null +++ b/certbot/files/hooks/postfix.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +daemon_found_and_running() { + test -n "$(pidof master)" && test -n "${postconf_bin}" +} +config_check() { + ${postconf_bin} > /dev/null 2>&1 +} +letsencrypt_used() { + ${postconf_bin} | grep -E "^smtpd_tls_cert_file" | grep -q "letsencrypt" +} +main() { + if daemon_found_and_running; then + if letsencrypt_used; then + if config_check; then + debug "Postfix detected... reloading" + systemctl reload postfix + else + error "Postfix config is broken, you must fix it !" + fi + else + debug "Postfix doesn't use Let's Encrypt certificate. Skip." + fi + else + debug "Postfix is not running or missing. Skip." + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly postconf_bin=$(command -v postconf) + +main diff --git a/certbot/files/hooks/z-commit-etc.sh b/certbot/files/hooks/z-commit-etc.sh new file mode 100644 index 00000000..e543b4ad --- /dev/null +++ b/certbot/files/hooks/z-commit-etc.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +main() { + export GIT_DIR="/etc/.git" + export GIT_WORK_TREE="/etc" + + if test -x "${git_bin}" && test -d "${GIT_DIR}" && test -d "${GIT_WORK_TREE}"; then + changed_lines=$(${git_bin} status --porcelain | wc -l | tr -d ' ') + + if [ "${changed_lines}" != "0" ]; then + debug "Committing for ${RENEWED_DOMAINS}" + ${git_bin} add --all + message="[letsencrypt] certificates renewal (${RENEWED_DOMAINS})" + ${git_bin} commit --message "${message}" --quiet + else + error "Weird, nothing has changed but the hook has been executed for '${RENEWED_DOMAINS}'" + fi + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly git_bin=$(command -v git) +readonly letsencrypt_dir=/etc/letsencrypt + +main diff --git a/certbot/handlers/main.yml b/certbot/handlers/main.yml new file mode 100644 index 00000000..4a9d305a --- /dev/null +++ b/certbot/handlers/main.yml @@ -0,0 +1,23 @@ +--- + +- name: reload nginx + service: + name: nginx + state: reloaded + +- name: reload apache + service: + name: apache2 + state: reloaded + +- name: reload haproxy + service: + name: haproxy + state: reloaded + +- name: systemd daemon-reload + systemd: + daemon_reload: yes + +- name: install certbot-auto + command: /usr/local/bin/certbot --install-only diff --git a/certbot/tasks/acme-challenge.yml b/certbot/tasks/acme-challenge.yml new file mode 100644 index 00000000..56b0c099 --- /dev/null +++ b/certbot/tasks/acme-challenge.yml @@ -0,0 +1,51 @@ +--- + +- name: Certbot work directory is present + file: + dest: "{{ certbot_work_dir }}" + state: directory + mode: "0755" + +- name: Check if Nginx is installed + stat: + path: /etc/nginx + register: is_nginx + +- name: ACME challenge for Nginx is installed + template: + src: acme-challenge/nginx.conf.j2 + dest: /etc/nginx/snippets/letsencrypt.conf + force: yes + notify: reload nginx + when: is_nginx.stat.exists + +- name: Check if Apache is installed + stat: + path: /usr/sbin/apachectl + register: is_apache + +- name: ACME challenge for Apache + block: + - name: ACME challenge for Apache is installed + template: + src: acme-challenge/apache.conf.j2 + dest: /etc/apache2/conf-available/letsencrypt.conf + force: yes + notify: reload apache + + - name: ACME challenge for Apache is enabled + command: "a2enconf letsencrypt" + register: command_result + changed_when: "'Enabling' in command_result.stderr" + notify: reload apache + when: is_apache.stat.exists + +- name: Check if HAProxy is installed + stat: + path: /etc/haproxy + register: is_haproxy + +- name: ACME challenge for HAProxy is installed + debug: + msg: "ACME challenge configuration for HAProxy must be configured manually" + when: is_haproxy.stat.exists diff --git a/certbot/tasks/install-package.yml b/certbot/tasks/install-package.yml new file mode 100644 index 00000000..06247db4 --- /dev/null +++ b/certbot/tasks/install-package.yml @@ -0,0 +1,6 @@ +--- + +- name: certbot package is installed + apt: + name: certbot + state: latest diff --git a/certbot/tasks/install-sources.yml b/certbot/tasks/install-sources.yml new file mode 100644 index 00000000..1bc7c864 --- /dev/null +++ b/certbot/tasks/install-sources.yml @@ -0,0 +1,35 @@ +--- + +- name: certbot package is removed + apt: + name: certbot + state: absent + +- include_role: + name: evolix/remount-usr + +- name: Certbot script is downloaded + get_url: + url: https://dl.eff.org/certbot-auto + dest: /usr/local/bin/certbot + mode: '0755' + owner: root + group: root + force: no + notify: install certbot-auto + +- name: systemd artefacts are absent + file: + dest: "{{ item }}" + state: absent + loop: + - /etc/systemd/system/certbot.service + - /etc/systemd/system/certbot.service.d + - /etc/systemd/system/certbot.timer + notify: systemd daemon-reload + +- name: custom crontab is present + copy: + src: cron_jessie + dest: /etc/cron.d/certbot + force: yes diff --git a/certbot/tasks/main.yml b/certbot/tasks/main.yml new file mode 100644 index 00000000..ed8e8b85 --- /dev/null +++ b/certbot/tasks/main.yml @@ -0,0 +1,44 @@ +--- + +- name: "System compatibility checks" + assert: + that: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('8', '>=') + msg: only compatible with Debian 9+ + +- name: Install from sources on Debian 8 + include: install-sources.yml + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('8', '=') + +- name: Install package on Debian 9+ + include: install-package.yml + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '>=') + +- include: acme-challenge.yml + +- name: Deploy hooks are present + copy: + src: hooks/ + dest: /etc/letsencrypt/renewal-hooks/deploy/ + mode: "0700" + owner: root + group: root + +- name: Move commit-etc.sh to z-commit-etc.sh if present + command: "mv /etc/letsencrypt/renewal-hooks/deploy/commit-etc.sh /etc/letsencrypt/renewal-hooks/deploy/z-commit-etc.sh" + args: + removes: /etc/letsencrypt/renewal-hooks/deploy/commit-etc.sh + creates: /etc/letsencrypt/renewal-hooks/deploy/z-commit-etc.sh + +- name: "certbot lock is ignored by Git" + lineinfile: + dest: /etc/.gitignore + line: letsencrypt/.certbot.lock + create: yes + owner: root + mode: "0600" diff --git a/certbot/templates/acme-challenge/apache.conf.j2 b/certbot/templates/acme-challenge/apache.conf.j2 new file mode 100644 index 00000000..f013957d --- /dev/null +++ b/certbot/templates/acme-challenge/apache.conf.j2 @@ -0,0 +1,12 @@ + + SetEnvIf Request_URI "/.well-known/acme-challenge/*" no-jk + + + ProxyPass /.well-known/acme-challenge/ ! + +Alias /.well-known/acme-challenge /var/lib/letsencrypt/.well-known/acme-challenge + + Options -Indexes + Allow from all + Require all granted + diff --git a/certbot/templates/acme-challenge/nginx.conf.j2 b/certbot/templates/acme-challenge/nginx.conf.j2 new file mode 100644 index 00000000..a0730f3c --- /dev/null +++ b/certbot/templates/acme-challenge/nginx.conf.j2 @@ -0,0 +1,5 @@ +location ~ /.well-known/acme-challenge { + alias {{ certbot_work_dir }}/; + try_files $uri =404; + allow all; +} diff --git a/clamav/meta/main.yml b/clamav/meta/main.yml index 510b6855..ae652cf8 100644 --- a/clamav/meta/main.yml +++ b/clamav/meta/main.yml @@ -1,3 +1,3 @@ --- dependencies: - - { role: amavis } + - { role: evolix/amavis } diff --git a/clamav/tasks/main.yml b/clamav/tasks/main.yml index b053fce4..27d30cbc 100644 --- a/clamav/tasks/main.yml +++ b/clamav/tasks/main.yml @@ -74,22 +74,20 @@ - name: install ClamAV apt: - name: "{{ item }}" + name: + - clamav-daemon + - clamav + - clamdscan + - clamav-freshclam + - arc + - arj + - pax + - bzip2 + - cabextract + - rpm + - lzop + - razor state: present - with_items: - - clamav-daemon - - clamav - - clamdscan - - clamav-freshclam - - arc - - arj - - zoo - - pax - - bzip2 - - cabextract - - rpm - - lzop - - razor tags: - clamav diff --git a/docker-host/tasks/jessie_backports.yml b/docker-host/tasks/jessie_backports.yml index 727ee7c8..e7c7e94f 100644 --- a/docker-host/tasks/jessie_backports.yml +++ b/docker-host/tasks/jessie_backports.yml @@ -1,6 +1,6 @@ --- - include_role: - name: apt + name: evolix/apt tasks_from: backports.yml tags: - packages @@ -18,6 +18,6 @@ - name: update apt apt: update_cache: yes - when: docker_apt_preferences | changed + when: docker_apt_preferences is changed tags: - packages diff --git a/docker-host/tasks/main.yml b/docker-host/tasks/main.yml index c15fc761..9a66bb92 100644 --- a/docker-host/tasks/main.yml +++ b/docker-host/tasks/main.yml @@ -2,22 +2,20 @@ --- - name: Remove older docker packages apt: - name: '{{ item }}' + name: + - docker + - docker-engine + - docker.io state: absent - with_items: - - docker - - docker-engine - - docker.io - name: Install source requirements apt: - name: '{{ item }}' + name: + - apt-transport-https + - ca-certificates + - gnupg2 state: present update_cache: yes - with_items: - - apt-transport-https - - ca-certificates - - gnupg2 - name: Add Docker repository apt_repository: @@ -36,11 +34,10 @@ - name: Install docker and python-docker apt: - name: "{{ item }}" + name: + - docker-ce + - python-docker update_cache: yes - with_items: - - docker-ce - - python-docker - name: Copy Docker daemon configuration file template: diff --git a/dovecot/tasks/main.yml b/dovecot/tasks/main.yml index 1a835160..8492e00a 100644 --- a/dovecot/tasks/main.yml +++ b/dovecot/tasks/main.yml @@ -1,13 +1,12 @@ - name: ensure packages are installed apt: - name: '{{ item }}' + name: + - dovecot-ldap + - dovecot-imapd + - dovecot-pop3d + - dovecot-sieve + - dovecot-managesieved state: present - with_items: - - dovecot-ldap - - dovecot-imapd - - dovecot-pop3d - - dovecot-sieve - - dovecot-managesieved tags: - dovecot @@ -26,13 +25,13 @@ regexp: "^#*{{ item.key }}" state: present with_items: - - { key: 'hosts', value: '127.0.0.1' } - - { key: 'auth_bind', value: 'yes' } - - { key: 'ldap_version', value: 3 } - - { key: 'base', value: "{{ ldap_suffix }}" } - - { key: 'user_attrs', value: 'homeDirectory=home' } - - { key: 'user_filter', value: '(&(isActive=TRUE)(uid=%u))' } - - { key: 'pass_attrs', value: 'uid=user,userPassword=password' } + - { key: 'hosts', value: '127.0.0.1' } + - { key: 'auth_bind', value: 'yes' } + - { key: 'ldap_version', value: 3 } + - { key: 'base', value: "{{ ldap_suffix }}" } + - { key: 'user_attrs', value: 'homeDirectory=home' } + - { key: 'user_filter', value: '(&(isActive=TRUE)(uid=%u))' } + - { key: 'pass_attrs', value: 'uid=user,userPassword=password' } when: ldap_suffix is defined notify: reload dovecot tags: diff --git a/dovecot/tasks/munin.yml b/dovecot/tasks/munin.yml index 7f5003f0..21d17519 100644 --- a/dovecot/tasks/munin.yml +++ b/dovecot/tasks/munin.yml @@ -6,12 +6,13 @@ check_mode: no register: munin_node_plugins_config -- block: - - name: Install munin plugin - copy: - src: munin_plugin - dest: /etc/munin/plugins/dovecot - mode: "0755" +- name: Munin plugins are present and configured + block: + - name: Install munin plugin + copy: + src: munin_plugin + dest: /etc/munin/plugins/dovecot + mode: "0755" # TODO : add in /etc/munin/plugin-conf.d/munin-node # [dovecot] diff --git a/drbd/tasks/nagios.yml b/drbd/tasks/nagios.yml index 91b06c57..ea436a5b 100644 --- a/drbd/tasks/nagios.yml +++ b/drbd/tasks/nagios.yml @@ -9,7 +9,7 @@ - drbd - include_role: - name: remount-usr + name: evolix/remount-usr tags: - drbd diff --git a/drbd/tasks/packages.yml b/drbd/tasks/packages.yml index 2876d386..59b4bb2e 100644 --- a/drbd/tasks/packages.yml +++ b/drbd/tasks/packages.yml @@ -1,9 +1,8 @@ - name: Install dependency apt: - name: "{{ item }}" - with_items: - - drbd-utils - - lvm2 + name: + - drbd-utils + - lvm2 tags: - drbd diff --git a/elasticsearch/README.md b/elasticsearch/README.md index e8184019..933beb0f 100644 --- a/elasticsearch/README.md +++ b/elasticsearch/README.md @@ -27,7 +27,7 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `elasticsearch_jvm_xmx`: maximum heap size reserved for the JVM (default: `2g`). * `elasticsearch_restart_on_upgrade`: restart the service after package upgrade (default: `true`) -By default, Elasticsearch will listen to the public interfaces (`_site_` cf. https://www.elastic.co/guide/en/elasticsearch/reference/5.0/important-settings.html#network.host), so you will have to secure it, with firewall rules for example. +By default, Elasticsearch will listen to the local interface (`_local_` cf. https://www.elastic.co/guide/en/elasticsearch/reference/5.0/important-settings.html#network.host). ## Curator diff --git a/elasticsearch/defaults/main.yml b/elasticsearch/defaults/main.yml index f5693bf2..91e81915 100644 --- a/elasticsearch/defaults/main.yml +++ b/elasticsearch/defaults/main.yml @@ -5,7 +5,7 @@ elasticsearch_cluster_name: Null elasticsearch_cluster_members: Null elasticsearch_minimum_master_nodes: Null elasticsearch_node_name: "${HOSTNAME}" -elasticsearch_network_host: "[_site_, _local_]" +elasticsearch_network_host: "[_local_]" elasticsearch_network_publish_host: Null elasticsearch_http_publish_host: Null elasticsearch_custom_datadir: Null diff --git a/elasticsearch/meta/main.yml b/elasticsearch/meta/main.yml index 0f511c51..407fbc76 100644 --- a/elasticsearch/meta/main.yml +++ b/elasticsearch/meta/main.yml @@ -25,4 +25,4 @@ galaxy_info: # alphanumeric characters. Maximum 20 tags per role. dependencies: - - { role: java, alternative: 'openjdk', java_version: 8 } + - { role: evolix/java, alternative: 'openjdk' } diff --git a/elasticsearch/tasks/additional_scripts.yml b/elasticsearch/tasks/additional_scripts.yml index 837acd6d..19e43535 100644 --- a/elasticsearch/tasks/additional_scripts.yml +++ b/elasticsearch/tasks/additional_scripts.yml @@ -1,8 +1,8 @@ --- - include_role: - name: remount-usr - when: elasticsearch_additional_scripts_dir | search ("/usr") + name: evolix/remount-usr + when: elasticsearch_additional_scripts_dir is search ("/usr") - name: "{{ elasticsearch_additional_scripts_dir }} exists" file: diff --git a/elasticsearch/tasks/datadir.yml b/elasticsearch/tasks/datadir.yml index 38d2218a..66ec48a1 100644 --- a/elasticsearch/tasks/datadir.yml +++ b/elasticsearch/tasks/datadir.yml @@ -1,44 +1,46 @@ --- -- block: - - name: "Is custom datadir present ?" - stat: - path: "{{ elasticsearch_custom_datadir }}" - register: elasticsearch_custom_datadir_test - check_mode: no +- name: Set real datadir value when customized + block: + - name: "Is custom datadir present ?" + stat: + path: "{{ elasticsearch_custom_datadir }}" + register: elasticsearch_custom_datadir_test + check_mode: no - - name: "read the real datadir" - command: readlink -f /var/lib/elasticsearch - changed_when: false - register: elasticsearch_current_real_datadir_test - check_mode: no + - name: "read the real datadir" + command: readlink -f /var/lib/elasticsearch + changed_when: false + register: elasticsearch_current_real_datadir_test + check_mode: no tags: - elasticsearch when: - elasticsearch_custom_datadir != '' - elasticsearch_custom_datadir != None -- block: - - name: elasticsearch is stopped - service: - name: elasticsearch - state: stopped +- name: Datadir is moved to custom path + block: + - name: elasticsearch is stopped + service: + name: elasticsearch + state: stopped - - name: Move elasticsearch datadir to custom datadir - command: mv {{ elasticsearch_current_real_datadir_test.stdout }} {{ elasticsearch_custom_datadir }} - args: - creates: "{{ elasticsearch_custom_datadir }}" + - name: Move elasticsearch datadir to custom datadir + command: mv {{ elasticsearch_current_real_datadir_test.stdout }} {{ elasticsearch_custom_datadir }} + args: + creates: "{{ elasticsearch_custom_datadir }}" - - name: Symlink {{ elasticsearch_custom_datadir }} to /var/lib/elasticsearch - file: - src: "{{ elasticsearch_custom_datadir }}" - dest: '/var/lib/elasticsearch' - state: link + - name: Symlink {{ elasticsearch_custom_datadir }} to /var/lib/elasticsearch + file: + src: "{{ elasticsearch_custom_datadir }}" + dest: '/var/lib/elasticsearch' + state: link - - name: elasticsearch is started - service: - name: elasticsearch - state: started + - name: elasticsearch is started + service: + name: elasticsearch + state: started tags: - elasticsearch when: diff --git a/elasticsearch/tasks/logs.yml b/elasticsearch/tasks/logs.yml index ee3d7680..16bbe5d6 100644 --- a/elasticsearch/tasks/logs.yml +++ b/elasticsearch/tasks/logs.yml @@ -1,5 +1,11 @@ --- +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" + failed_when: False + changed_when: False + register: is_cron_installed + - name: "log rotation script" template: src: rotate_elasticsearch_logs.j2 @@ -7,3 +13,4 @@ owner: root group: root mode: "0750" + when: is_cron_installed.rc == 0 diff --git a/elasticsearch/tasks/plugin_head.yml b/elasticsearch/tasks/plugin_head.yml index 81510fa0..2f7cae39 100644 --- a/elasticsearch/tasks/plugin_head.yml +++ b/elasticsearch/tasks/plugin_head.yml @@ -8,28 +8,29 @@ system: yes shell: /bin/false -- block: - - name: Head repository is checked-out - git: - repo: "https://github.com/mobz/elasticsearch-head.git" - dest: "{{ elasticsearch_plugin_head_clone_dir }}" - clone: yes - tags: - - packages +- name: Head plugin is installed + block: + - name: Head repository is checked-out + git: + repo: "https://github.com/mobz/elasticsearch-head.git" + dest: "{{ elasticsearch_plugin_head_clone_dir }}" + clone: yes + tags: + - packages - - name: Create tmpdir - file: - dest: "{{ elasticsearch_plugin_head_tmp_dir }}" - state: directory + - name: Create tmpdir + file: + dest: "{{ elasticsearch_plugin_head_tmp_dir }}" + state: directory - - name: NPM packages for head are installed - npm: - path: "{{ elasticsearch_plugin_head_clone_dir }}" - tags: - - packages - - npm - environment: - TMPDIR: "{{ elasticsearch_plugin_head_tmp_dir }}" + - name: NPM packages for head are installed + npm: + path: "{{ elasticsearch_plugin_head_clone_dir }}" + tags: + - packages + - npm + environment: + TMPDIR: "{{ elasticsearch_plugin_head_tmp_dir }}" become_user: "{{ elasticsearch_plugin_head_owner }}" become: yes diff --git a/elasticsearch/tasks/tmpdir.yml b/elasticsearch/tasks/tmpdir.yml index 44a9ce85..086870fe 100644 --- a/elasticsearch/tasks/tmpdir.yml +++ b/elasticsearch/tasks/tmpdir.yml @@ -7,50 +7,51 @@ changed_when: False check_mode: no -- block: - - name: "Create {{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" - file: - path: "{{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" - owner: elasticsearch - group: elasticsearch - mode: "0755" - state: directory - tags: - - elasticsearch +- name: Tmpdir is moved to custom path + block: + - name: "Create {{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" + file: + path: "{{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" + owner: elasticsearch + group: elasticsearch + mode: "0755" + state: directory + tags: + - elasticsearch - - name: change JVM tmpdir (< 6.x) - lineinfile: - dest: /etc/elasticsearch/jvm.options - line: "-Djava.io.tmpdir={{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" - regexp: "^-Djava.io.tmpdir=" - insertafter: "## JVM configuration" - notify: - - restart elasticsearch - tags: - - elasticsearch - when: elastic_stack_version | version_compare('6', '<') + - name: change JVM tmpdir (< 6.x) + lineinfile: + dest: /etc/elasticsearch/jvm.options + line: "-Djava.io.tmpdir={{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" + regexp: "^-Djava.io.tmpdir=" + insertafter: "## JVM configuration" + notify: + - restart elasticsearch + tags: + - elasticsearch + when: elastic_stack_version is version('6', '<') - - name: check if ES_TMPDIR is available (>= 6.x) - lineinfile: - dest: /etc/default/elasticsearch - line: "ES_TMPDIR={{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" - regexp: "^ES_TMPDIR=" - insertafter: "JAVA_HOME" - notify: - - restart elasticsearch - tags: - - elasticsearch - when: elastic_stack_version | version_compare('6', '>=') + - name: check if ES_TMPDIR is available (>= 6.x) + lineinfile: + dest: /etc/default/elasticsearch + line: "ES_TMPDIR={{ elasticsearch_custom_tmpdir or elasticsearch_default_tmpdir | mandatory }}" + regexp: "^ES_TMPDIR=" + insertafter: "JAVA_HOME" + notify: + - restart elasticsearch + tags: + - elasticsearch + when: elastic_stack_version is version('6', '>=') - - name: change JVM tmpdir (>= 6.x) - lineinfile: - dest: /etc/elasticsearch/jvm.options - line: "-Djava.io.tmpdir=${ES_TMPDIR}" - regexp: "^-Djava.io.tmpdir=" - insertafter: "## JVM configuration" - notify: - - restart elasticsearch - tags: - - elasticsearch - when: elastic_stack_version | version_compare('6', '>=') + - name: change JVM tmpdir (>= 6.x) + lineinfile: + dest: /etc/elasticsearch/jvm.options + line: "-Djava.io.tmpdir=${ES_TMPDIR}" + regexp: "^-Djava.io.tmpdir=" + insertafter: "## JVM configuration" + notify: + - restart elasticsearch + tags: + - elasticsearch + when: elastic_stack_version is version('6', '>=') when: (elasticsearch_custom_tmpdir != '' and elasticsearch_custom_tmpdir != None) or fstab_tmp_noexec.rc == 0 diff --git a/etc-git/README.md b/etc-git/README.md index 2d3c3840..9823206d 100644 --- a/etc-git/README.md +++ b/etc-git/README.md @@ -14,7 +14,7 @@ There is also an independant task that can be executed to commit changes made in pre_tasks: - include_role: - name: etc-git + name: evolix/etc-git tasks_from: commit.yml vars: commit_message: "Ansible pre-run my splendid playbook" @@ -24,7 +24,7 @@ There is also an independant task that can be executed to commit changes made in post_tasks: - include_role: - name: etc-git + name: evolix/etc-git tasks_from: commit.yml vars: commit_message: "Ansible post-run my splendid playbook" diff --git a/etc-git/tasks/commit.yml b/etc-git/tasks/commit.yml index 85c36641..58aee384 100644 --- a/etc-git/tasks/commit.yml +++ b/etc-git/tasks/commit.yml @@ -31,7 +31,7 @@ - name: "set commit author" set_fact: commit_author: '{% if ansible_env.SUDO_USER is not defined %}root{% else %}{{ ansible_env.SUDO_USER }}{% endif %}' - commit_email: '{% if git_config_user_email.config_value is not defined or git_config_user_email.config_value == "" %}root@localhost{% else %}{{ git_config_user_email.config_value }}{% endif %}' + commit_email: '{% if git_config_user_email.config_value is not defined or not git_config_user_email.config_value %}root@localhost{% else %}{{ git_config_user_email.config_value }}{% endif %}' # noqa 204 tags: - etc-git - commit-etc @@ -41,7 +41,9 @@ args: chdir: /etc register: etc_commit_end_run - when: not ansible_check_mode and git_status.stdout != "" + when: + - not ansible_check_mode + - git_status.stdout ignore_errors: yes tags: - etc-git diff --git a/etc-git/tasks/main.yml b/etc-git/tasks/main.yml index 4156d3fa..8d16b79f 100644 --- a/etc-git/tasks/main.yml +++ b/etc-git/tasks/main.yml @@ -7,80 +7,37 @@ tags: - etc-git -- name: /etc is versioned with git - command: "git init ." - args: - chdir: /etc - creates: /etc/.git/ - warn: no - register: git_init - tags: - - etc-git +- include: repository.yml + vars: + repository_path: "/etc" + gitignore_items: + - "aliases.db" + - "*.swp" + - "postfix/sa-blacklist.access" + - "postfix/*.db" + - "postfix/spamd.cidr" + - "evobackup/.keep-*" + - "letsencrypt/.certbot.lock" -- name: Git user.email is configured - git_config: - name: user.email - repo: /etc - scope: local - value: "root@{{ ansible_fqdn | default('localhost') }}" - tags: - - etc-git +- name: verify /usr/share/scripts presence + stat: + path: /usr/share/scripts + register: _usr_share_scripts -- name: /etc/.git is restricted to root - file: - path: /etc/.git - owner: root - mode: "0700" - state: directory - tags: - - etc-git +- include: repository.yml + vars: + repository_path: "/usr/share/scripts" + gitignore_items: [] + when: + - _usr_share_scripts.stat.isdir + - ansible_distribution_major_version is version('10', '>=') -- name: /etc/.gitignore is present - copy: - src: gitignore - dest: /etc/.gitignore - owner: root - mode: "0600" - force: no - tags: - - etc-git - -- name: Some entries MUST be in the /etc/.gitignore file - lineinfile: - dest: /etc/.gitignore - line: "{{ item }}" - with_items: - - "aliases.db" - - "*.swp" - - "postfix/sa-blacklist.access" - - "postfix/*.db" - - "postfix/spamd.cidr" - - "evobackup/.keep-*" - - "letsencrypt/.certbot.lock" - tags: - - etc-git - -- name: does /etc/ have any commit? - command: "git log" - args: - chdir: /etc - warn: no - changed_when: False +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" failed_when: False - register: git_log + changed_when: False check_mode: no - tags: - - etc-git - -- name: initial commit is present? - shell: "git add -A . && git commit -m \"Initial commit via Ansible\"" - args: - chdir: /etc - warn: no - register: git_commit - when: git_log.rc != 0 or (git_init is defined and git_init.changed) - tags: - - etc-git + register: is_cron_installed - name: Optimize script is installed in monthly crontab copy: @@ -88,6 +45,7 @@ dest: /etc/cron.monthly/optimize-etc-git mode: "0750" force: no + when: is_cron_installed.rc == 0 tags: - etc-git @@ -96,7 +54,7 @@ src: etc-git-status.j2 dest: /etc/cron.d/etc-git-status mode: "0644" - when: etc_git_monitor_status + when: is_cron_installed.rc == 0 and etc_git_monitor_status tags: - etc-git @@ -104,6 +62,6 @@ file: dest: /etc/cron.d/etc-git-status state: absent - when: not etc_git_monitor_status + when: is_cron_installed.rc == 0 and not etc_git_monitor_status tags: - etc-git diff --git a/etc-git/tasks/repository.yml b/etc-git/tasks/repository.yml new file mode 100644 index 00000000..1430c5bd --- /dev/null +++ b/etc-git/tasks/repository.yml @@ -0,0 +1,73 @@ +--- + +- include_role: + name: evolix/remount-usr + when: repository_path is search ("/usr") + +- name: "{{ repository_path }} is versioned with git" + command: "git init ." + args: + chdir: "{{ repository_path }}" + creates: "{{ repository_path }}/.git/" + warn: no + register: git_init + tags: + - etc-git + +- name: Git user.email is configured + git_config: + name: user.email + repo: "{{ repository_path }}" + scope: local + value: "root@{{ ansible_fqdn | default('localhost') }}" + tags: + - etc-git + +- name: "{{ repository_path }}/.git is restricted to root" + file: + path: "{{ repository_path }}/.git" + owner: root + mode: "0700" + state: directory + tags: + - etc-git + +- name: "{{ repository_path }}/.gitignore is present" + copy: + src: gitignore + dest: "{{ repository_path }}/.gitignore" + owner: root + mode: "0600" + force: no + tags: + - etc-git + +- name: "Some entries MUST be in the {{ repository_path }}/.gitignore file" + lineinfile: + dest: "{{ repository_path }}/.gitignore" + line: "{{ item }}" + with_items: "{{ gitignore_items | default([]) }}" + tags: + - etc-git + +- name: "does {{ repository_path }}/ have any commit?" + command: "git log" + args: + chdir: "{{ repository_path }}" + warn: no + changed_when: False + failed_when: False + register: git_log + check_mode: no + tags: + - etc-git + +- name: initial commit is present? + shell: "git add -A . && git commit -m \"Initial commit via Ansible\"" + args: + chdir: "{{ repository_path }}" + warn: no + register: git_commit + when: git_log.rc != 0 or (git_init is defined and git_init.changed) + tags: + - etc-git diff --git a/evoacme/README.md b/evoacme/README.md index 86886859..467a0c0d 100644 --- a/evoacme/README.md +++ b/evoacme/README.md @@ -1,10 +1,9 @@ # Evoacme 2.0 -EvoAcme is an [Ansible](https://www.ansible.com/) role and a [Certbot](https://certbot.eff.org) wrapper for generate [Let's Encrypt](https://letsencrypt.org/) certificates. +The upstream repository of EvoAcme is at -It is a project hosted at [Evolix's forge](https://gitea.evolix.org/evolix/ansible-roles/) - -Evoacme is open source software licensed under the AGPLv3 License. +Shell scripts are copied from the upstream repository after each release. +No changes must be applied directly here ; patch upstream, release then copy here. ## Install @@ -21,7 +20,7 @@ Evoacme is open source software licensed under the AGPLv3 License. ### 2 - Install evoacme prerequisite with ansible ~~~ -# ansible-playbook playbook.yml -K --diff --check --limit hostname +# ansible-playbook playbook.yml -K --limit hostname ~~~ ### 3 - Include letsencrypt.conf in your webserver diff --git a/evoacme/files/evoacme.sh b/evoacme/files/evoacme.sh index 770cd8d4..2009bd22 100755 --- a/evoacme/files/evoacme.sh +++ b/evoacme/files/evoacme.sh @@ -10,17 +10,33 @@ set -e set -u -usage() { +show_version() { + cat <, + Victor Laborie , + Jérémy Lecour , + Benoit Série + and others. + +evoacme comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU Affero General Public License v3.0 for details. +END +} + +show_help() { cat <&2 echo "${PROGNAME}: $1" - [ "$1" = "invalid argument(s)" ] && >&2 usage + [ "$1" = "invalid argument(s)" ] && >&2 show_help exit 1 } @@ -75,7 +91,7 @@ sed_cert_path_for_nginx() { sed -i "s~${search}~${replace}~" "${vhost_full_path}" debug "Config in ${vhost_full_path} has been updated" $(command -v nginx) -t 2>/dev/null - [ "${?}" -eq 0 ] || $(command -v nginx) -t + [ "${?}" -eq 0 ] || $(command -v nginx) -t -q fi } x509_verify() { @@ -98,7 +114,8 @@ main() { # check arguments [ "$#" -eq 1 ] || error "invalid argument(s)" - [ "$1" = "-h" ] || [ "$1" = "--help" ] && usage && exit 0 + [ "$1" = "-h" ] || [ "$1" = "--help" ] && show_help && exit 0 + [ "$1" = "-V" ] || [ "$1" = "--version" ] && show_version && exit 0 mkdir -p "${ACME_DIR}" chown acme: "${ACME_DIR}" @@ -287,6 +304,8 @@ readonly QUIET=${QUIET:-"0"} readonly TEST=${TEST:-"0"} readonly DRY_RUN=${DRY_RUN:-"0"} +readonly VERSION="19.11" + # Read configuration file, if it exists [ -r /etc/default/evoacme ] && . /etc/default/evoacme diff --git a/evoacme/files/hooks/reload_apache b/evoacme/files/hooks/reload_apache index 2cceb972..1db59b5b 100755 --- a/evoacme/files/hooks/reload_apache +++ b/evoacme/files/hooks/reload_apache @@ -1,6 +1,7 @@ #!/bin/sh readonly PROGNAME=$(basename "$0") +# shellcheck disable=SC2124,SC2034 readonly ARGS=$@ readonly VERBOSE=${VERBOSE:-"0"} @@ -17,6 +18,7 @@ debug() { } if [ -n "$(pidof apache2)" ]; then + # shellcheck disable=SC2091 if $($(command -v apache2ctl) -t 2> /dev/null); then debug "Apache detected... reloading" service apache2 reload diff --git a/evoacme/files/hooks/reload_dovecot b/evoacme/files/hooks/reload_dovecot index 31da4fb4..602e7c85 100755 --- a/evoacme/files/hooks/reload_dovecot +++ b/evoacme/files/hooks/reload_dovecot @@ -1,6 +1,7 @@ #!/bin/sh readonly PROGNAME=$(basename "$0") +# shellcheck disable=SC2124,SC2034 readonly ARGS=$@ readonly VERBOSE=${VERBOSE:-"0"} @@ -17,7 +18,9 @@ debug() { } if [ -n "$(pidof dovecot)" ]; then + # shellcheck disable=SC2091 if $($(command -v doveconf) > /dev/null); then + # shellcheck disable=SC2091 if $($(command -v doveconf)|grep -E "^ssl_cert[^_]"|grep -q "letsencrypt"); then debug "Dovecot detected... reloading" service dovecot reload diff --git a/evoacme/files/hooks/reload_nginx b/evoacme/files/hooks/reload_nginx index 35db3787..48c49da9 100755 --- a/evoacme/files/hooks/reload_nginx +++ b/evoacme/files/hooks/reload_nginx @@ -1,6 +1,7 @@ #!/bin/sh readonly PROGNAME=$(basename "$0") +# shellcheck disable=SC2124,SC2034 readonly ARGS=$@ readonly VERBOSE=${VERBOSE:-"0"} @@ -17,6 +18,7 @@ debug() { } if [ -n "$(pidof nginx)" ]; then + # shellcheck disable=SC2091 if $($(command -v nginx) -t 2> /dev/null); then debug "Nginx detected... reloading" service nginx reload diff --git a/evoacme/files/hooks/reload_postfix b/evoacme/files/hooks/reload_postfix index 50ee20ce..eb17b58e 100755 --- a/evoacme/files/hooks/reload_postfix +++ b/evoacme/files/hooks/reload_postfix @@ -1,6 +1,7 @@ #!/bin/sh readonly PROGNAME=$(basename "$0") +# shellcheck disable=SC2124,SC2034 readonly ARGS=$@ readonly VERBOSE=${VERBOSE:-"0"} @@ -17,7 +18,9 @@ debug() { } if [ -n "$(pidof master)" ]; then + # shellcheck disable=SC2091 if $($(command -v postconf) > /dev/null); then + # shellcheck disable=SC2091 if $($(command -v postconf)|grep -E "^smtpd_tls_cert_file"|grep -q "letsencrypt"); then debug "Postfix detected... reloading" service postfix reload diff --git a/evoacme/files/make-csr.sh b/evoacme/files/make-csr.sh index e2cbb297..b61d5adb 100755 --- a/evoacme/files/make-csr.sh +++ b/evoacme/files/make-csr.sh @@ -9,27 +9,52 @@ set -u -usage() { - cat <, + Victor Laborie , + Jérémy Lecour , + Benoit Série + and others. + +make-csr comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU Affero General Public License v3.0 for details. +END +} + +show_help() { + cat <&2 echo "${PROGNAME}: $1" fi } error() { >&2 echo "${PROGNAME}: $1" + [ "$1" = "invalid argument(s)" ] && >&2 show_help exit 1 } @@ -173,13 +198,15 @@ EOF } main() { + # We must have at least 1 argument + [ "$#" -ge 1 ] || error "invalid argument(s)" + [ "$1" = "-h" ] || [ "$1" = "--help" ] && show_help && exit 0 + [ "$1" = "-V" ] || [ "$1" = "--version" ] && show_version && exit 0 + if [ -t 0 ]; then - # We have STDIN, so we should have at least 2 arguments - if [ "$#" -lt 2 ]; then - >&2 echo "invalid arguments" - >&2 usage - exit 1 - fi + # We have STDIN, so we should have 2 arguments + [ "$#" -eq 2 ] || error "invalid argument(s)" + # read VHOST from first argument VHOST="$1" # remove the first argument @@ -187,12 +214,9 @@ main() { # read domains from remaining arguments DOMAINS=$@ else - # We don't have STDIN, so we should have only 1 argument - if [ "$#" != 1 ]; then - >&2 echo "invalid arguments" - >&2 usage - exit 1 - fi + # We don't have STDIN, so we should have 1 argument + [ "$#" -eq 1 ] || error "invalid argument(s)" + # read VHOST from first argument VHOST="$1" # read domains from input @@ -239,6 +263,9 @@ readonly PROGDIR=$(realpath -m $(dirname "$0")) readonly ARGS=$@ readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly VERSION="19.11" # Read configuration file, if it exists [ -r /etc/default/evoacme ] && . /etc/default/evoacme diff --git a/evoacme/files/vhost-domains.sh b/evoacme/files/vhost-domains.sh index fd25ce86..d2f9c61c 100755 --- a/evoacme/files/vhost-domains.sh +++ b/evoacme/files/vhost-domains.sh @@ -9,27 +9,50 @@ set -u -usage() { +show_version() { + cat <, + Victor Laborie , + Jérémy Lecour , + Benoit Série + and others. + +vhost-domains comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU Affero General Public License v3.0 for details. +END +} + +show_help() { cat <&2 echo "${PROGNAME}: $1" fi } error() { >&2 echo "${PROGNAME}: $1" + [ "$1" = "invalid argument(s)" ] && >&2 show_help exit 1 } @@ -118,14 +141,11 @@ first_vhost_file_found() { } main() { - if [ "$#" != 1 ]; then - >&2 usage - exit 1 - fi - if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then - usage - exit 0 - fi + # check arguments + [ "$#" -eq 1 ] || error "invalid argument(s)" + + [ "$1" = "-h" ] || [ "$1" = "--help" ] && show_help && exit 0 + [ "$1" = "-V" ] || [ "$1" = "--version" ] && show_version && exit 0 local vhost_name=$(basename "$1" .conf) local vhost_file=$(first_vhost_file_found "${vhost_name}") @@ -148,6 +168,10 @@ readonly PROGDIR=$(realpath -m $(dirname "$0")) readonly ARGS=$@ readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +readonly VERSION="19.11" + readonly SRV_IP=${SRV_IP:-""} main $ARGS diff --git a/evoacme/tasks/certbot.yml b/evoacme/tasks/certbot.yml index 003c92d0..5e18cfec 100644 --- a/evoacme/tasks/certbot.yml +++ b/evoacme/tasks/certbot.yml @@ -1,18 +1,19 @@ --- -- block: - - name: install jessie-backports - include_role: - name: apt - tasks_from: backports.yml +- name: Use backports for jessie + block: + - name: install jessie-backports + include_role: + name: evolix/apt + tasks_from: backports.yml - - name: Add exceptions for certbot dependencies - copy: - src: backports-certbot - dest: /etc/apt/preferences.d/z-backports-certbot - notify: apt update + - name: Add exceptions for certbot dependencies + copy: + src: backports-certbot + dest: /etc/apt/preferences.d/z-backports-certbot + notify: apt update - - meta: flush_handlers + - meta: flush_handlers when: ansible_distribution_release == "jessie" - name: Install certbot with apt @@ -21,7 +22,7 @@ state: latest - include_role: - name: remount-usr + name: evolix/remount-usr - name: Remove certbot symlink for apt install file: diff --git a/evoacme/tasks/main.yml b/evoacme/tasks/main.yml index 09edb86c..ac20cabb 100644 --- a/evoacme/tasks/main.yml +++ b/evoacme/tasks/main.yml @@ -3,7 +3,7 @@ - fail: msg: only compatible with Debian >= 8 when: - - ansible_distribution != "Debian" or ansible_distribution_major_version | version_compare('8', '<') + - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') - include: certbot.yml diff --git a/evoacme/templates/nginx.conf.j2 b/evoacme/templates/nginx.conf.j2 index e32da1d5..f76d927d 100644 --- a/evoacme/templates/nginx.conf.j2 +++ b/evoacme/templates/nginx.conf.j2 @@ -1,5 +1,5 @@ location ~ /.well-known/acme-challenge { -{% if ansible_distribution_major_version | version_compare('9', '>=') %} +{% if ansible_distribution_major_version is version('9', '>=') %} alias {{ evoacme_acme_dir }}/; {% else %} alias {{ evoacme_acme_dir }}/.well-known/acme-challenge; diff --git a/evobackup-client/README.md b/evobackup-client/README.md new file mode 100644 index 00000000..18ef132e --- /dev/null +++ b/evobackup-client/README.md @@ -0,0 +1,23 @@ +# evobackup-client + +Allows the configuration of backups to one or more remote filesystems. + +The backup hosts and the ports in use need to be defined in +evobackup-client__hosts before running it. + +The default zzz_evobackup.sh configures a system only backup, but the +template can be overriden to configure a full backup instead. If +you change the variables in defaults/main.yml you can easily run +this again and configure backups to a second set of hosts. + +Do not forget to set the evobackup-client__mail variable to an +email adress you control. + +You can add this example to an installation playbook to create the +ssh key without running the rest of the role. + +~~~ + post_tasks: + - include_role: + name: evobackup-client tasks_from: ssh_key.yml +~~~ diff --git a/evobackup-client/defaults/main.yml b/evobackup-client/defaults/main.yml new file mode 100644 index 00000000..56c0e5d8 --- /dev/null +++ b/evobackup-client/defaults/main.yml @@ -0,0 +1,15 @@ +--- +evobackup_client__root_key_path: "/root/.ssh/id_ed25519" +evobackup_client__root_key_type: "ed25519" +evobackup_client__cron_path: "/etc/cron.daily/zzz_evobackup" +evobackup_client__cron_template_name: "zzz_evobackup" +evobackup_client__mail: null +evobackup_client__servers_fallback: -1 +evobackup_client__pid_path: "/var/run/evobackup.pid" +evobackup_client__log_path: "/var/log/evobackup.log" +evobackup_client__backup_path: "/home/backup" +evobackup_client__hosts: null +# - name: "backups.example.org" +# ip: "xxx.xxx.xxx.xxx" +# fingerprint: "ecdsa-sha2-nistp256 ..." +# port: xxxx diff --git a/evobackup-client/handlers/main.yml b/evobackup-client/handlers/main.yml new file mode 100644 index 00000000..17df304a --- /dev/null +++ b/evobackup-client/handlers/main.yml @@ -0,0 +1,16 @@ +--- +- name: restart minifirewall + command: /etc/init.d/minifirewall restart + register: minifirewall_init_restart + failed_when: "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" + changed_when: "'starting IPTables rules is now finish : OK' in minifirewall_init_restart.stdout" + +- name: 'created new jail' + command: "bkctld restart {{ evolinux_hostname }}" + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + +- name: 'jail updated' + command: "bkctld restart {{ evolinux_hostname }}" +# - "bkctld sync {{ evolinux_hostname }}" + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + when: evobackup_client__hosts|length > 1 diff --git a/evobackup-client/tasks/jail.yml b/evobackup-client/tasks/jail.yml new file mode 100644 index 00000000..fbb6080c --- /dev/null +++ b/evobackup-client/tasks/jail.yml @@ -0,0 +1,57 @@ +--- + +- name: 'create jail' + command: "bkctld init {{ evolinux_hostname }}" + args: + creates: "/backup/jails/{{ evolinux_hostname }}/" + become: true + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + notify: + - 'created new jail' + tags: + - evobackup_client + - evobackup_client_jail + +# temp fix for bkctld 2.x because the ip and key command return 1 +# if the jail is not started, see https://gitea.evolix.org/evolix/evobackup/issues/31 +- name: 'start jail' + command: "bkctld restart {{ evolinux_hostname }}" + become: true + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + tags: + - evobackup_client + - evobackup_client_jail + +- name: 'add ip to jail' + command: "bkctld ip {{ evolinux_hostname }} {{ ansible_host }}" + become: true + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + notify: 'jail updated' + tags: + - evobackup_client + - evobackup_client_jail + +- name: 'add key to jail' + command: "bkctld key {{ evolinux_hostname }} /root/{{ evolinux_hostname }}.pub" + become: true + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + notify: 'jail updated' + tags: + - evobackup_client + - evobackup_client_jail + +- name: 'get jail port' + command: "bkctld port {{ evolinux_hostname }}" + become: true + register: bkctld_port + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + tags: + - evobackup_client + - evobackup_client_jail + +- name: 'register jail port' + set_fact: + evobackup_ssh_port={{ bkctld_port.stdout }} + tags: + - evobackup_client + - evobackup_client_jail diff --git a/evobackup-client/tasks/main.yml b/evobackup-client/tasks/main.yml new file mode 100644 index 00000000..a2dd4405 --- /dev/null +++ b/evobackup-client/tasks/main.yml @@ -0,0 +1,26 @@ +--- + +- include: "ssh_key.yml" + tags: + - evobackup_client + - evobackup_client_backup_ssh_key + +- include: "jail.yml" + tags: + - evobackup_client + - evobackup_client_jail + +- include: "upload_scripts.yml" + tags: + - evobackup_client + - evobackup_client_backup_scripts + +- include: "open_ssh_ports.yml" + tags: + - evobackup_client + - evobackup_client_backup_firewall + +- include: "verify_ssh.yml" + tags: + - evobackup_client + - evobackup_client_backup_hosts diff --git a/evobackup-client/tasks/open_ssh_ports.yml b/evobackup-client/tasks/open_ssh_ports.yml new file mode 100644 index 00000000..be96c161 --- /dev/null +++ b/evobackup-client/tasks/open_ssh_ports.yml @@ -0,0 +1,22 @@ +--- + +- name: Is there a Minifirewall ? + stat: + path: /etc/default/minifirewall + register: evobackup_client__minifirewall + tags: + - evobackup_client + - evobackup_client_backup_firewall + +- name: Add backup SSH port in /etc/default/minifirewall + blockinfile: + dest: /etc/default/minifirewall + marker: "# {mark} {{ item.name }}" + block: | + /sbin/iptables -A INPUT -p tcp --sport {{ item.port }} --dport 1024:65535 -s {{ item.ip }} -m state --state ESTABLISHED,RELATED -j ACCEPT + with_items: "{{ evobackup_client__hosts }}" + notify: restart minifirewall + when: evobackup_client__minifirewall.stat.exists + tags: + - evobackup_client + - evobackup_client_backup_firewall diff --git a/evobackup-client/tasks/ssh_key.yml b/evobackup-client/tasks/ssh_key.yml new file mode 100644 index 00000000..6438634e --- /dev/null +++ b/evobackup-client/tasks/ssh_key.yml @@ -0,0 +1,31 @@ +--- + +- name: Create SSH key + user: + name: root + generate_ssh_key: true + ssh_key_file: "{{ evobackup_client__root_key_path }}" + ssh_key_type: "{{ evobackup_client__root_key_type }}" + register: evobackup_client__root_key + tags: + - evobackup_client + - evobackup_client_backup_ssh_key + +- name: Print SSH key + debug: + var: evobackup_client__root_key.ssh_public_key + when: evobackup_client__root_key.ssh_public_key is defined + tags: + - evobackup_client + - evobackup_client_backup_ssh_key + +- name: 'copy ssh public key to backup server' + copy: + content: "{{ evobackup_client__root_key.ssh_public_key }}" + dest: "/root/{{ evolinux_hostname }}.pub" + become: true + delegate_to: "{{ evobackup_client__hosts[0].ip }}" + tags: + - evobackup_client + - evobackup_client_backup_ssh_key + - evobackup_client_jail diff --git a/evobackup-client/tasks/upload_scripts.yml b/evobackup-client/tasks/upload_scripts.yml new file mode 100644 index 00000000..8d698519 --- /dev/null +++ b/evobackup-client/tasks/upload_scripts.yml @@ -0,0 +1,16 @@ +--- + +- name: Upload evobackup script + template: + src: "{{ item }}" + dest: "{{ evobackup_client__cron_path }}" + force: true + mode: 0755 + with_first_found: + - "templates/evobackup-client/{{ evobackup_client__cron_template_name }}.{{ inventory_hostname }}.sh.j2" + - "templates/evobackup-client/{{ evobackup_client__cron_template_name }}.{{ host_group }}.sh.j2" + - "templates/evobackup-client/{{ evobackup_client__cron_template_name }}.sh.j2" + - "zzz_evobackup.default.sh.j2" + tags: + - evobackup_client + - evobackup_client_backup_scripts diff --git a/evobackup-client/tasks/verify_ssh.yml b/evobackup-client/tasks/verify_ssh.yml new file mode 100644 index 00000000..4e968197 --- /dev/null +++ b/evobackup-client/tasks/verify_ssh.yml @@ -0,0 +1,11 @@ +--- + +- name: Verify evolix backup servers + known_hosts: + path: /root/.ssh/known_hosts + name: "[{{ item.name }}]:{{ item.port }}" + key: "[{{ item.name }}]:{{ item.port }} {{ item.fingerprint }}" + with_list: "{{ evobackup_client__hosts }}" + tags: + - evobackup_client + - evobackup_client_backup_hosts diff --git a/evobackup-client/templates/zzz_evobackup.default.sh.j2 b/evobackup-client/templates/zzz_evobackup.default.sh.j2 new file mode 100644 index 00000000..f3674413 --- /dev/null +++ b/evobackup-client/templates/zzz_evobackup.default.sh.j2 @@ -0,0 +1,305 @@ +#!/bin/sh +# Careful, the zzz_evobackup template was last updated on 2020/04/15 +# +# Script Evobackup client +# See https://gitea.evolix.org/evolix/evobackup +# +# Author: Gregory Colpart +# Contributors: +# Romain Dessort +# Benoît Série +# Tristan Pilat +# Victor Laborie +# Jérémy Lecour +# +# Licence: AGPLv3 +# +# /!\ DON'T FORGET TO SET "MAIL" and "SERVERS" VARIABLES + +# Fail on unassigned variables +set -u + +##### Configuration ################################################### + +# email adress for notifications +MAIL={{ evobackup_client__mail }} + +# list of hosts (hostname or IP) and SSH port for Rsync +SERVERS="{% for host in evobackup_client__hosts %}{{ host.name }}:{{ host.port }}{% if loop.index != loop.length %} {% endif %}{% endfor %}" + +# Should we fallback on servers when the first is unreachable ? +SERVERS_FALLBACK={{ evobackup_client__servers_fallback }} + +# timeout (in seconds) for SSH connections +SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-30} + +## We use /home/backup : feel free to use your own dir +LOCAL_BACKUP_DIR="{{ evobackup_client__backup_path }}" + +# You can set "linux" or "bsd" manually or let it choose automatically +SYSTEM=$(uname | tr '[:upper:]' '[:lower:]') + +# Change these 2 variables if you have more than one backup cron +PIDFILE="{{ evobackup_client__pid_path }}" +LOGFILE="{{ evobackup_client__log_path }}" + +## Enable/Disable tasks +LOCAL_TASKS=${LOCAL_TASKS:-1} +SYNC_TASKS=${SYNC_TASKS:-1} + +##### SETUP AND FUNCTIONS ############################################# + +BEGINNING=$(/bin/date +"%d-%m-%Y ; %H:%M") + +# shellcheck disable=SC2174 +mkdir -p -m 700 ${LOCAL_BACKUP_DIR} + +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin + +## lang = C for english outputs +export LANGUAGE=C +export LANG=C + +## Force umask +umask 077 + +## Initialize variable to store SSH connection errors +SERVERS_SSH_ERRORS="" + +# Call test_server with "HOST:PORT" string +# It will return with 0 if the server is reachable. +# It will return with 1 and a message on stderr if not. +test_server() { + item=$1 + # split HOST and PORT from the input string + host=$(echo "${item}" | cut -d':' -f1) + port=$(echo "${item}" | cut -d':' -f2) + + # Test if the server is accepting connections + ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" -i /root/.ssh/evobackup_id "${host}" -p "${port}" -t "exit" + # shellcheck disable=SC2181 + if [ $? = 0 ]; then + # SSH connection is OK + return 0 + else + # SSH connection failed + new_error=$(printf "Failed to connect to \`%s' within %s seconds" "${item}" "${SSH_CONNECT_TIMEOUT}") + SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') + + return 1 + fi +} +# Call pick_server with an optional positive integer to get the nth server in the list. +pick_server() { + increment=${1:-0} + list_length=$(echo "${SERVERS}" | wc -w) + + if [ "${increment}" -ge "${list_length}" ]; then + # We've reached the end of the list + new_error="No more server available" + SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d') + + # Log errors to stderr + printf "%s\\n" "${SERVERS_SSH_ERRORS}" >&2 + # Log errors to logfile + printf "%s\\n" "${SERVERS_SSH_ERRORS}" >> $LOGFILE + return 1 + fi + + # Extract the day of month, without leading 0 (which would give an octal based number) + today=$(date +%e) + # A salt is useful to randomize the starting point in the list + # but stay identical each time it's called for a server (based on hostname). + salt=$(hostname | cksum | cut -d' ' -f1) + # Pick an integer between 0 and the length of the SERVERS list + # It changes each day + item=$(( (today + salt + increment) % list_length )) + # cut starts counting fields at 1, not 0. + field=$(( item + 1 )) + + echo "${SERVERS}" | cut -d' ' -f${field} +} + +## Verify other evobackup process and kill if needed +if [ -e "${PIDFILE}" ]; then + pid=$(cat "${PIDFILE}") + # Does process still exist ? + if kill -0 "${pid}" 2> /dev/null; then + # Killing the childs of evobackup. + for ppid in $(pgrep -P "${pid}"); do + kill -9 "${ppid}"; + done + # Then kill the main PID. + kill -9 "${pid}" + printf "%s is still running (PID %s). Process has been killed" "$0" "${pid}\\n" >&2 + else + rm -f ${PIDFILE} + fi +fi +echo "$$" > ${PIDFILE} +# shellcheck disable=SC2064 +trap "rm -f ${PIDFILE}" EXIT + + +##### LOCAL BACKUP #################################################### + +if [ "${LOCAL_TASKS}" = "1" ]; then + ## Dump system and kernel versions + uname -a > ${LOCAL_BACKUP_DIR}/uname + + ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls) + for addr in 8.8.8.8 www.evolix.fr travaux.evolix.net; do + mtr -r ${addr} > ${LOCAL_BACKUP_DIR}/mtr-${addr} + traceroute -n ${addr} > ${LOCAL_BACKUP_DIR}/traceroute-${addr} 2>&1 + done + + ## Dump process with ps + ps auwwx >${LOCAL_BACKUP_DIR}/ps.out + + if [ "${SYSTEM}" = "linux" ]; then + ## Dump network connections with ss + ss -taupen > ${LOCAL_BACKUP_DIR}/netstat.out + + ## List Debian packages + dpkg -l > ${LOCAL_BACKUP_DIR}/packages + dpkg --get-selections > ${LOCAL_BACKUP_DIR}/packages.getselections + apt-cache dumpavail > ${LOCAL_BACKUP_DIR}/packages.available + + ## Dump MBR / table partitions + disks=$(lsblk -l | grep disk | grep -v -E '(drbd|fd[0-9]+)' | awk '{print $1}') + for disk in ${disks}; do + dd if="/dev/${disk}" of="${LOCAL_BACKUP_DIR}/MBR-${disk}" bs=512 count=1 2>&1 | grep -Ev "(records in|records out|512 bytes)" + fdisk -l "/dev/${disk}" > "${LOCAL_BACKUP_DIR}/partitions-${disk}" 2>&1 + done + cat ${LOCAL_BACKUP_DIR}/partitions-* > ${LOCAL_BACKUP_DIR}/partitions + + ## Dump iptables + if [ -x /sbin/iptables ]; then + { /sbin/iptables -L -n -v; /sbin/iptables -t filter -L -n -v; } > ${LOCAL_BACKUP_DIR}/iptables.txt + fi + + ## Dump findmnt(8) output + FINDMNT_BIN=$(command -v findmnt) + if [ -x "${FINDMNT_BIN}" ]; then + ${FINDMNT_BIN} > ${LOCAL_BACKUP_DIR}/findmnt.txt + fi + else + ## Dump network connections with netstat + netstat -finet -atn > ${LOCAL_BACKUP_DIR}/netstat.out + + ## List OpenBSD packages + pkg_info -m > ${LOCAL_BACKUP_DIR}/packages + + ## Dump MBR / table partitions + disklabel sd0 > ${LOCAL_BACKUP_DIR}/partitions + + ## Dump pf infos + pfctl -sa > ${LOCAL_BACKUP_DIR}/pfctl-sa.txt + + fi + + ## Dump rights + #getfacl -R /var > ${LOCAL_BACKUP_DIR}/rights-var.txt + #getfacl -R /etc > ${LOCAL_BACKUP_DIR}/rights-etc.txt + #getfacl -R /usr > ${LOCAL_BACKUP_DIR}/rights-usr.txt + #getfacl -R /home > ${LOCAL_BACKUP_DIR}/rights-home.txt + +fi + +##### REMOTE BACKUP ################################################### + +n=0 +server="" +if [ "${SERVERS_FALLBACK}" = "1" ]; then + # We try to find a suitable server + while :; do + server=$(pick_server "${n}") + test $? = 0 || exit 2 + + if test_server "${server}"; then + break + else + server="" + n=$(( n + 1 )) + fi + done +else + # we force the server + server=$(pick_server "${n}") +fi + +SSH_SERVER=$(echo "${server}" | cut -d':' -f1) +SSH_PORT=$(echo "${server}" | cut -d':' -f2) + +HOSTNAME=$(hostname) + +if [ "${SYSTEM}" = "linux" ]; then + rep="/bin /boot /lib /opt /sbin /usr /srv" +else + rep="/bsd /bin /sbin /usr" +fi + + +if [ "${SYNC_TASKS}" = "1" ]; then + # /!\ DO NOT USE COMMENTS in the rsync command /!\ + # It breaks the command and destroys data, simply remove (or add) lines. + + # Remote shell command + RSH_COMMAND="ssh -i {{ evobackup_client__root_key_path }} -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'" + + # ignore check because we want it to split the different arguments to $rep + # shellcheck disable=SC2086 + rsync -avzh --stats --delete --delete-excluded --force --ignore-errors --partial \ + --exclude "lost+found" \ + --exclude ".nfs.*" \ + --exclude "/var/log" \ + --exclude "/var/log/evobackup*" \ + --exclude "/var/lib/mysql" \ + --exclude "/var/lib/postgres" \ + --exclude "/var/lib/postgresql" \ + --exclude "/var/lib/sympa" \ + --exclude "/var/lib/metche" \ + --exclude "/var/run" \ + --exclude "/var/lock" \ + --exclude "/var/state" \ + --exclude "/var/apt" \ + --exclude "/var/cache" \ + --exclude "/usr/src" \ + --exclude "/usr/doc" \ + --exclude "/usr/share/doc" \ + --exclude "/usr/obj" \ + --exclude "dev" \ + --exclude "/var/spool/postfix" \ + --exclude "/var/lib/amavis/amavisd.sock" \ + --exclude "/var/lib/munin/*tmp*" \ + --exclude "/var/lib/php5" \ + --exclude "/var/spool/squid" \ + --exclude "/var/lib/elasticsearch" \ + --exclude "/var/lib/amavis/tmp" \ + --exclude "/var/lib/clamav/*.tmp" \ + --exclude "/home/mysqltmp" \ + --exclude "/var/lib/php/sessions" \ + ${rep} \ + /etc \ + /root \ + /var \ + -e "${RSH_COMMAND}" \ + "root@${SSH_SERVER}:/var/backup/" \ + | tail -30 >> $LOGFILE +fi + +##### REPORTING ####################################################### + +END=$(/bin/date +"%d-%m-%Y ; %H:%M") + +printf "EvoBackup - %s - START %s ON %s (LOCAL_TASKS=%s SYNC_TASKS=%s)\\n" \ + "${HOSTNAME}" "${BEGINNING}" "${SSH_SERVER}" "${LOCAL_TASKS}" "${SYNC_TASKS}" \ + >> $LOGFILE + +printf "EvoBackup - %s - STOP %s ON %s (LOCAL_TASKS=%s SYNC_TASKS=%s)\\n" \ + "${HOSTNAME}" "${END}" "${SSH_SERVER}" "${LOCAL_TASKS}" "${SYNC_TASKS}" \ + >> $LOGFILE + +tail -10 $LOGFILE | \ + mail -s "[info] EvoBackup - Client ${HOSTNAME}" \ + ${MAIL} diff --git a/evocheck/README.md b/evocheck/README.md index 807b9a44..ce9999c0 100644 --- a/evocheck/README.md +++ b/evocheck/README.md @@ -10,7 +10,7 @@ A separate `exec.yml` file can be imported manually in playbooks or roles to exe ``` - include_role: - name: evocheck + name: evolix/evocheck tasks_from: exec.yml ``` ## Variables diff --git a/evocheck/files/evocheck.sh b/evocheck/files/evocheck.sh index 857b7919..5c3f1365 100644 --- a/evocheck/files/evocheck.sh +++ b/evocheck/files/evocheck.sh @@ -4,6 +4,8 @@ # Script to verify compliance of a Debian/OpenBSD server # powered by Evolix +readonly VERSION="20.04.3" + # base functions show_version() { @@ -59,6 +61,7 @@ detect_os() { 7) DEBIAN_RELEASE="wheezy";; 8) DEBIAN_RELEASE="jessie";; 9) DEBIAN_RELEASE="stretch";; + 10) DEBIAN_RELEASE="buster";; esac fi elif [ "$(uname -s)" = "OpenBSD" ]; then @@ -85,6 +88,9 @@ is_debian_jessie() { is_debian_stretch() { test "${DEBIAN_RELEASE}" = "stretch" } +is_debian_buster() { + test "${DEBIAN_RELEASE}" = "buster" +} debian_release() { printf "%s" "${DEBIAN_RELEASE}" } @@ -159,7 +165,7 @@ check_dpkgwarning() { test -e /etc/apt/apt.conf \ && failed "IS_DPKGWARNING" "/etc/apt/apt.conf is missing" fi - elif is_debian_stretch; then + elif is_debian_stretch || is_debian_buster; then test -e /etc/apt/apt.conf.d/z-evolinux.conf \ || failed "IS_DPKGWARNING" "/etc/apt/apt.conf.d/z-evolinux.conf is missing" fi @@ -196,7 +202,7 @@ check_modsecurity() { fi } check_customsudoers() { - grep -E -qr "umask=0077" /etc/sudoers* || failed "IS_CUSTOMSUDOERS" + grep -E -qr "umask=0077" /etc/sudoers* || failed "IS_CUSTOMSUDOERS" "missing umask=0077 in sudoers file" } check_vartmpfs() { df /var/tmp | grep -q tmpfs || failed "IS_VARTMPFS" "/var/tmp is not a tmpfs" @@ -208,29 +214,30 @@ check_serveurbase() { is_installed serveur-base || failed "IS_SERVEURBASE" "serveur-base package is not installed" } check_logrotateconf() { - test -e /etc/logrotate.d/zsyslog || failed "IS_LOGROTATECONF" + test -e /etc/logrotate.d/zsyslog || failed "IS_LOGROTATECONF" "missing zsyslog in logrotate.d" } check_syslogconf() { grep -q "^# Syslog for Pack Evolix serveur" /etc/*syslog.conf \ - || failed "IS_SYSLOGCONF" + || failed "IS_SYSLOGCONF" "syslog evolix config file missing" } check_debiansecurity() { grep -q "^deb.*security" /etc/apt/sources.list \ - || failed "IS_DEBIANSECURITY" + || failed "IS_DEBIANSECURITY" "missing debian security repository" } check_aptitudeonly() { if is_debian_squeeze || is_debian_wheezy; then - test -e /usr/bin/apt-get && failed "IS_APTITUDEONLY" + test -e /usr/bin/apt-get && failed "IS_APTITUDEONLY" \ + "only aptitude may be enabled on Debian <=7, apt-get should be disabled" fi } check_aptitude() { - if is_debian_jessie || is_debian_stretch; then - test -e /usr/bin/aptitude && failed "IS_APTITUDE" + if is_debian_jessie || is_debian_stretch || is_debian_buster; then + test -e /usr/bin/aptitude && failed "IS_APTITUDE" "aptitude may not be installed on Debian >=8" fi } check_aptgetbak() { - if is_debian_jessie || is_debian_stretch; then - test -e /usr/bin/apt-get.bak && failed "IS_APTGETBAK" + if is_debian_jessie || is_debian_stretch || is_debian_buster; then + test -e /usr/bin/apt-get.bak && failed "IS_APTGETBAK" "missing dpkg-divert apt-get.bak" fi } check_apticron() { @@ -240,28 +247,35 @@ check_apticron() { test "$status" = "fail" || test -e /usr/bin/apt-get.bak || status="fail" if is_debian_squeeze || is_debian_wheezy; then - test "$status" = "fail" && failed "IS_APTICRON" + test "$status" = "fail" && failed "IS_APTICRON" "apticron must be in cron.d not cron.daily" fi } check_usrro() { - grep /usr /etc/fstab | grep -q ro || failed "IS_USRRO" + grep /usr /etc/fstab | grep -qE "\bro\b" || failed "IS_USRRO" "missing ro directive on fstab for /usr" } check_tmpnoexec() { - mount | grep "on /tmp" | grep -q noexec || failed "IS_TMPNOEXEC" + FINDMNT_BIN=$(command -v findmnt) + if [ -x "${FINDMNT_BIN}" ]; then + options=$(${FINDMNT_BIN} --noheadings --first-only --output OPTIONS /tmp) + echo "${options}" | grep -qE "\bnoexec\b" || failed "IS_TMPNOEXEC" "/tmp is not mounted with 'noexec'" + else + mount | grep "on /tmp" | grep -qE "\bnoexec\b" || failed "IS_TMPNOEXEC" "/tmp is not mounted with 'noexec' (WARNING: findmnt(8) is not found)" + fi } check_mountfstab() { # Test if lsblk available, if not skip this test... LSBLK_BIN=$(command -v lsblk) if test -x "${LSBLK_BIN}"; then for mountPoint in $(${LSBLK_BIN} -o MOUNTPOINT -l -n | grep '/'); do - grep -Eq "$mountPoint\W" /etc/fstab || failed "IS_MOUNT_FSTAB" + grep -Eq "$mountPoint\W" /etc/fstab \ + || failed "IS_MOUNT_FSTAB" "partition(s) detected mounted but no presence in fstab" done fi } check_listchangesconf() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then if is_installed apt-listchanges; then - failed "IS_LISTCHANGESCONF" "apt-listchanges must not be installed on Stretch" + failed "IS_LISTCHANGESCONF" "apt-listchanges must not be installed on Debian >=9" fi else if [ -e "/etc/apt/listchanges.conf" ]; then @@ -276,72 +290,86 @@ check_listchangesconf() { } check_customcrontab() { found_lines=$(grep -c -E "^(17 \*|25 6|47 6|52 6)" /etc/crontab) - test "$found_lines" = 4 && failed "IS_CUSTOMCRONTAB" + test "$found_lines" = 4 && failed "IS_CUSTOMCRONTAB" "missing custom field in crontab" } check_sshallowusers() { - grep -E -qi "(AllowUsers|AllowGroups)" /etc/ssh/sshd_config || failed "IS_SSHALLOWUSERS" + grep -E -qi "(AllowUsers|AllowGroups)" /etc/ssh/sshd_config \ + || failed "IS_SSHALLOWUSERS" "missing AllowUsers or AllowGroups directive in sshd_config" } check_diskperf() { - test -e /root/disk-perf.txt || failed "IS_DISKPERF" + perfFile="/root/disk-perf.txt" + test -e $perfFile || failed "IS_DISKPERF" "missing ${perfFile}" } check_tmoutprofile() { grep -sq "TMOUT=" /etc/profile /etc/profile.d/evolinux.sh || failed "IS_TMOUTPROFILE" "TMOUT is not set" } check_alert5boot() { - if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then - grep -q "^date" /etc/rc2.d/S*alert5 || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 init script" + if is_debian_buster; then + grep -qs "^date" /usr/share/scripts/alert5.sh || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 init script" + test -f /etc/systemd/system/alert5.service || failed "IS_ALERT5BOOT" "alert5 unit file is missing" + systemctl is-enabled alert5 -q || failed "IS_ALERT5BOOT" "alert5 unit is not enabled" else - failed "IS_ALERT5BOOT" "alert5 init script is missing" + if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then + grep -q "^date" /etc/rc2.d/S*alert5 || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 init script" + else + failed "IS_ALERT5BOOT" "alert5 init script is missing" + fi fi } check_alert5minifw() { - if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then - grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 \ - || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + if is_debian_buster; then + grep -qs "^/etc/init.d/minifirewall" /usr/share/scripts/alert5.sh \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 script or script is missing" else - failed "IS_ALERT5MINIFW" "alert5 init script is missing" + if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then + grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + else + failed "IS_ALERT5MINIFW" "alert5 init script is missing" + fi fi } check_minifw() { /sbin/iptables -L -n | grep -q -E "^ACCEPT\s*all\s*--\s*31\.170\.8\.4\s*0\.0\.0\.0/0\s*$" \ - || failed "IS_MINIFW" + || failed "IS_MINIFW" "minifirewall seems not starded" } check_nrpeperms() { if [ -d /etc/nagios ]; then - actual=$(stat --format "%a" /etc/nagios) + nagiosDir="/etc/nagios" + actual=$(stat --format "%a" $nagiosDir) expected="750" - test "$expected" = "$actual" || failed "IS_NRPEPERMS" + test "$expected" = "$actual" || failed "IS_NRPEPERMS" "${nagiosDir} must be ${expected}" fi } check_minifwperms() { if [ -f "$MINIFW_FILE" ]; then actual=$(stat --format "%a" "$MINIFW_FILE") expected="600" - test "$expected" = "$actual" || failed "IS_MINIFWPERMS" + test "$expected" = "$actual" || failed "IS_MINIFWPERMS" "${MINIFW_FILE} must be ${expected}" fi } check_nrpedisks() { NRPEDISKS=$(grep command.check_disk /etc/nagios/nrpe.cfg | grep "^command.check_disk[0-9]" | sed -e "s/^command.check_disk\([0-9]\+\).*/\1/" | sort -n | tail -1) DFDISKS=$(df -Pl | grep -c -E -v "(^Filesystem|/lib/init/rw|/dev/shm|udev|rpc_pipefs)") - test "$NRPEDISKS" = "$DFDISKS" || failed "IS_NRPEDISKS" + test "$NRPEDISKS" = "$DFDISKS" || failed "IS_NRPEDISKS" "there must be $DFDISKS check_disk in nrpe.cfg" } check_nrpepid() { if ! is_debian_squeeze; then { test -e /etc/nagios/nrpe.cfg \ && grep -q "^pid_file=/var/run/nagios/nrpe.pid" /etc/nagios/nrpe.cfg; - } || failed "IS_NRPEPID" + } || failed "IS_NRPEPID" "missing or wrong pid_file directive in nrpe.cfg" fi } check_grsecprocs() { if uname -a | grep -q grsec; then { grep -q "^command.check_total_procs..sudo" /etc/nagios/nrpe.cfg \ && grep -A1 "^\[processes\]" /etc/munin/plugin-conf.d/munin-node | grep -q "^user root"; - } || failed "IS_GRSECPROCS" + } || failed "IS_GRSECPROCS" "missing munin's plugin processes directive for grsec" fi } check_apachemunin() { if test -e /etc/apache2/apache2.conf; then - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then { test -h /etc/apache2/mods-enabled/status.load \ && test -h /etc/munin/plugins/apache_accesses \ && test -h /etc/munin/plugins/apache_processes \ @@ -381,25 +409,26 @@ check_raidsoft() { { grep -q "^AUTOCHECK=true" /etc/default/mdadm \ && grep -q "^START_DAEMON=true" /etc/default/mdadm \ && grep -qv "^MAILADDR ___MAIL___" /etc/mdadm/mdadm.conf; - } || failed "IS_RAIDSOFT" + } || failed "IS_RAIDSOFT" "missing or wrong config for mdadm" fi } # Verification du LogFormat de AWStats check_awstatslogformat() { if is_installed apache2 awstats; then - grep -qE '^LogFormat=1' /etc/awstats/awstats.conf.local \ - || failed "IS_AWSTATSLOGFORMAT" + awstatsFile="/etc/awstats/awstats.conf.local" + grep -qE '^LogFormat=1' $awstatsFile \ + || failed "IS_AWSTATSLOGFORMAT" "missing or wrong LogFormat directive in $awstatsFile" fi } # Verification de la présence de la config logrotate pour Munin check_muninlogrotate() { { test -e /etc/logrotate.d/munin-node \ && test -e /etc/logrotate.d/munin; - } || failed "IS_MUNINLOGROTATE" + } || failed "IS_MUNINLOGROTATE" "missing lorotate file for munin" } # Verification de l'activation de Squid dans le cas d'un pack mail check_squid() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then squidconffile="/etc/squid/evolinux-custom.conf" else squidconffile="/etc/squid*/squid.conf" @@ -407,19 +436,20 @@ check_squid() { if is_pack_web && (is_installed squid || is_installed squid3); then host=$(hostname -i) # shellcheck disable=SC2086 - http_port=$(grep "http_port" $squidconffile | cut -f 2 -d " ") + http_port=$(grep -E "^http_port\s+[0-9]+" $squidconffile | awk '{ print $2 }') { grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" "$MINIFW_FILE" \ && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d $host -j ACCEPT" "$MINIFW_FILE" \ && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.(1|0/8) -j ACCEPT" "$MINIFW_FILE" \ && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port.* $http_port" "$MINIFW_FILE"; - } || failed "IS_SQUID" + } || failed "IS_SQUID" "missing squid rules in minifirewall" fi } check_evomaintenance_fw() { if [ -f "$MINIFW_FILE" ]; then + hook_db=$(grep -E '^\s*HOOK_DB' /etc/evomaintenance.cf | tr -d ' ' | cut -d= -f2) rulesNumber=$(grep -c "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s .* -m state --state ESTABLISHED,RELATED -j ACCEPT" "$MINIFW_FILE") - if [ "$rulesNumber" -lt 2 ]; then - failed "IS_EVOMAINTENANCE_FW" + if [ "$hook_db" = "1" ] && [ "$rulesNumber" -lt 2 ]; then + failed "IS_EVOMAINTENANCE_FW" "HOOK_DB is enabled but missing evomaintenance rules in minifirewall" fi fi } @@ -430,36 +460,36 @@ check_moddeflate() { { test -e $f && grep -q "AddOutputFilterByType DEFLATE text/html text/plain text/xml" $f \ && grep -q "AddOutputFilterByType DEFLATE text/css" $f \ && grep -q "AddOutputFilterByType DEFLATE application/x-javascript application/javascript" $f; - } || failed "IS_MODDEFLATE" + } || failed "IS_MODDEFLATE" "missing AddOutputFilterByType directive for apache mod deflate" fi } # Verification de la conf log2mail check_log2mailrunning() { if is_pack_web && is_installed log2mail; then - pgrep log2mail >/dev/null || failed 'IS_LOG2MAILRUNNING' + pgrep log2mail >/dev/null || failed "IS_LOG2MAILRUNNING" "log2mail is not running" fi } check_log2mailapache() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then conf=/etc/log2mail/config/apache else conf=/etc/log2mail/config/default fi if is_pack_web && is_installed log2mail; then grep -s -q "^file = /var/log/apache2/error.log" $conf \ - || failed "IS_LOG2MAILAPACHE" + || failed "IS_LOG2MAILAPACHE" "missing log2mail directive for apache" fi } check_log2mailmysql() { if is_pack_web && is_installed log2mail; then grep -s -q "^file = /var/log/syslog" /etc/log2mail/config/{default,mysql,mysql.conf} \ - || failed "IS_LOG2MAILMYSQL" + || failed "IS_LOG2MAILMYSQL" "missing log2mail directive for mysql" fi } check_log2mailsquid() { if is_pack_web && is_installed log2mail; then grep -s -q "^file = /var/log/squid.*/access.log" /etc/log2mail/config/* \ - || failed "IS_LOG2MAILSQUID" + || failed "IS_LOG2MAILSQUID" "missing log2mail directive for squid" fi } # Verification si bind est chroote @@ -470,7 +500,7 @@ check_bindchroot() { md5_original=$(md5sum /usr/sbin/named | cut -f 1 -d ' ') md5_chrooted=$(md5sum /var/chroot-bind/usr/sbin/named | cut -f 1 -d ' ') if [ "$md5_original" != "$md5_chrooted" ]; then - failed "IS_BINDCHROOT" "The chrooted bind binary is differet than the original binary" + failed "IS_BINDCHROOT" "the chrooted bind binary is different than the original binary" fi else failed "IS_BINDCHROOT" "bind process is not chrooted" @@ -482,11 +512,11 @@ check_bindchroot() { check_repvolatile() { if is_debian_lenny; then grep -qE "^deb http://volatile.debian.org/debian-volatile" /etc/apt/sources.list \ - || failed "IS_REPVOLATILE" + || failed "IS_REPVOLATILE" "missing debian-volatile repository" fi if is_debian_squeeze; then grep -qE "^deb.*squeeze-updates" /etc/apt/sources.list \ - || failed "IS_REPVOLATILE" + || failed "IS_REPVOLATILE" "missing squeeze-updates repository" fi } # /etc/network/interfaces should be present, we don't manage systemd-network yet @@ -499,7 +529,7 @@ check_network_interfaces() { } # Verify if all if are in auto check_autoif() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then interfaces=$(/sbin/ip address show up | grep "^[0-9]*:" | grep -E -v "(lo|vnet|docker|veth|tun|tap|macvtap)" | cut -d " " -f 2 | tr -d : | cut -d@ -f1 | tr "\n" " ") else interfaces=$(/sbin/ifconfig -s | tail -n +2 | grep -E -v "^(lo|vnet|docker|veth|tun|tap|macvtap)" | cut -d " " -f 1 |tr "\n" " ") @@ -521,18 +551,33 @@ check_interfacesgw() { # Verification de la mise en place d'evobackup check_evobackup() { evobackup_found=$(find /etc/cron* -name '*evobackup*' | wc -l) - test "$evobackup_found" -gt 0 || failed "IS_EVOBACKUP" + test "$evobackup_found" -gt 0 || failed "IS_EVOBACKUP" "missing evobackup cron" +} +# Vérification de l'exclusion des montages (NFS) dans les sauvegardes +check_evobackup_exclude_mount() { + excludes_file=$(mktemp) + # shellcheck disable=SC2064 + trap "rm -f ${excludes_file}" 0 + # shellcheck disable=SC2044 + for evobackup_file in $(find /etc/cron* -name '*evobackup*'); do + grep -- "--exclude " "${evobackup_file}" | grep -E -o "\"[^\"]+\"" | tr -d '"' > "${excludes_file}" + not_excluded=$(findmnt --type nfs,nfs4,fuse.sshfs, -o target --noheadings | grep -v -f "${excludes_file}") + for mount in ${not_excluded}; do + failed "IS_EVOBACKUP_EXCLUDE_MOUNT" "${mount} is not excluded from ${evobackup_file} backup script" + done + done } # Verification de la presence du userlogrotate check_userlogrotate() { if is_pack_web; then - test -x /etc/cron.weekly/userlogrotate || failed "IS_USERLOGROTATE" + test -x /etc/cron.weekly/userlogrotate || failed "IS_USERLOGROTATE" "missing userlogrotate cron" fi } # Verification de la syntaxe de la conf d'Apache check_apachectl() { if is_installed apache2; then - /usr/sbin/apache2ctl configtest 2>&1 | grep -q "^Syntax OK$" || failed "IS_APACHECTL" + /usr/sbin/apache2ctl configtest 2>&1 | grep -q "^Syntax OK$" \ + || failed "IS_APACHECTL" "apache errors detected, run a configtest" fi } # Check if there is regular files in Apache sites-enabled. @@ -559,7 +604,7 @@ check_apacheipinallow() { | grep -iv "from all" \ | grep -iv "env=" \ | perl -ne 'exit 1 unless (/from( [\da-f:.\/]+)+$/i)' \ - || failed "IS_APACHEIPINALLOW" + || failed "IS_APACHEIPINALLOW" "bad (Allow|Deny) directives in apache" fi } # Check if default Apache configuration file for munin is absent (or empty or commented). @@ -570,7 +615,8 @@ check_muninapacheconf() { muninconf="/etc/apache2/conf-available/munin.conf" fi if is_installed apache2; then - test -e $muninconf && grep -vEq "^( |\t)*#" "$muninconf" && failed "IS_MUNINAPACHECONF" + test -e $muninconf && grep -vEq "^( |\t)*#" "$muninconf" \ + && failed "IS_MUNINAPACHECONF" "default munin configuration may be commented or disabled" fi } # Verification de la priorité du package samba si les backports sont utilisés @@ -578,7 +624,7 @@ check_sambainpriority() { if is_debian_lenny && is_pack_samba; then if grep -qrE "^[^#].*backport" /etc/apt/sources.list{,.d}; then priority=$(grep -E -A2 "^Package:.*samba" /etc/apt/preferences | grep -A1 "^Pin: release a=lenny-backports" | grep "^Pin-Priority:" | cut -f2 -d" ") - test "$priority" -gt 500 || failed "IS_SAMBAPINPRIORITY" + test "$priority" -gt 500 || failed "IS_SAMBAPINPRIORITY" "bad pinning priority for samba" fi fi } @@ -589,7 +635,7 @@ check_kerneluptodate() { kernel_installed_at=$(date -d "$(ls --full-time -lcrt /boot | tail -n1 | awk '{print $6}')" +%s) last_reboot_at=$(($(date +%s) - $(cut -f1 -d '.' /proc/uptime))) if [ "$kernel_installed_at" -gt "$last_reboot_at" ]; then - failed "IS_KERNELUPTODATE" + failed "IS_KERNELUPTODATE" "machine is running an outdated kernel, reboot advised" fi fi } @@ -599,7 +645,7 @@ check_uptime() { limit=$(date -d "now - 2 year" +%s) last_reboot_at=$(($(date +%s) - $(cut -f1 -d '.' /proc/uptime))) if [ "$limit" -gt "$last_reboot_at" ]; then - failed "IS_UPTIME" + failed "IS_UPTIME" "machine has an uptime of more than 2 years, reboot on new kernel advised" fi fi } @@ -629,9 +675,10 @@ check_muninrunning() { } # Check if files in /home/backup/ are up-to-date check_backupuptodate() { - if [ -d /home/backup/ ]; then - if [ -n "$(ls -A /home/backup/)" ]; then - for file in /home/backup/*; do + backup_dir="/home/backup" + if [ -d "${backup_dir}" ]; then + if [ -n "$(ls -A ${backup_dir})" ]; then + for file in ${backup_dir}/*; do limit=$(date +"%s" -d "now - 2 day") updated_at=$(stat -c "%Y" "$file") @@ -641,21 +688,24 @@ check_backupuptodate() { fi done else - failed "IS_BACKUPUPTODATE" "/home/backup/ is empty" + failed "IS_BACKUPUPTODATE" "${backup_dir}/ is empty" fi else - failed "IS_BACKUPUPTODATE" "/home/backup/ is missing" + failed "IS_BACKUPUPTODATE" "${backup_dir}/ is missing" fi } check_etcgit() { - (cd /etc; git rev-parse --is-inside-work-tree > /dev/null 2>&1) || failed "IS_ETCGIT" "/etc is not a Git repository" + export GIT_DIR="/etc/.git" GIT_WORK_TREE="/etc" + git rev-parse --is-inside-work-tree > /dev/null 2>&1 \ + || failed "IS_ETCGIT" "/etc is not a git repository" } # Check if /etc/.git/ has read/write permissions for root only. check_gitperms() { - if test -d /etc/.git; then + GIT_DIR="/etc/.git" + if test -d $GIT_DIR; then expected="700" - actual=$(stat -c "%a" /etc/.git/) - [ "$expected" = "$actual" ] || failed "IS_GITPERMS" + actual=$(stat -c "%a" $GIT_DIR) + [ "$expected" = "$actual" ] || failed "IS_GITPERMS" "$GIT_DIR must be $expected" fi } # Check if no package has been upgraded since $limit. @@ -695,6 +745,7 @@ check_notupgraded() { check_tune2fs_m5() { min=5 parts=$(grep -E "ext(3|4)" /proc/mounts | cut -d ' ' -f1 | tr -s '\n' ' ') + FINDMNT_BIN=$(command -v findmnt) for part in $parts; do blockCount=$(dumpe2fs -h "$part" 2>/dev/null | grep -e "Block count:" | grep -Eo "[0-9]+") # If buggy partition, skip it. @@ -707,20 +758,25 @@ check_tune2fs_m5() { percentage=$(awk "BEGIN { pc=100*${reservedBlockCount}/${blockCount}; i=int(pc); print (pc-i<0.5)?i:i+1 }") if [ "$percentage" -lt "${min}" ]; then - failed "IS_TUNE2FS_M5" "Partition ${part} has less than ${min}% reserved blocks (${percentage}%)" + if [ -x "${FINDMNT_BIN}" ]; then + mount=$(${FINDMNT_BIN} --noheadings --first-only --output TARGET "${part}") + else + mount="unknown mount point" + fi + failed "IS_TUNE2FS_M5" "Partition ${part} (${mount}) has less than ${min}% reserved blocks (${percentage}%)" fi done } check_evolinuxsudogroup() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then if grep -q "^evolinux-sudo:" /etc/group; then - grep -q '^%evolinux-sudo ALL=(ALL:ALL) ALL' /etc/sudoers.d/evolinux \ - || failed "IS_EVOLINUXSUDOGROUP" + grep -qE '^%evolinux-sudo +ALL ?= ?\(ALL:ALL\) ALL' /etc/sudoers.d/evolinux \ + || failed "IS_EVOLINUXSUDOGROUP" "missing evolinux-sudo directive in sudoers file" fi fi } check_userinadmgroup() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then users=$(grep "^evolinux-sudo:" /etc/group | awk -F: '{print $4}' | tr ',' ' ') for user in $users; do if ! groups "$user" | grep -q adm; then @@ -731,15 +787,17 @@ check_userinadmgroup() { fi } check_apache2evolinuxconf() { - if is_debian_stretch && test -d /etc/apache2; then - { test -L /etc/apache2/conf-enabled/z-evolinux-defaults.conf \ - && test -L /etc/apache2/conf-enabled/zzz-evolinux-custom.conf \ - && test -f /etc/apache2/ipaddr_whitelist.conf; - } || failed "IS_APACHE2EVOLINUXCONF" + if is_debian_stretch || is_debian_buster; then + if test -d /etc/apache2; then + { test -L /etc/apache2/conf-enabled/z-evolinux-defaults.conf \ + && test -L /etc/apache2/conf-enabled/zzz-evolinux-custom.conf \ + && test -f /etc/apache2/ipaddr_whitelist.conf; + } || failed "IS_APACHE2EVOLINUXCONF" "missing custom evolinux apache config" + fi fi } check_backportsconf() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then grep -qsE "^[^#].*backports" /etc/apt/sources.list \ && failed "IS_BACKPORTSCONF" "backports can't be in main sources list" if grep -qsE "^[^#].*backports" /etc/apt/sources.list.d/*.list; then @@ -749,15 +807,19 @@ check_backportsconf() { fi } check_bind9munin() { - if is_debian_stretch && is_installed bind9; then - { test -L /etc/munin/plugins/bind9 \ - && test -e /etc/munin/plugin-conf.d/bind9; - } || failed "IS_BIND9MUNIN" + if is_debian_stretch || is_debian_buster; then + if is_installed bind9; then + { test -L /etc/munin/plugins/bind9 \ + && test -e /etc/munin/plugin-conf.d/bind9; + } || failed "IS_BIND9MUNIN" "missing bind plugin for munin" + fi fi } check_bind9logrotate() { - if is_debian_stretch && is_installed bind9; then - test -e /etc/logrotate.d/bind9 || failed "IS_BIND9LOGROTATE" + if is_debian_stretch || is_debian_buster; then + if is_installed bind9; then + test -e /etc/logrotate.d/bind9 || failed "IS_BIND9LOGROTATE" "missing bind logrotate file" + fi fi } check_broadcomfirmware() { @@ -766,10 +828,10 @@ check_broadcomfirmware() { if ${LSPCI_BIN} | grep -q 'NetXtreme II'; then { is_installed firmware-bnx2 \ && grep -q "^deb http://mirror.evolix.org/debian.* non-free" /etc/apt/sources.list; - } || failed "IS_BROADCOMFIRMWARE" + } || failed "IS_BROADCOMFIRMWARE" "missing non-free repository" fi else - failed "IS_BROADCOMFIRMWARE" "lspci is missing" + failed "IS_BROADCOMFIRMWARE" "lspci not found in ${PATH}" fi } check_hardwareraidtool() { @@ -784,28 +846,31 @@ check_hardwareraidtool() { is_installed cciss-vol-status || failed "IS_HARDWARERAIDTOOL" "cciss-vol-status not installed" fi else - failed "IS_HARDWARERAIDTOOL" "lspci is missing" + failed "IS_HARDWARERAIDTOOL" "lspci not found in ${PATH}" fi } check_log2mailsystemdunit() { - if is_debian_stretch; then - { systemctl -q is-active log2mail.service \ - && test -f /etc/systemd/system/log2mail.service \ - && ! test -f /etc/init.d/log2mail; - } || failed "IS_LOG2MAILSYSTEMDUNIT" + if is_debian_stretch || is_debian_buster; then + systemctl -q is-active log2mail.service \ + || failed "IS_LOG2MAILSYSTEMDUNIT" "log2mail unit not running" + test -f /etc/systemd/system/log2mail.service \ + || failed "IS_LOG2MAILSYSTEMDUNIT" "missing log2mail unit file" + test -f /etc/init.d/log2mail \ + && failed "IS_LOG2MAILSYSTEMDUNIT" "/etc/init.d/log2mail may be deleted (use systemd unit)" fi } check_listupgrade() { - { test -f /etc/cron.d/listupgrade \ - && test -x /usr/share/scripts/listupgrade.sh; - } || failed "IS_LISTUPGRADE" + test -f /etc/cron.d/listupgrade \ + || failed "IS_LISTUPGRADE" "missing listupgrade cron" + test -x /usr/share/scripts/listupgrade.sh \ + || failed "IS_LISTUPGRADE" "missing listupgrade script or not executable" } check_mariadbevolinuxconf() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then if is_installed mariadb-server; then { test -f /etc/mysql/mariadb.conf.d/z-evolinux-defaults.cnf \ && test -f /etc/mysql/mariadb.conf.d/zzz-evolinux-custom.cnf; - } || failed "IS_MARIADBEVOLINUXCONF" + } || failed "IS_MARIADBEVOLINUXCONF" "missing mariadb custom config" fi fi } @@ -862,70 +927,85 @@ check_redis_backup() { check_elastic_backup() { if is_installed elasticsearch; then # You could change the default path in /etc/evocheck.cf - ELASTIC_BACKUP_PATH=${ELASTIC_BACKUP_PATH:-"/home/backup/elasticsearch"} + ELASTIC_BACKUP_PATH=${ELASTIC_BACKUP_PATH:-"/home/backup-elasticsearch"} test -d "$ELASTIC_BACKUP_PATH" || failed "IS_ELASTIC_BACKUP" "Elastic snapshot is missing (${ELASTIC_BACKUP_PATH})" fi } check_mariadbsystemdunit() { - if is_debian_stretch && is_installed mariadb-server; then - { systemctl -q is-active mariadb.service \ - && test -f /etc/systemd/system/mariadb.service.d/evolinux.conf; - } || failed "IS_MARIADBSYSTEMDUNIT" + if is_debian_stretch || is_debian_buster; then + if is_installed mariadb-server; then + if systemctl -q is-active mariadb.service; then + test -f /etc/systemd/system/mariadb.service.d/evolinux.conf \ + || failed "IS_MARIADBSYSTEMDUNIT" "missing systemd override for mariadb unit" + fi + fi fi } check_mysqlmunin() { - if is_debian_stretch && is_installed mariadb-server; then - for file in mysql_bytes mysql_queries mysql_slowqueries \ - mysql_threads mysql_connections mysql_files_tables \ - mysql_innodb_bpool mysql_innodb_bpool_act mysql_innodb_io \ - mysql_innodb_log mysql_innodb_rows mysql_innodb_semaphores \ - mysql_myisam_indexes mysql_qcache mysql_qcache_mem \ - mysql_sorts mysql_tmp_tables; do + if is_debian_stretch || is_debian_buster; then + if is_installed mariadb-server; then + for file in mysql_bytes mysql_queries mysql_slowqueries \ + mysql_threads mysql_connections mysql_files_tables \ + mysql_innodb_bpool mysql_innodb_bpool_act mysql_innodb_io \ + mysql_innodb_log mysql_innodb_rows mysql_innodb_semaphores \ + mysql_myisam_indexes mysql_qcache mysql_qcache_mem \ + mysql_sorts mysql_tmp_tables; do - if [[ ! -L /etc/munin/plugins/$file ]]; then - failed "IS_MYSQLMUNIN" "Munin plugin '$file' is missing" - test "${VERBOSE}" = 1 || break - fi - done + if [[ ! -L /etc/munin/plugins/$file ]]; then + failed "IS_MYSQLMUNIN" "missing munin plugin '$file'" + test "${VERBOSE}" = 1 || break + fi + done + fi fi } check_mysqlnrpe() { - if is_debian_stretch && is_installed mariadb-server; then - nagios_file=~nagios/.my.cnf - - if ! test -f ${nagios_file}; then - failed "IS_MYSQLNRPE" "${nagios_file} is missing" - elif [ "$(stat -c %U ${nagios_file})" != "nagios" ] \ - || [ "$(stat -c %a ${nagios_file})" != "600" ]; then - failed "IS_MYSQLNRPE" "${nagios_file} has wrong permissions" - else - grep -q -F "command[check_mysql]=/usr/lib/nagios/plugins/check_mysql" /etc/nagios/nrpe.d/evolix.cfg \ - || failed "IS_MYSQLNRPE" "check_mysql is missing" + if is_debian_stretch || is_debian_buster; then + if is_installed mariadb-server; then + nagios_file=~nagios/.my.cnf + if ! test -f ${nagios_file}; then + failed "IS_MYSQLNRPE" "${nagios_file} is missing" + elif [ "$(stat -c %U ${nagios_file})" != "nagios" ] \ + || [ "$(stat -c %a ${nagios_file})" != "600" ]; then + failed "IS_MYSQLNRPE" "${nagios_file} has wrong permissions" + else + grep -q -F "command[check_mysql]=/usr/lib/nagios/plugins/check_mysql" /etc/nagios/nrpe.d/evolix.cfg \ + || failed "IS_MYSQLNRPE" "check_mysql is missing" + fi fi fi } check_phpevolinuxconf() { - if is_debian_stretch && is_installed php; then - { test -f /etc/php/7.0/cli/conf.d/z-evolinux-defaults.ini \ - && test -f /etc/php/7.0/cli/conf.d/zzz-evolinux-custom.ini; - } || failed "IS_PHPEVOLINUXCONF" + if is_debian_stretch || is_debian_buster; then + is_debian_stretch && phpVersion="7.0" + is_debian_buster && phpVersion="7.3" + if is_installed php; then + { test -f /etc/php/${phpVersion}/cli/conf.d/z-evolinux-defaults.ini \ + && test -f /etc/php/${phpVersion}/cli/conf.d/zzz-evolinux-custom.ini + } || failed "IS_PHPEVOLINUXCONF" "missing php evolinux config" + fi fi } check_squidlogrotate() { - if is_debian_stretch && is_installed squid; then - grep -q monthly /etc/logrotate.d/squid || failed "IS_SQUIDLOGROTATE" + if is_debian_stretch || is_debian_buster; then + if is_installed squid; then + grep -q monthly /etc/logrotate.d/squid \ + || failed "IS_SQUIDLOGROTATE" "missing squid logrotate file" + fi fi } check_squidevolinuxconf() { - if is_debian_stretch && is_installed squid; then - { grep -qs "^CONFIG=/etc/squid/evolinux-defaults.conf$" /etc/default/squid \ - && test -f /etc/squid/evolinux-defaults.conf \ - && test -f /etc/squid/evolinux-whitelist-defaults.conf \ - && test -f /etc/squid/evolinux-whitelist-custom.conf \ - && test -f /etc/squid/evolinux-acl.conf \ - && test -f /etc/squid/evolinux-httpaccess.conf \ - && test -f /etc/squid/evolinux-custom.conf; - } || failed "IS_SQUIDEVOLINUXCONF" + if is_debian_stretch || is_debian_buster; then + if is_installed squid; then + { grep -qs "^CONFIG=/etc/squid/evolinux-defaults.conf$" /etc/default/squid \ + && test -f /etc/squid/evolinux-defaults.conf \ + && test -f /etc/squid/evolinux-whitelist-defaults.conf \ + && test -f /etc/squid/evolinux-whitelist-custom.conf \ + && test -f /etc/squid/evolinux-acl.conf \ + && test -f /etc/squid/evolinux-httpaccess.conf \ + && test -f /etc/squid/evolinux-custom.conf; + } || failed "IS_SQUIDEVOLINUXCONF" "missing squid evolinux config" + fi fi } check_duplicate_fs_label() { @@ -947,11 +1027,12 @@ check_duplicate_fs_label() { fi rm "$tmpFile" else - failed "IS_DUPLICATE_FS_LABEL" "blkid not found" + failed "IS_DUPLICATE_FS_LABEL" "blkid not found in ${PATH}" fi } check_evolix_user() { - grep -q "evolix:" /etc/passwd && failed "IS_EVOLIX_USER" + grep -q "evolix:" /etc/passwd \ + && failed "IS_EVOLIX_USER" "evolix user should be deleted, used only for install" } check_evoacme_cron() { if [ -f "/usr/local/sbin/evoacme" ]; then @@ -989,20 +1070,23 @@ check_apache_confenabled() { # Starting from Jessie and Apache 2.4, /etc/apache2/conf.d/ # must be replaced by conf-available/ and config files symlinked # to conf-enabled/ - if is_debian_jessie || is_debian_stretch; then + if is_debian_jessie || is_debian_stretch || is_debian_buster; then if [ -f /etc/apache2/apache2.conf ]; then - test -d /etc/apache2/conf.d/ && failed "IS_APACHE_CONFENABLED" - grep -q 'Include conf.d' /etc/apache2/apache2.conf && failed "IS_APACHE_CONFENABLED" + test -d /etc/apache2/conf.d/ \ + && failed "IS_APACHE_CONFENABLED" "apache's conf.d directory must not exists" + grep -q 'Include conf.d' /etc/apache2/apache2.conf \ + && failed "IS_APACHE_CONFENABLED" "apache2.conf must not Include conf.d" fi fi } check_meltdown_spectre() { # For Stretch, detection is easy as the kernel use # /sys/devices/system/cpu/vulnerabilities/ - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then for vuln in meltdown spectre_v1 spectre_v2; do test -f "/sys/devices/system/cpu/vulnerabilities/$vuln" \ - || failed "IS_MELTDOWN_SPECTRE" + || failed "IS_MELTDOWN_SPECTRE" "vulnerable to $vuln" + test "${VERBOSE}" = 1 || break done # For Jessie this is quite complicated to verify and we need to use kernel config file elif is_debian_jessie; then @@ -1013,9 +1097,11 @@ check_meltdown_spectre() { # Sometimes autodetection of kernel config file fail, so we test if the file really exists. if [ -f "/boot/${kernelConfig}" ]; then grep -Eq '^CONFIG_PAGE_TABLE_ISOLATION=y' "/boot/$kernelConfig" \ - || failed "IS_MELTDOWN_SPECTRE" "PAGE_TABLE_ISOLATION vulnerability is not patched" + || failed "IS_MELTDOWN_SPECTRE" \ + "PAGE_TABLE_ISOLATION must be enabled in kernel, outdated kernel?" grep -Eq '^CONFIG_RETPOLINE=y' "/boot/$kernelConfig" \ - || failed "IS_MELTDOWN_SPECTRE" "RETPOLINE vulnerability is not patched" + || failed "IS_MELTDOWN_SPECTRE" \ + "RETPOLINE must be enabled in kernel, outdated kernel?" fi fi fi @@ -1036,29 +1122,31 @@ check_old_home_dir() { check_tmp_1777() { actual=$(stat --format "%a" /tmp) expected="1777" - test "$expected" = "$actual" || failed "IS_TMP_1777" + test "$expected" = "$actual" || failed "IS_TMP_1777" "/tmp must be $expected" } check_root_0700() { actual=$(stat --format "%a" /root) expected="700" - test "$expected" = "$actual" || failed "IS_ROOT_0700" + test "$expected" = "$actual" || failed "IS_ROOT_0700" "/root must be $expected" } check_usrsharescripts() { actual=$(stat --format "%a" /usr/share/scripts) expected="700" - test "$expected" = "$actual" || failed "IS_USRSHARESCRIPTS" + test "$expected" = "$actual" || failed "IS_USRSHARESCRIPTS" "/usr/share/scripts must be $expected" } check_sshpermitrootno() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then if grep -q "^PermitRoot" /etc/ssh/sshd_config; then - grep -E -qi "PermitRoot.*no" /etc/ssh/sshd_config || failed "IS_SSHPERMITROOTNO" + grep -E -qi "PermitRoot.*no" /etc/ssh/sshd_config \ + || failed "IS_SSHPERMITROOTNO" "PermitRoot should be set at no" fi else - grep -E -qi "PermitRoot.*no" /etc/ssh/sshd_config || failed "IS_SSHPERMITROOTNO" + grep -E -qi "PermitRoot.*no" /etc/ssh/sshd_config \ + || failed "IS_SSHPERMITROOTNO" "PermitRoot should be set at no" fi } check_evomaintenanceusers() { - if is_debian_stretch; then + if is_debian_stretch || is_debian_buster; then users=$(getent group evolinux-sudo | cut -d':' -f4 | tr ',' ' ') else if [ -f /etc/sudoers.d/evolinux ]; then @@ -1117,7 +1205,7 @@ check_evobackup_incs() { if [ -f "${bkctld_cron_file}" ]; then root_crontab=$(grep -v "^#" "${bkctld_cron_file}") echo "${root_crontab}" | grep -q "bkctld inc" || failed "IS_EVOBACKUP_INCS" "\`bkctld inc' is missing in ${bkctld_cron_file}" - echo "${root_crontab}" | grep -q "check-incs.sh" || failed "IS_EVOBACKUP_INCS" "\`check-incs.sh' is missing in ${bkctld_cron_file}" + echo "${root_crontab}" | grep -qE "(check-incs.sh|bkctld check-incs)" || failed "IS_EVOBACKUP_INCS" "\`check-incs.sh' is missing in ${bkctld_cron_file}" else failed "IS_EVOBACKUP_INCS" "Crontab \`${bkctld_cron_file}' is missing" fi @@ -1126,7 +1214,71 @@ check_evobackup_incs() { check_osprober() { if is_installed os-prober qemu-kvm; then - failed "IS_OSPROBER" "Removal of os-prober package is recommended as it can cause serious issue on KVM server" + failed "IS_OSPROBER" \ + "Removal of os-prober package is recommended as it can cause serious issue on KVM server" + fi +} + +check_jessie_backports() { + if is_debian_jessie; then + jessieBackports=$(grep -hs "jessie-backports" /etc/apt/sources.list /etc/apt/sources.list.d/*) + if test -n "$jessieBackports"; then + if ! grep -q "archive.debian.org" <<< "$jessieBackports"; then + failed "IS_JESSIE_BACKPORTS" "You must use deb http://archive.debian.org/debian/ jessie-backports main" + fi + fi + fi +} + +check_apt_valid_until() { + aptvalidFile="/etc/apt/apt.conf.d/99no-check-valid-until" + aptvalidText="Acquire::Check-Valid-Until no;" + if grep -qs "archive.debian.org" /etc/apt/sources.list /etc/apt/sources.list.d/*; then + if ! grep -qs "$aptvalidText" /etc/apt/apt.conf.d/*; then + failed "IS_APT_VALID_UNTIL" \ + "As you use archive.mirror.org you need ${aptvalidFile}: ${aptvalidText}" + fi + fi +} + +check_chrooted_binary_uptodate() { + # list of processes to check + process_list="sshd" + for process_name in ${process_list}; do + # what is the binary path? + original_bin=$(command -v "${process_name}") + for pid in $(pgrep ${process_name}); do + process_bin=$(realpath "/proc/${pid}/exe") + # Is the process chrooted? + real_root=$(realpath "/proc/${pid}/root") + if [ "${real_root}" != "/" ]; then + chrooted_md5=$(md5sum "${process_bin}" | cut -f 1 -d ' ') + original_md5=$(md5sum "${original_bin}" | cut -f 1 -d ' ') + # compare md5 checksums + if [ "$original_md5" != "$chrooted_md5" ]; then + failed "IS_CHROOTED_BINARY_UPTODATE" "${process_bin} (${pid}) is different than ${original_bin}." + test "${VERBOSE}" = 1 || break + fi + fi + done + done +} +check_nginx_letsencrypt_uptodate() { + if [ -d /etc/nginx ]; then + snippets=$(find /etc/nginx -type f -name "letsencrypt.conf") + if [ -n "${snippets}" ]; then + while read -r snippet; do + if is_debian_jessie; then + if ! grep -qE "^\s*alias\s+/.+/\.well-known/acme-challenge" "${snippet}"; then + failed "IS_NGINX_LETSENCRYPT_UPTODATE" "Nginx snippet ${snippet} is not compatible with Nginx on Debian 8." + fi + else + if grep -qE "^\s*alias\s+/.+/\.well-known/acme-challenge" "${snippet}"; then + failed "IS_NGINX_LETSENCRYPT_UPTODATE" "Nginx snippet ${snippet} is not compatible with Nginx on Debian 9+." + fi + fi + done <<< "${snippets}" + fi fi } @@ -1205,6 +1357,7 @@ main() { test "${IS_AUTOIF:=1}" = 1 && check_autoif test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup + test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate test "${IS_APACHECTL:=1}" = 1 && check_apachectl test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink @@ -1229,7 +1382,7 @@ main() { test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool test "${IS_LOG2MAILSYSTEMDUNIT:=1}" = 1 && check_log2mailsystemdunit test "${IS_LISTUPGRADE:=1}" = 1 && check_listupgrade - test "${IS_MARIADBEVOLINUXCONF:=1}" = 1 && check_mariadbevolinuxconf + test "${IS_MARIADBEVOLINUXCONF:=0}" = 1 && check_mariadbevolinuxconf test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup @@ -1239,7 +1392,7 @@ main() { test "${IS_MARIADBSYSTEMDUNIT:=1}" = 1 && check_mariadbsystemdunit test "${IS_MYSQLMUNIN:=1}" = 1 && check_mysqlmunin test "${IS_MYSQLNRPE:=1}" = 1 && check_mysqlnrpe - test "${IS_PHPEVOLINUXCONF:=1}" = 1 && check_phpevolinuxconf + test "${IS_PHPEVOLINUXCONF:=0}" = 1 && check_phpevolinuxconf test "${IS_SQUIDLOGROTATE:=1}" = 1 && check_squidlogrotate test "${IS_SQUIDEVOLINUXCONF:=1}" = 1 && check_squidevolinuxconf test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label @@ -1248,9 +1401,13 @@ main() { test "${IS_EVOACME_LIVELINKS:=1}" = 1 && check_evoacme_livelinks test "${IS_APACHE_CONFENABLED:=1}" = 1 && check_apache_confenabled test "${IS_MELTDOWN_SPECTRE:=1}" = 1 && check_meltdown_spectre - test "${IS_OLD_HOME_DIR:=1}" = 1 && check_old_home_dir + test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs test "${IS_OSPROBER:=1}" = 1 && check_osprober + test "${IS_JESSIE_BACKPORTS:=1}" = 1 && check_jessie_backports + test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until + test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate + test "${IS_NGINX_LETSENCRYPT_UPTODATE:=1}" = 1 && check_nginx_letsencrypt_uptodate fi #----------------------------------------------------------- @@ -1358,13 +1515,13 @@ main() { exit ${RC} } +# shellcheck disable=SC2034 readonly PROGNAME=$(basename "$0") +# shellcheck disable=SC2034 readonly PROGDIR=$(realpath -m "$(dirname "$0")") # shellcheck disable=2124 readonly ARGS=$@ -readonly VERSION="19.06" - # Disable LANG* export LANG=C export LANGUAGE=C @@ -1388,6 +1545,7 @@ while :; do --cron) IS_KERNELUPTODATE=0 IS_UPTIME=0 + IS_MELTDOWN_SPECTRE=0 ;; -v|--verbose) VERBOSE=1 diff --git a/evocheck/tasks/cron.yml b/evocheck/tasks/cron.yml index 82f56130..4ef10b05 100644 --- a/evocheck/tasks/cron.yml +++ b/evocheck/tasks/cron.yml @@ -1,5 +1,12 @@ --- +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" + failed_when: False + changed_when: False + check_mode: no + register: is_cron_installed + - name: evocheck crontab is updated template: src: crontab.j2 @@ -8,3 +15,4 @@ owner: root group: root force: yes + when: is_cron_installed.rc == 0 diff --git a/evocheck/tasks/install_local.yml b/evocheck/tasks/install_local.yml index e33d565e..7d4a0e6a 100644 --- a/evocheck/tasks/install_local.yml +++ b/evocheck/tasks/install_local.yml @@ -1,7 +1,7 @@ --- - include_role: - name: remount-usr - when: evocheck_bin_dir | search ("/usr") + name: evolix/remount-usr + when: evocheck_bin_dir is search ("/usr") tags: - evocheck diff --git a/evocheck/templates/crontab.j2 b/evocheck/templates/crontab.j2 index fdd07649..d23b1308 100644 --- a/evocheck/templates/crontab.j2 +++ b/evocheck/templates/crontab.j2 @@ -1,4 +1,5 @@ # {{ ansible_managed }} -33 1 1 * * root /usr/share/scripts/evocheck.sh -33 1 2-31 * * root /usr/share/scripts/evocheck.sh --cron +PATH=/usr/sbin:/usr/bin:/sbin:/bin +33 1 1 * * root /usr/share/scripts/evocheck.sh --verbose +33 1 2-31 * * root /usr/share/scripts/evocheck.sh --verbose --cron diff --git a/evolinux-base/defaults/main.yml b/evolinux-base/defaults/main.yml index 88ea931d..9681f0b7 100644 --- a/evolinux-base/defaults/main.yml +++ b/evolinux-base/defaults/main.yml @@ -67,6 +67,8 @@ evolinux_fstab_home: True evolinux_fstab_home_options: defaults,noexec,nosuid,nodev evolinux_fstab_var_tmp: True evolinux_fstab_var_tmp_options: defaults,noexec,nosuid,nodev,size=1024m +evolinux_fstab_dev_shm: True +evolinux_fstab_dev_shm_options: defaults,nodev,nosuid,noexec # packages @@ -77,13 +79,15 @@ evolinux_packages_diagnostic: True evolinux_packages_hardware: True evolinux_packages_common: True evolinux_packages_stretch: True +evolinux_packages_buster: True evolinux_packages_serveur_base: True evolinux_packages_purge_openntpd: True +evolinux_packages_purge_chrony: True evolinux_packages_purge_locate: True evolinux_packages_invalid_mta: True evolinux_packages_delete_nfs: True evolinux_packages_listchanges: True -evolinux_packages_logcheck_recipient: True +evolinux_packages_logcheck_recipient: False # system @@ -122,6 +126,7 @@ evolinux_ssh_password_auth_addresses: "{{ evolinux_default_ssh_password_auth_add evolinux_ssh_match_address: True evolinux_ssh_disable_acceptenv: True evolinux_ssh_allow_current_user: False +evolinux_ssh_group: "evolinux-ssh" ### disabled because of a memory leak # # evolinux users @@ -197,6 +202,11 @@ evolinux_nagios_nrpe_include: True evolinux_fail2ban_include: False +# Evocheck + +evolinux_evocheck_include: True +evolinux_evocheck_force_install: "local" + # Listupgrade evolinux_listupgrade_include: True diff --git a/evolinux-base/files/deny.sh b/evolinux-base/files/deny.sh new file mode 100644 index 00000000..b79c0182 --- /dev/null +++ b/evolinux-base/files/deny.sh @@ -0,0 +1,3 @@ +#!/bin/sh +iptables -I INPUT -s $1 -j DROP +echo $1 >> /root/BLACKLIST-SSH diff --git a/evolinux-base/files/hpePublicKey2048_key1.pub b/evolinux-base/files/hpePublicKey2048_key1.pub new file mode 100644 index 00000000..2db2acb8 --- /dev/null +++ b/evolinux-base/files/hpePublicKey2048_key1.pub @@ -0,0 +1,19 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQENBFZp0LkBCACXajRw3b4x7G7dulNYj0hUID4BtVFq/MjEb6PHckTxGxZDoQRX +RK54tiTFA9wq3b4P3yEFnOjbjRoI0d7Ls67FADugFO+cDCtsV9yuDlaYP/U/h2nX +N0R4AdYbsVd5yr6xr+GAy66Hmx5jFH3kbC+zJpOcI0tU9hcyU7gjbxu6KQ1ypI2Q +VRKf8sRBJXgmkOlbYx35ZUMFcmVxrLJXvUuxmAVXgT9f5M3Z3rsGt/ab+/+1TFSb +RsaqHsIPE0QH8ikqW4IeDQAo1T99pCdf7FWr45KFFTo7O4AZdLMWVgqeFHaSoZxJ +307VIINsWiwQoPp0tfU5NOOOwB1Sv3x9QgFtABEBAAG0P0hld2xldHQgUGFja2Fy +ZCBFbnRlcnByaXNlIENvbXBhbnkgUlNBLTIwNDgtMjUgPHNpZ25ocEBocGUuY29t +PokBPQQTAQIAJwUCVmnQuQIbLwUJEswDAAYLCQgHAwIGFQgCCQoLAxYCAQIeAQIX +gAAKCRDCCK3eJsK3l9G+B/0ekblsBeN+xHIJ28pvo2aGb2KtWBwbT1ugI+aIS17K +UQyHZJUQH+ZeRLvosuoiQEdcGIqmOxi2hVhSCQAOV1LAonY16ACveA5DFAEBz1+a +WQyx6sOLLEAVX1VqGlBXxh3XLEUWOhlAf1gZPNtHsmURTUy2h1Lv/Yoj8KLyuK2n +DmrLOS3Ro+RqWocaJfvAgXKgt6Fq/ChDUHOnar7lGswzMsbE/yzLJ7He4y89ImK+ +2ktR5HhDuxqgCe9CWH6Q/1WGhUa0hZ3nbluq7maa+kPe2g7JcRzPH/nJuDCAOZ7U +6mHE8j0kMQMYjgaYEx2wc02aQRmPyxhbDLjSbtjomXRr +=voON +-----END PGP PUBLIC KEY BLOCK----- diff --git a/evolinux-base/tasks/default_www.yml b/evolinux-base/tasks/default_www.yml index 665e7eb0..8956d824 100644 --- a/evolinux-base/tasks/default_www.yml +++ b/evolinux-base/tasks/default_www.yml @@ -25,24 +25,24 @@ # SSL cert -- block: +- name: Default certificate is present + block: + - name: Create private key and csr for default site ({{ ansible_fqdn }}) + command: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/{{ ansible_fqdn }}.csr -batch -subj "/CN={{ ansible_fqdn }}" + args: + creates: "/etc/ssl/private/{{ ansible_fqdn }}.key" - - name: Create private key and csr for default site ({{ ansible_fqdn }}) - command: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/{{ ansible_fqdn }}.csr -batch -subj "/CN={{ ansible_fqdn }}" - args: - creates: "/etc/ssl/private/{{ ansible_fqdn }}.key" + - name: Adjust rights on private key + file: + path: /etc/ssl/private/{{ ansible_fqdn }}.key + owner: root + group: ssl-cert + mode: "0640" - - name: Adjust rights on private key - file: - path: /etc/ssl/private/{{ ansible_fqdn }}.key - owner: root - group: ssl-cert - mode: "0640" - - - name: Create certificate for default site - command: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ ansible_fqdn }}.csr -signkey /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/certs/{{ ansible_fqdn }}.crt - args: - creates: "/etc/ssl/certs/{{ ansible_fqdn }}.crt" + - name: Create certificate for default site + command: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ ansible_fqdn }}.csr -signkey /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/certs/{{ ansible_fqdn }}.crt + args: + creates: "/etc/ssl/certs/{{ ansible_fqdn }}.crt" when: evolinux_default_www_ssl_cert - meta: flush_handlers diff --git a/evolinux-base/tasks/etc-evolinux.yml b/evolinux-base/tasks/etc-evolinux.yml index fddc82a8..56b0a976 100644 --- a/evolinux-base/tasks/etc-evolinux.yml +++ b/evolinux-base/tasks/etc-evolinux.yml @@ -10,4 +10,4 @@ # state: directory - include_role: - name: evolinux-todo + name: evolix/evolinux-todo diff --git a/evolinux-base/tasks/fstab.yml b/evolinux-base/tasks/fstab.yml index 9baa8a70..bb70fde8 100644 --- a/evolinux-base/tasks/fstab.yml +++ b/evolinux-base/tasks/fstab.yml @@ -57,4 +57,15 @@ when: - evolinux_fstab_var_tmp +- name: /dev/shm is created (Debian 10 and later) + mount: + src: tmpfs + name: /dev/shm + fstype: tmpfs + opts: "{{ evolinux_fstab_dev_shm_options | mandatory }}" + state: mounted + when: + - evolinux_fstab_dev_shm + - ansible_distribution_major_version is version('10', '>=') + - meta: flush_handlers diff --git a/evolinux-base/tasks/hardware.yml b/evolinux-base/tasks/hardware.yml index 8607a586..628cd6d1 100644 --- a/evolinux-base/tasks/hardware.yml +++ b/evolinux-base/tasks/hardware.yml @@ -18,7 +18,7 @@ - name: Add non-free repo for Broadcom NetXtreme II include_role: - name: apt + name: evolix/apt tasks_from: basics.yml vars: apt_basics_components: "main contrib non-free" @@ -33,57 +33,76 @@ changed_when: "'FAILED' in raidmodel.stdout" failed_when: "'FAILED' in raidmodel.stdout" -- block: - - name: Install packages for HP hardware - apt: - name: cciss-vol-status - state: present +- name: HP Smart Array package is present + block: + - name: Add HPE GPG key + apt_key: + #url: https://downloads.linux.hpe.com/SDR/hpePublicKey2048_key1.pub + data: "{{ lookup('file', 'hpePublicKey2048_key1.pub') }}" - - name: Configure packages for HP hardware - template: - src: hardware/cciss-vol-statusd.j2 - dest: /etc/init.d/cciss-vol-statusd - mode: "0755" + - name: Add HPE repository + apt_repository: + repo: 'deb https://downloads.linux.hpe.com/SDR/repo/mcp {{ ansible_distribution_release }}/current non-free' + state: present - - name: Enable HP hardware in systemd - service: - name: cciss-vol-statusd - enabled: true - state: started + - name: Install packages for HP hardware + apt: + name: + - cciss-vol-status + - ssacli + state: present + + - name: cciss-vol-statusd init script is present + template: + src: hardware/cciss-vol-statusd.j2 + dest: /etc/init.d/cciss-vol-statusd + mode: "0755" + + - name: Configure cciss-vol-statusd + lineinfile: + dest: /etc/default/cciss-vol-statusd + line: 'MAILTO="{{ raid_alert_email or general_alert_email | mandatory }}"' + regexp: 'MAILTO=' + create: yes + + - name: Enable HP hardware in systemd + service: + name: cciss-vol-statusd + enabled: true + state: restarted when: "'Hewlett-Packard Company Smart Array' in raidmodel.stdout" -- block: - - name: Add HW tool GPG key - apt_key: - # url: https://hwraid.le-vert.net/debian/hwraid.le-vert.net.gpg.key - data: "{{ lookup('file', 'hwraid.le-vert.net.gpg.key') }}" - when: ansible_distribution_release == "stretch" +- name: MegaRAID SAS package is present + block: + - name: Add HW tool GPG key + apt_key: + # url: https://hwraid.le-vert.net/debian/hwraid.le-vert.net.gpg.key + data: "{{ lookup('file', 'hwraid.le-vert.net.gpg.key') }}" + when: ansible_distribution_major_version is version('9', '>=') - - name: Add HW tool repository - apt_repository: - repo: 'deb http://hwraid.le-vert.net/debian stretch main' - state: present - when: ansible_distribution_release == "stretch" + - name: Add HW tool repository + apt_repository: + repo: 'deb http://hwraid.le-vert.net/debian {{ ansible_distribution_release }} main' + state: present - - name: Install packages for DELL/LSI hardware - apt: - name: "{{ item }}" - allow_unauthenticated: yes - with_items: - - megacli - - megaclisas-status + - name: Install packages for DELL/LSI hardware + apt: + name: + - megacli + - megaclisas-status + allow_unauthenticated: yes - - name: Configure packages for DELL/LSI hardware - template: - src: hardware/megaclisas-statusd.j2 - dest: /etc/default/megaclisas-statusd - mode: "0755" + - name: Configure packages for DELL/LSI hardware + template: + src: hardware/megaclisas-statusd.j2 + dest: /etc/default/megaclisas-statusd + mode: "0755" - - name: Enable DELL/LSI hardware in systemd - service: - name: megaclisas-statusd - enabled: true - state: started + - name: Enable DELL/LSI hardware in systemd + service: + name: megaclisas-statusd + enabled: true + state: started when: "'MegaRAID SAS' in raidmodel.stdout" - meta: flush_handlers diff --git a/evolinux-base/tasks/logs.yml b/evolinux-base/tasks/logs.yml index 101330b8..9c1f45e4 100644 --- a/evolinux-base/tasks/logs.yml +++ b/evolinux-base/tasks/logs.yml @@ -24,6 +24,12 @@ dest: /etc/logrotate.d/ when: evolinux_logs_logrotate_confs +- name: Copy rsyslog logrotate file + template: + src: logs/zsyslog.j2 + dest: /etc/logrotate.d/zsyslog + when: evolinux_logs_logrotate_confs + - name: Configure logrotate.conf replace: dest: /etc/logrotate.conf diff --git a/evolinux-base/tasks/main.yml b/evolinux-base/tasks/main.yml index ff0213a8..0b2fdf89 100644 --- a/evolinux-base/tasks/main.yml +++ b/evolinux-base/tasks/main.yml @@ -4,12 +4,12 @@ assert: that: - ansible_distribution == "Debian" - - ansible_distribution_major_version | version_compare('8', '>=') + - ansible_distribution_major_version is version('8', '>=') msg: only compatible with Debian >= 8 - name: Apt configuration include_role: - name: apt + name: evolix/apt vars: apt_install_basics: "{{ evolinux_apt_replace_default_sources }}" apt_install_evolix_public: "{{ evolinux_apt_public_sources }}" @@ -17,7 +17,7 @@ - name: /etc versioning with Git include_role: - name: etc-git + name: evolix/etc-git when: evolinux_etcgit_include - name: /etc/evolinux base @@ -46,12 +46,12 @@ - name: Minifirewall include_role: - name: minifirewall + name: evolix/minifirewall when: evolinux_minifirewall_include - name: Evomaintenance include_role: - name: evomaintenance + name: evolix/evomaintenance when: evolinux_evomaintenance_include - name: SSH configuration @@ -61,7 +61,7 @@ ### disabled because of a memory leak # - name: Create evolinux users # include_role: -# name: evolinux-users +# name: evolix/evolinux-users # when: evolinux_users_include - name: Root user configuration @@ -92,7 +92,7 @@ include: provider_orange_fce.yml when: evolinux_provider_orange_fce_include -- name: Override Logmail service +- name: Override Log2mail service include: log2mail.yml when: evolinux_log2mail_include @@ -100,25 +100,32 @@ - name: Munin include_role: - name: munin + name: evolix/munin when: evolinux_munin_include - name: Nagios/NRPE include_role: - name: nagios-nrpe + name: evolix/nagios-nrpe when: evolinux_nagios_nrpe_include - name: fail2ban include_role: - name: fail2ban + name: evolix/fail2ban when: evolinux_fail2ban_include +- name: Evocheck + include_role: + name: evolix/evocheck + vars: + evocheck_force_install: "{{ evolinux_evocheck_force_install }}" + when: evolinux_evocheck_include + - name: Listupgrade include_role: - name: listupgrade + name: evolix/listupgrade when: evolinux_listupgrade_include - name: Generate ldif script include_role: - name: generate-ldif + name: evolix/generate-ldif when: evolinux_generateldif_include diff --git a/evolinux-base/tasks/packages.yml b/evolinux-base/tasks/packages.yml index c510bab5..bbacfa98 100644 --- a/evolinux-base/tasks/packages.yml +++ b/evolinux-base/tasks/packages.yml @@ -2,82 +2,84 @@ - name: Install/Update system tools apt: - name: "{{ item }}" - with_items: - - locales - - sudo - - ntpdate - - lsb-release - - dnsutils - - pv - - apg - - conntrack - - logrotate - - bash-completion - - ssl-cert - - ca-certificates - - rename + name: + - locales + - sudo + - ntpdate + - lsb-release + - dnsutils + - pv + - apg + - conntrack + - logrotate + - bash-completion + - ssl-cert + - ca-certificates + - rename when: evolinux_packages_system - name: Install/Update diagnostic tools apt: - name: "{{ item }}" - with_items: - - strace - - htop - - iftop - - iptraf - - ncdu - - iotop - - tcpdump - - mtr-tiny - - curl - - telnet - - traceroute - - man + name: + - strace + - htop + - iftop + - iptraf + - ncdu + - iotop + - tcpdump + - mtr-tiny + - curl + - telnet + - traceroute + - man when: evolinux_packages_diagnostic - name: Install/Update hardware tools apt: - name: "{{ item }}" - with_items: - - hdparm - - smartmontools - - lm-sensors + name: + - hdparm + - smartmontools + - lm-sensors when: evolinux_packages_hardware - name: Install/Update common tools apt: - name: "{{ item }}" - with_items: - - vim - - screen - - tmux - - mutt - - tree - - git - - subversion - - rsync - - bc - - pinentry-curses - - ncurses-term + name: + - vim + - screen + - tmux + - mutt + - tree + - git + - subversion + - rsync + - bc + - pinentry-curses + - ncurses-term when: evolinux_packages_common - name: Be sure that openntpd package is absent/purged apt: name: openntpd state: absent - purge: yes + purge: True when: evolinux_packages_purge_openntpd +- name: the chrony package is absent + apt: + name: chrony + purge: True + state: absent + when: evolinux_packages_purge_chrony + - name: Be sure locate/mlocate is absent/purged apt: - name: "{{ item }}" + name: + - locate + - mlocate state: absent purge: yes - with_items: - - locate - - mlocate when: evolinux_packages_purge_locate - name: Install/Update serveur-base meta-package @@ -88,12 +90,19 @@ - name: Install/Update packages for Stretch and later apt: - name: "{{ item }}" - with_items: - - net-tools + name: net-tools when: - - evolinux_packages_stretch - - ansible_distribution_major_version | version_compare('9', '>=') + - evolinux_packages_stretch + - ansible_distribution_major_version is version('9', '>=') + +- name: Install/Update packages for Buster and later + apt: + name: + - spectre-meltdown-checker + - binutils + when: + - evolinux_packages_buster + - ansible_distribution_major_version is version('10', '>=') - name: Customize logcheck recipient lineinfile: @@ -104,11 +113,10 @@ - name: Deleting rpcbind and nfs-common apt: - name: "{{ item }}" + name: + - rpcbind + - nfs-common state: absent - with_items: - - rpcbind - - nfs-common when: evolinux_packages_delete_nfs @@ -133,6 +141,6 @@ state: absent when: - ansible_distribution == "Debian" - - ansible_distribution_major_version | version_compare('9', '>=') + - ansible_distribution_major_version is version('9', '>=') - meta: flush_handlers diff --git a/evolinux-base/tasks/postfix.yml b/evolinux-base/tasks/postfix.yml index d0c66a65..aa60e737 100644 --- a/evolinux-base/tasks/postfix.yml +++ b/evolinux-base/tasks/postfix.yml @@ -2,11 +2,10 @@ - name: Postfix packages are installed apt: - name: "{{ item }}" + name: + - postfix + - mailgraph state: present - with_items: - - postfix - - mailgraph when: evolinux_postfix_packages tags: - packages @@ -83,14 +82,13 @@ - name: exim4 is absent apt: - name: "{{ item }}" + name: + - exim4 + - exim4-base + - exim4-config + - exim4-daemon-light purge: yes state: absent - with_items: - - exim4 - - exim4-base - - exim4-config - - exim4-daemon-light when: evolinux_postfix_purge_exim tags: - packages diff --git a/evolinux-base/tasks/root.yml b/evolinux-base/tasks/root.yml index ae2d8a89..23f3cd9c 100644 --- a/evolinux-base/tasks/root.yml +++ b/evolinux-base/tasks/root.yml @@ -37,6 +37,12 @@ regexp: "umask [0-9]+" when: evolinux_root_umask +- name: "/usr/share/scripts is present in root's PATH" + lineinfile: + dest: "/root/.profile" + line: "PATH=\"${PATH}:/usr/share/scripts\"" + when: ansible_distribution_major_version is version('10', '>=') + - name: Custom git config for root copy: src: root/gitconfig @@ -87,7 +93,7 @@ dest: /etc/ssh/sshd_config regexp: '^PermitRootLogin (yes|without-password|prohibit-password)' replace: "PermitRootLogin no" - validate: '/usr/sbin/sshd -T -f %s' + validate: '/usr/sbin/sshd -t -f %s' notify: reload sshd when: evolinux_root_disable_ssh diff --git a/evolinux-base/tasks/ssh.yml b/evolinux-base/tasks/ssh.yml index c6b87cae..2816af7f 100644 --- a/evolinux-base/tasks/ssh.yml +++ b/evolinux-base/tasks/ssh.yml @@ -11,7 +11,7 @@ # only the first instance of the keyword is applied. » # # We want to allow any user from a list of IP addresses to login with password, -# but users of the "evolix" group can't login with password from other IP addresses +# but users of the "{{ evolinux_internal_group }}" group can't login with password from other IP addresses - name: "Security directives for Evolinux (Debian 10 or later)" blockinfile: @@ -20,14 +20,14 @@ block: | Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} PasswordAuthentication yes - Match Group evolix + Match Group {{ evolinux_internal_group }} PasswordAuthentication no insertafter: EOF validate: '/usr/sbin/sshd -t -f %s' notify: reload sshd when: - evolinux_ssh_password_auth_addresses != [] - - ansible_distribution_major_version | version_compare('10', '>=') + - ansible_distribution_major_version is version('10', '>=') - name: Security directives for Evolinux (Jessie/Stretch) blockinfile: @@ -41,7 +41,7 @@ notify: reload sshd when: - evolinux_ssh_password_auth_addresses != [] - - ansible_distribution_major_version | version_compare('10', '<') + - ansible_distribution_major_version is version('10', '<') # We disable AcceptEnv because it can be a security issue, but also because we # do not want clients to push their environment variables like LANG. @@ -59,7 +59,7 @@ regexp: '^#?LogLevel [A-Z]+' replace: "LogLevel VERBOSE" notify: reload sshd - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: "Get current user" command: logname diff --git a/evolinux-base/tasks/system.yml b/evolinux-base/tasks/system.yml index 4ef08fa3..55820890 100644 --- a/evolinux-base/tasks/system.yml +++ b/evolinux-base/tasks/system.yml @@ -22,7 +22,7 @@ - name: Reconfigure locales command: /usr/sbin/locale-gen - when: evolinux_system_locales and default_locales | changed + when: evolinux_system_locales and default_locales is changed - name: Setting default timezone timezone: @@ -34,7 +34,7 @@ # non-interactively (like tzdata ↑) - include_role: - name: remount-usr + name: evolix/remount-usr - name: Ensure automagic vim conf is disabled lineinfile: @@ -84,13 +84,20 @@ #- name: Customizing /etc/fstab +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" + failed_when: False + changed_when: False + check_mode: no + register: is_cron_installed + - name: Set verbose logging for cron deamon lineinfile: dest: /etc/default/cron line: "EXTRA_OPTS='-L 15'" create: yes state: present - when: evolinux_system_cron_verboselog + when: is_cron_installed.rc == 0 and evolinux_system_cron_verboselog - name: Modify default umask for cron deamon lineinfile: @@ -98,7 +105,7 @@ line: "umask 022" create: yes state: present - when: evolinux_system_cron_umask + when: is_cron_installed.rc == 0 and evolinux_system_cron_umask - name: Randomize periodic crontabs replace: @@ -110,10 +117,10 @@ - { regexp: '^25\s*6((\s*\*){3})', replace: '{{ 59|random(start=1) }} {{ [0,1,3,4,5,6,7]|random }}\1' } - { regexp: '^47\s*6((\s*\*){2}\s*7)', replace: '{{ 59|random(start=1) }} {{ [0,1,3,4,5,6,7]|random }}\1' } - { regexp: '^52\s*6(\s*1(\s*\*){2})', replace: '{{ 59|random(start=1) }} {{ [0,1,3,4,5,6,7]|random }}\1' } - when: evolinux_system_cron_random + when: is_cron_installed.rc == 0 and evolinux_system_cron_random - include_role: - name: ntpd + name: evolix/ntpd ## alert5 @@ -146,17 +153,17 @@ mode: "0755" when: - evolinux_system_alert5_init - - ansible_distribution_major_version | version_compare('10', '>=') + - ansible_distribution_major_version is version('10', '>=') - name: Install alert5 service (buster) copy: src: alert5.service dest: /etc/systemd/system/alert5.service force: yes - mode: "0755" + mode: "0644" when: - evolinux_system_alert5_init - - ansible_distribution_major_version | version_compare('10', '>=') + - ansible_distribution_major_version is version('10', '>=') - name: Enable alert5 init script (buster) systemd: @@ -166,7 +173,7 @@ when: - evolinux_system_alert5_init - evolinux_system_alert5_enable - - ansible_distribution_major_version | version_compare('10', '>=') + - ansible_distribution_major_version is version('10', '>=') ## network interfaces @@ -184,4 +191,15 @@ replace: "auto" when: evolinux_system_eni_auto and grep_hotplug_eni.rc == 0 +## /sbin/deny + +- name: "/sbin/deny script is present" + copy: + src: deny.sh + dest: /sbin/deny + mode: "0700" + owner: root + group: root + force: no + - meta: flush_handlers diff --git a/evolinux-base/templates/hardware/cciss-vol-statusd.j2 b/evolinux-base/templates/hardware/cciss-vol-statusd.j2 index c51a1a24..82c2c949 100644 --- a/evolinux-base/templates/hardware/cciss-vol-statusd.j2 +++ b/evolinux-base/templates/hardware/cciss-vol-statusd.j2 @@ -20,7 +20,7 @@ PIDFILE=/var/run/$NAME.pid STATUSFILE=/var/run/$NAME.status SCRIPTNAME=/etc/init.d/$NAME -MAILTO="{{ raid_alert_email or general_alert_email | mandatory }}" # Where to report problems +MAILTO="root" # Where to report problems PERIOD=600 # Seconds between each check (default 10 minutes) REMIND=86400 # Seconds between each reminder (default 2 hours) RUN_DAEMON=yes diff --git a/evolinux-base/files/logs/logrotate.d/zsyslog b/evolinux-base/templates/logs/zsyslog.j2 similarity index 78% rename from evolinux-base/files/logs/logrotate.d/zsyslog rename to evolinux-base/templates/logs/zsyslog.j2 index 016dcfa6..2fc2bd1a 100644 --- a/evolinux-base/files/logs/logrotate.d/zsyslog +++ b/evolinux-base/templates/logs/zsyslog.j2 @@ -8,7 +8,11 @@ notifempty delaycompress compress postrotate +{% if ansible_distribution_major_version is version('10', '>=') %} + /usr/lib/rsyslog/rsyslog-rotate +{% else %} invoke-rc.d rsyslog rotate > /dev/null +{% endif %} endscript /var/log/daemon.log @@ -32,4 +36,4 @@ endscript { daily rotate 365 -} \ No newline at end of file +} diff --git a/evolinux-todo/README.md b/evolinux-todo/README.md index 52c397c7..536490fa 100644 --- a/evolinux-todo/README.md +++ b/evolinux-todo/README.md @@ -10,6 +10,6 @@ A separate `cat.yml` file can be imported manually in playbooks or roles to get ``` - include_role: - name: evolinux-todo + name: evolix/evolinux-todo tasks_from: cat.yml ``` diff --git a/evolinux-users/tasks/main.yml b/evolinux-users/tasks/main.yml index e5872a91..b3be0c8d 100644 --- a/evolinux-users/tasks/main.yml +++ b/evolinux-users/tasks/main.yml @@ -4,7 +4,7 @@ assert: that: - ansible_distribution == "Debian" - - ansible_distribution_major_version | version_compare('8', '>=') + - ansible_distribution_major_version is version('8', '>=') msg: only compatible with Debian >= 8 - debug: diff --git a/evolinux-users/tasks/ssh.yml b/evolinux-users/tasks/ssh.yml index 70570c63..e21f0978 100644 --- a/evolinux-users/tasks/ssh.yml +++ b/evolinux-users/tasks/ssh.yml @@ -28,9 +28,9 @@ - set_fact: # If "AllowGroups is present" or "AllowUsers is absent and Debian 10+", - ssh_allowgroups: "{{ (grep_allowgroups_ssh.rc == 0) or (grep_allowusers_ssh.rc != 0 and (ansible_distribution_major_version | version_compare('10', '>='))) }}" + ssh_allowgroups: "{{ (grep_allowgroups_ssh.rc == 0) or (grep_allowusers_ssh.rc != 0 and (ansible_distribution_major_version is version('10', '>='))) }}" # If "AllowGroups is absent" and "AllowUsers is absent or Debian <10" - ssh_allowusers: "{{ (grep_allowusers_ssh.rc == 0) or (grep_allowgroups_ssh.rc != 0 and (ansible_distribution_major_version | version_compare('10', '<'))) }}" + ssh_allowusers: "{{ (grep_allowusers_ssh.rc == 0) or (grep_allowgroups_ssh.rc != 0 and (ansible_distribution_major_version is version('10', '<'))) }}" - debug: var: ssh_allowgroups diff --git a/evolinux-users/tasks/sudo.yml b/evolinux-users/tasks/sudo.yml index ed696b43..2f2ee07c 100644 --- a/evolinux-users/tasks/sudo.yml +++ b/evolinux-users/tasks/sudo.yml @@ -4,6 +4,6 @@ when: ansible_distribution_release == "jessie" - include: sudo_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - meta: flush_handlers diff --git a/evolinux-users/tasks/user.yml b/evolinux-users/tasks/user.yml index 96c70e31..2f5e4e43 100644 --- a/evolinux-users/tasks/user.yml +++ b/evolinux-users/tasks/user.yml @@ -63,14 +63,14 @@ group: name: "{{ evolinux_ssh_group }}" state: present - when: ansible_distribution_major_version | version_compare('10', '>=') + when: ansible_distribution_major_version is version('10', '>=') - name: "Unix user '{{ user.name }}' belongs to group '{{ evolinux_ssh_group }}' (Debian 10 or later)" user: name: '{{ user.name }}' groups: "{{ evolinux_ssh_group }}" append: yes - when: ansible_distribution_major_version | version_compare('10', '>=') + when: ansible_distribution_major_version is version('10', '>=') ## Optional group for all evolinux users @@ -81,7 +81,7 @@ when: - evolinux_internal_group is defined - evolinux_internal_group != "" - - ansible_distribution_major_version | version_compare('9', '>=') + - ansible_distribution_major_version is version('9', '>=') - name: "Unix user '{{ user.name }}' belongs to group '{{ evolinux_internal_group }}' (Debian 9 or later)" user: @@ -91,7 +91,7 @@ when: - evolinux_internal_group is defined - evolinux_internal_group != "" - - ansible_distribution_major_version | version_compare('9', '>=') + - ansible_distribution_major_version is version('9', '>=') ## Optional secondary groups, defined per user diff --git a/evomaintenance/defaults/main.yml b/evomaintenance/defaults/main.yml index e515230d..4a157976 100644 --- a/evomaintenance/defaults/main.yml +++ b/evomaintenance/defaults/main.yml @@ -12,7 +12,7 @@ evolinux_internal_domain: "{{ evolinux_domain }}" evolinux_internal_fqdn: "{{ evolinux_internal_hostname }}.{{ evolinux_internal_domain }}" ################################# -evomaintenance_install_vendor: False +evomaintenance_install_vendor: True evomaintenance_force_config: True evomaintenance_hostname: "{{ evolinux_internal_fqdn }}" @@ -34,8 +34,8 @@ evomaintenance_realm: "{{ evolinux_internal_domain }}" evomaintenance_api_endpoint: Null evomaintenance_api_key: Null -evomaintenance_hook_api: False -evomaintenance_hook_db: True +evomaintenance_hook_api: True +evomaintenance_hook_db: False evomaintenance_hook_commit: True evomaintenance_hook_mail: True diff --git a/evomaintenance/files/evomaintenance.sh b/evomaintenance/files/evomaintenance.sh index 40b36625..1cd4ce7f 100644 --- a/evomaintenance/files/evomaintenance.sh +++ b/evomaintenance/files/evomaintenance.sh @@ -4,18 +4,19 @@ # Dependencies (all OS): git postgresql-client # Dependencies (Debian): sudo -# version 0.5.0 # Copyright 2007-2019 Evolix , Gregory Colpart , # Jérémy Lecour and others. -VERSION="0.5.0" +VERSION="0.6.3" show_version() { cat <, Gregory Colpart , - Jérémy Lecour and others. +Copyright 2007-2019 Evolix , + Gregory Colpart , + Jérémy Lecour + and others. evomaintenance comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. @@ -53,6 +54,12 @@ Options END } +syslog() { + if [ -x "${LOGGER_BIN}" ]; then + ${LOGGER_BIN} -t "evomaintenance" "$1" + fi +} + get_system() { uname -s } @@ -167,12 +174,44 @@ print_session_data() { printf "Message : %s\n" "${MESSAGE}" } +is_repository_readonly() { + if [ "$(get_system)" = "OpenBSD" ]; then + partition=$(stat -f '%Sd' $1) + mount | grep ${partition} | grep -q "read-only" + else + mountpoint=$(stat -c '%m' $1) + findmnt ${mountpoint} --noheadings --output OPTIONS -O ro + fi +} +remount_repository_readwrite() { + if [ "$(get_system)" = "OpenBSD" ]; then + partition=$(stat -f '%Sd' $1) + mount -u -w /dev/${partition} 2>/dev/null + else + mountpoint=$(stat -c '%m' $1) + mount -o remount,rw ${mountpoint} + syslog "Re-mount ${mountpoint} as read-write to commit in repository $1" + fi +} +remount_repository_readonly() { + if [ "$(get_system)" = "OpenBSD" ]; then + partition=$(stat -f '%Sd' $1) + mount -u -r /dev/${partition} 2>/dev/null + else + mountpoint=$(stat -c '%m' $1) + mount -o remount,ro ${mountpoint} 2>/dev/null + syslog "Re-mount ${mountpoint} as read-only after commit to repository $1" + fi +} + hook_commit() { if [ -x "${GIT_BIN}" ]; then # loop on possible directories managed by GIT for dir in ${GIT_REPOSITORIES}; do # tell Git where to find the repository and the work tree (no need to `cd …` there) export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}" + # reset variable used to track if a mount point is readonly + READONLY_ORIG=0 # If the repository and the work tree exist, try to commit changes if [ -d "${GIT_DIR}" ] && [ -d "${GIT_WORK_TREE}" ]; then CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ') @@ -183,8 +222,13 @@ hook_commit() { # GIT_COMMITS_SHORT=$(printf "%s\n%s : %s" "${GIT_COMMITS_SHORT}" "${GIT_DIR}" "${STATS_SHORT}" | sed -e '/^$/d') GIT_COMMITS=$(printf "%s\n%s\n%s" "${GIT_COMMITS}" "${GIT_DIR}" "${STATS}" | sed -e '/^$/d') else + # remount mount point read-write if currently readonly + is_repository_readonly ${dir} && { READONLY_ORIG=1; remount_repository_readwrite ${dir}; } + # commit changes ${GIT_BIN} add --all ${GIT_BIN} commit --message "${MESSAGE}" --author="${USER} <${USER}@evolix.net>" --quiet + # remount mount point read-only if it was before + test "$READONLY_ORIG" = "1" && remount_repository_readonly ${dir} # Add the SHA to the log file if something has been committed SHA=$(${GIT_BIN} rev-parse --short HEAD) # STATS_SHORT=$(${GIT_BIN} show --stat | tail -1) @@ -347,7 +391,7 @@ while :; do show_help exit 0 ;; - --version) + -V|--version) show_version exit 0 ;; @@ -454,35 +498,38 @@ PATH=${PATH}:/usr/sbin SENDMAIL_BIN=$(command -v sendmail) readonly SENDMAIL_BIN -if [ -z "${SENDMAIL_BIN}" ]; then +if [ "${HOOK_MAIL}" = "1" ] && [ -z "${SENDMAIL_BIN}" ]; then echo "No \`sendmail' command has been found, can't send mail." 2>&1 fi GIT_BIN=$(command -v git) readonly GIT_BIN -if [ -z "${GIT_BIN}" ]; then +if [ "${HOOK_COMMIT}" = "1" ] && [ -z "${GIT_BIN}" ]; then echo "No \`git' command has been found, can't commit changes" 2>&1 fi PSQL_BIN=$(command -v psql) readonly PSQL_BIN -if [ -z "${PSQL_BIN}" ]; then +if [ "${HOOK_DB}" = "1" ] && [ -z "${PSQL_BIN}" ]; then echo "No \`psql' command has been found, can't save to the database." 2>&1 fi CURL_BIN=$(command -v curl) readonly CURL_BIN -if [ -z "${CURL_BIN}" ]; then +if [ "${HOOK_API}" = "1" ] && [ -z "${CURL_BIN}" ]; then echo "No \`curl' command has been found, can't call the API." 2>&1 fi -if [ -z "${API_ENDPOINT}" ]; then +LOGGER_BIN=$(command -v logger) +readonly LOGGER_BIN + +if [ "${HOOK_API}" = "1" ] && [ -z "${API_ENDPOINT}" ]; then echo "No API endpoint specified, can't call the API." 2>&1 fi EVOCHECK_BIN="/usr/share/scripts/evocheck.sh" -GIT_REPOSITORIES="/etc /etc/bind" +GIT_REPOSITORIES="/etc /etc/bind /usr/share/scripts" # initialize variable GIT_STATUSES="" @@ -511,7 +558,7 @@ if [ "${INTERACTIVE}" = "1" ] && [ "${EVOCHECK}" = "1" ]; then get_evocheck fi if [ -n "${GIT_STATUSES}" ] && [ "${INTERACTIVE}" = "1" ]; then - printf "/!\ There are some uncommited changes.\n%s\n\n" "${GIT_STATUSES}" + printf "/!\\\ There are some uncommited changes.\n%s\n\n" "${GIT_STATUSES}" fi if [ -z "${MESSAGE}" ]; then diff --git a/evomaintenance/tasks/install_package_debian.yml b/evomaintenance/tasks/install_package_debian.yml index 2c811d79..a5da77ea 100644 --- a/evomaintenance/tasks/install_package_debian.yml +++ b/evomaintenance/tasks/install_package_debian.yml @@ -2,7 +2,7 @@ - name: Evolix public repositry is installed include_role: - name: apt + name: evolix/apt tasks_from: evolix_public.yml tags: - evomaintenance diff --git a/evomaintenance/tasks/install_vendor_debian.yml b/evomaintenance/tasks/install_vendor_debian.yml index 744db54a..a3d29b95 100644 --- a/evomaintenance/tasks/install_vendor_debian.yml +++ b/evomaintenance/tasks/install_vendor_debian.yml @@ -2,17 +2,24 @@ - name: Dependencies are installed apt: - name: "{{ item }}" + name: + - sudo + - curl state: present - with_items: - - postgresql-client - - sudo - - curl + tags: + - evomaintenance + +- name: PG dependencies are installed + apt: + name: + - postgresql-client + state: present + when: evomaintenance_hook_db tags: - evomaintenance - include_role: - name: remount-usr + name: evolix/remount-usr tags: - evomaintenance @@ -38,7 +45,7 @@ with_items: - { src: 'evomaintenance.sh', dest: '/usr/share/scripts/', mode: '0700' } - { src: 'evomaintenance.tpl', dest: '/usr/share/scripts/', mode: '0600' } - tags: + tags: - evomaintenance - name: Configuration is installed diff --git a/evomaintenance/tasks/install_vendor_openbsd.yml b/evomaintenance/tasks/install_vendor_openbsd.yml index 5aa019ff..37307cfb 100644 --- a/evomaintenance/tasks/install_vendor_openbsd.yml +++ b/evomaintenance/tasks/install_vendor_openbsd.yml @@ -2,11 +2,10 @@ - name: Dependencies are installed openbsd_pkg: - name: "{{ item }}" + name: + - postgresql-client + - curl state: present - with_items: - - postgresql-client - - curl tags: - evomaintenance @@ -32,7 +31,7 @@ with_items: - { src: 'evomaintenance.sh', dest: '/usr/share/scripts/', mode: '0700' } - { src: 'evomaintenance.tpl', dest: '/usr/share/scripts/', mode: '0600' } - tags: + tags: - evomaintenance - name: Configuration is installed @@ -51,7 +50,7 @@ src: mailevomaintenance.sh.j2 dest: /usr/share/scripts/mailevomaintenance.sh owner: root - group: wheel + group: wheel mode: "0700" tags: - evomaintenance diff --git a/evomaintenance/tasks/main.yml b/evomaintenance/tasks/main.yml index 6d2cd26c..d56a124a 100644 --- a/evomaintenance/tasks/main.yml +++ b/evomaintenance/tasks/main.yml @@ -25,4 +25,5 @@ - include: minifirewall.yml when: + - evomaintenance_hook_db - ansible_distribution == "Debian" diff --git a/fail2ban/tasks/main.yml b/fail2ban/tasks/main.yml index 6e97fb2d..e496c07e 100644 --- a/fail2ban/tasks/main.yml +++ b/fail2ban/tasks/main.yml @@ -30,7 +30,7 @@ tags: - fail2ban -- name: Include ignoredips update task +- name: Include ignoredips update task include: ip_whitelist.yml when: fail2ban_force_update_ignore_ips tags: diff --git a/filebeat/tasks/main.yml b/filebeat/tasks/main.yml index e02ff6ab..0aca06d6 100644 --- a/filebeat/tasks/main.yml +++ b/filebeat/tasks/main.yml @@ -54,16 +54,17 @@ changed_when: false when: filebeat_logstash_plugin and logstash_plugin.stat.exists -- block: - - include_role: - name: remount-usr +- name: Logstash plugin is installed + block: + - include_role: + name: evolix/remount-usr - - name: Logstash plugin is installed - command: /usr/share/logstash/bin/logstash-plugin install logstash-input-beats + - name: logstash-plugin install logstash-input-beats + command: /usr/share/logstash/bin/logstash-plugin install logstash-input-beats when: - - filebeat_logstash_plugin - - logstash_plugin.stat.exists - - not logstash_plugin_installed | success + - filebeat_logstash_plugin + - logstash_plugin.stat.exists + - not logstash_plugin_installed | success - name: cloud_metadata processor is disabled replace: diff --git a/generate-ldif/README.md b/generate-ldif/README.md index a71be619..afc194a7 100644 --- a/generate-ldif/README.md +++ b/generate-ldif/README.md @@ -10,7 +10,7 @@ A separate `exec.yml` task file can be played manually in playbooks or roles to ``` - include_role: - name: generate-ldif + name: evolix/generate-ldif tasks_from: exec.yml ``` ## Variables diff --git a/generate-ldif/tasks/exec.yml b/generate-ldif/tasks/exec.yml index 6450c6bc..213560a5 100644 --- a/generate-ldif/tasks/exec.yml +++ b/generate-ldif/tasks/exec.yml @@ -6,10 +6,10 @@ failed_when: False check_mode: no tags: - - generateldif-exec + - generateldif-exec - debug: var: generateldif_run.stdout_lines verbosity: 1 tags: - - generateldif-exec + - generateldif-exec diff --git a/generate-ldif/tasks/main.yml b/generate-ldif/tasks/main.yml index 35da06be..019f5a83 100644 --- a/generate-ldif/tasks/main.yml +++ b/generate-ldif/tasks/main.yml @@ -1,7 +1,7 @@ --- - include_role: - name: remount-usr - when: general_scripts_dir | search("/usr") + name: evolix/remount-usr + when: general_scripts_dir is search("/usr") - name: "copy generateldif.sh" template: diff --git a/generate-ldif/templates/generateldif.sh.j2 b/generate-ldif/templates/generateldif.sh.j2 index 778c4fcf..da37817f 100755 --- a/generate-ldif/templates/generateldif.sh.j2 +++ b/generate-ldif/templates/generateldif.sh.j2 @@ -373,7 +373,9 @@ EOT fi # MariaDB -if is_pkg_installed mariadb-server-10.1; then +if is_pkg_installed mariadb-server-10.3; then + mariadb_version=$(get_pkg_version mariadb-server-10.3) +elif is_pkg_installed mariadb-server-10.1; then mariadb_version=$(get_pkg_version mariadb-server-10.1) elif is_pkg_installed mariadb-server-10.0; then mariadb_version=$(get_pkg_version mariadb-server-10.0) diff --git a/haproxy/defaults/main.yml b/haproxy/defaults/main.yml index 4a05ffb7..cfe3fcea 100644 --- a/haproxy/defaults/main.yml +++ b/haproxy/defaults/main.yml @@ -2,3 +2,5 @@ # backward compatibility with a previously used variable haproxy_backports: "{{ haproxy_jessie_backports | default(false, true) }}" haproxy_stats_url: "http://127.0.0.1:8080/" +haproxy_update_config: True +haproxy_force_config: True diff --git a/haproxy/tasks/main.yml b/haproxy/tasks/main.yml index 4ef1c9bd..80e846e6 100644 --- a/haproxy/tasks/main.yml +++ b/haproxy/tasks/main.yml @@ -22,7 +22,7 @@ template: src: "{{ item }}" dest: /etc/haproxy/haproxy.cfg - force: yes + force: "{{ haproxy_force_config }}" validate: "haproxy -c -f %s" with_first_found: - "templates/haproxy/haproxy.{{ inventory_hostname }}.cfg.j2" @@ -30,6 +30,7 @@ - "templates/haproxy/haproxy.default.cfg.j2" - "haproxy.default.cfg.j2" notify: reload haproxy + when: "{{ haproxy_update_config }}" tags: - haproxy - config diff --git a/haproxy/tasks/munin.yml b/haproxy/tasks/munin.yml index 7d21db54..1f65dbe3 100644 --- a/haproxy/tasks/munin.yml +++ b/haproxy/tasks/munin.yml @@ -1,11 +1,10 @@ --- - name: Install Munin plugin and dependencies apt: - name: "{{ item }}" + name: + - munin-plugins-extra + - liblwp-useragent-determined-perl state: present - with_items: - - munin-plugins-extra - - liblwp-useragent-determined-perl tags: - haproxy diff --git a/haproxy/tasks/packages_backports.yml b/haproxy/tasks/packages_backports.yml index 3be91268..66c2118e 100644 --- a/haproxy/tasks/packages_backports.yml +++ b/haproxy/tasks/packages_backports.yml @@ -1,7 +1,7 @@ --- - include_role: - name: apt + name: evolix/apt tasks_from: backports.yml tags: - haproxy @@ -21,7 +21,7 @@ - name: update apt apt: update_cache: yes - when: haproxy_apt_preferences | changed + when: haproxy_apt_preferences is changed tags: - haproxy - packages diff --git a/java/tasks/main.yml b/java/tasks/main.yml index 1249533e..f6de0b43 100644 --- a/java/tasks/main.yml +++ b/java/tasks/main.yml @@ -1,7 +1,7 @@ --- -- fail: - msg: "This role support only java 8 for now !" - when: java_version != 8 +# - fail: +# msg: "This role support only java 8 for now !" +# when: java_version != 8 - include: openjdk.yml when: "{{ java_alternative == 'openjdk' }}" diff --git a/java/tasks/openjdk.yml b/java/tasks/openjdk.yml index 9b4f0b7d..8e187d1b 100644 --- a/java/tasks/openjdk.yml +++ b/java/tasks/openjdk.yml @@ -7,7 +7,7 @@ - name: Install jessie-backports include_role: - name: apt + name: evolix/apt tasks_from: backports.yml when: ansible_distribution_release == "jessie" tags: @@ -25,7 +25,7 @@ - name: This openjdk version is the default alternative alternatives: name: java - path: "/usr/lib/jvm/java-{{ java_version }}-openjdk-amd64/jre/bin/java" + path: "{{ java_bin_path[java_version] }}" when: java_default_alternative tags: - java diff --git a/java/tasks/oracle.yml b/java/tasks/oracle.yml index dd80303b..a2268b7b 100644 --- a/java/tasks/oracle.yml +++ b/java/tasks/oracle.yml @@ -1,11 +1,10 @@ --- - name: Install dependencies for build java package apt: - name: "{{ item }}" + name: + - java-package + - build-essential state: present - with_items: - - java-package - - build-essential tags: - java @@ -41,7 +40,7 @@ - java - include_role: - name: remount-usr + name: evolix/remount-usr - name: Install java package apt: diff --git a/java/vars/main.yml b/java/vars/main.yml new file mode 100644 index 00000000..6cc0c9a5 --- /dev/null +++ b/java/vars/main.yml @@ -0,0 +1,4 @@ +--- +java_bin_path: + 8: /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java + 11: /usr/lib/jvm/java-11-openjdk-amd64/bin/java diff --git a/jenkins/meta/main.yml b/jenkins/meta/main.yml index 5666a334..7120e512 100644 --- a/jenkins/meta/main.yml +++ b/jenkins/meta/main.yml @@ -24,4 +24,4 @@ galaxy_info: # alphanumeric characters. Maximum 20 tags per role. dependencies: - - { role: java, java_alternative: 'openjdk', java_version: 8 } + - { role: evolix/java, java_alternative: 'openjdk', java_version: 8 } diff --git a/kibana/tasks/main.yml b/kibana/tasks/main.yml index b1e7cb42..46fbe980 100644 --- a/kibana/tasks/main.yml +++ b/kibana/tasks/main.yml @@ -91,7 +91,7 @@ # # - block: # - include_role: -# name: remount-usr +# name: evolix/remount-usr # # - name: Move kibana optimize directory # shell: "mv /usr/share/kibana/{{ item }} /var/lib/kibana/{{ item }} && ln -s /var/lib/kibana/{{ item }} /usr/share/kibana/{{ item }}" diff --git a/kvm-host/tasks/images.yml b/kvm-host/tasks/images.yml index 527eb048..420e83ec 100644 --- a/kvm-host/tasks/images.yml +++ b/kvm-host/tasks/images.yml @@ -1,20 +1,22 @@ --- -- block: - - name: "Is {{ kvm_custom_libvirt_images_path }} present ?" - stat: - path: "{{ kvm_custom_libvirt_images_path }}" - check_mode: no - register: kvm_custom_libvirt_images_path_test +- name: Set images path when customized + block: + - name: "Is {{ kvm_custom_libvirt_images_path }} present ?" + stat: + path: "{{ kvm_custom_libvirt_images_path }}" + check_mode: no + register: kvm_custom_libvirt_images_path_test - - name: "read the real datadir" - command: readlink -f /var/lib/libvirt/images - changed_when: False - check_mode: no - register: kvm_libvirt_images_current_real_path_test + - name: "read the real datadir" + command: readlink -f /var/lib/libvirt/images + changed_when: False + check_mode: no + register: kvm_libvirt_images_current_real_path_test when: kvm_custom_libvirt_images_path != '' -- block: +- name: Images directory is moved to custom path + block: - name: "Move libvirt images to {{ kvm_custom_libvirt_images_path }}" command: mv /var/lib/libvirt/images {{ kvm_custom_libvirt_images_path }} args: @@ -33,6 +35,6 @@ dest: '/var/lib/libvirt/images' state: link when: - - kvm_custom_libvirt_images_path != '' - - kvm_custom_libvirt_images_path != kvm_libvirt_images_current_real_path_test.stdout - - not kvm_custom_libvirt_images_path_test.stat.exists + - kvm_custom_libvirt_images_path != '' + - kvm_custom_libvirt_images_path != kvm_libvirt_images_current_real_path_test.stdout + - not kvm_custom_libvirt_images_path_test.stat.exists diff --git a/kvm-host/tasks/packages.yml b/kvm-host/tasks/packages.yml index 37225a80..99790e84 100644 --- a/kvm-host/tasks/packages.yml +++ b/kvm-host/tasks/packages.yml @@ -1,14 +1,13 @@ --- - name: Install packages for kvm/libvirt apt: - name: "{{ item }}" - with_items: - - qemu-kvm - - netcat-openbsd - - bridge-utils - - qemu-utils - - virtinst - - libvirt-daemon-system - - libvirt-clients - - kvm-tools - - vlan + name: + - qemu-kvm + - netcat-openbsd + - bridge-utils + - qemu-utils + - virtinst + - libvirt-daemon-system + - libvirt-clients + - kvm-tools + - vlan diff --git a/ldap/tasks/main.yml b/ldap/tasks/main.yml index 790b7367..8f6fbd67 100644 --- a/ldap/tasks/main.yml +++ b/ldap/tasks/main.yml @@ -1,12 +1,11 @@ - name: LDAP packages are installed apt: - name: '{{ item }}' + name: + - slapd + - ldap-utils + - ldapvi + - shelldap state: present - with_items: - - slapd - - ldap-utils - - ldapvi - - shelldap - name: change sldap listen ip:port lineinfile: @@ -68,10 +67,10 @@ value: "{{ item.value }}" mode: 0640 with_items: - - { option: 'hostname', value: '127.0.0.1' } - - { option: 'base', value: "{{ ldap_suffix }}" } - - { option: 'bind', value: "cn=nagios,ou=ldapusers,{{ ldap_suffix }}" } - - { option: 'pass', value: "{{ ldap_nagios_password.stdout }}" } + - { option: 'hostname', value: '127.0.0.1' } + - { option: 'base', value: "{{ ldap_suffix }}" } + - { option: 'bind', value: "cn=nagios,ou=ldapusers,{{ ldap_suffix }}" } + - { option: 'pass', value: "{{ ldap_nagios_password.stdout }}" } - name: upload ldap initial config template: diff --git a/listupgrade/README.md b/listupgrade/README.md index 727ce468..3d70c09b 100644 --- a/listupgrade/README.md +++ b/listupgrade/README.md @@ -1,6 +1,8 @@ # listupgrade -Install and configure a script not help manage Debian package updates. +Installs and configures a script to help manage Debian packages updates. +This script will send a mail when new packages are available to plan the +upgrade. ## Tasks diff --git a/listupgrade/templates/listupgrade.sh.j2 b/listupgrade/files/listupgrade.sh similarity index 84% rename from listupgrade/templates/listupgrade.sh.j2 rename to listupgrade/files/listupgrade.sh index c8e6f335..ad04c8ee 100644 --- a/listupgrade/templates/listupgrade.sh.j2 +++ b/listupgrade/files/listupgrade.sh @@ -78,7 +78,13 @@ if [[ "$1" != "--cron" ]]; then fi # Update APT cache and get packages to upgrade and packages on hold. -apt -q2 update 2>&1 | (egrep -ve '^(Listing|WARNING|$)' -e upgraded -e 'up to date' || true ) +aptUpdateOutput=$(apt update 2>&1 | (egrep -ve '^(Listing|WARNING|$)' -e upgraded -e 'up to date' || true )) + +if (echo "$aptUpdateOutput" | egrep "^Err(:[0-9]+)? http"); then + echo "FATAL - Not able to fetch all sources (probably a pesky (mini)firewall). Please, fix me" + exit 100 +fi + apt-mark showhold > $packagesHold apt list --upgradable 2>&1 | grep -v -f $packagesHold | egrep -v '^(Listing|WARNING|$)' > $packages packagesParsable=$(cut -f 1 -d / <$packages |tr '\n' ' ') @@ -140,9 +146,9 @@ for pkg in $packagesParsable; do elif echo "$pkg" |grep -qE "^tomcat[[:digit:]]+$"; then echo "Tomcat" >>$servicesToRestart elif [ "$pkg" = "redis-server" ]; then - echo "redis-server" >>$servicesToRestart + echo "Redis" >>$servicesToRestart elif [ "$pkg" = "mongodb-server" ]; then - echo "redis-server" >>$servicesToRestart + echo "MondoDB" >>$servicesToRestart elif echo "$pkg" |grep -qE "^courier-(pop|imap)"; then echo "Courier POP/IMAP" >>$servicesToRestart elif echo "$pkg" |grep -qE "^dovecot-(pop|imap)d"; then @@ -161,15 +167,19 @@ for pkg in $packagesParsable; do echo "Varnish" >>$servicesToRestart elif [ "$pkg" = "squid" ]; then echo "Squid" >>$servicesToRestart + elif [ "$pkg" = "elasticsearch" ]; then + echo "Elasticsearch" >>$servicesToRestart + elif [ "$pkg" = "logstash" ]; then + echo "Logstash" >>$servicesToRestart elif [ "$pkg" = "libc6" ]; then - echo "Tous les services (mise à jour de libc6)." >$servicesToRestart + echo "Tous les services sont susceptibles d'être redémarrés (mise à jour de libc6)." >$servicesToRestart break elif [ "$pkg" = "libstdc++6" ]; then - echo "Tous les services (mise à jour de libstdc++6)." >$servicesToRestart + echo "Tous les services sont susceptibles d'être redémarrés (mise à jour de libstdc++6)." >$servicesToRestart break elif echo "$pkg" |grep -q "^libssl"; then - echo "Tous les services (mise à jour de libssl)." >$servicesToRestart + echo "Tous les services sont susceptibles d'être redémarrés (mise à jour de libssl)." >$servicesToRestart break fi done @@ -215,8 +225,16 @@ tôt possible. Cordialement, -- -Équipe Evolix -Evolix - Hébergement et Infogérance Open Source http://www.evolix.fr/ +Équipe Evolix - Hébergement et Infogérance Open Source +http://evolix.com | Twitter: @Evolix @EvolixNOC | http://blog.evolix.com EOT <$template /usr/sbin/sendmail $mailto + +# Now we try to fetch all the packages for the next update session +downloadstatus=$(apt dist-upgrade --assume-yes --download-only -q2 2>&1) +echo "$downloadstatus" | grep -q 'Download complete and in download only mode' + +if [ $? -ne 0 ]; then + echo "$downloadstatus" +fi; diff --git a/listupgrade/templates/listupgrade_cron.j2 b/listupgrade/files/listupgrade_cron similarity index 100% rename from listupgrade/templates/listupgrade_cron.j2 rename to listupgrade/files/listupgrade_cron diff --git a/listupgrade/files/old-kernel-autoremoval.sh b/listupgrade/files/old-kernel-autoremoval.sh new file mode 100644 index 00000000..2d5df786 --- /dev/null +++ b/listupgrade/files/old-kernel-autoremoval.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +# fork by reg from /etc/kernel/postinst.d/apt-auto-removal script + +set -e + +eval $(apt-config shell DPKG Dir::bin::dpkg/f) +test -n "$DPKG" || DPKG="/usr/bin/dpkg" + +# Detect which one of apt/aptitude we should use. +get_apt_binary() { + root="$1" + # apt could be a Java binary... + if [ -x $root/usr/bin/apt ] && ! $root/usr/bin/apt --version 2>&1 |grep -q "javac"; then + echo "apt" + elif [ -x $root/usr/bin/aptitude ]; then + echo "aptitude" + # Usually in containers + else + echo "apt-get" + fi +} +APT=$(get_apt_binary) + +list="$("${DPKG}" -l | awk '/^[ih][^nc][ ]+(linux|kfreebsd|gnumach)-image-[0-9]+\./ && $2 !~ /-dbg(:.*)?$/ && $2 !~ /-dbgsym(:.*)?$/ { print $2,$3; }' \ + | sed -e 's#^\(linux\|kfreebsd\|gnumach\)-image-##' -e 's#:[^:]\+ # #')" +debverlist="$(echo "$list" | cut -d' ' -f 2 | sort --unique --reverse --version-sort)" + +if [ -n "$1" ]; then + installed_version="$(echo "$list" | awk "\$1 == \"$1\" { print \$2;exit; }")" +fi +unamer="$(uname -r | tr '[A-Z]' '[a-z]')" +if [ -n "$unamer" ]; then + running_version="$(echo "$list" | awk "\$1 == \"$unamer\" { print \$2;exit; }")" +fi +# ignore the currently running version if attempting a reproducible build +if [ -n "${SOURCE_DATE_EPOCH}" ]; then + unamer="" + running_version="" +fi +latest_version="$(echo "$debverlist" | sed -n 1p)" +previous_version="$(echo "$debverlist" | sed -n 2p)" + +debkernels="$(echo "$latest_version +$installed_version +$running_version" | sort -u | sed -e '/^$/ d')" +kernels="$( (echo "$1 +$unamer"; for deb in $debkernels; do echo "$list" | awk "\$2 == \"$deb\" { print \$1; }"; done; ) \ + | sed -e 's#\([\.\+]\)#\\\1#g' -e '/^$/ d' | sort -u|tr '\n' '|' | sed -e 's/|$//')" + + +echo " +List of installed kernel packages: +$list + +# Running kernel: ${running_version:-ignored} (${unamer:-ignored}) +# Last kernel: $latest_version +# Previous kernel: $previous_version +# Kernel versions list to keep: +$debkernels + +# Kernel packages (version part) to protect: +$kernels +" + +echo "BEFORE" +dpkg -l | grep linux-image + +dpkg --get-selections | tr '\t' ' ' | cut -d" " -f1 | grep ^linux-image-[234] | egrep -v "($kernels)" | xargs --no-run-if-empty $APT -y purge + +echo " +AFTER" +dpkg -l | grep linux-image +echo "" diff --git a/listupgrade/tasks/main.yml b/listupgrade/tasks/main.yml index 9bfe764e..a1449b04 100644 --- a/listupgrade/tasks/main.yml +++ b/listupgrade/tasks/main.yml @@ -1,7 +1,7 @@ --- - include_role: - name: remount-usr + name: evolix/remount-usr - name: Scripts dir is present file: @@ -12,8 +12,8 @@ mode: "0700" - name: Copy listupgrade script - template: - src: listupgrade.sh.j2 + copy: + src: listupgrade.sh dest: "/usr/share/scripts/listupgrade.sh" mode: "0700" owner: root @@ -46,9 +46,18 @@ group: root - name: Enable listupgrade cron - template: - src: listupgrade_cron.j2 + copy: + src: listupgrade_cron dest: /etc/cron.d/listupgrade mode: "0600" owner: root group: root + force: no + +- name: old-kernel-autoremoval script is present + copy: + src: old-kernel-autoremoval.sh + dest: /usr/share/scripts/old-kernel-autoremoval.sh + mode: "0755" + owner: root + group: root diff --git a/logstash/meta/main.yml b/logstash/meta/main.yml index dff3c72e..489fa419 100644 --- a/logstash/meta/main.yml +++ b/logstash/meta/main.yml @@ -14,4 +14,4 @@ galaxy_info: - jessie dependencies: - - { role: java, java_alternative: 'openjdk', java_version: 8 } + - { role: evolix/java, java_alternative: 'openjdk', java_version: 8 } diff --git a/logstash/tasks/logs.yml b/logstash/tasks/logs.yml index 9d5b5023..4417bd89 100644 --- a/logstash/tasks/logs.yml +++ b/logstash/tasks/logs.yml @@ -1,5 +1,10 @@ --- - +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" + failed_when: False + changed_when: False + register: is_cron_installed + - name: "log rotation script" template: src: rotate_logstash_logs.j2 @@ -7,3 +12,4 @@ owner: root group: root mode: "0750" + when: is_cron_installed.rc == 0 diff --git a/lxc-php/defaults/main.yml b/lxc-php/defaults/main.yml index 24342068..ef564b2a 100644 --- a/lxc-php/defaults/main.yml +++ b/lxc-php/defaults/main.yml @@ -6,3 +6,10 @@ php_conf_log_errors: "On" php_conf_html_errors: "Off" php_conf_allow_url_fopen: "Off" php_conf_disable_functions: "exec,shell-exec,system,passthru,putenv,popen" + +lxc_php_version: Null + +lxc_php_container_releases: + php56: "jessie" + php70: "stretch" + php73: "buster" diff --git a/lxc-php/handlers/main.yml b/lxc-php/handlers/main.yml index 365179ea..06953b4f 100644 --- a/lxc-php/handlers/main.yml +++ b/lxc-php/handlers/main.yml @@ -1,9 +1,20 @@ --- -- name: Reload php56-fpm - command: "lxc-attach -n php56 -- systemctl reload php5-fpm" +- name: Reload php73-fpm + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl reload php7.3-fpm" - name: Reload php70-fpm - command: "lxc-attach -n php70 -- systemctl reload php7.0-fpm" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl reload php7.0-fpm" -- name: Reload php73-fpm - command: "lxc-attach -n php73 -- systemctl reload php7.3-fpm" +- name: Reload php56-fpm + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl reload php5-fpm" + +- name: Restart opensmtpd + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl restart opensmtpd" diff --git a/lxc-php/meta/main.yml b/lxc-php/meta/main.yml new file mode 100644 index 00000000..3c965d43 --- /dev/null +++ b/lxc-php/meta/main.yml @@ -0,0 +1,23 @@ +galaxy_info: + author: Evolix + description: Creation of LXC Containers & Setting up PHP-FPM for a multiphp setup + + issue_tracker_url: https://gitea.evolix.org/evolix/ansible-roles/issues + + license: GPLv2 + + min_ansible_version: 2.2 + + platforms: + - name: Debian + versions: + - stretch + - buster + +allow_duplicates: yes + +dependencies: + - { role: evolix/lxc, + lxc_containers: [ { name: "{{ lxc_php_version }}", release: "{{ lxc_php_container_releases[lxc_php_version] }}" } ], + when: lxc_php_version is defined + } diff --git a/lxc-php/tasks/mail_opensmtpd.yml b/lxc-php/tasks/mail_opensmtpd.yml new file mode 100644 index 00000000..20fe91e3 --- /dev/null +++ b/lxc-php/tasks/mail_opensmtpd.yml @@ -0,0 +1,13 @@ +--- + +- name: "{{ lxc_php_version }} - Install opensmtpd" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y ssmtp" + +- name: "{{ lxc_php_version }} - Configure opensmtpd (in the container)" + template: + src: smtpd.conf.j2 + dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/smtpd.conf" + mode: "0644" + notify: "Restart opensmtpd" diff --git a/lxc-php/tasks/mail_ssmtp.yml b/lxc-php/tasks/mail_ssmtp.yml new file mode 100644 index 00000000..01e8216a --- /dev/null +++ b/lxc-php/tasks/mail_ssmtp.yml @@ -0,0 +1,12 @@ +--- + +- name: "{{ lxc_php_version }} - Install ssmtp" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y ssmtp" + +- name: "{{ lxc_php_version }} - Configure ssmtp" + template: + src: ssmtp.conf.j2 + dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/ssmtp/ssmtp.conf" + mode: "0644" diff --git a/lxc-php/tasks/main.yml b/lxc-php/tasks/main.yml index f17cad4a..d3fcad4a 100644 --- a/lxc-php/tasks/main.yml +++ b/lxc-php/tasks/main.yml @@ -1,18 +1,21 @@ --- -- name: LXC configuration - include_role: - name: lxc +- name: "Ensure that lxc_php_version is defined" + fail: + msg: Please configure var lxc_php_version + when: lxc_php_version is none -- include: "php.yml name={{item.name}}" - with_items: - - "{{ lxc_containers }}" +- name: "Update APT cache in container {{lxc_php_version}}" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "apt-get update" -- name: Ensure containers root directory is 755 - file: - path: "/var/lib/lxc/{{ item.name }}/rootfs" - state: directory - mode: '0755' - with_items: - - "{{ lxc_containers }}" +- include: "php56.yml" + when: lxc_php_version == "php56" +- include: "php70.yml" + when: lxc_php_version == "php70" +- include: "php73.yml" + when: lxc_php_version == "php73" + +- include: "misc.yml" diff --git a/lxc-php/tasks/misc.yml b/lxc-php/tasks/misc.yml new file mode 100644 index 00000000..af848213 --- /dev/null +++ b/lxc-php/tasks/misc.yml @@ -0,0 +1,25 @@ +--- + +- name: "{{ lxc_php_version }} - Configure timezone for the container" + copy: + remote_src: yes + src: "/etc/timezone" + dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/timezone" + +- name: "{{ lxc_php_version }} - Ensure container's root directory is 755" + file: + path: "/var/lib/lxc/{{ lxc_php_version }}/rootfs" + state: directory + mode: '0755' + +- name: "{{ lxc_php_version }} - Configure mailname for the container" + copy: + content: "{{ evolinux_hostname }}.{{ evolinux_domain }}\n" + dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/mailname" + notify: "Restart opensmtpd" + + +- name: "{{ lxc_php_version }} - Install misc packages" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y cron logrotate git zip unzip" diff --git a/lxc-php/tasks/php.yml b/lxc-php/tasks/php.yml deleted file mode 100644 index 49cb8116..00000000 --- a/lxc-php/tasks/php.yml +++ /dev/null @@ -1,130 +0,0 @@ -# -# This playbook configures PHP (php-fpm and specific extensions) inside -# container {{name}}. -# - ---- -- name: Update APT cache - command: "lxc-attach -n {{name}} -- apt-get update" - -- name: Install PHP packages - command: "lxc-attach -n {{name}} -- apt-get install -y php5-fpm php5-cli php5-gd php5-imap php5-ldap php5-mcrypt php5-mysql php5-pgsql php-gettext php5-intl php5-curl php5-ssh2 libphp-phpmailer ssmtp git zip unzip" - when: name == 'php56' - -- name: Update APT cache - command: "lxc-attach -n {{name}} -- apt-get update" - when: name == 'php70' - -- name: Install PHP packages - command: "lxc-attach -n {{name}} -- apt-get install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mcrypt php-mysql php-pgsql php-gettext php-curl php-ssh2 composer libphp-phpmailer ssmtp git zip unzip php-zip" - when: name == 'php70' - -- name: Update APT cache - command: "lxc-attach -n {{name}} -- apt-get update" - when: name == 'php73' - -- name: Install requirements for sury repository - command: "lxc-attach -n {{name}} -- apt-get install -y --no-install-recommends wget apt-transport-https ca-certificates gnupg" - when: name == 'php73' - -- name: Add sury APT repository - copy: - content: "deb https://packages.sury.org/php/ stretch main" - dest: "/var/lib/lxc/{{name}}/rootfs/etc/apt/sources.list.d/sury.list" - mode: "0644" - when: name == 'php73' - -- name: Add sury GPG key - shell: "wget -O- https://packages.sury.org/php/apt.gpg |lxc-attach -n {{name}} -- apt-key add -" - when: name == 'php73' - -- name: Update APT cache - command: "lxc-attach -n {{name}} -- apt-get update" - when: name == 'php73' - -- name: Install PHP packages - command: "lxc-attach -n {{name}} -- apt-get install -y php7.3 php7.3-fpm php7.3-cli php7.3-curl php7.3-mysql php7.3-pgsql php7.3-ldap php7.3-imap php7.3-gd php-ssh2 php-gettext composer libphp-phpmailer ssmtp git zip unzip php7.3-zip" - when: name == 'php73' - -- name: Remove default FPM 5.6 pool - file: - name: "/var/lib/lxc/{{name}}/rootfs/etc/php5/fpm/pool.d/www.conf" - state: absent - notify: "Reload {{name}}-fpm" - when: name == 'php56' - -- name: Remove default FPM 7.0 pool - file: - name: "/var/lib/lxc/{{name}}/rootfs/etc/php/7.0/fpm/pool.d/www.conf" - state: absent - notify: "Reload {{name}}-fpm" - when: name == 'php70' - -- name: Remove default FPM 7.3 pool - file: - name: "/var/lib/lxc/{{name}}/rootfs/etc/php/7.3/fpm/pool.d/www.conf" - state: absent - notify: "Reload {{name}}-fpm" - when: name == 'php73' - -- name: Copy evolinux PHP 5.6 configuration - template: - src: z-evolinux-defaults.ini.j2 - dest: "{{ line_item }}" - mode: "0644" - notify: "Reload {{name}}-fpm" - when: name == 'php56' - with_items: - - "/var/lib/lxc/{{name}}/rootfs/etc/php5/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{name}}/rootfs/etc/php5/cli/conf.d/z-evolinux-defaults.ini" - loop_control: - loop_var: line_item - -- name: Copy evolinux PHP 7.0 configuration - template: - src: z-evolinux-defaults.ini.j2 - dest: "{{ line_item }}" - mode: "0644" - notify: "Reload {{name}}-fpm" - when: name == 'php70' - with_items: - - "/var/lib/lxc/{{name}}/rootfs/etc/php/7.0/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{name}}/rootfs/etc/php/7.0/cli/conf.d/z-evolinux-defaults.ini" - loop_control: - loop_var: line_item - -- name: Copy evolinux PHP 7.3 configuration - template: - src: z-evolinux-defaults.ini.j2 - dest: "{{ line_item }}" - mode: "0644" - notify: "Reload {{name}}-fpm" - when: name == 'php73' - with_items: - - "/var/lib/lxc/{{name}}/rootfs/etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{name}}/rootfs/etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini" - loop_control: - loop_var: line_item - -- name: Configure ssmtp - replace: - name: "/var/lib/lxc/{{name}}/rootfs/etc/ssmtp/ssmtp.conf" - regexp: "^mailhub=.*$" - replace: "mailhub=127.0.0.1" - -- name: Configure ssmtp - replace: - name: "/var/lib/lxc/{{name}}/rootfs/etc/ssmtp/ssmtp.conf" - regexp: "^#FromLineOverride=.*$" - replace: "FromLineOverride=YES" - -- name: Configure ssmtp - replace: - name: "/var/lib/lxc/{{name}}/rootfs/etc/ssmtp/ssmtp.conf" - regexp: "^hostname=.*" - replace: "hostname={{ansible_fqdn}}" - -- name: Configure timezone - copy: - dest: "/var/lib/lxc/{{name}}/rootfs/etc/timezone" - content: "Europe/Paris\n" diff --git a/lxc-php/tasks/php56.yml b/lxc-php/tasks/php56.yml new file mode 100644 index 00000000..1f254b35 --- /dev/null +++ b/lxc-php/tasks/php56.yml @@ -0,0 +1,20 @@ +--- + +- name: "{{ lxc_php_version }} - Install PHP packages" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php5-fpm php5-cli php5-gd php5-imap php5-ldap php5-mcrypt php5-mysql php5-pgsql php-gettext php5-intl php5-curl php5-ssh2 libphp-phpmailer ssmtp" + +- name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" + template: + src: z-evolinux-defaults.ini.j2 + dest: "{{ line_item }}" + mode: "0644" + notify: "Reload {{ lxc_php_version }}-fpm" + with_items: + - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php5/fpm/conf.d/z-evolinux-defaults.ini" + - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php5/cli/conf.d/z-evolinux-defaults.ini" + loop_control: + loop_var: line_item + +- include: "mail_ssmtp.yml" diff --git a/lxc-php/tasks/php70.yml b/lxc-php/tasks/php70.yml new file mode 100644 index 00000000..2e45890d --- /dev/null +++ b/lxc-php/tasks/php70.yml @@ -0,0 +1,20 @@ +--- + +- name: "{{ lxc_php_version }} - Install PHP packages" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mcrypt php-mysql php-pgsql php-gettext php-curl php-ssh2 php-zip php-mbstring composer libphp-phpmailer" + +- name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" + template: + src: z-evolinux-defaults.ini.j2 + dest: "{{ line_item }}" + mode: "0644" + notify: "Reload {{ lxc_php_version }}-fpm" + with_items: + - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.0/fpm/conf.d/z-evolinux-defaults.ini" + - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.0/cli/conf.d/z-evolinux-defaults.ini" + loop_control: + loop_var: line_item + +- include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/php73.yml b/lxc-php/tasks/php73.yml new file mode 100644 index 00000000..0149d6ee --- /dev/null +++ b/lxc-php/tasks/php73.yml @@ -0,0 +1,20 @@ +--- + +- name: "{{ lxc_php_version }} - Install PHP packages" + lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-gettext php-curl php-ssh2 php-zip php-mbstring php-zip composer libphp-phpmailer" + +- name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" + template: + src: z-evolinux-defaults.ini.j2 + dest: "{{ line_item }}" + mode: "0644" + notify: "Reload {{ lxc_php_version }}-fpm" + with_items: + - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini" + - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini" + loop_control: + loop_var: line_item + +- include: "mail_opensmtpd.yml" diff --git a/lxc-php/templates/.z-evolinux-defaults.ini.j2.swp b/lxc-php/templates/.z-evolinux-defaults.ini.j2.swp deleted file mode 100644 index 394086f5..00000000 Binary files a/lxc-php/templates/.z-evolinux-defaults.ini.j2.swp and /dev/null differ diff --git a/lxc-php/templates/smtpd.conf.j2 b/lxc-php/templates/smtpd.conf.j2 new file mode 100644 index 00000000..8458d4b4 --- /dev/null +++ b/lxc-php/templates/smtpd.conf.j2 @@ -0,0 +1,14 @@ +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. + +# To accept external mail, replace with: listen on all +#listen on localhost + +# If you edit the file, you have to run "smtpctl update table aliases" +table aliases file:/etc/aliases + +# Uncomment the following to accept external mail for domain "example.org" +#accept from any for domain "example.org" alias deliver to mbox + +accept for local alias deliver to mbox +accept for any relay via "smtp://127.0.0.1" diff --git a/lxc-php/templates/ssmtp.conf.j2 b/lxc-php/templates/ssmtp.conf.j2 new file mode 100644 index 00000000..dafba814 --- /dev/null +++ b/lxc-php/templates/ssmtp.conf.j2 @@ -0,0 +1,21 @@ +# +# Config file for sSMTP sendmail +# +# The person who gets all mail for userids < 1000 +# Make this empty to disable rewriting. +root=postmaster + +# The place where the mail goes. The actual machine name is required no +# MX records are consulted. Commonly mailhosts are named mail.domain.com +mailhub=127.0.0.1 + +# Where will the mail seem to come from? +#rewriteDomain= + +# The full hostname +hostname={{ ansible_fqdn }} + +# Are users allowed to set their own From: address? +# YES - Allow the user to specify their own From: address +# NO - Use the system generated From: address +FromLineOverride=YES diff --git a/lxc-solr/README.md b/lxc-solr/README.md new file mode 100644 index 00000000..6fb31c40 --- /dev/null +++ b/lxc-solr/README.md @@ -0,0 +1,27 @@ +# lxc-solr + +Create one or more LXC containers with Solr in the version of your choice. + +*note : this role depend on the lxc role.* + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +Since this role depend on the lxc role, please refer to it for a full variable list related to the lxc containers setup. + +* `lxc_containers`: list of LXC containers to create. Default: `[]` (empty). + * `name`: name of the LXC container to create. + * `release`: Debian version to install + * `solr_version`: Solr version to install *(refer to https://archive.apache.org/dist/lucene/solr/ for a full version list)* + * `solr_port`: port for Solr to listen on + Eg.: + ``` + lxc_containers: + - name: solr8 + release: stretch + solr_version: 6.6.6 + solr_port: 8983 + ``` diff --git a/lxc-solr/defaults/main.yml b/lxc-solr/defaults/main.yml new file mode 100644 index 00000000..ad2a3e23 --- /dev/null +++ b/lxc-solr/defaults/main.yml @@ -0,0 +1,18 @@ +--- +# List of LXC containers to create alongside with the version of Solr to install for each of them +# Eg.: +# lxc_containers: +# - name: solr6 +# release: stretch +# solr_version: 6.6.6 +# solr_port: 8983 +# - name: solr7 +# release: stretch +# solr_version: 7.7.2 +# solr_port: 8984 +# - name: solr8 +# release: stretch +# solr_version: 8.4.1 +# solr_port: 8985 +lxc_containers: [] + diff --git a/lxc-solr/tasks/main.yml b/lxc-solr/tasks/main.yml new file mode 100644 index 00000000..a18c46dc --- /dev/null +++ b/lxc-solr/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: LXC configuration + include_role: + name: evolix/lxc + +- name: Ensure containers root directory is 755 + file: + path: "/var/lib/lxc/{{ item.name }}/rootfs" + state: directory + mode: '0755' + with_items: + - "{{ lxc_containers }}" + +- include: "solr.yml name={{item.name}} solr_version={{item.solr_version}} solr_port={{item.solr_port}}" + with_items: + - "{{ lxc_containers }}" diff --git a/lxc-solr/tasks/solr.yml b/lxc-solr/tasks/solr.yml new file mode 100644 index 00000000..4cf521ae --- /dev/null +++ b/lxc-solr/tasks/solr.yml @@ -0,0 +1,42 @@ +--- +- name: Install openjdk-8-jre-headless and lsof packages + command: "lxc-attach -n {{name}} -- apt-get install -y openjdk-8-jre-headless lsof" + +- name: "Download Solr {{ solr_version }}" + get_url: + url: "https://archive.apache.org/dist/lucene/solr/{{ solr_version }}/solr-{{ solr_version }}.tgz" + dest: "/var/lib/lxc/{{ name }}/rootfs/root/solr-{{ solr_version }}.tgz" + mode: '0644' + +- name: "Extract solr-{{ solr_version }}.tgz" + unarchive: + src: /var/lib/lxc/{{ name }}/rootfs/root/solr-{{ solr_version }}.tgz + dest: /var/lib/lxc/{{ name }}/rootfs/root/ + remote_src: yes + +- name: "Install Solr {{ solr_version }}" + command: "lxc-attach -n {{name}} -- /root/solr-{{ solr_version }}/bin/install_solr_service.sh /root/solr-{{ solr_version }}.tgz" + +- name: "Stop Solr" + command: "lxc-attach -n {{name}} -- /etc/init.d/solr stop" + ignore_errors: True + +- name: "Make sure /home/solr exists" + file: + path: /home/solr + state: directory + mode: '0755' + +- name: "Move Solr data directory to /home/solr/{{name}}" + command: "lxc-attach -n {{name}} -- mv /var/solr /home/solr/{{name}}" + +- name: "Create a symbolic link to /home/solr/{{name}}" + command: "lxc-attach -n {{name}} -- ln -s /home/solr/{{name}} /var/solr" + +- name: "Set Solr port to {{ solr_port }}" + lineinfile: + dest: /var/lib/lxc/{{ name }}/rootfs/etc/default/solr.in.sh + line: "SOLR_PORT={{ solr_port }}" + +- name: "Start Solr" + command: "lxc-attach -n {{name}} -- /etc/init.d/solr start" diff --git a/lxc-solr/templates/rc.local.j2 b/lxc-solr/templates/rc.local.j2 new file mode 100644 index 00000000..5a74a8b5 --- /dev/null +++ b/lxc-solr/templates/rc.local.j2 @@ -0,0 +1,3 @@ +#!/bin/bash +/opt/solr-{{ solr_version }}/bin/solr start -p {{ solr_port }} -force +exit 0 diff --git a/lxc/meta/main.yml b/lxc/meta/main.yml new file mode 100644 index 00000000..f6af051d --- /dev/null +++ b/lxc/meta/main.yml @@ -0,0 +1,17 @@ +galaxy_info: + author: Evolix + description: Creation of LXC Containers + + issue_tracker_url: https://gitea.evolix.org/evolix/ansible-roles/issues + + license: GPLv2 + + min_ansible_version: 2.2 + + platforms: + - name: Debian + versions: + - stretch + - buster + +allow_duplicates: yes diff --git a/lxc/tasks/create-container.yml b/lxc/tasks/create-container.yml index 6faeeabf..8b8a68e3 100644 --- a/lxc/tasks/create-container.yml +++ b/lxc/tasks/create-container.yml @@ -1,61 +1,58 @@ --- -- name: Check if container exists - command: "lxc-ls {{name}}" +- name: "Check if container {{ name }} exists" + command: "lxc-ls {{ name }}" changed_when: false + check_mode: no register: container_exists -- name: Create container - command: "lxc-create --name {{name}} --template debian --bdev dir --logfile /var/log/lxc/lxc-{{name}}.log --logpriority INFO -- --arch amd64 --release {{release}}" +- name: "Create container {{ name }}" + lxc_container: + name: "{{ name }}" + container_log: true + template: debian + state: stopped + template_options: "--arch amd64 --release {{ release }}" when: container_exists.stdout_lines == [] -#- name: Create container -# lxc_container: -# name: "{{name}}" -# container_log: true -# template: download -# state: stopped -# template_options: "--release {{release}}" - -- name: Disable network configuration inside container +- name: "Disable network configuration inside container {{ name }}" replace: - name: "/var/lib/lxc/{{name}}/rootfs/etc/default/networking" + name: "/var/lib/lxc/{{ name }}/rootfs/etc/default/networking" regexp: "^#CONFIGURE_INTERFACES=yes" replace: CONFIGURE_INTERFACES=no when: lxc_network_type == "none" -- name: Disable interface shut down on halt inside container +- name: "Disable interface shut down on halt inside container {{ name }} (Jessie container)" lineinfile: - name: "/var/lib/lxc/{{name}}/rootfs/etc/default/halt" + name: "/var/lib/lxc/{{ name }}/rootfs/etc/default/halt" line: "NETDOWN=no" - when: lxc_network_type == "none" and release != "stretch" + when: lxc_network_type == "none" and release == "jessie" -- name: Make the container poweroff on SIGPWR (sent by lxc-stop) on jessie +- name: "Make the container {{ name }} poweroff on SIGPWR sent by lxc-stop (Jessie container)" file: src: /lib/systemd/system/poweroff.target - dest: "/var/lib/lxc/{{name}}/rootfs/etc/systemd/system/sigpwr.target" + dest: "/var/lib/lxc/{{ name }}/rootfs/etc/systemd/system/sigpwr.target" state: link when: release == 'jessie' -- name: Set the DNS resolvers - command: "cp /etc/resolv.conf /var/lib/lxc/{{name}}/rootfs/etc/" +- name: "Configure the DNS resolvers in the container {{ name }}" + copy: + remote_src: yes + src: /etc/resolv.conf + dest: "/var/lib/lxc/{{ name }}/rootfs/etc/" -- name: Add hostname in /etc/hosts +- name: "Add hostname in /etc/hosts for container {{ name }}" lineinfile: - name: "/var/lib/lxc/{{name}}/rootfs/etc/hosts" - line: "127.0.0.1 {{name}}" + name: "/var/lib/lxc/{{ name }}/rootfs/etc/hosts" + line: "127.0.0.1 {{ name }}" -- name: Fix permission on /dev +- name: "Fix permission on /dev for container {{ name }}" lineinfile: - name: "/var/lib/lxc/{{name}}/rootfs/etc/rc.local" + name: "/var/lib/lxc/{{ name }}/rootfs/etc/rc.local" line: "chmod 755 /dev" insertbefore: "^exit 0$" - when: release != 'stretch' + when: release == 'jessie' -- name: Check if container is running - command: "lxc-ls --running {{name}}" - changed_when: false - register: container_running - -- name: "Start {{name}} container" - command: "lxc-start -dn {{name}}" - when: container_running.stdout_lines == [] +- name: "Ensure that {{ name }} container is running" + lxc_container: + name: "{{ name }}" + state: started diff --git a/lxc/tasks/main.yml b/lxc/tasks/main.yml index 11d267c5..df8dc86f 100644 --- a/lxc/tasks/main.yml +++ b/lxc/tasks/main.yml @@ -1,11 +1,18 @@ --- - name: Install lxc tools apt: - name: '{{ item }}' - with_items: - - lxc - - debootstrap - - xz-utils + name: + - lxc + - debootstrap + - xz-utils + - python-lxc + +- name: Install additional packages on Buster + apt: + name: + - apparmor + - lxc-templates + when: ansible_distribution_major_version is version('10', '>=') - name: Copy LXC default containers configuration template: @@ -29,5 +36,5 @@ include: create-container.yml vars: name: "{{ item.name }}" - release: "{{item.release}}" - with_items: "{{lxc_containers}}" + release: "{{ item.release }}" + loop: "{{ lxc_containers }}" diff --git a/lxc/templates/default.conf b/lxc/templates/default.conf index 94e7c1d6..a656fd3c 100644 --- a/lxc/templates/default.conf +++ b/lxc/templates/default.conf @@ -7,7 +7,12 @@ lxc.id_map = g 0 100000 10000 {% endif %} # Set the default network virtualization method. +{% if ansible_distribution_major_version is version('9', '<=') %} lxc.network.type = {{lxc_network_type}} +{% else %} +lxc.net.0.type = {{lxc_network_type}} +{% endif %} + {% if lxc_mount_part %} # Mount {{lxc_mount_part}} into containers. @@ -16,7 +21,11 @@ lxc.mount.entry = {{lxc_mount_part}} {{lxc_mount_part |replace('/', '')}} none b {% endif %} # Only one tty is enough. # This require that you disabled others tty ([2-6]) in systemd. +{% if ansible_distribution_major_version is version('9', '<=') %} lxc.tty = 1 +{% else %} +lxc.tty.max = 1 +{% endif %} # Run 64bits containers lxc.arch = x86_64 diff --git a/memcached/defaults/main.yml b/memcached/defaults/main.yml index a99f576a..1ee00faa 100644 --- a/memcached/defaults/main.yml +++ b/memcached/defaults/main.yml @@ -1,5 +1,6 @@ --- memcached_instance_name: "" +multi: "" memcached_logfile: '/var/log/memcached.log' memcached_mem: 64 memcached_user: 'nobody' diff --git a/memcached/tasks/munin.yml b/memcached/tasks/munin.yml index 4d98ca5d..c7ea3da9 100644 --- a/memcached/tasks/munin.yml +++ b/memcached/tasks/munin.yml @@ -1,8 +1,8 @@ --- - name: Choose packages (Oracle) set_fact: - multi: "multi_" - when: memcached_instance_name != False + multi: "multi_" + when: memcached_instance_name !="" - name: is Munin present ? stat: @@ -16,11 +16,10 @@ - block: - name: Install munin-plugins-extra and libcache-memcached-perl for Munin apt: - name: "{{ item }}" + name: + - 'munin-plugins-extra' + - 'libcache-memcached-perl' state: present - with_items: - - 'munin-plugins-extra' - - 'libcache-memcached-perl' - name: Enable core Munin plugins file: diff --git a/memcached/tasks/nrpe.yml b/memcached/tasks/nrpe.yml index 178db3ad..21070aec 100644 --- a/memcached/tasks/nrpe.yml +++ b/memcached/tasks/nrpe.yml @@ -1,6 +1,6 @@ --- - include_role: - name: remount-usr + name: evolix/remount-usr - name: Is nrpe present ? stat: diff --git a/minifirewall/defaults/main.yml b/minifirewall/defaults/main.yml index 3f173962..f5719e3c 100644 --- a/minifirewall/defaults/main.yml +++ b/minifirewall/defaults/main.yml @@ -41,6 +41,8 @@ minifirewall_autostart: False minifirewall_restart_if_needed: True minifirewall_restart_force: False +minifirewall_force_update_nrpe_scripts: False + evomaintenance_hosts: [] nagios_plugins_directory: "/usr/local/lib/nagios/plugins" diff --git a/minifirewall/files/check_minifirewall b/minifirewall/files/check_minifirewall index 632f3e8a..17943994 100644 --- a/minifirewall/files/check_minifirewall +++ b/minifirewall/files/check_minifirewall @@ -10,7 +10,7 @@ is_alert5_enabled() { if test -f /etc/init.d/alert5; then test -f /etc/rc2.d/S*alert5 else - systemctl is-active alert5 | grep -q "^active$" + systemctl is-enabled alert5 -q fi } diff --git a/minifirewall/files/minifirewall.conf b/minifirewall/files/minifirewall.conf index 85246940..8f535230 100644 --- a/minifirewall/files/minifirewall.conf +++ b/minifirewall/files/minifirewall.conf @@ -50,7 +50,8 @@ DNSSERVEURS='0.0.0.0/0' # HTTP authorizations # (you can use DNS names but set cron to reload minifirewall regularly) # (if you have HTTP proxy, set 0.0.0.0/0) -HTTPSITES='security.debian.org security-cdn.debian.org pub.evolix.net volatile.debian.org mirror.evolix.org backports.debian.org hwraid.le-vert.net antispam00.evolix.org spamassassin.apache.org sa-update.space-pro.be sa-update.secnap.net www.sa-update.pccc.com sa-update.dnswl.org' +# HTTPSITES='security.debian.org security-cdn.debian.org pub.evolix.net volatile.debian.org mirror.evolix.org backports.debian.org hwraid.le-vert.net antispam00.evolix.org spamassassin.apache.org sa-update.space-pro.be sa-update.secnap.net www.sa-update.pccc.com sa-update.dnswl.org' +HTTPSITES='0.0.0.0/0' # HTTPS authorizations HTTPSSITES='0.0.0.0/0' diff --git a/minifirewall/tasks/activate.yml b/minifirewall/tasks/activate.yml index 1ecd0dc3..21fb8cfd 100644 --- a/minifirewall/tasks/activate.yml +++ b/minifirewall/tasks/activate.yml @@ -1,7 +1,29 @@ --- + +- name: check if /etc/init.d/alert5 exists + stat: + path: /etc/init.d/alert5 + register: initd_alert5 + - name: Uncomment minifirewall start line replace: dest: /etc/init.d/alert5 regexp: '^#/etc/init.d/minifirewall start' replace: '/etc/init.d/minifirewall start' - when: minifirewall_autostart + when: + - initd_alert5.stat.exists + - minifirewall_autostart + +- name: check if /usr/share/scripts/alert5 exists + stat: + path: /usr/share/scripts/alert5.sh + register: usr_share_scripts_alert5 + +- name: Uncomment minifirewall start line + replace: + dest: /usr/share/scripts/alert5.sh + regexp: '^#/etc/init.d/minifirewall start' + replace: '/etc/init.d/minifirewall start' + when: + - usr_share_scripts_alert5.stat.exists + - minifirewall_autostart diff --git a/minifirewall/tasks/config.yml b/minifirewall/tasks/config.yml index 13cb6145..82be385c 100644 --- a/minifirewall/tasks/config.yml +++ b/minifirewall/tasks/config.yml @@ -37,9 +37,12 @@ line: "# END ANSIBLE MANAGED BLOCK FOR IPS" insertafter: '^PRIVILEGIEDIPS=' -- fail: +- name: Verify that at least 1 trusted IP is provided + assert: + that: + - minifirewall_trusted_ips != [] msg: You must provide at least 1 trusted IP - when: minifirewall_trusted_ips == [] + - debug: msg: "Warning: minifirewall_trusted_ips='0.0.0.0/0', the firewall is useless!" when: minifirewall_trusted_ips == ["0.0.0.0/0"] diff --git a/minifirewall/tasks/install.yml b/minifirewall/tasks/install.yml index 77f60c11..a4bcf734 100644 --- a/minifirewall/tasks/install.yml +++ b/minifirewall/tasks/install.yml @@ -2,10 +2,8 @@ - name: dependencies are satisfied apt: - name: '{{ item }}' + name: iptables state: present - with_items: - - iptables - name: init script is copied template: diff --git a/minifirewall/tasks/main.yml b/minifirewall/tasks/main.yml index d45183ac..99a478e0 100644 --- a/minifirewall/tasks/main.yml +++ b/minifirewall/tasks/main.yml @@ -1,6 +1,7 @@ --- -- set_fact: +- name: Compose minifirewall_restart_handler_name variable + set_fact: minifirewall_restart_handler_name: "{{ minifirewall_restart_if_needed | ternary('restart minifirewall', 'restart minifirewall (noop)') }}" - include: install.yml @@ -17,4 +18,5 @@ - name: Force restart minifirewall command: /bin/true notify: restart minifirewall + changed_when: False when: minifirewall_restart_force diff --git a/minifirewall/tasks/nrpe.yml b/minifirewall/tasks/nrpe.yml index 20629ad0..2e9674f7 100644 --- a/minifirewall/tasks/nrpe.yml +++ b/minifirewall/tasks/nrpe.yml @@ -1,7 +1,7 @@ --- - include_role: - name: remount-usr + name: evolix/remount-usr - name: /usr/share/scripts exists file: @@ -15,7 +15,7 @@ copy: src: minifirewall_status dest: /usr/share/scripts/minifirewall_status - force: no + force: "{{ minifirewall_force_update_nrpe_scripts | bool }}" mode: "0700" owner: root group: root @@ -32,7 +32,7 @@ copy: src: check_minifirewall dest: "{{ nagios_plugins_directory }}/check_minifirewall" - force: no + force: "{{ minifirewall_force_update_nrpe_scripts | bool }}" mode: "0755" owner: root group: staff diff --git a/minifirewall/tasks/tail.yml b/minifirewall/tasks/tail.yml index ab382fe6..1f6b715d 100644 --- a/minifirewall/tasks/tail.yml +++ b/minifirewall/tasks/tail.yml @@ -36,7 +36,7 @@ failed_when: "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" changed_when: "'starting IPTables rules is now finish : OK' in minifirewall_init_restart.stdout" when: - - minifirewall_tail_template | changed + - minifirewall_tail_template is changed - minifirewall_restart_if_needed - name: restart minifirewall (noop) diff --git a/minifirewall/templates/minifirewall.j2 b/minifirewall/templates/minifirewall.j2 index 49852e42..8045ce60 100755 --- a/minifirewall/templates/minifirewall.j2 +++ b/minifirewall/templates/minifirewall.j2 @@ -7,7 +7,7 @@ # Copyright (c) 2007-2015 Evolix # 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 +# as published by the Free Software Foundation; either version 3 # of the License. # Description @@ -262,14 +262,14 @@ for x in $SSHOK # SMTP authorizations for x in $SMTPOK do - $IPT -A INPUT -p tcp ! --syn --sport 25 --dport $PORTSUSER -j ACCEPT + $IPT -A INPUT -p tcp ! --syn --sport 25 --dport $PORTSUSER -s $x -j ACCEPT done # secure SMTP (TCP/465 et TCP/587) authorizations for x in $SMTPSECUREOK do - $IPT -A INPUT -p tcp ! --syn --sport 465 --dport $PORTSUSER -j ACCEPT - $IPT -A INPUT -p tcp ! --syn --sport 587 --dport $PORTSUSER -j ACCEPT + $IPT -A INPUT -p tcp ! --syn --sport 465 --dport $PORTSUSER -s $x -j ACCEPT + $IPT -A INPUT -p tcp ! --syn --sport 587 --dport $PORTSUSER -s $x -j ACCEPT done # NTP authorizations diff --git a/mongodb/README.md b/mongodb/README.md index e876fe7c..24048b5e 100644 --- a/mongodb/README.md +++ b/mongodb/README.md @@ -2,7 +2,7 @@ Install MongoDB -We use packages from 10Gen for Jessie and packages from Debian for Stretch. +We use Debian packages for Stretch, but MongoDB.org packages for Jessie/Buster ## Tasks diff --git a/mongodb/files/server-4.2.asc b/mongodb/files/server-4.2.asc new file mode 100644 index 00000000..693cafb2 --- /dev/null +++ b/mongodb/files/server-4.2.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.11 (GNU/Linux) + +mQINBFrXrqYBEACscLj2qgPpHBCQtgW1Yh29Ddgv6jssyWLAYmj0qngFLKoQMMbt +SNBZylIKxfS+pUD9J5xfRZwfZOmtMIOFVWS9tcpeQXsiwC126tRyoFCIpoTmH7+R +8/FfPrCYyXLP+ftEZfRV60wTwr5drR0S5pVIST3oaXXGkHkFC35U++udUG2Tl4Cs +OPSCp1tsK6UOTjHFDH8PnasImJgD37QC8OOMIJS0jCtDZywJW6OCdpIRbuTWPK3P +P48NLwGUJHixhVCmOgPPu9kDAfG3wLxiN85S2UbaaSXsdA4fF4SSwWNHTIYAg0yT +xGepVyW4lkfcvng4jva24rQ9j1cm1b7bWeOkMH1aAcSyFzKCeNCNxbVOYVrcWNP4 +zrSUvsTKhwX8rPFMq9LkcKirDL9bRILvn/24VU6NdJfGbRjR6+Q7ooj7hYKLXtO5 +q0Q4nhjigpTWIoU6jdfbM9YqpKSELNnkDRAU+bRYSrNaFuizYgDZQvcVT6gbq26f +JbgihoeJogEfim3kqRRJ3EUhE+EaVijl9iLDKkpurod26P2QSq9RKSuOCeauPjQv +3BIiEMXco8O3v8W1y4TbnSQ3d28W9pN28IgAhmN2EU2sKqWPzNeG0V+L6mE5pA4o +nD3z3JRpxAUFw08+9LnLRZ4D1u54OrHADsU8UpYZJCm1xw6T0e4dlxW6rQARAQAB +tDdNb25nb0RCIDQuMiBSZWxlYXNlIFNpZ25pbmcgS2V5IDxwYWNrYWdpbmdAbW9u +Z29kYi5jb20+iQI+BBMBAgAoBQJa166mAhsDBQkJZgGABgsJCAcDAgYVCAIJCgsE +FgIDAQIeAQIXgAAKCRBLfFSaBY+LaxdXEACJMvkgr3Nt2xme9/6brGMbrEy6mQn7 +DZP98DXuS0tWvO5vkEO5IfRIvzG3zA0pATSBDVA0BvGnebQrGXZZ7Xfh0gz+zxlt +TXv4eCyb6T4gRJuuQSFPTyDnZ3MbPESqj0UpIALmcLDJ01nqvbNPKxx5r08XQOtE +i44Kcwc1Px5cPcYP9nmpDNLZjz3gkTm+zBygdE9beP02qXq7WcyghFmQZoLBW53e +TqNPnMrrm5+6vgq+r/ttyiYTo7Zw8MrifN5okevzB0JhhSAW9g+4ZOp1QYbV8u8V +pksJQDOIaBWIw8zosIQJTCVyd4hOyl8Ib2s2R0/grT51RgLYCNbUG6WTpKGgYBtr +Mng10gozyDrnA3B+RiDx5uq+dNzuuMXWMit2nbcdanXdKNkaPmC6WVeU0rG5K1Wz +jQMDvAInTszLcqH6zfEsjCoXj0z8UwcC4jahFDNMDBk3OhjMSL+fnvIhW84nKVHf +AWL5jjSQdkrM/M8QRpRqls5apuIYHQwo6Oyd2Nk0n9T/GOMJ1jilxiPw9ihusf+k +DfU0JI7T8fgxIv/wHNXUg7FOaaDJIfgGlCPUgtsNUDZZ9lFq+Zc5H8Wff3LNo7Se +2xnzzoy2e+C3tsxAmVUTs+q0lyIzEK24lf71cp074KVV7rIYBELYtO2hAlJYjXJU +bscTTjCKLf9leA== +=UXPP +-----END PGP PUBLIC KEY BLOCK----- diff --git a/mongodb/tasks/main.yml b/mongodb/tasks/main.yml index a13183c6..a054a5fd 100644 --- a/mongodb/tasks/main.yml +++ b/mongodb/tasks/main.yml @@ -9,4 +9,7 @@ when: ansible_distribution_release == "jessie" - include: main_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_release == "stretch" + +- include: main_buster.yml + when: ansible_distribution_major_version is version('10', '>=') diff --git a/mongodb/tasks/main_buster.yml b/mongodb/tasks/main_buster.yml new file mode 100644 index 00000000..b04047e0 --- /dev/null +++ b/mongodb/tasks/main_buster.yml @@ -0,0 +1,46 @@ +--- + +- name: MongoDB public GPG Key + apt_key: + # url: https://www.mongodb.org/static/pgp/server-4.2.asc + data: "{{ lookup('file', 'server-4.2.asc') }}" + +- name: enable APT sources list + apt_repository: + repo: deb http://repo.mongodb.org/apt/debian buster/mongodb-org/4.2 main + state: present + filename: mongodb-org-4.2 + update_cache: yes + +- name: Install packages + apt: + name: mongodb-org + update_cache: yes + state: present + register: _mongodb_install_package + +- name: MongoDB service in enabled and started + systemd: + name: mongod + enabled: yes + state: started + when: _mongodb_install_package.changed + +- name: install dependency for monitoring + apt: + name: python-pymongo + state: present + +- name: Custom configuration + template: + src: mongodb_buster.conf.j2 + dest: "/etc/mongod.conf" + force: "{{ mongodb_force_config | bool | ternary('yes', 'no') }}" + notify: restart mongod + +- name: Configure logrotate + template: + src: logrotate_buster.j2 + dest: /etc/logrotate.d/mongodb + force: yes + backup: no diff --git a/mongodb/tasks/main_stretch.yml b/mongodb/tasks/main_stretch.yml index bed975e3..fe44e259 100644 --- a/mongodb/tasks/main_stretch.yml +++ b/mongodb/tasks/main_stretch.yml @@ -2,11 +2,10 @@ - name: Install packages apt: - name: "{{ item }}" + name: + - mongodb + - mongo-tools state: present - with_items: - - mongodb - - mongo-tools - name: install dependency for monitoring apt: diff --git a/mongodb/templates/logrotate_buster.j2 b/mongodb/templates/logrotate_buster.j2 new file mode 100644 index 00000000..fe5926a0 --- /dev/null +++ b/mongodb/templates/logrotate_buster.j2 @@ -0,0 +1,15 @@ +# {{ ansible_managed }} + +/var/log/mongodb/mongodb.log { + daily + missingok + rotate 365 + dateext + compress + delaycompress + notifempty + sharedscripts + postrotate + pidof mongod | xargs kill -USR1 + endscript +} diff --git a/mongodb/templates/mongodb_buster.conf.j2 b/mongodb/templates/mongodb_buster.conf.j2 new file mode 100644 index 00000000..b61479bd --- /dev/null +++ b/mongodb/templates/mongodb_buster.conf.j2 @@ -0,0 +1,39 @@ +# mongodb.conf - {{ ansible_managed }} + +# for documentation of all options, see: +# http://docs.mongodb.org/manual/reference/configuration-options/ + +# Where and how to store data. +storage: + dbPath: /var/lib/mongodb + journal: + enabled: true +# engine: +# mmapv1: +# wiredTiger: + +# where to write logging data. +systemLog: + destination: file + logRotate: reopen + logAppend: true + path: /var/log/mongodb/mongodb.log + +# network interfaces +net: + port: {{ mongodb_port }} + bindIp: {{ mongodb_bind }} + +#security: + +#operationProfiling: + +#replication: + +#sharding: + +## Enterprise-Only Options: + +#auditLog: + +#snmp: diff --git a/munin/tasks/main.yml b/munin/tasks/main.yml index 6dcf1a26..344962f8 100644 --- a/munin/tasks/main.yml +++ b/munin/tasks/main.yml @@ -2,13 +2,12 @@ - name: Ensure that Munin is installed apt: - name: '{{ item }}' + name: + - munin + - munin-node + - munin-plugins-core + - munin-plugins-extra state: present - with_items: - - munin - - munin-node - - munin-plugins-core - - munin-plugins-extra tags: - munin - packages @@ -88,4 +87,4 @@ [swap] user root - when: ansible_kernel | search("-grs-") + when: ansible_kernel is search("-grs-") diff --git a/mysql-oracle/README.md b/mysql-oracle/README.md index 011e1ac3..a4219919 100644 --- a/mysql-oracle/README.md +++ b/mysql-oracle/README.md @@ -21,6 +21,8 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `mysql_replace_root_with_mysqladmin`: switch from `root` to `mysqladmin` user or not ; * `mysql_thread_cache_size`: number of threads for the cache ; * `mysql_innodb_buffer_pool_size`: amount of RAM dedicated to InnoDB ; +* `mysql_log_bin`: (default: `Null`, activates binlogs if used) ; +* `mysql_server_id`: (default: `Null`, MySQL version default is then used) ; * `mysql_custom_datadir`: custom datadir * `mysql_custom_tmpdir`: custom tmpdir. * `general_alert_email`: email address to send various alert messages (default: `root@localhost`). diff --git a/mysql-oracle/defaults/main.yml b/mysql-oracle/defaults/main.yml index d9724ca2..e24ff917 100644 --- a/mysql-oracle/defaults/main.yml +++ b/mysql-oracle/defaults/main.yml @@ -14,6 +14,8 @@ mysql_custom_tmpdir: '' mysql_thread_cache_size: '{{ ansible_processor_cores }}' mysql_innodb_buffer_pool_size: '{{ (ansible_memtotal_mb * 0.3) | int }}M' +mysql_log_bin: Null +mysql_server_id: Null mysql_cron_optimize: True mysql_cron_optimize_frequency: weekly diff --git a/mysql-oracle/tasks/munin.yml b/mysql-oracle/tasks/munin.yml index 52b6eed4..70e871e1 100644 --- a/mysql-oracle/tasks/munin.yml +++ b/mysql-oracle/tasks/munin.yml @@ -12,11 +12,10 @@ - block: - name: Install perl libraries for Munin apt: - name: "{{ item }}" + name: + - libdbd-mysql-perl + - libcache-cache-perl state: present - with_items: - - libdbd-mysql-perl - - libcache-cache-perl - name: Enable core Munin plugins file: diff --git a/mysql-oracle/tasks/packages.yml b/mysql-oracle/tasks/packages.yml index e77288c5..af1a0460 100644 --- a/mysql-oracle/tasks/packages.yml +++ b/mysql-oracle/tasks/packages.yml @@ -16,7 +16,7 @@ dest: "/root/{{ mysql_apt_config_package }}" - include_role: - name: remount-usr + name: evolix/remount-usr - name: MySQL APT config package is installed apt: @@ -34,7 +34,7 @@ - meta: flush_handlers - include_role: - name: remount-usr + name: evolix/remount-usr - name: /usr/share/mysql exists file: @@ -66,27 +66,24 @@ - name: APT cache is up-to-date apt: update_cache: yes - when: mysql_apt_config_deb | changed + when: mysql_apt_config_deb is changed - name: Install MySQL packages apt: - name: '{{ item }}' + name: + - mysql-server + - mysql-client update_cache: yes state: present - with_items: - - mysql-server - - mysql-client tags: - mysql - packages - name: Install MySQL dev packages apt: - name: '{{ item }}' + name: libmysqlclient20 update_cache: yes state: present - with_items: - - libmysqlclient20 tags: - mysql - packages diff --git a/mysql-oracle/tasks/users.yml b/mysql-oracle/tasks/users.yml index 7a7ee8ba..50e0fb58 100644 --- a/mysql-oracle/tasks/users.yml +++ b/mysql-oracle/tasks/users.yml @@ -39,7 +39,7 @@ with_items: - { option: 'user', value: 'mysqladmin' } - { option: 'password', value: '{{ mysql_admin_password.stdout }}' } - when: create_mysqladmin_user | changed + when: create_mysqladmin_user is changed tags: - mysql @@ -76,7 +76,7 @@ - [ { option: 'user', value: 'debian-sys-maint' }, { option: 'password', value: '{{ mysql_debian_password.stdout }}' } ] - when: create_debian_user | changed + when: create_debian_user is changed tags: - mysql diff --git a/mysql-oracle/tasks/utils.yml b/mysql-oracle/tasks/utils.yml index 79587f16..bf0013df 100644 --- a/mysql-oracle/tasks/utils.yml +++ b/mysql-oracle/tasks/utils.yml @@ -1,8 +1,8 @@ --- - include_role: - name: remount-usr - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + name: evolix/remount-usr + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: Scripts directory exists file: @@ -26,28 +26,33 @@ # - name: "mysql-utilities are installed (Debian 9 or later)" # apt: -# name: "{{ item }}" -# with_items: -# - mysql-utilities -# when: ansible_distribution_major_version | version_compare('9', '>=') +# name: mysql-utilities +# when: ansible_distribution_major_version is version('9', '>=') -- name: "mytop dependencies are installed (Debian 9 or later)" +- name: "mytop dependencies are installed (stretch)" apt: - name: "{{ item }}" - with_items: - - libconfig-inifiles-perl - - libdbd-mysql-perl - - libdbi-perl - - libterm-readkey-perl - - libtime-hires-perl + name: + - libconfig-inifiles-perl + - libdbd-mysql-perl + - libdbi-perl + - libterm-readkey-perl + - libtime-hires-perl tags: - packages - mytop - mysql - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_release == "stretch" + +- name: "Install dependencies for mytop (Debian 10 or later)" + apt: + name: + - mariadb-client-10.3 + - libconfig-inifiles-perl + - libterm-readkey-perl + when: ansible_distribution_major_version is version('10', '>=') - include_role: - name: remount-usr + name: evolix/remount-usr tags: - mytop - mysql @@ -63,7 +68,7 @@ tags: - mytop - mysql - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: Read debian-sys-maint password shell: 'cat /etc/mysql/debian.cnf | grep -m1 "password = .*" | cut -d" " -f3' @@ -87,10 +92,10 @@ # mysqltuner - include_role: - name: remount-usr + name: evolix/remount-usr tags: - mysql - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: mysqltuner is installed # copy: @@ -113,10 +118,10 @@ # automatic optimizations - include_role: - name: remount-usr + name: evolix/remount-usr tags: - mysql - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: mysql-optimize.sh is installed copy: @@ -186,8 +191,8 @@ # my-add.sh - include_role: - name: remount-usr - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + name: evolix/remount-usr + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: Install my-add.sh copy: diff --git a/mysql/README.md b/mysql/README.md index 6941df20..ff4fcbe9 100644 --- a/mysql/README.md +++ b/mysql/README.md @@ -30,6 +30,8 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `mysql_max_heap_table_size`: (default: `Null`, default evolinux config is then used) ; * `mysql_query_cache_limit`: (default: `Null`, default evolinux config is then used) ; * `mysql_query_cache_size`: (default: `Null`, default evolinux config is then used) ; +* `mysql_log_bin`: (default: `Null`, activates binlogs if used) ; +* `mysql_server_id`: (default: `Null`, MySQL version default is then used) ; * `mysql_custom_datadir`: custom datadir. * `mysql_custom_tmpdir`: custom tmpdir. * `general_alert_email`: email address to send various alert messages (default: `root@localhost`). diff --git a/mysql/defaults/main.yml b/mysql/defaults/main.yml index c2cfa4b0..633619cf 100644 --- a/mysql/defaults/main.yml +++ b/mysql/defaults/main.yml @@ -29,7 +29,8 @@ mysql_tmp_table_size: Null mysql_max_heap_table_size: Null mysql_query_cache_limit: Null mysql_query_cache_size: Null - +mysql_log_bin: Null +mysql_server_id: Null mysql_cron_optimize: True mysql_cron_optimize_frequency: weekly diff --git a/mysql/tasks/main.yml b/mysql/tasks/main.yml index 8e14c8de..89ee6866 100644 --- a/mysql/tasks/main.yml +++ b/mysql/tasks/main.yml @@ -1,22 +1,23 @@ --- -- set_fact: +- name: Set if MySQL should be restart (if needed) or not at all + set_fact: mysql_restart_handler_name: "{{ mysql_restart_if_needed | bool | ternary('restart mysql', 'restart mysql (noop)') }}" - include: packages_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - include: packages_jessie.yml when: ansible_distribution_release == "jessie" - include: users_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - include: users_jessie.yml when: ansible_distribution_release == "jessie" - include: config_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - include: config_jessie.yml when: ansible_distribution_release == "jessie" diff --git a/mysql/tasks/munin.yml b/mysql/tasks/munin.yml index 52b6eed4..c7017aa2 100644 --- a/mysql/tasks/munin.yml +++ b/mysql/tasks/munin.yml @@ -12,11 +12,10 @@ - block: - name: Install perl libraries for Munin apt: - name: "{{ item }}" + name: + - libdbd-mysql-perl + - libcache-cache-perl state: present - with_items: - - libdbd-mysql-perl - - libcache-cache-perl - name: Enable core Munin plugins file: @@ -58,6 +57,14 @@ - tmp_tables notify: restart munin-node + - name: verify Munin configuration for mysql + replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqluser (.+)$' + replace: 'env.mysqluser debian-sys-maint' + notify: restart munin-node + when: munin_node_plugins_config.stat.exists tags: - mysql diff --git a/mysql/tasks/packages_jessie.yml b/mysql/tasks/packages_jessie.yml index 06359e35..8d27de52 100644 --- a/mysql/tasks/packages_jessie.yml +++ b/mysql/tasks/packages_jessie.yml @@ -18,21 +18,18 @@ - name: Install MySQL packages apt: - name: '{{ item }}' + name: "{{ mysql_packages }}" update_cache: yes state: present - with_items: "{{ mysql_packages }}" tags: - mysql - packages - name: Install MySQL dev packages apt: - name: '{{ item }}' + name: libmysqlclient-dev update_cache: yes state: present - with_items: - - libmysqlclient-dev tags: - mysql - packages diff --git a/mysql/tasks/packages_stretch.yml b/mysql/tasks/packages_stretch.yml index 83256472..901543af 100644 --- a/mysql/tasks/packages_stretch.yml +++ b/mysql/tasks/packages_stretch.yml @@ -2,23 +2,20 @@ - name: Install MySQL packages apt: - name: '{{ item }}' + name: + - mariadb-server + - mariadb-client update_cache: yes state: present - with_items: - - mariadb-server - - mariadb-client tags: - mysql - packages - name: Install MySQL dev packages apt: - name: '{{ item }}' + name: default-libmysqlclient-dev update_cache: yes state: present - with_items: - - default-libmysqlclient-dev tags: - mysql - packages diff --git a/mysql/tasks/utils.yml b/mysql/tasks/utils.yml index 390acc0e..7609a81e 100644 --- a/mysql/tasks/utils.yml +++ b/mysql/tasks/utils.yml @@ -1,8 +1,8 @@ --- - include_role: - name: remount-usr - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + name: evolix/remount-usr + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: Ensure scripts directory exists file: @@ -24,14 +24,21 @@ - mysql when: ansible_distribution_release == "jessie" -- name: "Install depends for mytop (Debian 9 or later)" +- name: "Install dependencies for mytop (stretch)" apt: - name: "{{ item }}" - with_items: - - mariadb-client-10.1 - - libconfig-inifiles-perl - - libterm-readkey-perl - when: ansible_distribution_major_version | version_compare('9', '>=') + name: + - mariadb-client-10.1 + - libconfig-inifiles-perl + - libterm-readkey-perl + when: ansible_distribution_release == "stretch" + +- name: "Install dependencies for mytop (Debian 10 or later)" + apt: + name: + - mariadb-client-10.3 + - libconfig-inifiles-perl + - libterm-readkey-perl + when: ansible_distribution_major_version is version('10', '>=') - name: Read debian-sys-maint password shell: 'cat /etc/mysql/debian.cnf | grep -m1 "password = .*" | cut -d" " -f3' @@ -54,8 +61,8 @@ # mysqltuner - include_role: - name: remount-usr - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + name: evolix/remount-usr + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: Install mysqltuner # copy: @@ -75,11 +82,23 @@ tags: - mysql +# Percona Toolkit + +- name: "Install percona-toolkit (Debian 9 or later)" + apt: + name: percona-toolkit + state: present + tags: + - packages + - percona-toolkit + - mysql + when: ansible_distribution_major_version is version('9', '>=') + # automatic optimizations - include_role: - name: remount-usr - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + name: evolix/remount-usr + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: Optimize script for MySQL copy: @@ -142,8 +161,8 @@ # my-add.sh - include_role: - name: remount-usr - when: (mysql_scripts_dir or general_scripts_dir) | search ("/usr") + name: evolix/remount-usr + when: (mysql_scripts_dir or general_scripts_dir) is search ("/usr") - name: Install my-add.sh copy: diff --git a/mysql/templates/evolinux-custom.cnf.j2 b/mysql/templates/evolinux-custom.cnf.j2 index 796a1429..f8ee104e 100644 --- a/mysql/templates/evolinux-custom.cnf.j2 +++ b/mysql/templates/evolinux-custom.cnf.j2 @@ -29,3 +29,9 @@ query_cache_limit = {{ mysql_query_cache_limit }} {% if mysql_query_cache_limit %} query_cache_size = {{ mysql_query_cache_size }} {% endif %} +{% if mysql_log_bin %} +log_bin = {{ mysql_log_bin }} +{% endif %} +{% if mysql_server_id %} +server_id = {{ mysql_server_id }} +{% endif %} diff --git a/nagios-nrpe/files/plugins/check_mysql_health b/nagios-nrpe/files/plugins/check_mysql_health new file mode 100755 index 00000000..8bdfd2bb --- /dev/null +++ b/nagios-nrpe/files/plugins/check_mysql_health @@ -0,0 +1,3932 @@ +#! /usr/bin/perl -w +# nagios: -epn + +my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 ); +my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' ); +package DBD::MySQL::Server::Instance::Innodb; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + internals => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::innodb/) { + $self->{internals} = + DBD::MySQL::Server::Instance::Innodb::Internals->new(%params); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::instance::innodb/) { + $self->{internals}->nagios(%params); + $self->merge_nagios($self->{internals}); + } +} + + +package DBD::MySQL::Server::Instance::Innodb::Internals; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance::Innodb); + +our $internals; # singleton, nur ein einziges mal instantiierbar + +sub new { + my $class = shift; + my %params = @_; + unless ($internals) { + $internals = { + handle => $params{handle}, + bufferpool_hitrate => undef, + wait_free => undef, + log_waits => undef, + have_innodb => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless($internals, $class); + $internals->init(%params); + } + return($internals); +} + +sub init { + my $self = shift; + my %params = @_; + my $dummy; + $self->debug("enter init"); + $self->init_nagios(); + if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1")) { + ($dummy, $self->{have_innodb}) = $self->{handle}->fetchrow_array(q{ + SELECT ENGINE, SUPPORT FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE='InnoDB' + }); + } else { + ($dummy, $self->{have_innodb}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'have_innodb' + }); + } + if ($self->{have_innodb} eq "NO") { + $self->add_nagios_critical("the innodb engine has a problem (have_innodb=no)"); + } elsif ($self->{have_innodb} eq "DISABLED") { + # add_nagios_ok later + } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) { + ($dummy, $self->{bufferpool_reads}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_reads' + }); + ($dummy, $self->{bufferpool_read_requests}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_read_requests' + }); + if (! defined $self->{bufferpool_reads}) { + $self->add_nagios_critical("no innodb buffer pool info available"); + } else { + $self->valdiff(\%params, qw(bufferpool_reads + bufferpool_read_requests)); + $self->{bufferpool_hitrate_now} = + $self->{delta_bufferpool_read_requests} > 0 ? + 100 - (100 * $self->{delta_bufferpool_reads} / + $self->{delta_bufferpool_read_requests}) : 100; + $self->{bufferpool_hitrate} = + $self->{bufferpool_read_requests} > 0 ? + 100 - (100 * $self->{bufferpool_reads} / + $self->{bufferpool_read_requests}) : 100; + } + } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) { + ($dummy, $self->{bufferpool_wait_free}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_wait_free' + }); + if (! defined $self->{bufferpool_wait_free}) { + $self->add_nagios_critical("no innodb buffer pool info available"); + } else { + $self->valdiff(\%params, qw(bufferpool_wait_free)); + $self->{bufferpool_wait_free_rate} = + $self->{delta_bufferpool_wait_free} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /server::instance::innodb::logwaits/) { + ($dummy, $self->{log_waits}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Innodb_log_waits' + }); + if (! defined $self->{log_waits}) { + $self->add_nagios_critical("no innodb log info available"); + } else { + $self->valdiff(\%params, qw(log_waits)); + $self->{log_waits_rate} = + $self->{delta_log_waits} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /server::instance::innodb::needoptimize/) { +#fragmentation=$(($datafree * 100 / $datalength)) + +#http://www.electrictoolbox.com/optimize-tables-mysql-php/ + my @result = $self->{handle}->fetchall_array(q{ +SHOW TABLE STATUS WHERE Data_free / Data_length > 0.1 AND Data_free > 102400 +}); +printf "%s\n", Data::Dumper::Dumper(\@result); + + } +} + +sub nagios { + my $self = shift; + my %params = @_; + my $now = $params{lookback} ? '_now' : ''; + if ($self->{have_innodb} eq "DISABLED") { + $self->add_nagios_ok("the innodb engine has been disabled"); + } elsif (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) { + my $refkey = 'bufferpool_hitrate'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "99:", "95:"), + sprintf "innodb buffer pool hitrate at %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "bufferpool_hitrate=%.2f%%;%s;%s;0;100", + $self->{bufferpool_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "bufferpool_hitrate_now=%.2f%%", + $self->{bufferpool_hitrate_now}); + } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) { + $self->add_nagios( + $self->check_thresholds($self->{bufferpool_wait_free_rate}, "1", "10"), + sprintf "%ld innodb buffer pool waits in %ld seconds (%.4f/sec)", + $self->{delta_bufferpool_wait_free}, $self->{delta_timestamp}, + $self->{bufferpool_wait_free_rate}); + $self->add_perfdata(sprintf "bufferpool_free_waits_rate=%.4f;%s;%s;0;100", + $self->{bufferpool_wait_free_rate}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::innodb::logwaits/) { + $self->add_nagios( + $self->check_thresholds($self->{log_waits_rate}, "1", "10"), + sprintf "%ld innodb log waits in %ld seconds (%.4f/sec)", + $self->{delta_log_waits}, $self->{delta_timestamp}, + $self->{log_waits_rate}); + $self->add_perfdata(sprintf "innodb_log_waits_rate=%.4f;%s;%s;0;100", + $self->{log_waits_rate}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + + + + +package DBD::MySQL::Server::Instance::MyISAM; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + internals => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::myisam/) { + $self->{internals} = + DBD::MySQL::Server::Instance::MyISAM::Internals->new(%params); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::instance::myisam/) { + $self->{internals}->nagios(%params); + $self->merge_nagios($self->{internals}); + } +} + + +package DBD::MySQL::Server::Instance::MyISAM::Internals; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance::MyISAM); + +our $internals; # singleton, nur ein einziges mal instantiierbar + +sub new { + my $class = shift; + my %params = @_; + unless ($internals) { + $internals = { + handle => $params{handle}, + keycache_hitrate => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless($internals, $class); + $internals->init(%params); + } + return($internals); +} + +sub init { + my $self = shift; + my %params = @_; + my $dummy; + $self->debug("enter init"); + $self->init_nagios(); + if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) { + ($dummy, $self->{key_reads}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Key_reads' + }); + ($dummy, $self->{key_read_requests}) + = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Key_read_requests' + }); + if (! defined $self->{key_read_requests}) { + $self->add_nagios_critical("no myisam keycache info available"); + } else { + $self->valdiff(\%params, qw(key_reads key_read_requests)); + $self->{keycache_hitrate} = + $self->{key_read_requests} > 0 ? + 100 - (100 * $self->{key_reads} / + $self->{key_read_requests}) : 100; + $self->{keycache_hitrate_now} = + $self->{delta_key_read_requests} > 0 ? + 100 - (100 * $self->{delta_key_reads} / + $self->{delta_key_read_requests}) : 100; + } + } elsif ($params{mode} =~ /server::instance::myisam::sonstnochwas/) { + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) { + my $refkey = 'keycache_hitrate'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "99:", "95:"), + sprintf "myisam keycache hitrate at %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "keycache_hitrate=%.2f%%;%s;%s", + $self->{keycache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "keycache_hitrate_now=%.2f%%;%s;%s", + $self->{keycache_hitrate_now}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + + +package DBD::MySQL::Server::Instance::Replication; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + internals => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::replication/) { + $self->{internals} = + DBD::MySQL::Server::Instance::Replication::Internals->new(%params); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::instance::replication/) { + $self->{internals}->nagios(%params); + $self->merge_nagios($self->{internals}); + } +} + + +package DBD::MySQL::Server::Instance::Replication::Internals; + +use strict; + +our @ISA = qw(DBD::MySQL::Server::Instance::Replication); + +our $internals; # singleton, nur ein einziges mal instantiierbar + +sub new { + my $class = shift; + my %params = @_; + unless ($internals) { + $internals = { + handle => $params{handle}, + seconds_behind_master => undef, + slave_io_running => undef, + slave_sql_running => undef, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless($internals, $class); + $internals->init(%params); + } + return($internals); +} + +sub init { + my $self = shift; + my %params = @_; + $self->debug("enter init"); + $self->init_nagios(); + if ($params{mode} =~ /server::instance::replication::slavelag/) { + # "show slave status", "Seconds_Behind_Master" + my $slavehash = $self->{handle}->selectrow_hashref(q{ + SHOW SLAVE STATUS + }); + if ((! defined $slavehash->{Seconds_Behind_Master}) && + (lc $slavehash->{Slave_IO_Running} eq 'no')) { + $self->add_nagios_critical( + "unable to get slave lag, because io thread is not running"); + } elsif (! defined $slavehash->{Seconds_Behind_Master}) { + $self->add_nagios_critical(sprintf "unable to get replication info%s", + $self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); + } else { + $self->{seconds_behind_master} = $slavehash->{Seconds_Behind_Master}; + } + } elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) { + # "show slave status", "Slave_IO_Running" + my $slavehash = $self->{handle}->selectrow_hashref(q{ + SHOW SLAVE STATUS + }); + if (! defined $slavehash->{Slave_IO_Running}) { + $self->add_nagios_critical(sprintf "unable to get replication info%s", + $self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); + } else { + $self->{slave_io_running} = $slavehash->{Slave_IO_Running}; + } + } elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) { + # "show slave status", "Slave_SQL_Running" + my $slavehash = $self->{handle}->selectrow_hashref(q{ + SHOW SLAVE STATUS + }); + if (! defined $slavehash->{Slave_SQL_Running}) { + $self->add_nagios_critical(sprintf "unable to get replication info%s", + $self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); + } else { + $self->{slave_sql_running} = $slavehash->{Slave_SQL_Running}; + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::replication::slavelag/) { + $self->add_nagios( + $self->check_thresholds($self->{seconds_behind_master}, "10", "20"), + sprintf "Slave is %d seconds behind master", + $self->{seconds_behind_master}); + $self->add_perfdata(sprintf "slave_lag=%d;%s;%s", + $self->{seconds_behind_master}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) { + if (lc $self->{slave_io_running} eq "yes") { + $self->add_nagios_ok("Slave io is running"); + } else { + $self->add_nagios_critical("Slave io is not running"); + } + } elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) { + if (lc $self->{slave_sql_running} eq "yes") { + $self->add_nagios_ok("Slave sql is running"); + } else { + $self->add_nagios_critical("Slave sql is not running"); + } + } + } +} + + + +package DBD::MySQL::Server::Instance; + +use strict; + +our @ISA = qw(DBD::MySQL::Server); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + uptime => $params{uptime}, + replication_user => $params{replication_user}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + threads_connected => undef, + threads_created => undef, + connections => undef, + threadcache_hitrate => undef, + querycache_hitrate => undef, + lowmem_prunes_per_sec => undef, + slow_queries_per_sec => undef, + longrunners => undef, + tablecache_hitrate => undef, + index_usage => undef, + engine_innodb => undef, + engine_myisam => undef, + replication => undef, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + my $dummy; + $self->init_nagios(); + if ($params{mode} =~ /server::instance::connectedthreads/) { + ($dummy, $self->{threads_connected}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_connected' + }); + } elsif ($params{mode} =~ /server::instance::createdthreads/) { + ($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_created' + }); + $self->valdiff(\%params, qw(threads_created)); + $self->{threads_created_per_sec} = $self->{delta_threads_created} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::runningthreads/) { + ($dummy, $self->{threads_running}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_running' + }); + } elsif ($params{mode} =~ /server::instance::cachedthreads/) { + ($dummy, $self->{threads_cached}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_cached' + }); + } elsif ($params{mode} =~ /server::instance::abortedconnects/) { + ($dummy, $self->{connects_aborted}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Aborted_connects' + }); + $self->valdiff(\%params, qw(connects_aborted)); + $self->{connects_aborted_per_sec} = $self->{delta_connects_aborted} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::abortedclients/) { + ($dummy, $self->{clients_aborted}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Aborted_clients' + }); + $self->valdiff(\%params, qw(clients_aborted)); + $self->{clients_aborted_per_sec} = $self->{delta_clients_aborted} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::threadcachehitrate/) { + ($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Threads_created' + }); + ($dummy, $self->{connections}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Connections' + }); + $self->valdiff(\%params, qw(threads_created connections)); + if ($self->{delta_connections} > 0) { + $self->{threadcache_hitrate_now} = + 100 - ($self->{delta_threads_created} * 100.0 / + $self->{delta_connections}); + } else { + $self->{threadcache_hitrate_now} = 100; + } + $self->{threadcache_hitrate} = 100 - + ($self->{threads_created} * 100.0 / $self->{connections}); + $self->{connections_per_sec} = $self->{delta_connections} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::querycachehitrate/) { + ($dummy, $self->{qcache_inserts}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Qcache_inserts' + }); + ($dummy, $self->{qcache_not_cached}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Qcache_not_cached' + }); + ($dummy, $self->{com_select}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Com_select' + }); + ($dummy, $self->{qcache_hits}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Qcache_hits' + }); + # SHOW VARIABLES WHERE Variable_name = 'have_query_cache' for 5.x, but LIKE is compatible + ($dummy, $self->{have_query_cache}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'have_query_cache' + }); + # SHOW VARIABLES WHERE Variable_name = 'query_cache_size' + ($dummy, $self->{query_cache_size}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'query_cache_size' + }); + $self->valdiff(\%params, qw(com_select qcache_hits)); + $self->{querycache_hitrate_now} = + ($self->{delta_com_select} + $self->{delta_qcache_hits}) > 0 ? + 100 * $self->{delta_qcache_hits} / + ($self->{delta_com_select} + $self->{delta_qcache_hits}) : + 0; + $self->{querycache_hitrate} = + ($self->{qcache_not_cached} + $self->{qcache_inserts} + $self->{qcache_hits}) > 0 ? + 100 * $self->{qcache_hits} / + ($self->{qcache_not_cached} + $self->{qcache_inserts} + $self->{qcache_hits}) : + 0; + $self->{selects_per_sec} = + $self->{delta_com_select} / $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) { + ($dummy, $self->{lowmem_prunes}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Qcache_lowmem_prunes' + }); + $self->valdiff(\%params, qw(lowmem_prunes)); + $self->{lowmem_prunes_per_sec} = $self->{delta_lowmem_prunes} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::slowqueries/) { + ($dummy, $self->{slow_queries}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Slow_queries' + }); + $self->valdiff(\%params, qw(slow_queries)); + $self->{slow_queries_per_sec} = $self->{delta_slow_queries} / + $self->{delta_timestamp}; + } elsif ($params{mode} =~ /server::instance::longprocs/) { + if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1")) { + ($self->{longrunners}) = $self->{handle}->fetchrow_array(qq( + SELECT + COUNT(*) + FROM + information_schema.processlist + WHERE user <> ? + AND id <> CONNECTION_ID() + AND time > 60 + AND command <> 'Sleep' + ), $self->{replication_user}); + } else { + $self->{longrunners} = 0 if ! defined $self->{longrunners}; + foreach ($self->{handle}->fetchall_array(q{ + SHOW PROCESSLIST + })) { + my($id, $user, $host, $db, $command, $tme, $state, $info) = @{$_}; + if (($user ne $self->{replication_user}) && + ($tme > 60) && + ($command ne 'Sleep')) { + $self->{longrunners}++; + } + } + } + } elsif ($params{mode} =~ /server::instance::tablecachehitrate/) { + ($dummy, $self->{open_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Open_tables' + }); + ($dummy, $self->{opened_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Opened_tables' + }); + if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1.3")) { + # SHOW VARIABLES WHERE Variable_name = 'table_open_cache' + ($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'table_open_cache' + }); + } else { + # SHOW VARIABLES WHERE Variable_name = 'table_cache' + ($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'table_cache' + }); + } + $self->{table_cache} ||= 0; + #$self->valdiff(\%params, qw(open_tables opened_tables table_cache)); + # _now ist hier sinnlos, da opened_tables waechst, aber open_tables wieder + # schrumpfen kann weil tabellen geschlossen werden. + if ($self->{opened_tables} != 0 && $self->{table_cache} != 0) { + $self->{tablecache_hitrate} = + 100 * $self->{open_tables} / $self->{opened_tables}; + $self->{tablecache_fillrate} = + 100 * $self->{open_tables} / $self->{table_cache}; + } elsif ($self->{opened_tables} == 0 && $self->{table_cache} != 0) { + $self->{tablecache_hitrate} = 100; + $self->{tablecache_fillrate} = + 100 * $self->{open_tables} / $self->{table_cache}; + } else { + $self->{tablecache_hitrate} = 0; + $self->{tablecache_fillrate} = 0; + $self->add_nagios_critical("no table cache"); + } + } elsif ($params{mode} =~ /server::instance::tablelockcontention/) { + ($dummy, $self->{table_locks_waited}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Table_locks_waited' + }); + ($dummy, $self->{table_locks_immediate}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Table_locks_immediate' + }); + $self->valdiff(\%params, qw(table_locks_waited table_locks_immediate)); + $self->{table_lock_contention} = + ($self->{table_locks_waited} + $self->{table_locks_immediate}) > 0 ? + 100 * $self->{table_locks_waited} / + ($self->{table_locks_waited} + $self->{table_locks_immediate}) : + 100; + $self->{table_lock_contention_now} = + ($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) > 0 ? + 100 * $self->{delta_table_locks_waited} / + ($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) : + 100; + } elsif ($params{mode} =~ /server::instance::tableindexusage/) { + # http://johnjacobm.wordpress.com/2007/06/ + # formula for calculating the percentage of full table scans + ($dummy, $self->{handler_read_first}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_first' + }); + ($dummy, $self->{handler_read_key}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_key' + }); + ($dummy, $self->{handler_read_next}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_next' + }); + ($dummy, $self->{handler_read_prev}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_prev' + }); + ($dummy, $self->{handler_read_rnd}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd' + }); + ($dummy, $self->{handler_read_rnd_next}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd_next' + }); + $self->valdiff(\%params, qw(handler_read_first handler_read_key + handler_read_next handler_read_prev handler_read_rnd + handler_read_rnd_next)); + my $delta_reads = $self->{delta_handler_read_first} + + $self->{delta_handler_read_key} + + $self->{delta_handler_read_next} + + $self->{delta_handler_read_prev} + + $self->{delta_handler_read_rnd} + + $self->{delta_handler_read_rnd_next}; + my $reads = $self->{handler_read_first} + + $self->{handler_read_key} + + $self->{handler_read_next} + + $self->{handler_read_prev} + + $self->{handler_read_rnd} + + $self->{handler_read_rnd_next}; + $self->{index_usage_now} = ($delta_reads == 0) ? 0 : + 100 - (100.0 * ($self->{delta_handler_read_rnd} + + $self->{delta_handler_read_rnd_next}) / + $delta_reads); + $self->{index_usage} = ($reads == 0) ? 0 : + 100 - (100.0 * ($self->{handler_read_rnd} + + $self->{handler_read_rnd_next}) / + $reads); + } elsif ($params{mode} =~ /server::instance::tabletmpondisk/) { + ($dummy, $self->{created_tmp_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_tables' + }); + ($dummy, $self->{created_tmp_disk_tables}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_disk_tables' + }); + $self->valdiff(\%params, qw(created_tmp_tables created_tmp_disk_tables)); + $self->{pct_tmp_on_disk} = $self->{created_tmp_tables} > 0 ? + 100 * $self->{created_tmp_disk_tables} / $self->{created_tmp_tables} : + 100; + $self->{pct_tmp_on_disk_now} = $self->{delta_created_tmp_tables} > 0 ? + 100 * $self->{delta_created_tmp_disk_tables} / $self->{delta_created_tmp_tables} : + 100; + } elsif ($params{mode} =~ /server::instance::openfiles/) { + ($dummy, $self->{open_files_limit}) = $self->{handle}->fetchrow_array(q{ + SHOW VARIABLES LIKE 'open_files_limit' + }); + ($dummy, $self->{open_files}) = $self->{handle}->fetchrow_array(q{ + SHOW /*!50000 global */ STATUS LIKE 'Open_files' + }); + $self->{pct_open_files} = 100 * $self->{open_files} / $self->{open_files_limit}; + } elsif ($params{mode} =~ /server::instance::needoptimize/) { + $self->{fragmented} = []; + #http://www.electrictoolbox.com/optimize-tables-mysql-php/ + my @result = $self->{handle}->fetchall_array(q{ + SHOW TABLE STATUS + }); + foreach (@result) { + my ($name, $engine, $data_length, $data_free) = + ($_->[0], $_->[1], $_->[6 ], $_->[9]); + next if ($params{name} && $params{name} ne $name); + my $fragmentation = $data_length ? $data_free * 100 / $data_length : 0; + push(@{$self->{fragmented}}, + [$name, $fragmentation, $data_length, $data_free]); + } + } elsif ($params{mode} =~ /server::instance::myisam/) { + $self->{engine_myisam} = DBD::MySQL::Server::Instance::MyISAM->new( + %params + ); + } elsif ($params{mode} =~ /server::instance::innodb/) { + $self->{engine_innodb} = DBD::MySQL::Server::Instance::Innodb->new( + %params + ); + } elsif ($params{mode} =~ /server::instance::replication/) { + $self->{replication} = DBD::MySQL::Server::Instance::Replication->new( + %params + ); + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::instance::connectedthreads/) { + $self->add_nagios( + $self->check_thresholds($self->{threads_connected}, 10, 20), + sprintf "%d client connection threads", $self->{threads_connected}); + $self->add_perfdata(sprintf "threads_connected=%d;%d;%d", + $self->{threads_connected}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::createdthreads/) { + $self->add_nagios( + $self->check_thresholds($self->{threads_created_per_sec}, 10, 20), + sprintf "%.2f threads created/sec", $self->{threads_created_per_sec}); + $self->add_perfdata(sprintf "threads_created_per_sec=%.2f;%.2f;%.2f", + $self->{threads_created_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::runningthreads/) { + $self->add_nagios( + $self->check_thresholds($self->{threads_running}, 10, 20), + sprintf "%d running threads", $self->{threads_running}); + $self->add_perfdata(sprintf "threads_running=%d;%d;%d", + $self->{threads_running}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::cachedthreads/) { + $self->add_nagios( + $self->check_thresholds($self->{threads_cached}, 10, 20), + sprintf "%d cached threads", $self->{threads_cached}); + $self->add_perfdata(sprintf "threads_cached=%d;%d;%d", + $self->{threads_cached}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::abortedconnects/) { + $self->add_nagios( + $self->check_thresholds($self->{connects_aborted_per_sec}, 1, 5), + sprintf "%.2f aborted connections/sec", $self->{connects_aborted_per_sec}); + $self->add_perfdata(sprintf "connects_aborted_per_sec=%.2f;%.2f;%.2f", + $self->{connects_aborted_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::abortedclients/) { + $self->add_nagios( + $self->check_thresholds($self->{clients_aborted_per_sec}, 1, 5), + sprintf "%.2f aborted (client died) connections/sec", $self->{clients_aborted_per_sec}); + $self->add_perfdata(sprintf "clients_aborted_per_sec=%.2f;%.2f;%.2f", + $self->{clients_aborted_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::threadcachehitrate/) { + my $refkey = 'threadcache_hitrate'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "90:", "80:"), + sprintf "thread cache hitrate %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "thread_cache_hitrate=%.2f%%;%s;%s", + $self->{threadcache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "thread_cache_hitrate_now=%.2f%%", + $self->{threadcache_hitrate_now}); + $self->add_perfdata(sprintf "connections_per_sec=%.2f", + $self->{connections_per_sec}); + } elsif ($params{mode} =~ /server::instance::querycachehitrate/) { + my $refkey = 'querycache_hitrate'.($params{lookback} ? '_now' : ''); + if ((lc $self->{have_query_cache} eq 'yes') && ($self->{query_cache_size})) { + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "90:", "80:"), + sprintf "query cache hitrate %.2f%%", $self->{$refkey}); + } else { + $self->check_thresholds($self->{$refkey}, "90:", "80:"); + $self->add_nagios_ok( + sprintf "query cache hitrate %.2f%% (because it's turned off)", + $self->{querycache_hitrate}); + } + $self->add_perfdata(sprintf "qcache_hitrate=%.2f%%;%s;%s", + $self->{querycache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "qcache_hitrate_now=%.2f%%", + $self->{querycache_hitrate_now}); + $self->add_perfdata(sprintf "selects_per_sec=%.2f", + $self->{selects_per_sec}); + } elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) { + $self->add_nagios( + $self->check_thresholds($self->{lowmem_prunes_per_sec}, "1", "10"), + sprintf "%d query cache lowmem prunes in %d seconds (%.2f/sec)", + $self->{delta_lowmem_prunes}, $self->{delta_timestamp}, + $self->{lowmem_prunes_per_sec}); + $self->add_perfdata(sprintf "qcache_lowmem_prunes_rate=%.2f;%s;%s", + $self->{lowmem_prunes_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::slowqueries/) { + $self->add_nagios( + $self->check_thresholds($self->{slow_queries_per_sec}, "0.1", "1"), + sprintf "%d slow queries in %d seconds (%.2f/sec)", + $self->{delta_slow_queries}, $self->{delta_timestamp}, + $self->{slow_queries_per_sec}); + $self->add_perfdata(sprintf "slow_queries_rate=%.2f%%;%s;%s", + $self->{slow_queries_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::longprocs/) { + $self->add_nagios( + $self->check_thresholds($self->{longrunners}, 10, 20), + sprintf "%d long running processes", $self->{longrunners}); + $self->add_perfdata(sprintf "long_running_procs=%d;%d;%d", + $self->{longrunners}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::instance::tablecachehitrate/) { + if ($self->{tablecache_fillrate} < 95) { + $self->add_nagios_ok( + sprintf "table cache hitrate %.2f%%, %.2f%% filled", + $self->{tablecache_hitrate}, + $self->{tablecache_fillrate}); + $self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"); + } else { + $self->add_nagios( + $self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"), + sprintf "table cache hitrate %.2f%%", $self->{tablecache_hitrate}); + } + $self->add_perfdata(sprintf "tablecache_hitrate=%.2f%%;%s;%s", + $self->{tablecache_hitrate}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "tablecache_fillrate=%.2f%%", + $self->{tablecache_fillrate}); + } elsif ($params{mode} =~ /server::instance::tablelockcontention/) { + my $refkey = 'table_lock_contention'.($params{lookback} ? '_now' : ''); + if ($self->{uptime} > 10800) { # MySQL Bug #30599 + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "1", "2"), + sprintf "table lock contention %.2f%%", $self->{$refkey}); + } else { + $self->check_thresholds($self->{$refkey}, "1", "2"); + $self->add_nagios_ok( + sprintf "table lock contention %.2f%% (uptime < 10800)", + $self->{$refkey}); + } + $self->add_perfdata(sprintf "tablelock_contention=%.2f%%;%s;%s", + $self->{table_lock_contention}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "tablelock_contention_now=%.2f%%", + $self->{table_lock_contention_now}); + } elsif ($params{mode} =~ /server::instance::tableindexusage/) { + my $refkey = 'index_usage'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "90:", "80:"), + sprintf "index usage %.2f%%", $self->{$refkey}); + $self->add_perfdata(sprintf "index_usage=%.2f%%;%s;%s", + $self->{index_usage}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "index_usage_now=%.2f%%", + $self->{index_usage_now}); + } elsif ($params{mode} =~ /server::instance::tabletmpondisk/) { + my $refkey = 'pct_tmp_on_disk'.($params{lookback} ? '_now' : ''); + $self->add_nagios( + $self->check_thresholds($self->{$refkey}, "25", "50"), + sprintf "%.2f%% of %d tables were created on disk", + $self->{$refkey}, $self->{delta_created_tmp_tables}); + $self->add_perfdata(sprintf "pct_tmp_table_on_disk=%.2f%%;%s;%s", + $self->{pct_tmp_on_disk}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "pct_tmp_table_on_disk_now=%.2f%%", + $self->{pct_tmp_on_disk_now}); + } elsif ($params{mode} =~ /server::instance::openfiles/) { + $self->add_nagios( + $self->check_thresholds($self->{pct_open_files}, 80, 95), + sprintf "%.2f%% of the open files limit reached (%d of max. %d)", + $self->{pct_open_files}, + $self->{open_files}, $self->{open_files_limit}); + $self->add_perfdata(sprintf "pct_open_files=%.3f%%;%.3f;%.3f", + $self->{pct_open_files}, + $self->{warningrange}, + $self->{criticalrange}); + $self->add_perfdata(sprintf "open_files=%d;%d;%d", + $self->{open_files}, + $self->{open_files_limit} * $self->{warningrange} / 100, + $self->{open_files_limit} * $self->{criticalrange} / 100); + } elsif ($params{mode} =~ /server::instance::needoptimize/) { + foreach (@{$self->{fragmented}}) { + $self->add_nagios( + $self->check_thresholds($_->[1], 10, 25), + sprintf "table %s is %.2f%% fragmented", $_->[0], $_->[1]); + if ($params{name}) { + $self->add_perfdata(sprintf "'%s_frag'=%.2f%%;%d;%d", + $_->[0], $_->[1], $self->{warningrange}, $self->{criticalrange}); + } + } + } elsif ($params{mode} =~ /server::instance::myisam/) { + $self->{engine_myisam}->nagios(%params); + $self->merge_nagios($self->{engine_myisam}); + } elsif ($params{mode} =~ /server::instance::innodb/) { + $self->{engine_innodb}->nagios(%params); + $self->merge_nagios($self->{engine_innodb}); + } elsif ($params{mode} =~ /server::instance::replication/) { + $self->{replication}->nagios(%params); + $self->merge_nagios($self->{replication}); + } + } +} + + + +package DBD::MySQL::Server; + +use strict; +use Time::HiRes; +use IO::File; +use File::Copy 'cp'; +use Data::Dumper; + + +{ + our $verbose = 0; + our $scream = 0; # scream if something is not implemented + our $access = "dbi"; # how do we access the database. + our $my_modules_dyn_dir = ""; # where we look for self-written extensions + + my @servers = (); + my $initerrors = undef; + + sub add_server { + push(@servers, shift); + } + + sub return_servers { + return @servers; + } + + sub return_first_server() { + return $servers[0]; + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + mode => $params{mode}, + access => $params{method} || 'dbi', + hostname => $params{hostname}, + database => $params{database} || 'information_schema', + port => $params{port}, + socket => $params{socket}, + username => $params{username}, + password => $params{password}, + replication_user => $params{replication_user}, + mycnf => $params{mycnf}, + mycnfgroup => $params{mycnfgroup}, + timeout => $params{timeout}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + verbose => $params{verbose}, + report => $params{report}, + negate => $params{negate}, + labelformat => $params{labelformat}, + version => 'unknown', + instance => undef, + handle => undef, + }; + bless $self, $class; + $self->init_nagios(); + if ($self->dbconnect(%params)) { + ($self->{dummy}, $self->{version}) = $self->{handle}->fetchrow_array( + #q{ SHOW VARIABLES WHERE Variable_name = 'version' } + q{ SHOW VARIABLES LIKE 'version' } + ); + $self->{version} = (split "-", $self->{version})[0]; + ($self->{dummy}, $self->{uptime}) = $self->{handle}->fetchrow_array( + q{ SHOW STATUS LIKE 'Uptime' } + ); + DBD::MySQL::Server::add_server($self); + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $params{handle} = $self->{handle}; + $params{uptime} = $self->{uptime}; + $self->set_global_db_thresholds(\%params); + if ($params{mode} =~ /^server::instance/) { + $self->{instance} = DBD::MySQL::Server::Instance->new(%params); + } elsif ($params{mode} =~ /^server::sql/) { + $self->set_local_db_thresholds(%params); + if ($params{regexp}) { + # sql output is treated as text + if ($params{name2} eq $params{name}) { + $self->add_nagios_unknown(sprintf "where's the regexp????"); + } else { + $self->{genericsql} = + $self->{handle}->fetchrow_array($params{selectname}); + if (! defined $self->{genericsql}) { + $self->add_nagios_unknown(sprintf "got no valid response for %s", + $params{selectname}); + } + } + } else { + # sql output must be a number (or array of numbers) + @{$self->{genericsql}} = + $self->{handle}->fetchrow_array($params{selectname}); + if ($self->{handle}->{errstr}) { + $self->add_nagios_unknown(sprintf "got no valid response for %s: %s", + $params{selectname}, $self->{handle}->{errstr}); + } elsif (! (defined $self->{genericsql} && + (scalar(grep { + /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/ + } @{$self->{genericsql}})) == + scalar(@{$self->{genericsql}}))) { + $self->add_nagios_unknown(sprintf "got no valid response for %s", + $params{selectname}); + } elsif (! defined $self->{genericsql}) { + $self->add_nagios_unknown(sprintf "got no valid response for %s", + $params{selectname}); + } else { + # name2 in array + # units in array + } + } + } elsif ($params{mode} =~ /^server::uptime/) { + # already set with the connection. but use minutes here + } elsif ($params{mode} =~ /^server::connectiontime/) { + $self->{connection_time} = $self->{tac} - $self->{tic}; + } elsif ($params{mode} =~ /^my::([^:.]+)/) { + my $class = $1; + my $loaderror = undef; + substr($class, 0, 1) = uc substr($class, 0, 1); + foreach my $libpath (split(":", $DBD::MySQL::Server::my_modules_dyn_dir)) { + foreach my $extmod (glob $libpath."/CheckMySQLHealth*.pm") { + eval { + $self->trace(sprintf "loading module %s", $extmod); + require $extmod; + }; + if ($@) { + $loaderror = $extmod; + $self->trace(sprintf "failed loading module %s: %s", $extmod, $@); + } + } + } + my $obj = { + handle => $params{handle}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + }; + bless $obj, "My$class"; + $self->{my} = $obj; + if ($self->{my}->isa("DBD::MySQL::Server")) { + my $dos_init = $self->can("init"); + my $dos_nagios = $self->can("nagios"); + my $my_init = $self->{my}->can("init"); + my $my_nagios = $self->{my}->can("nagios"); + if ($my_init == $dos_init) { + $self->add_nagios_unknown( + sprintf "Class %s needs an init() method", ref($self->{my})); + } elsif ($my_nagios == $dos_nagios) { + $self->add_nagios_unknown( + sprintf "Class %s needs a nagios() method", ref($self->{my})); + } else { + $self->{my}->init_nagios(%params); + $self->{my}->init(%params); + } + } else { + $self->add_nagios_unknown( + sprintf "Class %s is not a subclass of DBD::MySQL::Server%s", + ref($self->{my}), + $loaderror ? sprintf " (syntax error in %s?)", $loaderror : "" ); + } + } else { + printf "broken mode %s\n", $params{mode}; + } +} + +sub dump { + my $self = shift; + my $message = shift || ""; + printf "%s %s\n", $message, Data::Dumper::Dumper($self); +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /^server::instance/) { + $self->{instance}->nagios(%params); + $self->merge_nagios($self->{instance}); + } elsif ($params{mode} =~ /^server::database/) { + $self->{database}->nagios(%params); + $self->merge_nagios($self->{database}); + } elsif ($params{mode} =~ /^server::uptime/) { + $self->add_nagios( + $self->check_thresholds($self->{uptime} / 60, "10:", "5:"), + sprintf "database is up since %d minutes", $self->{uptime} / 60); + $self->add_perfdata(sprintf "uptime=%ds", + $self->{uptime}); + } elsif ($params{mode} =~ /^server::connectiontime/) { + $self->add_nagios( + $self->check_thresholds($self->{connection_time}, 1, 5), + sprintf "%.2f seconds to connect as %s", + $self->{connection_time}, ($self->{username} || getpwuid($<))); + $self->add_perfdata(sprintf "connection_time=%.4fs;%d;%d", + $self->{connection_time}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::sql/) { + if ($params{regexp}) { + if (substr($params{name2}, 0, 1) eq '!') { + $params{name2} =~ s/^!//; + if ($self->{genericsql} !~ /$params{name2}/) { + $self->add_nagios_ok( + sprintf "output %s does not match pattern %s", + $self->{genericsql}, $params{name2}); + } else { + $self->add_nagios_critical( + sprintf "output %s matches pattern %s", + $self->{genericsql}, $params{name2}); + } + } else { + if ($self->{genericsql} =~ /$params{name2}/) { + $self->add_nagios_ok( + sprintf "output %s matches pattern %s", + $self->{genericsql}, $params{name2}); + } else { + $self->add_nagios_critical( + sprintf "output %s does not match pattern %s", + $self->{genericsql}, $params{name2}); + } + } + } else { + $self->add_nagios( + # the first item in the list will trigger the threshold values + $self->check_thresholds($self->{genericsql}[0], 1, 5), + sprintf "%s: %s%s", + $params{name2} ? lc $params{name2} : lc $params{selectname}, + # float as float, integers as integers + join(" ", map { + (sprintf("%d", $_) eq $_) ? $_ : sprintf("%f", $_) + } @{$self->{genericsql}}), + $params{units} ? $params{units} : ""); + my $i = 0; + # workaround... getting the column names from the database would be nicer + my @names2_arr = split(/\s+/, $params{name2}); + foreach my $t (@{$self->{genericsql}}) { + $self->add_perfdata(sprintf "\'%s\'=%s%s;%s;%s", + $names2_arr[$i] ? lc $names2_arr[$i] : lc $params{selectname}, + # float as float, integers as integers + (sprintf("%d", $t) eq $t) ? $t : sprintf("%f", $t), + $params{units} ? $params{units} : "", + ($i == 0) ? $self->{warningrange} : "", + ($i == 0) ? $self->{criticalrange} : "" + ); + $i++; + } + } + } elsif ($params{mode} =~ /^my::([^:.]+)/) { + $self->{my}->nagios(%params); + $self->merge_nagios($self->{my}); + } + } +} + + +sub init_nagios { + my $self = shift; + no strict 'refs'; + if (! ref($self)) { + my $nagiosvar = $self."::nagios"; + my $nagioslevelvar = $self."::nagios_level"; + $$nagiosvar = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $$nagioslevelvar = $ERRORS{OK}, + } else { + $self->{nagios} = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $self->{nagios_level} = $ERRORS{OK}, + } +} + +sub check_thresholds { + my $self = shift; + my $value = shift; + my $defaultwarningrange = shift; + my $defaultcriticalrange = shift; + my $level = $ERRORS{OK}; + $self->{warningrange} = defined $self->{warningrange} ? + $self->{warningrange} : $defaultwarningrange; + $self->{criticalrange} = defined $self->{criticalrange} ? + $self->{criticalrange} : $defaultcriticalrange; + + if ($self->{warningrange} =~ /^([-+]?[0-9]*\.?[0-9]+)$/) { + # warning = 10, warn if > 10 or < 0 + $level = $ERRORS{WARNING} + if ($value > $1 || $value < 0); + } elsif ($self->{warningrange} =~ /^([-+]?[0-9]*\.?[0-9]+):$/) { + # warning = 10:, warn if < 10 + $level = $ERRORS{WARNING} + if ($value < $1); + } elsif ($self->{warningrange} =~ /^~:([-+]?[0-9]*\.?[0-9]+)$/) { + # warning = ~:10, warn if > 10 + $level = $ERRORS{WARNING} + if ($value > $1); + } elsif ($self->{warningrange} =~ /^([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { + # warning = 10:20, warn if < 10 or > 20 + $level = $ERRORS{WARNING} + if ($value < $1 || $value > $2); + } elsif ($self->{warningrange} =~ /^@([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { + # warning = @10:20, warn if >= 10 and <= 20 + $level = $ERRORS{WARNING} + if ($value >= $1 && $value <= $2); + } + if ($self->{criticalrange} =~ /^([-+]?[0-9]*\.?[0-9]+)$/) { + # critical = 10, crit if > 10 or < 0 + $level = $ERRORS{CRITICAL} + if ($value > $1 || $value < 0); + } elsif ($self->{criticalrange} =~ /^([-+]?[0-9]*\.?[0-9]+):$/) { + # critical = 10:, crit if < 10 + $level = $ERRORS{CRITICAL} + if ($value < $1); + } elsif ($self->{criticalrange} =~ /^~:([-+]?[0-9]*\.?[0-9]+)$/) { + # critical = ~:10, crit if > 10 + $level = $ERRORS{CRITICAL} + if ($value > $1); + } elsif ($self->{criticalrange} =~ /^([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { + # critical = 10:20, crit if < 10 or > 20 + $level = $ERRORS{CRITICAL} + if ($value < $1 || $value > $2); + } elsif ($self->{criticalrange} =~ /^@([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { + # critical = @10:20, crit if >= 10 and <= 20 + $level = $ERRORS{CRITICAL} + if ($value >= $1 && $value <= $2); + } + return $level; + # + # syntax error must be reported with returncode -1 + # +} + +sub add_nagios { + my $self = shift; + my $level = shift; + my $message = shift; + push(@{$self->{nagios}->{messages}->{$level}}, $message); + # recalc current level + foreach my $llevel (qw(CRITICAL WARNING UNKNOWN OK)) { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) { + $self->{nagios_level} = $ERRORS{$llevel}; + } + } +} + +sub add_nagios_ok { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{OK}, $message); +} + +sub add_nagios_warning { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{WARNING}, $message); +} + +sub add_nagios_critical { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{CRITICAL}, $message); +} + +sub add_nagios_unknown { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{UNKNOWN}, $message); +} + +sub add_perfdata { + my $self = shift; + my $data = shift; + push(@{$self->{nagios}->{perfdata}}, $data); +} + +sub merge_nagios { + my $self = shift; + my $child = shift; + foreach my $level (0..3) { + foreach (@{$child->{nagios}->{messages}->{$level}}) { + $self->add_nagios($level, $_); + } + #push(@{$self->{nagios}->{messages}->{$level}}, + # @{$child->{nagios}->{messages}->{$level}}); + } + push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}}); +} + +sub calculate_result { + my $self = shift; + my $labels = shift || {}; + my $multiline = 0; + map { + $self->{nagios_level} = $ERRORS{$_} if + (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}})); + } ("OK", "UNKNOWN", "WARNING", "CRITICAL"); + if ($ENV{NRPE_MULTILINESUPPORT} && + length join(" ", @{$self->{nagios}->{perfdata}}) > 200) { + $multiline = 1; + } + my $all_messages = join(($multiline ? "\n" : ", "), map { + join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } grep { + scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } ("CRITICAL", "WARNING", "UNKNOWN", "OK")); + my $bad_messages = join(($multiline ? "\n" : ", "), map { + join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } grep { + scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } ("CRITICAL", "WARNING", "UNKNOWN")); + my $good_messages = join(($multiline ? "\n" : ", "), map { + join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } grep { + scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } ("OK")); + my $all_messages_short = $bad_messages ? $bad_messages : 'no problems'; + # if mode = my-.... + # and there are some ok-messages + # output them instead of "no problems" + if ($self->{mode} =~ /^my\:\:/ && $good_messages) { + $all_messages_short = $bad_messages ? $bad_messages : $good_messages; + } + my $all_messages_html = "". + join("", map { + my $level = $_; + join("", map { + sprintf "", + $level, $_; + } @{$self->{nagios}->{messages}->{$ERRORS{$_}}}); + } grep { + scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) + } ("CRITICAL", "WARNING", "UNKNOWN", "OK")). + "
%s
"; + if (exists $self->{identstring}) { + $self->{nagios_message} .= $self->{identstring}; + } + if ($self->{report} eq "long") { + $self->{nagios_message} .= $all_messages; + } elsif ($self->{report} eq "short") { + $self->{nagios_message} .= $all_messages_short; + } elsif ($self->{report} eq "html") { + $self->{nagios_message} .= $all_messages_short."\n".$all_messages_html; + } + foreach my $from (keys %{$self->{negate}}) { + if ((uc $from) =~ /^(OK|WARNING|CRITICAL|UNKNOWN)$/ && + (uc $self->{negate}->{$from}) =~ /^(OK|WARNING|CRITICAL|UNKNOWN)$/) { + if ($self->{nagios_level} == $ERRORS{uc $from}) { + $self->{nagios_level} = $ERRORS{uc $self->{negate}->{$from}}; + } + } + } + if ($self->{labelformat} eq "pnp4nagios") { + $self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}}); + } else { + $self->{perfdata} = join(" ", map { + my $perfdata = $_; + if ($perfdata =~ /^(.*?)=(.*)/) { + my $label = $1; + my $data = $2; + if (exists $labels->{$label} && + exists $labels->{$label}->{$self->{labelformat}}) { + $labels->{$label}->{$self->{labelformat}}."=".$data; + } else { + $perfdata; + } + } else { + $perfdata; + } + } @{$self->{nagios}->{perfdata}}); + } +} + +sub set_global_db_thresholds { + my $self = shift; + my $params = shift; + my $warning = undef; + my $critical = undef; + return unless defined $params->{dbthresholds}; + $params->{name0} = $params->{dbthresholds}; + # :pluginmode :name :warning :critical + # mode empty + # + eval { + if ($self->{handle}->fetchrow_array(q{ + SELECT table_name FROM information_schema.tables + WHERE table_schema = ? + AND table_name = 'CHECK_MYSQL_HEALTH_THRESHOLDS'; + }, $self->{database})) { # either --database... or information_schema + my @dbthresholds = $self->{handle}->fetchall_array(q{ + SELECT * FROM check_mysql_health_thresholds + }); + $params->{dbthresholds} = \@dbthresholds; + foreach (@dbthresholds) { + if (($_->[0] eq $params->{cmdlinemode}) && + (! defined $_->[1] || ! $_->[1])) { + ($warning, $critical) = ($_->[2], $_->[3]); + } + } + } + }; + if (! $@) { + if ($warning) { + $params->{warningrange} = $warning; + $self->trace("read warningthreshold %s from database", $warning); + } + if ($critical) { + $params->{criticalrange} = $critical; + $self->trace("read criticalthreshold %s from database", $critical); + } + } +} + +sub set_local_db_thresholds { + my $self = shift; + my %params = @_; + my $warning = undef; + my $critical = undef; + # :pluginmode :name :warning :critical + # mode name0 + # mode name2 + # mode name + # + # first: argument of --dbthresholds, it it exists + # second: --name2 + # third: --name + if (ref($params{dbthresholds}) eq 'ARRAY') { + my $marker; + foreach (@{$params{dbthresholds}}) { + if ($_->[0] eq $params{cmdlinemode}) { + if (defined $_->[1] && $params{name0} && $_->[1] eq $params{name0}) { + ($warning, $critical) = ($_->[2], $_->[3]); + $marker = $params{name0}; + last; + } elsif (defined $_->[1] && $params{name2} && $_->[1] eq $params{name2}) { + ($warning, $critical) = ($_->[2], $_->[3]); + $marker = $params{name2}; + last; + } elsif (defined $_->[1] && $params{name} && $_->[1] eq $params{name}) { + ($warning, $critical) = ($_->[2], $_->[3]); + $marker = $params{name}; + last; + } + } + } + if ($warning) { + $self->{warningrange} = $warning; + $self->trace("read warningthreshold %s for %s from database", + $marker, $warning); + } + if ($critical) { + $self->{criticalrange} = $critical; + $self->trace("read criticalthreshold %s for %s from database", + $marker, $critical); + } + } +} + +sub debug { + my $self = shift; + my $msg = shift; + if ($DBD::MySQL::Server::verbose) { + printf "%s %s\n", $msg, ref($self); + } +} + +sub dbconnect { + my $self = shift; + my %params = @_; + my $retval = undef; + $self->{tic} = Time::HiRes::time(); + $self->{handle} = DBD::MySQL::Server::Connection->new(%params); + if ($self->{handle}->{errstr}) { + if ($params{mode} =~ /^server::tnsping/ && + $self->{handle}->{errstr} =~ /ORA-01017/) { + $self->add_nagios($ERRORS{OK}, + sprintf "connection established to %s.", $self->{connect}); + $retval = undef; + } elsif ($self->{handle}->{errstr} eq "alarm\n") { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "connection could not be established within %d seconds", + $self->{timeout}); + } else { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "cannot connect to %s. %s", + $self->{database}, $self->{handle}->{errstr}); + $retval = undef; + } + } else { + $retval = $self->{handle}; + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub trace { + my $self = shift; + my $format = shift; + $self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0; + if ($self->{verbose}) { + printf("%s: ", scalar localtime); + printf($format, @_); + } + if ($self->{trace}) { + my $logfh = new IO::File; + $logfh->autoflush(1); + if ($logfh->open("/tmp/check_mysql_health.trace", "a")) { + $logfh->printf("%s: ", scalar localtime); + $logfh->printf($format, @_); + $logfh->printf("\n"); + $logfh->close(); + } + } +} + +sub DESTROY { + my $self = shift; + my $handle1 = "null"; + my $handle2 = "null"; + if (defined $self->{handle}) { + $handle1 = ref($self->{handle}); + if (defined $self->{handle}->{handle}) { + $handle2 = ref($self->{handle}->{handle}); + } + } + $self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Server") { + } + $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Server") { + #printf "humpftata\n"; + } +} + +sub save_state { + my $self = shift; + my %params = @_; + my $extension = ""; + my $mode = $params{mode}; + if ($params{connect} && $params{connect} =~ /(\w+)\/(\w+)@(\w+)/) { + $params{connect} = $3; + } elsif ($params{connect}) { + # just to be sure + $params{connect} =~ s/\//_/g; + } + if ($^O =~ /MSWin/) { + $mode =~ s/::/_/g; + $params{statefilesdir} = $self->system_vartmpdir(); + } + if (! -d $params{statefilesdir}) { + eval { + use File::Path; + mkpath $params{statefilesdir}; + }; + } + if ($@ || ! -w $params{statefilesdir}) { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "statefilesdir %s does not exist or is not writable\n", + $params{statefilesdir}); + return; + } + my $statefile = sprintf "%s_%s", $params{hostname}, $mode; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + $statefile = sprintf "%s/%s", $params{statefilesdir}, $statefile; + if (open(STATE, ">$statefile")) { + if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) { + $params{save}->{localtime} = scalar localtime $params{save}->{timestamp}; + } + printf STATE Data::Dumper::Dumper($params{save}); + close STATE; + } else { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "statefile %s is not writable", $statefile); + } + $self->debug(sprintf "saved %s to %s", + Data::Dumper::Dumper($params{save}), $statefile); +} + +sub load_state { + my $self = shift; + my %params = @_; + my $extension = ""; + my $mode = $params{mode}; + if ($params{connect} && $params{connect} =~ /(\w+)\/(\w+)@(\w+)/) { + $params{connect} = $3; + } elsif ($params{connect}) { + # just to be sure + $params{connect} =~ s/\//_/g; + } + if ($^O =~ /MSWin/) { + $mode =~ s/::/_/g; + $params{statefilesdir} = $self->system_vartmpdir(); + } + my $statefile = sprintf "%s_%s", $params{hostname}, $mode; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + $statefile = sprintf "%s/%s", $params{statefilesdir}, $statefile; + if ( -f $statefile) { + our $VAR1; + eval { + require $statefile; + }; + if($@) { + $self->add_nagios($ERRORS{CRITICAL}, + sprintf "statefile %s is corrupt", $statefile); + } + $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); + return $VAR1; + } else { + return undef; + } +} + +sub valdiff { + my $self = shift; + my $pparams = shift; + my %params = %{$pparams}; + my @keys = @_; + my $now = time; + my $last_values = $self->load_state(%params) || eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = 0; + } + $empty_events->{timestamp} = 0; + if ($params{lookback}) { + $empty_events->{lookback_history} = {}; + } + $empty_events; + }; + foreach (@keys) { + if ($params{lookback}) { + # find a last_value in the history which fits lookback best + # and overwrite $last_values->{$_} with historic data + if (exists $last_values->{lookback_history}->{$_}) { + foreach my $date (sort {$a <=> $b} keys %{$last_values->{lookback_history}->{$_}}) { + if ($date >= ($now - $params{lookback})) { + $last_values->{$_} = $last_values->{lookback_history}->{$_}->{$date}; + $last_values->{timestamp} = $date; + last; + } else { + delete $last_values->{lookback_history}->{$_}->{$date}; + } + } + } + } + $last_values->{$_} = 0 if ! exists $last_values->{$_}; + if ($self->{$_} >= $last_values->{$_}) { + $self->{'delta_'.$_} = $self->{$_} - $last_values->{$_}; + } else { + # vermutlich db restart und zaehler alle auf null + $self->{'delta_'.$_} = $self->{$_}; + } + $self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_}); + } + $self->{'delta_timestamp'} = $now - $last_values->{timestamp}; + $params{save} = eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = $self->{$_}; + } + $empty_events->{timestamp} = $now; + if ($params{lookback}) { + $empty_events->{lookback_history} = $last_values->{lookback_history}; + foreach (@keys) { + $empty_events->{lookback_history}->{$_}->{$now} = $self->{$_}; + } + } + $empty_events; + }; + $self->save_state(%params); +} + +sub requires_version { + my $self = shift; + my $version = shift; + my @instances = DBD::MySQL::Server::return_servers(); + my $instversion = $instances[0]->{version}; + if (! $self->version_is_minimum($version)) { + $self->add_nagios($ERRORS{UNKNOWN}, + sprintf "not implemented/possible for MySQL release %s", $instversion); + } +} + +sub version_is_minimum { + # the current version is newer or equal + my $self = shift; + my $version = shift; + my $newer = 1; + my @instances = DBD::MySQL::Server::return_servers(); + my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version); + my @v2 = split(/\./, $instances[0]->{version}); + if (scalar(@v1) > scalar(@v2)) { + push(@v2, (0) x (scalar(@v1) - scalar(@v2))); + } elsif (scalar(@v2) > scalar(@v1)) { + push(@v1, (0) x (scalar(@v2) - scalar(@v1))); + } + foreach my $pos (0..$#v1) { + if ($v2[$pos] > $v1[$pos]) { + $newer = 1; + last; + } elsif ($v2[$pos] < $v1[$pos]) { + $newer = 0; + last; + } + } + #printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1); + return $newer; +} + +sub instance_thread { + my $self = shift; + my @instances = DBD::MySQL::Server::return_servers(); + return $instances[0]->{thread}; +} + +sub windows_server { + my $self = shift; + my @instances = DBD::MySQL::Server::return_servers(); + if ($instances[0]->{os} =~ /Win/i) { + return 1; + } else { + return 0; + } +} + +sub system_vartmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $self->system_tmpdir(); + } else { + return "/var/tmp/check_mysql_health"; + } +} + +sub system_oldvartmpdir { + my $self = shift; + return "/tmp"; +} + +sub system_tmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $ENV{TEMP} if defined $ENV{TEMP}; + return $ENV{TMP} if defined $ENV{TMP}; + return File::Spec->catfile($ENV{windir}, 'Temp') + if defined $ENV{windir}; + return 'C:\Temp'; + } else { + return "/tmp"; + } +} + +sub decode_password { + my $self = shift; + my $password = shift; + if ($password && $password =~ /^rfc3986:\/\/(.*)/) { + $password = $1; + $password =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; + } + return $password; +} + + +package DBD::MySQL::Server::Connection; + +use strict; + +our @ISA = qw(DBD::MySQL::Server); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + mode => $params{mode}, + timeout => $params{timeout}, + access => $params{method} || "dbi", + hostname => $params{hostname}, + database => $params{database} || "information_schema", + port => $params{port}, + socket => $params{socket}, + username => $params{username}, + password => $params{password}, + mycnf => $params{mycnf}, + mycnfgroup => $params{mycnfgroup}, + handle => undef, + }; + bless $self, $class; + if ($params{method} eq "dbi") { + bless $self, "DBD::MySQL::Server::Connection::Dbi"; + } elsif ($params{method} eq "mysql") { + bless $self, "DBD::MySQL::Server::Connection::Mysql"; + } elsif ($params{method} eq "sqlrelay") { + bless $self, "DBD::MySQL::Server::Connection::Sqlrelay"; + } + $self->init(%params); + return $self; +} + + +package DBD::MySQL::Server::Connection::Dbi; + +use strict; +use Net::Ping; + +our @ISA = qw(DBD::MySQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + if ($self->{mode} =~ /^server::tnsping/) { + if (! $self->{connect}) { + $self->{errstr} = "Please specify a database"; + } else { + $self->{sid} = $self->{connect}; + $self->{username} ||= time; # prefer an existing user + $self->{password} = time; + } + } else { + if ( + ($self->{hostname} ne 'localhost' && (! $self->{username} || ! $self->{password})) && + (! $self->{mycnf}) ) { + $self->{errstr} = "Please specify hostname, username and password or a .cnf file"; + return undef; + } + $self->{dsn} = "DBI:mysql:"; + $self->{dsn} .= sprintf "database=%s", $self->{database}; + if ($self->{mycnf}) { + $self->{dsn} .= sprintf ";mysql_read_default_file=%s", $self->{mycnf}; + if ($self->{mycnfgroup}) { + $self->{dsn} .= sprintf ";mysql_read_default_group=%s", $self->{mycnfgroup}; + } + } else { + $self->{dsn} .= sprintf ";host=%s", $self->{hostname}; + $self->{dsn} .= sprintf ";port=%s", $self->{port} + unless $self->{socket} || $self->{hostname} eq 'localhost'; + $self->{dsn} .= sprintf ";mysql_socket=%s", $self->{socket} + if $self->{socket}; + } + } + if (! exists $self->{errstr}) { + eval { + require DBI; + use POSIX ':signal_h'; + if ($^O =~ /MSWin/) { + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + } else { + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "alarm\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + } + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + if ($self->{handle} = DBI->connect( + $self->{dsn}, + $self->{username}, + $self->decode_password($self->{password}), + { RaiseError => 0, AutoCommit => 0, PrintError => 1 })) { +# $self->{handle}->do(q{ +# ALTER SESSION SET NLS_NUMERIC_CHARACTERS=".," }); + $retval = $self; + } else { + $self->{errstr} = DBI::errstr(); + } + }; + if ($@) { + $self->{errstr} = $@; + $retval = undef; + } + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub selectrow_hashref { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $hashref = undef; + eval { + $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + # helm auf! jetzt wirds dreckig. + if ($sql =~ /^\s*SHOW/) { + $hashref = $self->{handle}->selectrow_hashref($sql); + } else { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $hashref = $sth->selectrow_hashref(); + } + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($hashref)); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + # keine lust auf den scheiss + } + return $hashref; +} + +sub fetchrow_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my @row = (); + my $stderrvar; + *SAVEERR = *STDERR; + open ERR ,'>',\$stderrvar; + *STDERR = *ERR; + eval { + $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + @row = $sth->fetchrow_array(); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper(\@row)); + }; + *STDERR = *SAVEERR; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + $self->{errstr} = $@; + return (undef); + } elsif ($stderrvar) { + $self->{errstr} = $stderrvar; + return (undef); + } elsif ($sth->errstr()) { + $self->{errstr} = $sth->errstr(); + return (undef); + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @row = split(/\s+/, (split(/\n/, $simulation))[0]); + } + return $row[0] unless wantarray; + return @row; +} + +sub fetchall_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $rows = undef; + eval { + $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $rows = $sth->fetchall_arrayref(); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($rows)); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); + } + return @{$rows}; +} + +sub func { + my $self = shift; + $self->{handle}->func(@_); +} + + +sub execute { + my $self = shift; + my $sql = shift; + eval { + my $sth = $self->{handle}->prepare($sql); + $sth->execute(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } +} + +sub errstr { + my $self = shift; + return $self->{errstr}; +} + +sub DESTROY { + my $self = shift; + $self->trace(sprintf "disconnecting DBD %s", + $self->{handle} ? "with handle" : "without handle"); + $self->{handle}->disconnect() if $self->{handle}; +} + +package DBD::MySQL::Server::Connection::Mysql; + +use strict; +use File::Temp qw/tempfile/; + +our @ISA = qw(DBD::MySQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + $self->{loginstring} = "traditional"; + ($self->{sql_commandfile_handle}, $self->{sql_commandfile}) = + tempfile($self->{mode}."XXXXX", SUFFIX => ".sql", + DIR => $self->system_tmpdir() ); + close $self->{sql_commandfile_handle}; + ($self->{sql_resultfile_handle}, $self->{sql_resultfile}) = + tempfile($self->{mode}."XXXXX", SUFFIX => ".out", + DIR => $self->system_tmpdir() ); + close $self->{sql_resultfile_handle}; + if ($self->{mode} =~ /^server::tnsping/) { + if (! $self->{connect}) { + $self->{errstr} = "Please specify a database"; + } else { + $self->{sid} = $self->{connect}; + $self->{username} ||= time; # prefer an existing user + $self->{password} = time; + } + } else { + if (! $self->{username} || ! $self->{password}) { + $self->{errstr} = "Please specify database, username and password"; + return undef; + } elsif (! (($self->{hostname} && $self->{port}) || $self->{socket})) { + $self->{errstr} = "Please specify hostname and port or socket"; + return undef; + } + } + if (! exists $self->{errstr}) { + $self->{password} = $self->decode_password($self->{password}); + eval { + my $mysql = '/'.'usr'.'/'.'bin'.'/'.'mysql'; + if (! -x $mysql) { + die "nomysql\n"; + } + if ($self->{loginstring} eq "traditional") { + $self->{sqlplus} = sprintf "%s ", $mysql; + $self->{sqlplus} .= sprintf "--batch --raw --skip-column-names "; + $self->{sqlplus} .= sprintf "--database=%s ", $self->{database}; + $self->{sqlplus} .= sprintf "--host=%s ", $self->{hostname}; + $self->{sqlplus} .= sprintf "--port=%s ", $self->{port} + unless $self->{socket} || $self->{hostname} eq "localhost"; + $self->{sqlplus} .= sprintf "--socket=%s ", $self->{socket} + if $self->{socket}; + $self->{sqlplus} .= sprintf "--user=%s --password='%s' < %s > %s", + $self->{username}, $self->{password}, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } + + use POSIX ':signal_h'; + if ($^O =~ /MSWin/) { + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + } else { + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "alarm\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + } + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + + my $answer = $self->fetchrow_array( + q{ SELECT 42 FROM dual}); + die unless defined $answer and $answer == 42; + $retval = $self; + }; + if ($@) { + $self->{errstr} = $@; + $self->{errstr} =~ s/at $0 .*//g; + chomp $self->{errstr}; + $retval = undef; + } + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub selectrow_hashref { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $hashref = undef; + foreach (@arguments) { + # replace the ? by the parameters + if (/^\d+$/) { + $sql =~ s/\?/$_/; + } else { + $sql =~ s/\?/'$_'/; + } + } + if ($sql =~ /^\s*SHOW/) { + $sql .= '\G'; # http://dev.mysql.com/doc/refman/5.1/de/show-slave-status.html + } + $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $self->create_commandfile($sql); + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "fetchrow_array exit bumm \n"; + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @oerrs = map { + /((ERROR \d+).*)/ ? $1 : (); + } split(/\n/, $output); + $self->{errstr} = join(" ", @oerrs); + } else { + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + if ($sql =~ /^\s*SHOW/) { + map { + if (/^\s*([\w_]+):\s*(.*)/) { + $hashref->{$1} = $2; + } + } split(/\n/, $output); + } else { + # i dont mess around here and you shouldn't either + } + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($hashref)); + } + unlink $self->{sql_commandfile}; + unlink $self->{sql_resultfile}; + return $hashref; +} + +sub fetchrow_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my @row = (); + foreach (@arguments) { + # replace the ? by the parameters + if (/^\d+$/) { + $sql =~ s/\?/$_/; + } else { + $sql =~ s/\?/'$_'/; + } + } + $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $self->create_commandfile($sql); + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "fetchrow_array exit bumm \n"; + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @oerrs = map { + /((ERROR \d+).*)/ ? $1 : (); + } split(/\n/, $output); + $self->{errstr} = join(" ", @oerrs); + } else { + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + @row = map { convert($_) } + map { s/^\s+([\.\d]+)$/$1/g; $_ } # strip leading space from numbers + map { s/\s+$//g; $_ } # strip trailing space + split(/\t/, (split(/\n/, $output))[0]); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper(\@row)); + } + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + unlink $self->{sql_commandfile}; + unlink $self->{sql_resultfile}; + return $row[0] unless wantarray; + return @row; +} + +sub fetchall_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $rows = undef; + foreach (@arguments) { + # replace the ? by the parameters + if (/^\d+$/) { + $sql =~ s/\?/$_/; + } else { + $sql =~ s/\?/'$_'/; + } + } + $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", + $sql, Data::Dumper::Dumper(\@arguments)); + $self->create_commandfile($sql); + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "fetchrow_array exit bumm %s\n", $exit_output; + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @oerrs = map { + /((ERROR \d+).*)/ ? $1 : (); + } split(/\n/, $output); + $self->{errstr} = join(" ", @oerrs); + } else { + my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; + my @rows = map { [ + map { convert($_) } + map { s/^\s+([\.\d]+)$/$1/g; $_ } + map { s/\s+$//g; $_ } + split /\t/ + ] } grep { ! /^\d+ rows selected/ } + grep { ! /^Elapsed: / } + grep { ! /^\s*$/ } split(/\n/, $output); + $rows = \@rows; + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($rows)); + } + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + unlink $self->{sql_commandfile}; + unlink $self->{sql_resultfile}; + return @{$rows}; +} + +sub func { + my $self = shift; + my $function = shift; + $self->{handle}->func(@_); +} + +sub convert { + my $n = shift; + # mostly used to convert numbers in scientific notation + if ($n =~ /^\s*\d+\s*$/) { + return $n; + } elsif ($n =~ /^\s*([-+]?)(\d*[\.,]*\d*)[eE]{1}([-+]?)(\d+)\s*$/) { + my ($vor, $num, $sign, $exp) = ($1, $2, $3, $4); + $n =~ s/E/e/g; + $n =~ s/,/\./g; + $num =~ s/,/\./g; + my $sig = $sign eq '-' ? "." . ($exp - 1 + length $num) : ''; + my $dec = sprintf "%${sig}f", $n; + $dec =~ s/\.[0]+$//g; + return $dec; + } elsif ($n =~ /^\s*([-+]?)(\d+)[\.,]*(\d*)\s*$/) { + return $1.$2.".".$3; + } elsif ($n =~ /^\s*(.*?)\s*$/) { + return $1; + } else { + return $n; + } +} + + +sub execute { + my $self = shift; + my $sql = shift; + eval { + my $sth = $self->{handle}->prepare($sql); + $sth->execute(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } +} + +sub errstr { + my $self = shift; + return $self->{errstr}; +} + +sub DESTROY { + my $self = shift; + $self->trace("try to clean up command and result files"); + unlink $self->{sql_commandfile} if -f $self->{sql_commandfile}; + unlink $self->{sql_resultfile} if -f $self->{sql_resultfile}; +} + +sub create_commandfile { + my $self = shift; + my $sql = shift; + open CMDCMD, "> $self->{sql_commandfile}"; + printf CMDCMD "%s\n", $sql; + close CMDCMD; +} + +sub decode_password { + my $self = shift; + my $password = shift; + $password = $self->SUPER::decode_password($password); + # we call '...%s/%s@...' inside backticks where the second %s is the password + # abc'xcv -> ''abc'\''xcv'' + # abc'`xcv -> ''abc'\''\`xcv'' + if ($password && $password =~ /'/) { + $password = "'".join("\\'", map { "'".$_."'"; } split("'", $password))."'"; + } + return $password; +} + + +package DBD::MySQL::Server::Connection::Sqlrelay; + +use strict; +use Net::Ping; + +our @ISA = qw(DBD::MySQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + if ($self->{mode} =~ /^server::tnsping/) { + if (! $self->{connect}) { + $self->{errstr} = "Please specify a database"; + } else { + if ($self->{connect} =~ /([\.\w]+):(\d+)/) { + $self->{host} = $1; + $self->{port} = $2; + $self->{socket} = ""; + } elsif ($self->{connect} =~ /([\.\w]+):([\w\/]+)/) { + $self->{host} = $1; + $self->{socket} = $2; + $self->{port} = ""; + } + } + } else { + if (! $self->{hostname} || ! $self->{username} || ! $self->{password}) { + if ($self->{hostname} && $self->{hostname} =~ /(\w+?)\/(.+)@([\.\w]+):(\d+)/) { + $self->{username} = $1; + $self->{password} = $2; + $self->{hostname} = $3; + $self->{port} = $4; + $self->{socket} = ""; + } elsif ($self->{hostname} && $self->{hostname} =~ /(\w+?)\/(.+)@([\.\w]+):([\w\/]+)/) { + $self->{username} = $1; + $self->{password} = $2; + $self->{hostname} = $3; + $self->{socket} = $4; + $self->{port} = ""; + } else { + $self->{errstr} = "Please specify database, username and password"; + return undef; + } + } else { + if ($self->{hostname} =~ /([\.\w]+):(\d+)/) { + $self->{hostname} = $1; + $self->{port} = $2; + $self->{socket} = ""; + } elsif ($self->{hostname} =~ /([\.\w]+):([\w\/]+)/) { + $self->{hostname} = $1; + $self->{socket} = $2; + $self->{port} = ""; + } else { + $self->{errstr} = "Please specify hostname, username, password and port/socket"; + return undef; + } + } + } + if (! exists $self->{errstr}) { + eval { + require DBI; + use POSIX ':signal_h'; + if ($^O =~ /MSWin/) { + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + } else { + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "alarm\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + } + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + if ($self->{handle} = DBI->connect( + sprintf("DBI:SQLRelay:host=%s;port=%d;socket=%s", + $self->{hostname}, $self->{port}, $self->{socket}), + $self->{username}, + $self->decode_password($self->{password}), + { RaiseError => 1, AutoCommit => 0, PrintError => 1 })) { + $retval = $self; + if ($self->{mode} =~ /^server::tnsping/ && $self->{handle}->ping()) { + # database connected. fake a "unknown user" + $self->{errstr} = "ORA-01017"; + } + } else { + $self->{errstr} = DBI::errstr(); + } + }; + if ($@) { + $self->{errstr} = $@; + $self->{errstr} =~ s/at [\w\/\.]+ line \d+.*//g; + $retval = undef; + } + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub fetchrow_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my @row = (); + $self->trace(sprintf "fetchrow_array: %s", $sql); + eval { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + @row = $sth->fetchrow_array(); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @row = split(/\s+/, (split(/\n/, $simulation))[0]); + } + return $row[0] unless wantarray; + return @row; +} + +sub fetchall_array { + my $self = shift; + my $sql = shift; + my @arguments = @_; + my $sth = undef; + my $rows = undef; + $self->trace(sprintf "fetchall_array: %s", $sql); + eval { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $rows = $sth->fetchall_arrayref(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } + if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; + @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); + } + return @{$rows}; +} + +sub func { + my $self = shift; + $self->{handle}->func(@_); +} + +sub execute { + my $self = shift; + my $sql = shift; + eval { + my $sth = $self->{handle}->prepare($sql); + $sth->execute(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } +} + +sub DESTROY { + my $self = shift; + #$self->trace(sprintf "disconnecting DBD %s", + # $self->{handle} ? "with handle" : "without handle"); + #$self->{handle}->disconnect() if $self->{handle}; +} + + + + +package DBD::MySQL::Cluster; + +use strict; +use Time::HiRes; +use IO::File; +use Data::Dumper; + + +{ + our $verbose = 0; + our $scream = 0; # scream if something is not implemented + our $access = "dbi"; # how do we access the database. + our $my_modules_dyn_dir = ""; # where we look for self-written extensions + + my @clusters = (); + my $initerrors = undef; + + sub add_cluster { + push(@clusters, shift); + } + + sub return_clusters { + return @clusters; + } + + sub return_first_cluster() { + return $clusters[0]; + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + hostname => $params{hostname}, + port => $params{port}, + username => $params{username}, + password => $params{password}, + timeout => $params{timeout}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + version => 'unknown', + nodes => [], + ndbd_nodes => 0, + ndb_mgmd_nodes => 0, + mysqld_nodes => 0, + }; + bless $self, $class; + $self->init_nagios(); + if ($self->connect(%params)) { + DBD::MySQL::Cluster::add_cluster($self); + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + if ($self->{show}) { + my $type = undef; + foreach (split /\n/, $self->{show}) { + if (/\[(\w+)\((\w+)\)\]\s+(\d+) node/) { + $type = uc $2; + } elsif (/id=(\d+)(.*)/) { + push(@{$self->{nodes}}, DBD::MySQL::Cluster::Node->new( + type => $type, + id => $1, + status => $2, + )); + } + } + } else { + } + if ($params{mode} =~ /^cluster::ndbdrunning/) { + foreach my $node (@{$self->{nodes}}) { + $node->{type} eq "NDB" && $node->{status} eq "running" && $self->{ndbd_nodes}++; + $node->{type} eq "MGM" && $node->{status} eq "running" && $self->{ndb_mgmd_nodes}++; + $node->{type} eq "API" && $node->{status} eq "running" && $self->{mysqld_nodes}++; + } + } else { + printf "broken mode %s\n", $params{mode}; + } +} + +sub dump { + my $self = shift; + my $message = shift || ""; + printf "%s %s\n", $message, Data::Dumper::Dumper($self); +} + +sub nagios { + my $self = shift; + my %params = @_; + my $dead_ndb = 0; + my $dead_api = 0; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /^cluster::ndbdrunning/) { + foreach my $node (grep { $_->{type} eq "NDB"} @{$self->{nodes}}) { + next if $params{selectname} && $params{selectname} ne $_->{id}; + if (! $node->{connected}) { + $self->add_nagios_critical( + sprintf "ndb node %d is not connected", $node->{id}); + $dead_ndb++; + } + } + foreach my $node (grep { $_->{type} eq "API"} @{$self->{nodes}}) { + next if $params{selectname} && $params{selectname} ne $_->{id}; + if (! $node->{connected}) { + $self->add_nagios_critical( + sprintf "api node %d is not connected", $node->{id}); + $dead_api++; + } + } + if (! $dead_ndb) { + $self->add_nagios_ok("all ndb nodes are connected"); + } + if (! $dead_api) { + $self->add_nagios_ok("all api nodes are connected"); + } + } + } + $self->add_perfdata(sprintf "ndbd_nodes=%d ndb_mgmd_nodes=%d mysqld_nodes=%d", + $self->{ndbd_nodes}, $self->{ndb_mgmd_nodes}, $self->{mysqld_nodes}); +} + + +sub init_nagios { + my $self = shift; + no strict 'refs'; + if (! ref($self)) { + my $nagiosvar = $self."::nagios"; + my $nagioslevelvar = $self."::nagios_level"; + $$nagiosvar = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $$nagioslevelvar = $ERRORS{OK}, + } else { + $self->{nagios} = { + messages => { + 0 => [], + 1 => [], + 2 => [], + 3 => [], + }, + perfdata => [], + }; + $self->{nagios_level} = $ERRORS{OK}, + } +} + +sub check_thresholds { + my $self = shift; + my $value = shift; + my $defaultwarningrange = shift; + my $defaultcriticalrange = shift; + my $level = $ERRORS{OK}; + $self->{warningrange} = $self->{warningrange} ? + $self->{warningrange} : $defaultwarningrange; + $self->{criticalrange} = $self->{criticalrange} ? + $self->{criticalrange} : $defaultcriticalrange; + if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) { + # warning = 10, critical = 20, warn if > 10, crit if > 20 + $level = $ERRORS{WARNING} if $value > $self->{warningrange}; + $level = $ERRORS{CRITICAL} if $value > $self->{criticalrange}; + } elsif ($self->{warningrange} =~ /([\d\.]+):/ && + $self->{criticalrange} =~ /([\d\.]+):/) { + # warning = 98:, critical = 95:, warn if < 98, crit if < 95 + $self->{warningrange} =~ /([\d\.]+):/; + $level = $ERRORS{WARNING} if $value < $1; + $self->{criticalrange} =~ /([\d\.]+):/; + $level = $ERRORS{CRITICAL} if $value < $1; + } + return $level; + # + # syntax error must be reported with returncode -1 + # +} + +sub add_nagios { + my $self = shift; + my $level = shift; + my $message = shift; + push(@{$self->{nagios}->{messages}->{$level}}, $message); + # recalc current level + foreach my $llevel (qw(CRITICAL WARNING UNKNOWN OK)) { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) { + $self->{nagios_level} = $ERRORS{$llevel}; + } + } +} + +sub add_nagios_ok { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{OK}, $message); +} + +sub add_nagios_warning { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{WARNING}, $message); +} + +sub add_nagios_critical { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{CRITICAL}, $message); +} + +sub add_nagios_unknown { + my $self = shift; + my $message = shift; + $self->add_nagios($ERRORS{UNKNOWN}, $message); +} + +sub add_perfdata { + my $self = shift; + my $data = shift; + push(@{$self->{nagios}->{perfdata}}, $data); +} + +sub merge_nagios { + my $self = shift; + my $child = shift; + foreach my $level (0..3) { + foreach (@{$child->{nagios}->{messages}->{$level}}) { + $self->add_nagios($level, $_); + } + #push(@{$self->{nagios}->{messages}->{$level}}, + # @{$child->{nagios}->{messages}->{$level}}); + } + push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}}); +} + + +sub calculate_result { + my $self = shift; + if ($ENV{NRPE_MULTILINESUPPORT} && + length join(" ", @{$self->{nagios}->{perfdata}}) > 200) { + foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { + # first the bad news + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_message} .= + "\n".join("\n", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}); + } + } + $self->{nagios_message} =~ s/^\n//g; + $self->{perfdata} = join("\n", @{$self->{nagios}->{perfdata}}); + } else { + foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { + # first the bad news + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_message} .= + join(", ", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}).", "; + } + } + $self->{nagios_message} =~ s/, $//g; + $self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}}); + } + foreach my $level ("OK", "UNKNOWN", "WARNING", "CRITICAL") { + if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { + $self->{nagios_level} = $ERRORS{$level}; + } + } +} + +sub debug { + my $self = shift; + my $msg = shift; + if ($DBD::MySQL::Cluster::verbose) { + printf "%s %s\n", $msg, ref($self); + } +} + +sub connect { + my $self = shift; + my %params = @_; + my $retval = undef; + $self->{tic} = Time::HiRes::time(); + eval { + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + my $mask = POSIX::SigSet->new( SIGALRM ); + my $action = POSIX::SigAction->new( + sub { die "connection timeout\n" ; }, $mask); + my $oldaction = POSIX::SigAction->new(); + sigaction(SIGALRM ,$action ,$oldaction ); + alarm($self->{timeout} - 1); # 1 second before the global unknown timeout + my $ndb_mgm = "ndb_mgm"; + $params{hostname} = "127.0.0.1" if ! $params{hostname}; + $ndb_mgm .= sprintf " --ndb-connectstring=%s", $params{hostname} + if $params{hostname}; + $ndb_mgm .= sprintf ":%d", $params{port} + if $params{port}; + $self->{show} = `$ndb_mgm -e show 2>&1`; + if ($? == -1) { + $self->add_nagios_critical("ndb_mgm failed to execute $!"); + } elsif ($? & 127) { + $self->add_nagios_critical("ndb_mgm failed to execute $!"); + } elsif ($? >> 8 != 0) { + $self->add_nagios_critical("ndb_mgm unable to connect"); + } else { + if ($self->{show} !~ /Cluster Configuration/) { + $self->add_nagios_critical("got no cluster configuration"); + } else { + $retval = 1; + } + } + }; + if ($@) { + $self->{errstr} = $@; + $self->{errstr} =~ s/at $0 .*//g; + chomp $self->{errstr}; + $self->add_nagios_critical($self->{errstr}); + $retval = undef; + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub trace { + my $self = shift; + my $format = shift; + $self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0; + if ($self->{verbose}) { + printf("%s: ", scalar localtime); + printf($format, @_); + } + if ($self->{trace}) { + my $logfh = new IO::File; + $logfh->autoflush(1); + if ($logfh->open("/tmp/check_mysql_health.trace", "a")) { + $logfh->printf("%s: ", scalar localtime); + $logfh->printf($format, @_); + $logfh->printf("\n"); + $logfh->close(); + } + } +} + +sub DESTROY { + my $self = shift; + my $handle1 = "null"; + my $handle2 = "null"; + if (defined $self->{handle}) { + $handle1 = ref($self->{handle}); + if (defined $self->{handle}->{handle}) { + $handle2 = ref($self->{handle}->{handle}); + } + } + $self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Cluster") { + } + $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MySQL::Cluster") { + #printf "humpftata\n"; + } +} + +sub save_state { + my $self = shift; + my %params = @_; + my $extension = ""; + mkdir $params{statefilesdir} unless -d $params{statefilesdir}; + my $statefile = sprintf "%s/%s_%s", + $params{statefilesdir}, $params{hostname}, $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + open(STATE, ">$statefile"); + if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) { + $params{save}->{localtime} = scalar localtime $params{save}->{timestamp}; + } + printf STATE Data::Dumper::Dumper($params{save}); + close STATE; + $self->debug(sprintf "saved %s to %s", + Data::Dumper::Dumper($params{save}), $statefile); +} + +sub load_state { + my $self = shift; + my %params = @_; + my $extension = ""; + my $statefile = sprintf "%s/%s_%s", + $params{statefilesdir}, $params{hostname}, $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{socket} ? "_".$params{socket} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $extension .= $params{tablespace} ? "_".$params{tablespace} : ""; + $extension .= $params{datafile} ? "_".$params{datafile} : ""; + $extension .= $params{name} ? "_".$params{name} : ""; + $extension =~ s/\//_/g; + $extension =~ s/\(/_/g; + $extension =~ s/\)/_/g; + $extension =~ s/\*/_/g; + $extension =~ s/\s/_/g; + $statefile .= $extension; + $statefile = lc $statefile; + if ( -f $statefile) { + our $VAR1; + eval { + require $statefile; + }; + if($@) { +printf "rumms\n"; + } + $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); + return $VAR1; + } else { + return undef; + } +} + +sub valdiff { + my $self = shift; + my $pparams = shift; + my %params = %{$pparams}; + my @keys = @_; + my $last_values = $self->load_state(%params) || eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = 0; + } + $empty_events->{timestamp} = 0; + $empty_events; + }; + foreach (@keys) { + $self->{'delta_'.$_} = $self->{$_} - $last_values->{$_}; + $self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_}); + } + $self->{'delta_timestamp'} = time - $last_values->{timestamp}; + $params{save} = eval { + my $empty_events = {}; + foreach (@keys) { + $empty_events->{$_} = $self->{$_}; + } + $empty_events->{timestamp} = time; + $empty_events; + }; + $self->save_state(%params); +} + +sub requires_version { + my $self = shift; + my $version = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + my $instversion = $instances[0]->{version}; + if (! $self->version_is_minimum($version)) { + $self->add_nagios($ERRORS{UNKNOWN}, + sprintf "not implemented/possible for MySQL release %s", $instversion); + } +} + +sub version_is_minimum { + # the current version is newer or equal + my $self = shift; + my $version = shift; + my $newer = 1; + my @instances = DBD::MySQL::Cluster::return_clusters(); + my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version); + my @v2 = split(/\./, $instances[0]->{version}); + if (scalar(@v1) > scalar(@v2)) { + push(@v2, (0) x (scalar(@v1) - scalar(@v2))); + } elsif (scalar(@v2) > scalar(@v1)) { + push(@v1, (0) x (scalar(@v2) - scalar(@v1))); + } + foreach my $pos (0..$#v1) { + if ($v2[$pos] > $v1[$pos]) { + $newer = 1; + last; + } elsif ($v2[$pos] < $v1[$pos]) { + $newer = 0; + last; + } + } + #printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1); + return $newer; +} + +sub instance_rac { + my $self = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + return (lc $instances[0]->{parallel} eq "yes") ? 1 : 0; +} + +sub instance_thread { + my $self = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + return $instances[0]->{thread}; +} + +sub windows_cluster { + my $self = shift; + my @instances = DBD::MySQL::Cluster::return_clusters(); + if ($instances[0]->{os} =~ /Win/i) { + return 1; + } else { + return 0; + } +} + +sub system_vartmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $self->system_tmpdir(); + } else { + return "/var/tmp/check_mysql_health"; + } +} + +sub system_oldvartmpdir { + my $self = shift; + return "/tmp"; +} + +sub system_tmpdir { + my $self = shift; + if ($^O =~ /MSWin/) { + return $ENV{TEMP} if defined $ENV{TEMP}; + return $ENV{TMP} if defined $ENV{TMP}; + return File::Spec->catfile($ENV{windir}, 'Temp') + if defined $ENV{windir}; + return 'C:\Temp'; + } else { + return "/tmp"; + } +} + + +package DBD::MySQL::Cluster::Node; + +use strict; + +our @ISA = qw(DBD::MySQL::Cluster); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + mode => $params{mode}, + timeout => $params{timeout}, + type => $params{type}, + id => $params{id}, + status => $params{status}, + }; + bless $self, $class; + $self->init(%params); + if ($params{type} eq "NDB") { + bless $self, "DBD::MySQL::Cluster::Node::NDB"; + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + if ($self->{status} =~ /@(\d+\.\d+\.\d+\.\d+)\s/) { + $self->{addr} = $1; + $self->{connected} = 1; + } elsif ($self->{status} =~ /accepting connect from (\d+\.\d+\.\d+\.\d+)/) { + $self->{addr} = $1; + $self->{connected} = 0; + } + if ($self->{status} =~ /starting,/) { + $self->{status} = "starting"; + } elsif ($self->{status} =~ /shutting,/) { + $self->{status} = "shutting"; + } else { + $self->{status} = $self->{connected} ? "running" : "dead"; + } +} + + +package DBD::MySQL::Cluster::Node::NDB; + +use strict; + +our @ISA = qw(DBD::MySQL::Cluster::Node); + + +sub init { + my $self = shift; + my %params = @_; + if ($self->{status} =~ /Nodegroup:\s*(\d+)/) { + $self->{nodegroup} = $1; + } + $self->{master} = ($self->{status} =~ /Master\)/) ? 1 : 0; +} + + +package Extraopts; + +use strict; +use File::Basename; +use Data::Dumper; + +sub new { + my $class = shift; + my %params = @_; + my $self = { + file => $params{file}, + commandline => $params{commandline}, + config => {}, + section => 'default_no_section', + }; + bless $self, $class; + $self->prepare_file_and_section(); + $self->init(); + return $self; +} + +sub prepare_file_and_section { + my $self = shift; + if (! defined $self->{file}) { + # ./check_stuff --extra-opts + $self->{section} = basename($0); + $self->{file} = $self->get_default_file(); + } elsif ($self->{file} =~ /^[^@]+$/) { + # ./check_stuff --extra-opts=special_opts + $self->{section} = $self->{file}; + $self->{file} = $self->get_default_file(); + } elsif ($self->{file} =~ /^@(.*)/) { + # ./check_stuff --extra-opts=@/etc/myconfig.ini + $self->{section} = basename($0); + $self->{file} = $1; + } elsif ($self->{file} =~ /^(.*?)@(.*)/) { + # ./check_stuff --extra-opts=special_opts@/etc/myconfig.ini + $self->{section} = $1; + $self->{file} = $2; + } +} + +sub get_default_file { + my $self = shift; + foreach my $default (qw(/etc/nagios/plugins.ini + /usr/local/nagios/etc/plugins.ini + /usr/local/etc/nagios/plugins.ini + /etc/opt/nagios/plugins.ini + /etc/nagios-plugins.ini + /usr/local/etc/nagios-plugins.ini + /etc/opt/nagios-plugins.ini)) { + if (-f $default) { + return $default; + } + } + return undef; +} + +sub init { + my $self = shift; + if (! defined $self->{file}) { + $self->{errors} = sprintf 'no extra-opts file specified and no default file found'; + } elsif (! -f $self->{file}) { + $self->{errors} = sprintf 'could not open %s', $self->{file}; + } else { + my $data = do { local (@ARGV, $/) = $self->{file}; <> }; + my $in_section = 'default_no_section'; + foreach my $line (split(/\n/, $data)) { + if ($line =~ /\[(.*)\]/) { + $in_section = $1; + } elsif ($line =~ /(.*?)\s*=\s*(.*)/) { + $self->{config}->{$in_section}->{$1} = $2; + } + } + } +} + +sub is_valid { + my $self = shift; + return ! exists $self->{errors}; +} + +sub overwrite { + my $self = shift; + my %commandline = (); + if (scalar(keys %{$self->{config}->{default_no_section}}) > 0) { + foreach (keys %{$self->{config}->{default_no_section}}) { + $commandline{$_} = $self->{config}->{default_no_section}->{$_}; + } + } + if (exists $self->{config}->{$self->{section}}) { + foreach (keys %{$self->{config}->{$self->{section}}}) { + $commandline{$_} = $self->{config}->{$self->{section}}->{$_}; + } + } + foreach (keys %commandline) { + if (! exists $self->{commandline}->{$_}) { + $self->{commandline}->{$_} = $commandline{$_}; + } + } +} + + + +package main; + +use strict; +use Getopt::Long qw(:config no_ignore_case); +use File::Basename; +use lib dirname($0); + + + +use vars qw ($PROGNAME $REVISION $CONTACT $TIMEOUT $STATEFILESDIR $needs_restart %commandline); + +$PROGNAME = "check_mysql_health"; +$REVISION = '$Revision: 2.2.2 $'; +$CONTACT = 'gerhard.lausser@consol.de'; +$TIMEOUT = 60; +$STATEFILESDIR = '/tmp'; +$needs_restart = 0; + +my @modes = ( + ['server::connectiontime', + 'connection-time', undef, + 'Time to connect to the server' ], + ['server::uptime', + 'uptime', undef, + 'Time the server is running' ], + ['server::instance::connectedthreads', + 'threads-connected', undef, + 'Number of currently open connections' ], + ['server::instance::threadcachehitrate', + 'threadcache-hitrate', undef, + 'Hit rate of the thread-cache' ], + ['server::instance::createdthreads', + 'threads-created', undef, + 'Number of threads created per sec' ], + ['server::instance::runningthreads', + 'threads-running', undef, + 'Number of currently running threads' ], + ['server::instance::cachedthreads', + 'threads-cached', undef, + 'Number of currently cached threads' ], + ['server::instance::abortedconnects', + 'connects-aborted', undef, + 'Number of aborted connections per sec' ], + ['server::instance::abortedclients', + 'clients-aborted', undef, + 'Number of aborted connections (because the client died) per sec' ], + ['server::instance::replication::slavelag', + 'slave-lag', ['replication-slave-lag'], + 'Seconds behind master' ], + ['server::instance::replication::slaveiorunning', + 'slave-io-running', ['replication-slave-io-running'], + 'Slave io running: Yes' ], + ['server::instance::replication::slavesqlrunning', + 'slave-sql-running', ['replication-slave-sql-running'], + 'Slave sql running: Yes' ], + ['server::instance::querycachehitrate', + 'qcache-hitrate', ['querycache-hitrate'], + 'Query cache hitrate' ], + ['server::instance::querycachelowmemprunes', + 'qcache-lowmem-prunes', ['querycache-lowmem-prunes'], + 'Query cache entries pruned because of low memory' ], + ['server::instance::myisam::keycache::hitrate', + 'keycache-hitrate', ['myisam-keycache-hitrate'], + 'MyISAM key cache hitrate' ], + ['server::instance::innodb::bufferpool::hitrate', + 'bufferpool-hitrate', ['innodb-bufferpool-hitrate'], + 'InnoDB buffer pool hitrate' ], + ['server::instance::innodb::bufferpool::waitfree', + 'bufferpool-wait-free', ['innodb-bufferpool-wait-free'], + 'InnoDB buffer pool waits for clean page available' ], + ['server::instance::innodb::logwaits', + 'log-waits', ['innodb-log-waits'], + 'InnoDB log waits because of a too small log buffer' ], + ['server::instance::tablecachehitrate', + 'tablecache-hitrate', undef, + 'Table cache hitrate' ], + ['server::instance::tablelockcontention', + 'table-lock-contention', undef, + 'Table lock contention' ], + ['server::instance::tableindexusage', + 'index-usage', undef, + 'Usage of indices' ], + ['server::instance::tabletmpondisk', + 'tmp-disk-tables', undef, + 'Percent of temp tables created on disk' ], + ['server::instance::needoptimize', + 'table-fragmentation', undef, + 'Show tables which should be optimized' ], + ['server::instance::openfiles', + 'open-files', undef, + 'Percent of opened files' ], + ['server::instance::slowqueries', + 'slow-queries', undef, + 'Slow queries' ], + ['server::instance::longprocs', + 'long-running-procs', undef, + 'long running processes' ], + ['cluster::ndbdrunning', + 'cluster-ndbd-running', undef, + 'ndnd nodes are up and running' ], + ['server::sql', + 'sql', undef, + 'any sql command returning a single number' ], +); + +# rrd data store names are limited to 19 characters +my %labels = ( + bufferpool_hitrate => { + groundwork => 'bp_hitrate', + }, + bufferpool_hitrate_now => { + groundwork => 'bp_hitrate_now', + }, + bufferpool_free_waits_rate => { + groundwork => 'bp_freewaits', + }, + innodb_log_waits_rate => { + groundwork => 'inno_log_waits', + }, + keycache_hitrate => { + groundwork => 'kc_hitrate', + }, + keycache_hitrate_now => { + groundwork => 'kc_hitrate_now', + }, + threads_created_per_sec => { + groundwork => 'thrds_creat_per_s', + }, + connects_aborted_per_sec => { + groundwork => 'conn_abrt_per_s', + }, + clients_aborted_per_sec => { + groundwork => 'clnt_abrt_per_s', + }, + thread_cache_hitrate => { + groundwork => 'tc_hitrate', + }, + thread_cache_hitrate_now => { + groundwork => 'tc_hitrate_now', + }, + qcache_lowmem_prunes_rate => { + groundwork => 'qc_lowm_prnsrate', + }, + slow_queries_rate => { + groundwork => 'slow_q_rate', + }, + tablecache_hitrate => { + groundwork => 'tac_hitrate', + }, + tablecache_fillrate => { + groundwork => 'tac_fillrate', + }, + tablelock_contention => { + groundwork => 'tl_contention', + }, + tablelock_contention_now => { + groundwork => 'tl_contention_now', + }, + pct_tmp_table_on_disk => { + groundwork => 'tmptab_on_disk', + }, + pct_tmp_table_on_disk_now => { + groundwork => 'tmptab_on_disk_now', + }, +); + +sub print_usage () { + print <] [[--hostname ] + [--port | --socket ] + --username --password ] --mode + [--method mysql] + $PROGNAME [-h | --help] + $PROGNAME [-V | --version] + + Options: + --hostname + the database server's hostname + --port + the database's port. (default: 3306) + --socket + the database's unix socket. + --username + the mysql db user + --password + the mysql db user's password + --database + the database's name. (default: information_schema) + --replication-user + the database's replication user name (default: replication) + --warning + the warning range + --critical + the critical range + --mode + the mode of the plugin. select one of the following keywords: +EOUS + my $longest = length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0]); + my $format = " %-". + (length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0])). + "s\t(%s)\n"; + foreach (@modes) { + printf $format, $_->[1], $_->[3]; + } + printf "\n"; + print <new(file => $commandline{'extra-opts'}, commandline => + \%commandline); + if (! $extras->is_valid()) { + printf "extra-opts are not valid: %s\n", $extras->{errors}; + exit $ERRORS{UNKNOWN}; + } else { + $extras->overwrite(); + } +} + +if (exists $commandline{version}) { + print_revision($PROGNAME, $REVISION); + exit $ERRORS{OK}; +} + +if (exists $commandline{help}) { + print_help(); + exit $ERRORS{OK}; +} elsif (! exists $commandline{mode}) { + printf "Please select a mode\n"; + print_help(); + exit $ERRORS{OK}; +} + +if ($commandline{mode} eq "encode") { + my $input = <>; + chomp $input; + $input =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; + printf "%s\n", $input; + exit $ERRORS{OK}; +} + +if (exists $commandline{3}) { + $ENV{NRPE_MULTILINESUPPORT} = 1; +} + +if (exists $commandline{timeout}) { + $TIMEOUT = $commandline{timeout}; +} + +if (exists $commandline{verbose}) { + $DBD::MySQL::Server::verbose = exists $commandline{verbose}; +} + +if (exists $commandline{scream}) { +# $DBD::MySQL::Server::hysterical = exists $commandline{scream}; +} + +if (exists $commandline{method}) { + # snmp or mysql cmdline +} else { + $commandline{method} = "dbi"; +} + +if (exists $commandline{report}) { + # short, long, html +} else { + $commandline{report} = "long"; +} + +if (exists $commandline{labelformat}) { + # groundwork +} else { + $commandline{labelformat} = "pnp4nagios"; +} + +if (exists $commandline{'with-mymodules-dyn-dir'}) { + $DBD::MySQL::Server::my_modules_dyn_dir = $commandline{'with-mymodules-dyn-dir'}; +} else { + $DBD::MySQL::Server::my_modules_dyn_dir = '/usr/local/nagios/libexec'; +} + +if (exists $commandline{environment}) { + # if the desired environment variable values are different from + # the environment of this running script, then a restart is necessary. + # because setting $ENV does _not_ change the environment of the running script. + foreach (keys %{$commandline{environment}}) { + if ((! $ENV{$_}) || ($ENV{$_} ne $commandline{environment}->{$_})) { + $needs_restart = 1; + $ENV{$_} = $commandline{environment}->{$_}; + printf STDERR "new %s=%s forces restart\n", $_, $ENV{$_} + if $DBD::MySQL::Server::verbose; + } + } + # e.g. called with --runas dbnagio. shlib_path environment variable is stripped + # during the sudo. + # so the perl interpreter starts without a shlib_path. but --runas cares for + # a --environment shlib_path=... + # so setting the environment variable in the code above and restarting the + # perl interpreter will help it find shared libs +} + +if (exists $commandline{runas}) { + # remove the runas parameter + # exec sudo $0 ... the remaining parameters + $needs_restart = 1; + # if the calling script has a path for shared libs and there is no --environment + # parameter then the called script surely needs the variable too. + foreach my $important_env (qw(LD_LIBRARY_PATH SHLIB_PATH + ORACLE_HOME TNS_ADMIN ORA_NLS ORA_NLS33 ORA_NLS10)) { + if ($ENV{$important_env} && ! scalar(grep { /^$important_env=/ } + keys %{$commandline{environment}})) { + $commandline{environment}->{$important_env} = $ENV{$important_env}; + printf STDERR "add important --environment %s=%s\n", + $important_env, $ENV{$important_env} if $DBD::MySQL::Server::verbose; + } + } +} + +if ($needs_restart) { + my @newargv = (); + my $runas = undef; + if (exists $commandline{runas}) { + $runas = $commandline{runas}; + delete $commandline{runas}; + } + foreach my $option (keys %commandline) { + if (grep { /^$option/ && /=/ } @params) { + if (ref ($commandline{$option}) eq "HASH") { + foreach (keys %{$commandline{$option}}) { + push(@newargv, sprintf "--%s", $option); + push(@newargv, sprintf "%s=%s", $_, $commandline{$option}->{$_}); + } + } else { + push(@newargv, sprintf "--%s", $option); + push(@newargv, sprintf "%s", $commandline{$option}); + } + } else { + push(@newargv, sprintf "--%s", $option); + } + } + if ($runas) { + exec "sudo", "-S", "-u", $runas, $0, @newargv; + } else { + exec $0, @newargv; + # this makes sure that even a SHLIB or LD_LIBRARY_PATH are set correctly + # when the perl interpreter starts. Setting them during runtime does not + # help loading e.g. libclntsh.so + } + exit; +} + +if (exists $commandline{shell}) { + # forget what you see here. + system("/bin/sh"); +} + +if (! exists $commandline{statefilesdir}) { + if (exists $ENV{OMD_ROOT}) { + $commandline{statefilesdir} = $ENV{OMD_ROOT}."/var/tmp/check_mysql_health"; + } else { + $commandline{statefilesdir} = $STATEFILESDIR; + } +} + +if (exists $commandline{name}) { + if ($^O =~ /MSWin/ && $commandline{name} =~ /^'(.*)'$/) { + # putting arguments in single ticks under Windows CMD leaves the ' intact + # we remove them + $commandline{name} = $1; + } + # objects can be encoded like an url + # with s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; + if (($commandline{mode} ne "sql") || + (($commandline{mode} eq "sql") && + ($commandline{name} =~ /select%20/i))) { # protect ... like '%cac%' ... from decoding + $commandline{name} =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; + } + if ($commandline{name} =~ /^0$/) { + # without this, $params{selectname} would be treated like undef + $commandline{name} = "00"; + } +} + +$SIG{'ALRM'} = sub { + printf "UNKNOWN - %s timed out after %d seconds\n", $PROGNAME, $TIMEOUT; + exit $ERRORS{UNKNOWN}; +}; +alarm($TIMEOUT); + +my $nagios_level = $ERRORS{UNKNOWN}; +my $nagios_message = ""; +my $perfdata = ""; +if ($commandline{mode} =~ /^my-([^\-.]+)/) { + my $param = $commandline{mode}; + $param =~ s/\-/::/g; + push(@modes, [$param, $commandline{mode}, undef, 'my extension']); +} elsif ((! grep { $commandline{mode} eq $_ } map { $_->[1] } @modes) && + (! grep { $commandline{mode} eq $_ } map { defined $_->[2] ? @{$_->[2]} : () } @modes)) { + printf "UNKNOWN - mode %s\n", $commandline{mode}; + print_usage(); + exit 3; +} +my %params = ( + timeout => $TIMEOUT, + mode => ( + map { $_->[0] } + grep { + ($commandline{mode} eq $_->[1]) || + ( defined $_->[2] && grep { $commandline{mode} eq $_ } @{$_->[2]}) + } @modes + )[0], + cmdlinemode => $commandline{mode}, + method => $commandline{method} || + $ENV{NAGIOS__SERVICEMYSQL_METH} || + $ENV{NAGIOS__HOSTMYSQL_METH} || 'dbi', + hostname => $commandline{hostname} || + $ENV{NAGIOS__SERVICEMYSQL_HOST} || + $ENV{NAGIOS__HOSTMYSQL_HOST} || 'localhost', + database => $commandline{database} || + $ENV{NAGIOS__SERVICEMYSQL_DATABASE} || + $ENV{NAGIOS__HOSTMYSQL_DATABASE} || 'information_schema', + port => $commandline{port} || (($commandline{mode} =~ /^cluster/) ? + ($ENV{NAGIOS__SERVICENDBMGM_PORT} || $ENV{NAGIOS__HOSTNDBMGM_PORT} || 1186) : + ($ENV{NAGIOS__SERVICEMYSQL_PORT} || $ENV{NAGIOS__HOSTMYSQL_PORT} || 3306)), + socket => $commandline{socket} || + $ENV{NAGIOS__SERVICEMYSQL_SOCKET} || + $ENV{NAGIOS__HOSTMYSQL_SOCKET}, + username => $commandline{username} || + $ENV{NAGIOS__SERVICEMYSQL_USER} || + $ENV{NAGIOS__HOSTMYSQL_USER}, + password => $commandline{password} || + $ENV{NAGIOS__SERVICEMYSQL_PASS} || + $ENV{NAGIOS__HOSTMYSQL_PASS}, + replication_user => $commandline{'replication-user'} || 'replication', + mycnf => $commandline{mycnf} || + $ENV{NAGIOS__SERVICEMYSQL_MYCNF} || + $ENV{NAGIOS__HOSTMYSQL_MYCNF}, + mycnfgroup => $commandline{mycnfgroup} || + $ENV{NAGIOS__SERVICEMYSQL_MYCNFGROUP} || + $ENV{NAGIOS__HOSTMYSQL_MYCNFGROUP}, + warningrange => $commandline{warning}, + criticalrange => $commandline{critical}, + dbthresholds => $commandline{dbthresholds}, + absolute => $commandline{absolute}, + lookback => $commandline{lookback}, + selectname => $commandline{name} || $commandline{tablespace} || $commandline{datafile}, + regexp => $commandline{regexp}, + name => $commandline{name}, + name2 => $commandline{name2} || $commandline{name}, + units => $commandline{units}, + lookback => $commandline{lookback} || 0, + eyecandy => $commandline{eyecandy}, + statefilesdir => $commandline{statefilesdir}, + verbose => $commandline{verbose}, + report => $commandline{report}, + labelformat => $commandline{labelformat}, + negate => $commandline{negate}, +); + +my $server = undef; +my $cluster = undef; + +if ($params{mode} =~ /^(server|my)/) { + $server = DBD::MySQL::Server->new(%params); + $server->nagios(%params); + $server->calculate_result(\%labels); + $nagios_message = $server->{nagios_message}; + $nagios_level = $server->{nagios_level}; + $perfdata = $server->{perfdata}; +} elsif ($params{mode} =~ /^cluster/) { + $cluster = DBD::MySQL::Cluster->new(%params); + $cluster->nagios(%params); + $cluster->calculate_result(\%labels); + $nagios_message = $cluster->{nagios_message}; + $nagios_level = $cluster->{nagios_level}; + $perfdata = $cluster->{perfdata}; +} + +printf "%s - %s", $ERRORCODES{$nagios_level}, $nagios_message; +printf " | %s", $perfdata if $perfdata; +printf "\n"; +exit $nagios_level; + + +__END__ + + diff --git a/nagios-nrpe/files/plugins/check_redis_instances b/nagios-nrpe/files/plugins/check_redis_instances index bd6ed634..5923eef7 100755 --- a/nagios-nrpe/files/plugins/check_redis_instances +++ b/nagios-nrpe/files/plugins/check_redis_instances @@ -1,5 +1,7 @@ #!/bin/sh +# {{ ansible_managed }} + set -u return=0 @@ -9,35 +11,66 @@ nb_ok=0 nb_unchk=0 output="" -instances=$(ls /etc/redis/redis-*.conf) -for instance in ${instances}; do - name=$(basename "${instance}"| sed '{s/redis-//;s/.conf//}') - port=$(grep "port" "${instance}"|grep -oE "[0-9]*") - socket=$(grep "unixsocket " "${instance}"|awk '{ print $2 }') - if [ -h "/etc/systemd/system/multi-user.target.wants/redis-server@${name}.service" ]; then - if [ "${port}" -ne 0 ]; then - /usr/lib/nagios/plugins/check_tcp -p "${port}" >/dev/null 2>&1 - ret="${?}" - else - /usr/lib/nagios/plugins/check_tcp -H "${socket}" >/dev/null 2>&1 - ret="${?}" - fi - if [ "${ret}" -ge 2 ]; then - nb_crit=$((nb_crit + 1)) - output="${output}CRITICAL - ${name} (${port})\n" - [ "${return}" -le 2 ] && return=2 - elif [ "${ret}" -ge 1 ]; then - nb_warn=$((nb_warn + 1)) - output="${output}WARNING - ${name} (${port})\n" - [ "${return}" -le 1 ] && return=1 - else - nb_ok=$((nb_ok + 1)) - output="${output}OK - ${name} (${port})\n" - [ "${return}" -le 0 ] && return=0 - fi +packaged_check=/usr/lib/nagios/plugins/check_redis +vendored_check=/usr/local/lib/nagios/plugins/check_redis + +if [ -x $packaged_check ]; then + check_bin=$packaged_check +elif [ -x $vendored_check ]; then + check_bin=$vendored_check +else + echo "UNCHK - can't find check_redis" + exit 3 +fi + +check_server() { + name=$1 + conf_file=$2 + + host=$(config_var "bind" "${conf_file}") + port=$(config_var "port" "${conf_file}") + pass=$(config_var "requirepass" "${conf_file}") + + cmd="${check_bin} -H ${host} -p ${port}" + if [ -n "${pass}" ]; then + cmd="${cmd} -x ${pass}" + fi + result=$($cmd) + ret="${?}" + if [ "${ret}" -ge 2 ]; then + nb_crit=$((nb_crit + 1)) + output="${output}${result}\n" + [ "${return}" -le 2 ] && return=2 + elif [ "${ret}" -ge 1 ]; then + nb_warn=$((nb_warn + 1)) + output="${output}${result}\n" + [ "${return}" -le 1 ] && return=1 + else + nb_ok=$((nb_ok + 1)) + output="${output}${result}\n" + [ "${return}" -le 0 ] && return=0 + fi +} +config_var() { + variable=$1 + file=$2 + test -f $file && grep -E "^${variable}\s+.+$" $file | awk '{ print $2 }' +} + +# default instance +if systemctl is-enabled -q redis-server; then + check_server "default" "/etc/redis/redis.conf" +fi + +# additional instances +conf_files=$(ls -1 /etc/redis-*/redis.conf) +for conf_file in ${conf_files}; do + name=$(dirname ${conf_file} | sed '{s|/etc/redis-||}') + if systemctl is-enabled -q "redis-server@${name}.service"; then + check_server $name $conf_file else nb_unchk=$((nb_unchk + 1)) - output="${output}UNCHK - ${name} (${port})\n" + output="${output}UNCHK - ${name} (unit is disabled or missing)\n" fi done @@ -47,9 +80,9 @@ done printf "%s - %s UNCHK / %s CRIT / %s WARN / %s OK\n\n" "${header}" "${nb_unchk}" "${nb_crit}" "${nb_warn}" "${nb_ok}" -printf "${output}" | grep -E "^CRITICAL" -printf "${output}" | grep -E "^WARNING" -printf "${output}" | grep -E "^OK" -printf "${output}" | grep -E "^UNCHK" +printf "%s" "${output}" | grep -E "^CRITICAL" +printf "%s" "${output}" | grep -E "^WARNING" +printf "%s" "${output}" | grep -E "^OK" +printf "%s" "${output}" | grep -E "^UNCHK" exit "${return}" diff --git a/nagios-nrpe/files/plugins/check_vrrpd b/nagios-nrpe/files/plugins/check_vrrpd new file mode 100755 index 00000000..9390aa6e --- /dev/null +++ b/nagios-nrpe/files/plugins/check_vrrpd @@ -0,0 +1,94 @@ +#!/bin/bash + +# README +# +# Variable to adjust : is_master and vrrpd_processes. +# vrrpd_processes is the number of vrrpd processes that should run on the server. +# is_master defines whether the vrrpd group should be master (1) or backup (0). +# +# If some instances have to be master and some other have to be backup, +# then the value of is_master is 2 and the states has to be precised in arguments. +# e.g. : ./check_vrrpd master backup master +# The order is defined by the output order of `ps auwx | grep vrrp` + +RC=0 +IFS=' +' + +is_master=2 # 1 if master ; 0 if backup ; 2 if mixed master and backup, in this case, it has to be precised in arguments +vrrpd_processes=3 # number of vrrpd processes that should be running +is_vrrpd_running=$(sudo /usr/lib/nagios//plugins/check_procs -C vrrpd -c $vrrpd_processes:$vrrpd_processes) +rc_is_vrrpd_running=$? +IP_vrrpd=($(for i in $(ps auwx | grep vrrpd | grep -v grep | grep -v check); do echo $i | awk '{print $--NF}'; done)) +INT_vrrpd=($(for i in $(ps auwx | grep vrrpd | grep -v grep | grep -v check); do echo $i | awk '{print $13}'; done)) +ID_vrrpd=($(for i in $(ps auwx | grep vrrpd | grep -v grep | grep -v check); do echo $i | awk '{print $19}'; done)) + +if [[ $rc_is_vrrpd_running -ne 0 ]]; then + echo $is_vrrpd_running instead of $vrrpd_processes + exit 2 +fi + +for i in $(seq 0 $((${#ID_vrrpd[*]}-1))); do + ifconfig vrrp_${ID_vrrpd[$i]}_${INT_vrrpd[$i]} >/dev/null 2>&1 + # If has interface + if [[ $? -eq 0 ]]; then + # If has to be master : OK + if [[ $is_master -eq 1 ]]; then + echo OK - ${IP_vrrpd[$i]} exists and is master + # If has to be backup : KO + elif [[ $is_master -eq 0 ]]; then + echo CRITICAL - ${IP_vrrpd[$i]} exists whereas it should be backup + RC=2 + # We retrieve the state it should be from args + elif [[ $is_master -eq 2 ]]; then + arg=$(($i+1)) + state=${!arg} + # If has to be master : OK + if [[ $state = master ]]; then + echo OK - ${IP_vrrpd[$i]} exists and is master + # If has to be backup : KO + elif [[ $state = backup ]]; then + echo CRITICAL - ${IP_vrrpd[$i]} exists whereas it should be backup + RC=2 + else + echo "CRITICAL - The arguments have to be master or backup. Exiting" + exit 2 + fi + # Unknown + else + RC=3 + fi + # If hasn't interface + elif [[ $? -ne 0 ]]; then + # If has to be master : KO + if [[ $is_master -eq 1 ]]; then + echo CRITICAL - ${IP_vrrpd[$i]} does not exist whereas it should be master + RC=2 + # If has to be backup : OK + elif [[ $is_master -eq 0 ]]; then + echo OK - ${IP_vrrpd[$i]} is backup + # We retrieve the state it should be from args + elif [[ $is_master -eq 2 ]]; then + arg=$(($i+1)) + state=${!arg} + # If has to be master : KO + if [[ $state = master ]]; then + echo CRITICAL - ${IP_vrrpd[$i]} does not exist whereas it should be master + RC=2 + # If has to be backup : OK + elif [[ $state = backup ]]; then + echo OK - ${IP_vrrpd[$i]} is backup + else + echo "CRITICAL - The arguments have to be master or backup. Exiting" + exit 2 + fi + # Unknown + else + RC=3 + fi + # Unknown + else + RC=3 + fi +done +exit $RC diff --git a/nagios-nrpe/tasks/main.yml b/nagios-nrpe/tasks/main.yml index 0731c74c..7125cbf7 100644 --- a/nagios-nrpe/tasks/main.yml +++ b/nagios-nrpe/tasks/main.yml @@ -1,15 +1,14 @@ --- - name: packages are installed apt: - name: "{{ item }}" + name: + - nagios-nrpe-server + - monitoring-plugins + - monitoring-plugins-basic + - monitoring-plugins-common + - monitoring-plugins-standard + - nagios-plugins-contrib state: present - with_items: - - nagios-nrpe-server - - monitoring-plugins - - monitoring-plugins-basic - - monitoring-plugins-common - - monitoring-plugins-standard - - nagios-plugins-contrib tags: - nagios-nrpe @@ -46,8 +45,8 @@ - nagios-nrpe - include_role: - name: remount-usr - when: nagios_plugins_directory | search ("/usr") + name: evolix/remount-usr + when: nagios_plugins_directory is search ("/usr") tags: - nagios-nrpe - nagios-plugins diff --git a/nagios-nrpe/templates/evolix.cfg.j2 b/nagios-nrpe/templates/evolix.cfg.j2 index aca1eb05..89d7e7d9 100644 --- a/nagios-nrpe/templates/evolix.cfg.j2 +++ b/nagios-nrpe/templates/evolix.cfg.j2 @@ -7,7 +7,7 @@ allowed_hosts={{ nagios_nrpe_allowed_hosts | join(',') }} # System checks -command[check_load]=/usr/lib/nagios/plugins/check_load -w {{ ansible_processor_vcpus * 0.7 }},{{ ansible_processor_vcpus * 0.6 }},{{ ansible_processor_vcpus * 0.5 }} -c {{ ansible_processor_vcpus * 0.9 }},{{ ansible_processor_vcpus * 0.8 }},{{ ansible_processor_vcpus * 0.7 }} +command[check_load]=/usr/lib/nagios/plugins/check_load --percpu --warning=0.7,0.6,0.5 --critical=0.9,0.8,0.7 command[check_swap]=/usr/lib/nagios/plugins/check_swap -a -w 30% -c 20% command[check_disk1]=/usr/lib/nagios/plugins/check_disk -x /lib/init/rw -x /dev -x /dev/shm -x /sys/kernel/debug/tracing -w 10% -c 3% -W 10% -K 3% -C -w 5% -c 2% -W 5% -K 2% -p /home command[check_zombie_procs]=sudo /usr/lib/nagios/plugins/check_procs -w 5 -c 10 -s Z @@ -66,7 +66,7 @@ command[check_mongodb_connect]={{ nagios_plugins_directory }}/check_mongodb -H l command[check_glusterfs]={{ nagios_plugins_directory }}/check_glusterfs -v all -n 0 command[check_supervisord_status]={{ nagios_plugins_directory }}/check_supervisord command[check_varnish]={{ nagios_plugins_directory }}/check_varnish_health -i 127.0.0.1 -p 6082 -s /etc/varnish/secret -w 2 -c 4 -command[check_haproxy]=sudo {{ nagios_plugins_directory }}/check_haproxy_stats -s /var/run/haproxy.sock -w 80 -c 90 --ignore-maint --ignore-nolb +command[check_haproxy]=sudo {{ nagios_plugins_directory }}/check_haproxy_stats -s /run/haproxy/admin.sock -w 80 -c 90 --ignore-maint --ignore-nolb command[check_minifirewall]=sudo {{ nagios_plugins_directory }}/check_minifirewall command[check_redis_instances]={{ nagios_plugins_directory }}/check_redis_instances diff --git a/networkd-to-ifconfig/defaults/main.yml b/networkd-to-ifconfig/defaults/main.yml new file mode 100644 index 00000000..eb808063 --- /dev/null +++ b/networkd-to-ifconfig/defaults/main.yml @@ -0,0 +1,9 @@ +--- + +force_update_eni_file: False +eni_interface_name: Null +eni_ipv4_address: Null +eni_ipv4_gateway: Null +eni_ipv6_address: Null +eni_ipv6_gateway: Null +ipv6_gateway_mask: "xxxx.xxxx.xxxx.xxFF:FF:FF:FF:FF" diff --git a/networkd-to-ifconfig/tasks/main.yml b/networkd-to-ifconfig/tasks/main.yml index 02ef0c53..29ca3b4f 100644 --- a/networkd-to-ifconfig/tasks/main.yml +++ b/networkd-to-ifconfig/tasks/main.yml @@ -7,26 +7,38 @@ - debug: msg: A /etc/network/interfaces file already exists, nothing is done. - when: interfaces_file.stat.exists + when: interfaces_file.stat.exists and not force_update_eni_file - block: - - name: "Parse addresses" - shell: "grep Address= /etc/systemd/network/50-default.network | cut -d'=' -f2" - register: network_address_grep - check_mode: no - - - name: "Parse gateways" - shell: "grep Gateway= /etc/systemd/network/50-default.network | cut -d'=' -f2" - register: network_gateway_grep - check_mode: no + - name: "Look for systemd network config" + stat: + path: /etc/systemd/network/50-default.network + register: systemd_network_file - - name: Prepare variables + - name: Set interface name set_fact: eni_interface_name: "{{ ansible_default_ipv4.interface }}" - eni_ipv4_address: "{{ network_address_grep.stdout_lines | ipv4 | first }}" - eni_ipv4_gateway: "{{ network_gateway_grep.stdout_lines | ipv4 | first }}" - eni_ipv6_address: "{{ network_address_grep.stdout_lines | ipv6 | first }}" - eni_ipv6_gateway: "{{ network_gateway_grep.stdout_lines | ipv6 | first }}" + + - include: set_facts_from_systemd.yml + when: systemd_network_file.stat.exists + + - include: set_facts_from_ansible.yml + when: not systemd_network_file.stat.exists + + - name: Check config (IPv4) + assert: + that: + - eni_ipv4_address | ipv4 + - eni_ipv4_gateway | ipv4 + msg: "IPv4 configuration is invalid" + + - name: Check config (IPV6) + assert: + that: + - eni_ipv6_address | ipv6 + - eni_ipv6_gateway | ipv6 + msg: "IPv6 configuration is invalid" + when: eni_ipv6_address or eni_ipv6_gateway - name: "A new /etc/network/interfaces is generated" template: @@ -48,7 +60,7 @@ enabled: True state: restarted ignore_errors: True - + - debug: msg: You should verify your configuration, then reboot the server. - when: not interfaces_file.stat.exists + when: force_update_eni_file or not interfaces_file.stat.exists diff --git a/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml b/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml new file mode 100644 index 00000000..21de7357 --- /dev/null +++ b/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml @@ -0,0 +1,13 @@ +--- + +- name: Prepare variables (IPv4) + set_fact: + eni_ipv4_address: "{{ ansible_default_ipv4.address | ipv4 }}" + eni_ipv4_gateway: "{{ ansible_default_ipv4.gateway | ipv4 }}" + when: ansible_default_ipv4 + +- name: Prepare variables (IPv6) + set_fact: + eni_ipv6_address: "{{ ansible_default_ipv6.address | ipv6 | first }}" + eni_ipv6_gateway: "{{ ansible_default_ipv6.gateway | ipv6 | first }}" + when: ansible_default_ipv6 diff --git a/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml b/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml new file mode 100644 index 00000000..d21012fd --- /dev/null +++ b/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml @@ -0,0 +1,18 @@ +--- + +- name: "Parse addresses" + shell: "grep Address= /etc/systemd/network/50-default.network | cut -d'=' -f2" + register: network_address_grep + check_mode: no + +- name: "Parse gateways" + shell: "grep Gateway= /etc/systemd/network/50-default.network | cut -d'=' -f2" + register: network_gateway_grep + check_mode: no + +- name: Prepare variables + set_fact: + eni_ipv4_address: "{{ network_address_grep.stdout_lines | ipv4 | first }}" + eni_ipv4_gateway: "{{ network_gateway_grep.stdout_lines | ipv4 | first }}" + eni_ipv6_address: "{{ network_address_grep.stdout_lines | ipv6 | first }}" + eni_ipv6_gateway: "{{ network_gateway_grep.stdout_lines | ipv6 | first }}" diff --git a/networkd-to-ifconfig/templates/interfaces.j2 b/networkd-to-ifconfig/templates/interfaces.j2 index f1030430..a5edc9c5 100644 --- a/networkd-to-ifconfig/templates/interfaces.j2 +++ b/networkd-to-ifconfig/templates/interfaces.j2 @@ -10,8 +10,13 @@ iface {{ eni_interface_name }} inet static address {{ eni_ipv4_address }} gateway {{ eni_ipv4_gateway }} +{% if eni_ipv6_address and eni_ipv6_gateway %} iface {{ eni_interface_name }} inet6 static address {{ eni_ipv6_address }} - gateway {{ eni_ipv6_gateway }} - post-up /sbin/ip -6 route add {{ eni_ipv6_gateway }} dev {{ eni_interface_name }} - post-up /sbin/ip -6 route add default via IPV6 + netmask 128 + +post-up /sbin/ip -f inet6 route add {{ eni_ipv6_gateway }} dev {{ eni_interface_name }} +post-up /sbin/ip -f inet6 route add default via {{ eni_ipv6_address }} +pre-down /sbin/ip -f inet6 route del {{ eni_ipv6_gateway }} dev {{ eni_interface_name }} +pre-down /sbin/ip -f inet6 route del default via {{ eni_ipv6_gateway }} +{% endif %} diff --git a/nginx/tasks/main_minimal.yml b/nginx/tasks/main_minimal.yml index 281aed7f..58d177af 100644 --- a/nginx/tasks/main_minimal.yml +++ b/nginx/tasks/main_minimal.yml @@ -1,11 +1,10 @@ --- - name: Ensure Nginx is installed apt: - name: "{{ item }}" + name: + - nginx-light + - ssl-cert state: present - with_items: - - nginx-light - - ssl-cert notify: reload nginx tags: - nginx diff --git a/nginx/tasks/munin_vhost.yml b/nginx/tasks/munin_vhost.yml index bd6c5ee6..83754ba4 100644 --- a/nginx/tasks/munin_vhost.yml +++ b/nginx/tasks/munin_vhost.yml @@ -8,21 +8,27 @@ - name: Ensure packages for Munin CGI are installed apt: - name: '{{ item }}' + name: + - liblwp-useragent-determined-perl + - libcgi-fast-perl + - spawn-fcgi state: present - with_items: - - liblwp-useragent-determined-perl - - libcgi-fast-perl - - spawn-fcgi -- name: Adjust rights for munin-cgi +- name: Adjust owner for munin-cgi shell: "chown --verbose www-data:munin /var/log/munin/munin-cgi-*" register: command_result changed_when: "'changed' in command_result.stdout" args: warn: no -- name: Install Init script for Munin-fcgi +- name: Adjust rights for munin-cgi + shell: "chmod --verbose 660 /var/log/munin/munin-cgi-*" + register: command_result + changed_when: "'changed' in command_result.stdout" + args: + warn: no + +- name: Systemd unit for Munin-fcgi copy: src: systemd/spawn-fcgi-munin-graph.service dest: /etc/systemd/system/spawn-fcgi-munin-graph.service diff --git a/nginx/tasks/packages_backports.yml b/nginx/tasks/packages_backports.yml index dbb3a76a..3ac5088f 100644 --- a/nginx/tasks/packages_backports.yml +++ b/nginx/tasks/packages_backports.yml @@ -1,7 +1,7 @@ --- - include_role: - name: apt + name: evolix/apt tasks_from: backports.yml tags: - nginx @@ -21,7 +21,7 @@ - name: update apt apt: update_cache: yes - when: nginx_apt_preferences | changed + when: nginx_apt_preferences is changed tags: - nginx - packages diff --git a/nodejs/defaults/main.yml b/nodejs/defaults/main.yml index bae84bfa..70c5d483 100644 --- a/nodejs/defaults/main.yml +++ b/nodejs/defaults/main.yml @@ -1,3 +1,3 @@ --- -nodejs_apt_version: 'node_8.x' +nodejs_apt_version: 'node_12.x' nodejs_install_yarn: False diff --git a/nodejs/files/yarnpkg.gpg.key b/nodejs/files/yarnpkg.gpg.key index c5064ec5..4d5f77ef 100644 --- a/nodejs/files/yarnpkg.gpg.key +++ b/nodejs/files/yarnpkg.gpg.key @@ -158,29 +158,63 @@ V1L7FROM6fKydeSLJbx17SNjVdQnq1OsyqSO0catAFNptMHBsN+tiCI29gpGegao umV9cnND69aYvyPBgvdtmzPChjSmc6rzW1yXCJDm2qzwm/BcwJNXW5B3EUPxc0qS Wste9fUna0G4l/WMuaIzVkuTgXf1/r9HeQbjtxAztxH0d0VgdHAWPDkUYmztcZ4s d0PWkVa18qSrOvyhI96gCzdvMRLX17m1kPvP5PlPulvqizjDs8BScqeSzGgSbbQV -m5Tx4w2uF4/n3FBnABEBAAGJBEQEGAECAA8FAlwsRBECGwIFCQIKEgACKQkQFkaw -G4blAxDBXSAEGQECAAYFAlwsRBEACgkQI+cWZ4i2Ph6B0g//cPis3v2M6XvAbVoM -3GIMXnsVj1WAHuwA/ja7UfZJ9+kV/PiMLkAbW0fBj0/y0O3Ry12VVQGXhC+Vo4j6 -C8qwFP4OXa6EsxHXuvWMIztBaX1Kav613aXBtxp6tTrud0FFUh4sDc1RREb3tMr6 -y5cvFJgnrdWcX1gsl6ODcgWBGNc6ZX7H7j48hMR6KmNeZocW7p8W+BgDQJqXYwVN -L15qOHzVAh0dWsFLE9gwBTmDCY03x9arxSNDGCXyxt6E77LbNVIoSRlEbkvi6j33 -nEbuERICYl6CltXQCyiVKjheJcLMjbgv5+bLCv2zfeJ/WyOmOGKpHRu+lBV1Gvli -RxUblVlmjWPhYPBZXGyjII16Tqr+ilREcZFW+STccbrVct75JWLbxwlEmix+W1Hw -SRCR+KHx3Cur4ZPMOBlPsFilOOsNa7ROUB56t7zv21Ef3BeeaCd9c4kzNGN8d1ic -EqSXoWWPqgST0LZPtZyqWZVnWrHChVHfrioxhSnw8O3wY1A2GSahiCSvvjvOeEoJ -yU21ZMw6AVyHCh6v42oYadBfGgFwNo5OCMhNxNy/CcUrBSDqyLVTM5QlNsT75Ys7 -kHHnc+Jk+xx4JpiyNCz5LzcPhlwpqnJQcjJdY1hDhK75Ormj/NfCMeZ8g1aVPX4x -Eq8AMyZYhZ5/lmM+13Rdv8ZW6FK7HQ/+IAKzntxOjw0MzCXkksKdmIOZ2bLeOVI8 -aSLaUmoT5CLuoia9g7iFHlYrSY+01riRrAaPtYx0x8onfyVxL9dlW/Fv5+qc1fF5 -FxdhyIgdqgzm82TnXHu/haUxYmUvNrbsmmNl5UTTOf+YQHMccKFdYfZ2rCBtbN2n -iXG1tuz2+k83pozu4mJ1rOOLNAsQoY3yR6OODte1FyOgp7blwDhTIoQb8/UiJ7CM -BI3OPrfoXFAnhYoxeRSAN4UFu9/HIkqfaQgRPCZS1gNerWF6r6yz9AZWUZqjSJss -jBqXCtK9bGbTYBZk+pw3H9Nd0RJ2WJ9qPqmlmUr1wdqct0ChsJx1xAT86QrssicJ -/HFFmF45hlnGkHUBWLaVJt8YkLb/DqOIbVbwyCLQtJ80VQLEeupfmu5QNsTpntRY -NKf8cr00uc8vSYXYFRxa5H5oRT1eoFEEjDDvokNnHXfT+Hya44IjYpzaqvAgeDp6 -sYlOdtWIv/V3s+trxACwTkRN7zw3lLTbT8PK9szK0fYZ5KHG1/AKH+mbZ6qNc/25 -PNbAFRtttLGuEIC3HJ12IAp2JdjioeD2OnWLu4ZeCT2CKKFsleZPrSyCrn3gyZPm -fYvv5h2JbQNO6uweOrZENWX5SU43OBoplbuKJZsMP6p6NahuGnIeJLlv509JYAf/ -HN4ARyvvOpM= -=SQ7t +m5Tx4w2uF4/n3FBnABEBAAGJBFsEGAEIACYCGwIWIQRy7PRqVrStOckHu7cWRrAb +huUDEAUCXiUQEgUJA+3GAQIpwV0gBBkBAgAGBQJcLEQRAAoJECPnFmeItj4egdIP +/3D4rN79jOl7wG1aDNxiDF57FY9VgB7sAP42u1H2SffpFfz4jC5AG1tHwY9P8tDt +0ctdlVUBl4QvlaOI+gvKsBT+Dl2uhLMR17r1jCM7QWl9Smr+td2lwbcaerU67ndB +RVIeLA3NUURG97TK+suXLxSYJ63VnF9YLJejg3IFgRjXOmV+x+4+PITEeipjXmaH +Fu6fFvgYA0Cal2MFTS9eajh81QIdHVrBSxPYMAU5gwmNN8fWq8UjQxgl8sbehO+y +2zVSKEkZRG5L4uo995xG7hESAmJegpbV0AsolSo4XiXCzI24L+fmywr9s33if1sj +pjhiqR0bvpQVdRr5YkcVG5VZZo1j4WDwWVxsoyCNek6q/opURHGRVvkk3HG61XLe ++SVi28cJRJosfltR8EkQkfih8dwrq+GTzDgZT7BYpTjrDWu0TlAeere879tRH9wX +nmgnfXOJMzRjfHdYnBKkl6Flj6oEk9C2T7WcqlmVZ1qxwoVR364qMYUp8PDt8GNQ +NhkmoYgkr747znhKCclNtWTMOgFchwoer+NqGGnQXxoBcDaOTgjITcTcvwnFKwUg +6si1UzOUJTbE++WLO5Bx53PiZPsceCaYsjQs+S83D4ZcKapyUHIyXWNYQ4Su+Tq5 +o/zXwjHmfINWlT1+MRKvADMmWIWef5ZjPtd0Xb/GVuhSCRAWRrAbhuUDEOZ9EACs +2cj3d+FlGLVh+Y2MXhfUabCTERX5b9bl4oYQ0+gLH3z8y3BdhfGmh9OXqjyCTbp7 +FBmkUpCp8FIGBgEX3VVbW/lzEfbWatBj89xaUY/oV7CfXHjBqt6YVDVZEzMvJus2 +7MrLYocwx9kBFhSEM+WUFXE0TD1JctmZZFJiuV7wPj78gwRfY3ZDZBLChvroMX1j +FjKSzK+qQrfxbbjsHIMq4lJWnlXwT8uIgV8O3zLPAQlOC94442wFiyjt6w4uISeA +LjrgdvtT5vBaaf/H/YJxS8mSpzHjAgh3/WlRQY0olLJ8WdEQbzTfHzXcCt5y66Yz +gn97wnjTSti5l+/JxkwJRKZTd7OqtKn7oXvRTES92LK63AdIqdO0c4gV4TdG1DTJ +DMD41TicmJ+bsV4C5VmUKTa7KOuJYQoZDx3fOpxGt4bVyS8wSKHnDpqZbq+A5OqO +KBTsPFkVOFgeRJIjLCkg8PgnNIpR8tsSazaXvsToXFYncoFLpSxrTd3gVlBAY4Sd +dKFEZONl5k9i3fXUUeX20JxWNPOme1HHhF/JrP7i2okOmBvW5NxjW0orhAPGutPi +w41oNxwcO0TjuZKtBgTPSuU9C7fanlRx0JGw6laHqwKfM24WdgNwzl+QirkPtzxV +fJrV82uhCm9ZTEryg60+MUFe52NglHHRygwN7UlHc7kCDQRcN/VvARAAoEHIkyjF +DsfoCxA/b2qNjz+l8OI2WhAMdqxReg7JN9R61qbetj9RYIcWswPSO84c0ioRUk+x +JavEFh/6Lg00QKwJKPf0kd1Us6SfqklxGczOaWNLyiM7JthFRNMp0qVX6NjLqGoC +NO+d/+nNk6s2x4rLECj/EROmE3ZQQEo5nBXmPlhXpVem23rGfXEQvXDNqFmvqrP+ +Befn/+aDpo89QIm3sE8G0LfgcajIdSfgLH+NJTvOVAtXXVXJPK39Njr1aBzWTbWh +LS2bji7DwP7hshdh7DE2rS623vlzvkkrms8oKkiRpKATdhQ8CEx+mhTFKCj6GtNq +hwttCbf98N9GpiHD0has65YtgQQjk2pLR62rZf6czagRfKbFQzXjl2JxS/bsHVhT +khyJFqgDcHCSXe7K8uGTAE2AkakGhGyDJYqGVSl0w5IAU8dqDQMc0IpsVMbFk4nX +4GgOwixwrzrgCh0jRi+EwUHJYZHBAyzNCkr++D25R0gwNhPMjSKe8Ks6G3hH3XP/ +ZVlceW/gPfxRixUTk/q7s3xPpPhLMREEpKS1aGcmYxEkrkVBDAzNYKdKP1MYwLn4 +lh4yNFXWlTClnDyI6UODTHwt8xDddtnT9u+U+xc6OJiYcCOstl+ovS9HmM/Kt9VT +EX9cckEEL1IS+9esQMr4b5X02Y1q9Q2uEucAEQEAAYkEWwQYAQgAJgIbAhYhBHLs +9GpWtK05yQe7txZGsBuG5QMQBQJeJRAwBQkD4hTBAinBXSAEGQECAAYFAlw39W8A +CgkQT3dnk2lHW6p0eg/+K2JJu1RbTSLJPFYQhLcxX+5d2unkuNLIy3kArtZuB992 +E2Fw00okPGtuPdSyk2ygh4DeYnwmabIWChi7LDp+YnqcI4GfMxNG6RsHs+A/77rL +BST3BB1sejZppmKCQZDSC2pvYaZBpS80UvftCZ9RFdY+kTC22Btn/5ekiQOfIqhU +H9CyGWS/YlGciomVIVn1hSPN8l4EpBCDtceRaephvzjQIZT3AxOfSlpwJviYjAOk +SX4qWyIjC5Ke5kfEOldUuBN1JGAm45tKlrz/LD/+VOc2IWpbkOIAVSldUgpRyiIJ +QAZ80trNxrJI7ncaID8lAa7pBptJiL0KorRjk3c6Y7p830Nwe0J5e5+W1RzN4wlR +8+9uuRyP8Mcwz/Hz2jwMiv38Vk4tAOe4PYNZuDnpjZ28yCpF3UUgvzjarubFAcg2 +jd8SauCQFlmOfvT+1qIMSeLmWBOdlzJTUpJRcZqnkEE4WtiMSlxyWVFvUwOmKSGi +8CLoGW1Ksh9thQ9zKhvVUiVoKn4Z79HXr4pX6rnp+mweJ2dEZtlqD7HxjVTlCHn9 +fzClt/Nt0h721fJbS587AC/ZMgg5GV+GKu6Mij0sPAowUJVCIwN9uK/GHICZEAoM +SngP8xzKnhU5FD38vwBvsqbKxTtICrv2NuwnQ0WBBQ58w5mv2RCMr2W6iegSKIAJ +EBZGsBuG5QMQri4QAI5sCxkA785fla2Ud4cti2Wu/XnY7dRl7ySIUReVaNpvJLez +ZR2SrR2DgNB8n+K8/ub/4vvKJzmM35RJOGaX72CtDMWe+b8JuGx/nVWjhUZxVujg +JdpwlaK8/+cdaQQFDkFTqAREgbQArfEFteQgOfyvB02WCCGRj1HGuckde30OaCa3 +J34BxC37Awtfg6uRhYhSP32mK1U7XApRdGcDLqeybN8hnFN8rlr+1GiCu3L5P/RB +DljH10TKzyScy8SVb8gI1twN1huqqqUsz77GuBl2OXIY573GxGX83DPNUhSCKaP4 +uWI5PWUF1Vc0ugrLw2wsL4uEErdnKgT5BCvfC88zd4scm3zmLoWpMDqOrnuLFMMH +SJcVg4Z+R56o5/vJfFuhhDbBuoioaZlPkYMba2by/8d4i/CFHjaiGVpnTJRTaNfv +wYt1ycwdm//EuWjm21zESkkbcTNn0fVYpxYtIUbCojGzbX6eHxtXPGqkU45+Gwkq +lW5cnwIHU8XNKwb2jR0zyFCNMD/Vb1371RpT0KS/0PyJS66J0P386ANQWEsllgdU +CkESjj5AT1KordMHBt78XR5ju9T0AcLfJvaDjoA0sSz7Bi2gwd0lqRukY2bwgbpd +1K/aPN5Njt4wSdGVBhZI9l/68oyBmedl1jvKQfr7mVrSNFeOh8scZrBldYN1 +=nmWU -----END PGP PUBLIC KEY BLOCK----- diff --git a/opendkim/tasks/main.yml b/opendkim/tasks/main.yml index 1769870a..9b309825 100644 --- a/opendkim/tasks/main.yml +++ b/opendkim/tasks/main.yml @@ -1,13 +1,12 @@ --- - name: install OpenDKIM apt: - name: "{{ item }}" + name: + - opendkim + - opendkim-tools + - ssl-cert + - dns-root-data state: present - with_items: - - opendkim - - opendkim-tools - - ssl-cert - - dns-root-data tags: - opendkim @@ -59,7 +58,7 @@ - name: Set folder permissions to 0750 file: - path: "/etc/opendkim/" + path: "/etc/opendkim/" mode: "0750" force: yes tags: @@ -74,7 +73,7 @@ - opendkim - include_role: - name: remount-usr + name: evolix/remount-usr - name: deploy opendkim-add.sh script copy: diff --git a/packweb-apache/defaults/main.yml b/packweb-apache/defaults/main.yml index fa7a701a..0c6b41f7 100644 --- a/packweb-apache/defaults/main.yml +++ b/packweb-apache/defaults/main.yml @@ -11,3 +11,5 @@ packweb_phpmyadmin_suffix: "" packweb_phpmyadmin_suffix_file: "/etc/evolinux/phpmyadmin_suffix" packweb_mysql_variant: "debian" + +packweb_multiphp_versions: [] diff --git a/packweb-apache/files/multiphp-sudoers b/packweb-apache/files/multiphp-sudoers new file mode 100644 index 00000000..8bd0f446 --- /dev/null +++ b/packweb-apache/files/multiphp-sudoers @@ -0,0 +1,3 @@ + +Defaults env_keep += "LOGNAME PWD" +ALL ALL = NOPASSWD: /usr/local/bin/phpContainer diff --git a/packweb-apache/files/opcache.php b/packweb-apache/files/opcache.php new file mode 100644 index 00000000..22b4b2f4 --- /dev/null +++ b/packweb-apache/files/opcache.php @@ -0,0 +1,744 @@ +You do not have the Zend OPcache extension loaded, sample data is being shown instead.'; + require 'data-sample.php'; +} + +class OpCacheDataModel +{ + private $_configuration; + private $_status; + private $_d3Scripts = array(); + + public function __construct() + { + $this->_configuration = opcache_get_configuration(); + $this->_status = opcache_get_status(); + } + + public function getPageTitle() + { + return 'PHP ' . phpversion() . " with OpCache {$this->_configuration['version']['version']}"; + } + + public function getStatusDataRows() + { + $rows = array(); + foreach ($this->_status as $key => $value) { + if ($key === 'scripts') { + continue; + } + + if (is_array($value)) { + foreach ($value as $k => $v) { + if ($v === false) { + $value = 'false'; + } + if ($v === true) { + $value = 'true'; + } + if ($k === 'used_memory' || $k === 'free_memory' || $k === 'wasted_memory') { + $v = $this->_size_for_humans( + $v + ); + } + if ($k === 'current_wasted_percentage' || $k === 'opcache_hit_rate') { + $v = number_format( + $v, + 2 + ) . '%'; + } + if ($k === 'blacklist_miss_ratio') { + $v = number_format($v, 2) . '%'; + } + if ($k === 'start_time' || $k === 'last_restart_time') { + $v = ($v ? date(DATE_RFC822, $v) : 'never'); + } + if (THOUSAND_SEPARATOR === true && is_int($v)) { + $v = number_format($v); + } + + $rows[] = "$k$v\n"; + } + continue; + } + if ($value === false) { + $value = 'false'; + } + if ($value === true) { + $value = 'true'; + } + $rows[] = "$key$value\n"; + } + + return implode("\n", $rows); + } + + public function getConfigDataRows() + { + $rows = array(); + foreach ($this->_configuration['directives'] as $key => $value) { + if ($value === false) { + $value = 'false'; + } + if ($value === true) { + $value = 'true'; + } + if ($key == 'opcache.memory_consumption') { + $value = $this->_size_for_humans($value); + } + $rows[] = "$key$value\n"; + } + + return implode("\n", $rows); + } + + public function getScriptStatusRows() + { + foreach ($this->_status['scripts'] as $key => $data) { + $dirs[dirname($key)][basename($key)] = $data; + $this->_arrayPset($this->_d3Scripts, $key, array( + 'name' => basename($key), + 'size' => $data['memory_consumption'], + )); + } + + asort($dirs); + + $basename = ''; + while (true) { + if (count($this->_d3Scripts) !=1) break; + $basename .= DIRECTORY_SEPARATOR . key($this->_d3Scripts); + $this->_d3Scripts = reset($this->_d3Scripts); + } + + $this->_d3Scripts = $this->_processPartition($this->_d3Scripts, $basename); + $id = 1; + + $rows = array(); + foreach ($dirs as $dir => $files) { + $count = count($files); + $file_plural = $count > 1 ? 's' : null; + $m = 0; + foreach ($files as $file => $data) { + $m += $data["memory_consumption"]; + } + $m = $this->_size_for_humans($m); + + if ($count > 1) { + $rows[] = ''; + $rows[] = "{$dir} ({$count} file{$file_plural}, {$m})"; + $rows[] = ''; + } + + foreach ($files as $file => $data) { + $rows[] = ""; + $rows[] = "" . $this->_format_value($data["hits"]) . ""; + $rows[] = "" . $this->_size_for_humans($data["memory_consumption"]) . ""; + $rows[] = $count > 1 ? "{$file}" : "{$dir}/{$file}"; + $rows[] = ''; + } + + ++$id; + } + + return implode("\n", $rows); + } + + public function getScriptStatusCount() + { + return count($this->_status["scripts"]); + } + + public function getGraphDataSetJson() + { + $dataset = array(); + $dataset['memory'] = array( + $this->_status['memory_usage']['used_memory'], + $this->_status['memory_usage']['free_memory'], + $this->_status['memory_usage']['wasted_memory'], + ); + + $dataset['keys'] = array( + $this->_status['opcache_statistics']['num_cached_keys'], + $this->_status['opcache_statistics']['max_cached_keys'] - $this->_status['opcache_statistics']['num_cached_keys'], + 0 + ); + + $dataset['hits'] = array( + $this->_status['opcache_statistics']['misses'], + $this->_status['opcache_statistics']['hits'], + 0, + ); + + $dataset['restarts'] = array( + $this->_status['opcache_statistics']['oom_restarts'], + $this->_status['opcache_statistics']['manual_restarts'], + $this->_status['opcache_statistics']['hash_restarts'], + ); + + if (THOUSAND_SEPARATOR === true) { + $dataset['TSEP'] = 1; + } else { + $dataset['TSEP'] = 0; + } + + return json_encode($dataset); + } + + public function getHumanUsedMemory() + { + return $this->_size_for_humans($this->getUsedMemory()); + } + + public function getHumanFreeMemory() + { + return $this->_size_for_humans($this->getFreeMemory()); + } + + public function getHumanWastedMemory() + { + return $this->_size_for_humans($this->getWastedMemory()); + } + + public function getUsedMemory() + { + return $this->_status['memory_usage']['used_memory']; + } + + public function getFreeMemory() + { + return $this->_status['memory_usage']['free_memory']; + } + + public function getWastedMemory() + { + return $this->_status['memory_usage']['wasted_memory']; + } + + public function getWastedMemoryPercentage() + { + return number_format($this->_status['memory_usage']['current_wasted_percentage'], 2); + } + + public function getD3Scripts() + { + return $this->_d3Scripts; + } + + private function _processPartition($value, $name = null) + { + if (array_key_exists('size', $value)) { + return $value; + } + + $array = array('name' => $name,'children' => array()); + + foreach ($value as $k => $v) { + $array['children'][] = $this->_processPartition($v, $k); + } + + return $array; + } + + private function _format_value($value) + { + if (THOUSAND_SEPARATOR === true) { + return number_format($value); + } else { + return $value; + } + } + + private function _size_for_humans($bytes) + { + if ($bytes > 1048576) { + return sprintf('%.2f MB', $bytes / 1048576); + } else { + if ($bytes > 1024) { + return sprintf('%.2f kB', $bytes / 1024); + } else { + return sprintf('%d bytes', $bytes); + } + } + } + + // Borrowed from Laravel + private function _arrayPset(&$array, $key, $value) + { + if (is_null($key)) return $array = $value; + $keys = explode(DIRECTORY_SEPARATOR, ltrim($key, DIRECTORY_SEPARATOR)); + while (count($keys) > 1) { + $key = array_shift($keys); + if ( ! isset($array[$key]) || ! is_array($array[$key])) { + $array[$key] = array(); + } + $array =& $array[$key]; + } + $array[array_shift($keys)] = $value; + return $array; + } + +} + +$dataModel = new OpCacheDataModel(); +?> + + + + + + + + + <?php echo $dataModel->getPageTitle(); ?> + + + +
+

getPageTitle(); ?>

+ +
+ +
+ + +
+ + getStatusDataRows(); ?> +
+
+
+ +
+ + +
+ + getConfigDataRows(); ?> +
+
+
+ +
+ + +
+ + + + + + + getScriptStatusRows(); ?> +
HitsMemoryPath
+
+
+ +
+ + +
+
+ +
+ +
+
+ + + + +
+ +
+
+
+ +
✖ Close Visualisation
+
+ + + + diff --git a/packweb-apache/files/phpContainer b/packweb-apache/files/phpContainer new file mode 100644 index 00000000..0f634d6a --- /dev/null +++ b/packweb-apache/files/phpContainer @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# If this script isn't run as root, then, re-run it with sudo. +if [ "$EUID" -ne 0 ]; then + sudo $(readlink -f ${BASH_SOURCE[0]}) "$@" + exit 0; +fi; + +PHPVersion=$(grep SetHandler /etc/apache2/sites-enabled/$LOGNAME.conf 2>/dev/null | grep -m 1 -o 'fpm[0-9][0-9]' | head -n 1 | sed 's/php//g' | sed 's/fpm//g') + +if [ "$PHPVersion" != "" ]; then + lxc-attach -n php$PHPVersion -- su - $LOGNAME -c "cd \"${PWD@E}\" && php ${*@Q}" +else + # TODO: fallback? + # command php $* + echo "ERROR - Could not determine \$PHPVersion - Are you a web account ?" + exit 1 +fi diff --git a/packweb-apache/meta/main.yml b/packweb-apache/meta/main.yml index 0afeb1e0..c8981b6e 100644 --- a/packweb-apache/meta/main.yml +++ b/packweb-apache/meta/main.yml @@ -13,12 +13,19 @@ galaxy_info: versions: - jessie - stretch + - buster + +allow_duplicates: true dependencies: - - { role: apache } - - { role: php, php_apache_enable: True, when: packweb_apache_modphp } - - { role: php, php_fpm_enable: True, when: packweb_apache_fpm } - - { role: squid, squid_localproxy_enable: True } - - { role: mysql, when: packweb_mysql_variant == "debian" } - - { role: mysql-oracle, when: packweb_mysql_variant == "oracle" } - - { role: webapps/evoadmin-web, evoadmin_enable_vhost: "{{ packweb_enable_evoadmin_vhost }}" } + - { role: evolix/apache } + - { role: evolix/php, php_apache_enable: True, when: packweb_apache_modphp } + - { role: evolix/php, php_fpm_enable: True, when: packweb_apache_fpm } + - { role: evolix/lxc-php, lxc_php_version: php56, when: "'php56' in packweb_multiphp_versions" } + - { role: evolix/lxc-php, lxc_php_version: php70, when: "'php70' in packweb_multiphp_versions" } + - { role: evolix/lxc-php, lxc_php_version: php73, when: "'php73' in packweb_multiphp_versions" } + - { role: evolix/squid, squid_localproxy_enable: True } + - { role: evolix/mysql, when: packweb_mysql_variant == "debian" } + - { role: evolix/mysql-oracle, when: packweb_mysql_variant == "oracle" } + - { role: evolix/webapps/evoadmin-web, evoadmin_enable_vhost: "{{ packweb_enable_evoadmin_vhost }}", evoadmin_multiphp_versions: "{{ packweb_multiphp_versions }}" } + - { role: evolix/evoacme } diff --git a/packweb-apache/tasks/apache.yml b/packweb-apache/tasks/apache.yml index 31570944..61d37341 100644 --- a/packweb-apache/tasks/apache.yml +++ b/packweb-apache/tasks/apache.yml @@ -18,22 +18,21 @@ - name: Additional packages are installed apt: - name: '{{ item }}' + name: + - libapache2-mod-security2 + - modsecurity-crs + - apg state: present - with_items: - - libapache2-mod-security2 - - modsecurity-crs - - apg - name: Additional modules are enabled apache2_module: name: '{{ item }}' state: present with_items: - - ssl - - include - - negotiation - - alias + - ssl + - include + - negotiation + - alias - name: Copy Apache settings for modules copy: @@ -58,6 +57,5 @@ register: command_result changed_when: "'Enabling' in command_result.stderr" with_items: - - evolinux-evasive - - evolinux-modsec - + - evolinux-evasive + - evolinux-modsec diff --git a/packweb-apache/tasks/fhs_retrictions.yml b/packweb-apache/tasks/fhs_retrictions.yml index 2308db2a..1d370038 100644 --- a/packweb-apache/tasks/fhs_retrictions.yml +++ b/packweb-apache/tasks/fhs_retrictions.yml @@ -6,23 +6,23 @@ changed_when: "'changed' in command_result.stdout" failed_when: False with_items: - - / - - /etc - - /usr - - /usr/bin - - /var - - /var/log - - /home - - /bin - - /sbin - - /lib - - /usr/lib - - /usr/include - - /usr/bin - - /usr/sbin - - /usr/share - - /usr/share/doc - - /etc/default + - / + - /etc + - /usr + - /usr/bin + - /var + - /var/log + - /home + - /bin + - /sbin + - /lib + - /usr/lib + - /usr/include + - /usr/bin + - /usr/sbin + - /usr/share + - /usr/share/doc + - /etc/default - name: Set 750 permission on some folders (/var/log/apt, /var/log/munin, ...) shell: "test -d {{ item }} && chmod --verbose 750 {{ item }}" @@ -30,20 +30,21 @@ changed_when: "'changed' in command_result.stdout" failed_when: False with_items: - - /var/log/apt - - /var/lib/dpkg - - /var/log/munin - - /var/backups - - /etc/init.d - - /etc/apache2 - - /etc/network - - /etc/phpmyadmin - - /var/log/installer + - /var/log/apt + - /var/lib/dpkg + - /var/log/munin + - /var/backups + - /etc/init.d + - /etc/apache2 + - /etc/network + - /etc/phpmyadmin + - /var/log/installer - name: Change group to www-data for /etc/phpmyadmin/ file: dest: /etc/phpmyadmin/ group: www-data + state: directory - name: Set u-s permission on some binaries (/bin/ping, /usr/bin/mtr, ...) shell: "test -f {{ item }} && chmod --verbose u-s {{ item }}" @@ -51,11 +52,11 @@ changed_when: "'changed' in command_result.stdout" failed_when: False with_items: - - /bin/ping - - /bin/ping6 - - /usr/bin/fping - - /usr/bin/fping6 - - /usr/bin/mtr + - /bin/ping + - /bin/ping6 + - /usr/bin/fping + - /usr/bin/fping6 + - /usr/bin/mtr - name: Set 640 permission on some files (/var/log/evolix.log, ...) shell: "test -f {{ item }} && chmod --verbose 640 {{ item }}" @@ -63,6 +64,5 @@ changed_when: "'changed' in command_result.stdout" failed_when: False with_items: - - /var/log/evolix.log - - /etc/warnquota.conf - + - /var/log/evolix.log + - /etc/warnquota.conf diff --git a/packweb-apache/tasks/main.yml b/packweb-apache/tasks/main.yml index f5d0f35e..9c36c888 100644 --- a/packweb-apache/tasks/main.yml +++ b/packweb-apache/tasks/main.yml @@ -3,7 +3,7 @@ - fail: msg: only compatible with Debian >= 8 when: - - ansible_distribution != "Debian" or ansible_distribution_major_version | version_compare('8', '<') + - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') - name: install info.php copy: @@ -17,6 +17,18 @@ line: '
  • Infos PHP
  • ' regexp: "Infos PHP" +- name: install opcache.php + copy: + src: opcache.php + dest: /var/www/opcache.php + mode: "0644" + +- name: enable opcache.php link in default site index + lineinfile: + dest: /var/www/index.html + line: '
  • Infos OpCache PHP
  • ' + regexp: "Infos OpCache PHP" + - name: Add elements to user account template file: path: "/etc/skel/{{ item.path }}" @@ -55,7 +67,7 @@ src: userlogrotate dest: /etc/cron.weekly/userlogrotate mode: "0755" - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: Force DIR_MODE to 0750 in /etc/adduser.conf lineinfile: @@ -66,6 +78,7 @@ - include: apache.yml - include: phpmyadmin.yml + when: ansible_distribution_release != "buster" - include: awstats.yml @@ -77,3 +90,6 @@ name: "ProFTPd directory size caching" special_time: daily job: "/usr/share/scripts/evoadmin/stats.sh" + +- include: multiphp.yml + when: packweb_multiphp_versions | length > 0 diff --git a/packweb-apache/tasks/multiphp.yml b/packweb-apache/tasks/multiphp.yml new file mode 100644 index 00000000..01f0b130 --- /dev/null +++ b/packweb-apache/tasks/multiphp.yml @@ -0,0 +1,34 @@ +--- + +- name: Enable proxy_fcgi + apache2_module: + state: present + name: proxy_fcgi + notify: restart apache2 + +- include_role: + name: remount-usr + +- name: Copy phpContainer script + copy: + src: phpContainer + dest: /usr/local/bin/phpContainer + mode: 0755 + +# - name: Copy php shim to call phpContainer when the user is a web user +# copy: +# src: multiphp-shim +# dest: /usr/local/bin/php +# mode: 0755 + +# - name: Modify bashrc skel file +# lineinfile: +# dest: /etc/skel/.bashrc +# line: "alias php='sudo /usr/local/bin/phpContainer'" + +- name: Add multiphp sudoers file + copy: + src: multiphp-sudoers + dest: /etc/sudoers.d/multiphp + mode: "0600" + validate: "visudo -cf %s" diff --git a/packweb-apache/tasks/phpmyadmin.yml b/packweb-apache/tasks/phpmyadmin.yml index eac50843..fc3e6d32 100644 --- a/packweb-apache/tasks/phpmyadmin.yml +++ b/packweb-apache/tasks/phpmyadmin.yml @@ -1,12 +1,23 @@ --- -- name: Install phpmyadmin +- name: Install apg apt: - name: '{{ item }}' - state: present - with_items: - - phpmyadmin - - apg + name: apg + +- name: Install phpmyadmin (Debian <=9) + apt: + name: phpmyadmin + when: ansible_distribution_major_version is version('9', '<=') + +- include_role: + name: evolix/remount-usr + +# /!\ Warning: this is a temporary hack as phpmyadmin for Buster is not yet +# available +- name: Install phpmyadmin using sid package (Debian >=10) + apt: + deb: http://mirror.evolix.org/debian/pool/main/p/phpmyadmin/phpmyadmin_4.6.6-4_all.deb + when: ansible_distribution_major_version is version('10', '>=') - name: Check if phpmyadmin default configuration is present stat: diff --git a/percona/tasks/main.yml b/percona/tasks/main.yml index cce78c3a..f0591b0e 100644 --- a/percona/tasks/main.yml +++ b/percona/tasks/main.yml @@ -20,7 +20,7 @@ when: not percona__apt_config_package_installed # - include_role: -# name: remount-usr +# name: evolix/remount-usr - name: Percona APT config package is installed from deb file apt: @@ -39,7 +39,7 @@ - name: APT cache is up-to-date apt: update_cache: yes - when: percona__apt_config_deb | changed + when: percona__apt_config_deb is changed - include: xtrabackup.yml when: percona__install_xtrabackup diff --git a/percona/tasks/xtrabackup.yml b/percona/tasks/xtrabackup.yml index 8fe6a7c5..2cf221bb 100644 --- a/percona/tasks/xtrabackup.yml +++ b/percona/tasks/xtrabackup.yml @@ -8,7 +8,7 @@ - name: APT cache is up-to-date apt: update_cache: yes - # when: percona__release_enable_tools | changed + # when: percona__release_enable_tools is changed - name: Percona XtraBackup package is installed apt: diff --git a/php/defaults/main.yml b/php/defaults/main.yml index d12014b2..19040baf 100644 --- a/php/defaults/main.yml +++ b/php/defaults/main.yml @@ -4,3 +4,7 @@ php_sury_enable: False php_fpm_enable: False php_apache_enable: False php_symfony_requirements: False + +php_modules_mysqlnd: False + +php_fpm_remove_default_pool: False diff --git a/php/handlers/main.yml b/php/handlers/main.yml index 63a3e0a6..1aade6c1 100644 --- a/php/handlers/main.yml +++ b/php/handlers/main.yml @@ -9,3 +9,8 @@ service: name: php7.0-fpm state: restarted + +- name: restart php7.3-fpm + service: + name: php7.3-fpm + state: restarted diff --git a/php/tasks/config_apache.yml b/php/tasks/config_apache.yml index a199c651..6b4a3200 100644 --- a/php/tasks/config_apache.yml +++ b/php/tasks/config_apache.yml @@ -15,6 +15,8 @@ - { option: "log_errors", value: "On" } - { option: "html_errors", value: "Off" } - { option: "allow_url_fopen", value: "Off" } + - { option: "opcache.memory_consumption", value: "128M" } + - { option: "opcache.max_accelerated_files", value: "8000" } - name: Disable PHP functions ini_file: diff --git a/php/tasks/config_cli.yml b/php/tasks/config_cli.yml index 75c81080..23ed695c 100644 --- a/php/tasks/config_cli.yml +++ b/php/tasks/config_cli.yml @@ -1,5 +1,5 @@ --- -- name: "Set default php.ini values for CLI (jessie)" +- name: "Set default php.ini values for CLI" ini_file: dest: "{{ php_cli_defaults_ini_file }}" section: PHP @@ -8,21 +8,11 @@ mode: "0644" create: yes with_items: - - { option: "short_open_tag", value: "Off" } - - { option: "expose_php", value: "Off" } - - { option: "display_errors", value: "Off" } - - { option: "log_errors", value: "On" } - - { option: "html_errors", value: "Off" } - - { option: "allow_url_fopen", value: "Off" } + - { option: "display_errors", value: "On" } + - { option: "allow_url_fopen", value: "On" } + - { option: "disable_functions", value: "" } -- name: "Disable PHP functions for CLI (jessie)" - ini_file: - dest: "{{ php_cli_defaults_ini_file }}" - section: PHP - option: disable_functions - value: "exec,shell-exec,system,passthru,putenv,popen" - -- name: Custom php.ini for CLI (jessie) +- name: Custom php.ini for CLI copy: dest: "{{ php_cli_custom_ini_file }}" content: | @@ -31,12 +21,12 @@ # This task is not merged with the above copy # because "force: no" prevents any fix after the fact -- name: "Permissions for custom php.ini for CLI (jessie)" +- name: "Permissions for custom php.ini for CLI" file: dest: "{{ php_cli_custom_ini_file }}" mode: "0644" -- name: "Set custom values for PHP to enable Symfony (jessie)" +- name: "Set custom values for PHP to enable Symfony" ini_file: dest: "{{ php_cli_custom_ini_file }}" section: PHP diff --git a/php/tasks/config_fpm.yml b/php/tasks/config_fpm.yml index b92d24f5..1f6ae6ed 100644 --- a/php/tasks/config_fpm.yml +++ b/php/tasks/config_fpm.yml @@ -15,6 +15,8 @@ - { option: "log_errors", value: "On" } - { option: "html_errors", value: "Off" } - { option: "allow_url_fopen", value: "Off" } + - { option: "opcache.memory_consumption", value: "128M" } + - { option: "opcache.max_accelerated_files", value: "8000" } notify: "restart {{ php_fpm_service_name }}" - name: Disable PHP functions for FPM @@ -35,13 +37,18 @@ - name: Set default PHP FPM values ini_file: - dest: "{{ php_fpm_defaults_conf_file }}" + dest: "{{ php_fpm_default_pool_file }}" section: www option: "{{ item.option }}" value: "{{ item.value }}" mode: "0644" create: yes with_items: + - { option: "user", value: "www-data" } + - { option: "group", value: "www-data" } + - { option: "listen", value: "{{ php_fpm_default_pool_socket }}" } + - { option: "listen.owner", value: "www-data" } + - { option: "listen.group", value: "www-data" } - { option: "pm", value: "ondemand" } - { option: "pm.max_children", value: "100" } - { option: "pm.process_idle_timeout", value: "10s" } @@ -49,13 +56,12 @@ - { option: "request_slowlog_timeout", value: "5s" } - { option: "pm.status_path", value: "/fpm_status" } - { option: "request_terminate_timeout", value: "60s" } - - { option: "chroot", value: "/var/www" } notify: "restart {{ php_fpm_service_name }}" - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: Custom PHP FPM values copy: - dest: "{{ php_fpm_custom_conf_file }}" + dest: "{{ php_fpm_default_pool_custom_file }}" content: | ; Put customized values here. ; default_charset = "ISO-8859-1" @@ -74,3 +80,10 @@ - { option: "date.timezone", value: "Europe/Paris" } notify: "restart {{ php_fpm_service_name }}" when: php_symfony_requirements + +- name: Delete debian default pool + file: + path: "{{ php_fpm_debian_default_pool_file }}" + state: absent + notify: "restart {{ php_fpm_service_name }}" + when: php_fpm_remove_default_pool diff --git a/php/tasks/main.yml b/php/tasks/main.yml index 38ece76b..2fd1a250 100644 --- a/php/tasks/main.yml +++ b/php/tasks/main.yml @@ -3,10 +3,13 @@ - fail: msg: only compatible with Debian >= 8 when: - - ansible_distribution != "Debian" or ansible_distribution_major_version | version_compare('8', '<') + - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') - include: main_jessie.yml when: ansible_distribution_release == "jessie" - include: main_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_release == "stretch" + +- include: main_buster.yml + when: ansible_distribution_release == "buster" diff --git a/php/tasks/main_buster.yml b/php/tasks/main_buster.yml new file mode 100644 index 00000000..240f9b54 --- /dev/null +++ b/php/tasks/main_buster.yml @@ -0,0 +1,96 @@ +--- + +- name: "Set variables (Debian 10 or later)" + set_fact: + php_cli_defaults_ini_file: /etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini + php_cli_custom_ini_file: /etc/php/7.3/cli/conf.d/zzz-evolinux-custom.ini + php_apache_defaults_ini_file: /etc/php/7.3/apache2/conf.d/z-evolinux-defaults.ini + php_apache_custom_ini_file: /etc/php/7.3/apache2/conf.d/zzz-evolinux-custom.ini + php_fpm_defaults_ini_file: /etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini + php_fpm_custom_ini_file: /etc/php/7.3/fpm/conf.d/zzz-evolinux-custom.ini + php_fpm_debian_default_pool_file: /etc/php/7.3/fpm/pool.d/www.conf + php_fpm_default_pool_file: /etc/php/7.3/fpm/pool.d/www-evolinux-defaults.conf + php_fpm_default_pool_custom_file: /etc/php/7.3/fpm/pool.d/www-evolinux-zcustom.conf + php_fpm_default_pool_socket: /var/run/php/php7.3-fpm.sock + php_fpm_service_name: php7.3-fpm + +# Packages + +- name: "Set package list (Debian 9 or later)" + set_fact: + php_stretch_packages: + - php-cli + - php-gd + - php-intl + - php-imap + - php-ldap + - php-mysql + # php-mcrypt is no longer packaged for PHP 7.2 + - php-pgsql + - php-gettext + - php-curl + - php-ssh2 + - php-zip + - composer + - libphp-phpmailer + +- include: sury_pre.yml + when: php_sury_enable + +- name: "Install PHP packages (Debian 9 or later)" + apt: + name: '{{ php_stretch_packages }}' + state: present + +- name: "Install mod_php packages (Debian 9 or later)" + apt: + name: + - libapache2-mod-php + - php + state: present + when: php_apache_enable + +- name: "Install PHP FPM packages (Debian 9 or later)" + apt: + name: + - php-fpm + - php + state: present + when: php_fpm_enable + +# Configuration + +- name: Enforce permissions on PHP directory + file: + dest: "{{ item }}" + mode: "0755" + with_items: + - /etc/php + - /etc/php/7.3 + +- include: config_cli.yml +- name: Enforce permissions on PHP cli directory + file: + dest: /etc/php/7.3/cli + mode: "0755" + +- include: config_fpm.yml + when: php_fpm_enable + +- name: Enforce permissions on PHP fpm directory + file: + dest: /etc/php/7.3/fpm + mode: "0755" + when: php_fpm_enable + +- include: config_apache.yml + when: php_apache_enable + +- name: Enforce permissions on PHP apache2 directory + file: + dest: /etc/php/7.3/apache2 + mode: "0755" + when: php_apache_enable + +- include: sury_post.yml + when: php_sury_enable diff --git a/php/tasks/main_jessie.yml b/php/tasks/main_jessie.yml index e8d8d85e..fdeed8c8 100644 --- a/php/tasks/main_jessie.yml +++ b/php/tasks/main_jessie.yml @@ -8,46 +8,45 @@ php_apache_custom_ini_file: /etc/php5/apache2/conf.d/zzz-evolinux-custom.ini php_fpm_defaults_ini_file: /etc/php5/fpm/conf.d/z-evolinux-defaults.ini php_fpm_custom_ini_file: /etc/php5/fpm/conf.d/zzz-evolinux-custom.ini - php_fpm_defaults_conf_file: /etc/php5/fpm/pool.d/z-evolinux-defaults.conf - php_fpm_custom_conf_file: /etc/php5/fpm/pool.d/zzz-evolinux-custom.conf + php_fpm_debian_default_pool_file: /etc/php5/fpm/pool.d/www.conf + php_fpm_default_pool_file: /etc/php5/fpm/pool.d/www-evolinux-defaults.conf + php_fpm_default_pool_custom_file: /etc/php5/fpm/pool.d/www-evolinux-zcustom.conf + php_fpm_default_pool_socket: /var/run/php/php5-fpm.sock php_fpm_service_name: php5-fpm # Packages - name: "Install PHP packages (jessie)" apt: - name: '{{ item }}' + name: + - php5-cli + - php5-gd + - php5-imap + - php5-ldap + - php5-mcrypt + - "{{ php_modules_mysqlnd | bool | ternary('php5-mysqlnd','php5-mysql') }}" + - php5-pgsql + - php-gettext + - php5-intl + - php5-curl + - php5-ssh2 + - libphp-phpmailer state: present - with_items: - - php5-cli - - php5-gd - - php5-imap - - php5-ldap - - php5-mcrypt - - php5-mysql - - php5-pgsql - - php-gettext - - php5-intl - - php5-curl - - php5-ssh2 - - libphp-phpmailer - name: "Install mod_php packages (jessie)" apt: - name: '{{ item }}' + name: + - libapache2-mod-php5 + - php5 state: present - with_items: - - libapache2-mod-php5 - - php5 when: php_apache_enable - name: "Install PHP FPM packages (jessie)" apt: - name: '{{ item }}' + name: + - php5-fpm + - php5 state: present - with_items: - - php5-fpm - - php5 when: php_fpm_enable # Configuration @@ -58,6 +57,7 @@ mode: "0755" - include: config_cli.yml + - name: Enforce permissions on PHP cli directory file: dest: /etc/php5/cli @@ -65,6 +65,7 @@ - include: config_fpm.yml when: php_fpm_enable + - name: Enforce permissions on PHP fpm directory file: dest: /etc/php5/fpm @@ -73,6 +74,7 @@ - include: config_apache.yml when: php_apache_enable + - name: Enforce permissions on PHP apache2 directory file: dest: /etc/php5/apache2 diff --git a/php/tasks/main_stretch.yml b/php/tasks/main_stretch.yml index 4191519b..75f69ab0 100644 --- a/php/tasks/main_stretch.yml +++ b/php/tasks/main_stretch.yml @@ -8,8 +8,10 @@ php_apache_custom_ini_file: /etc/php/7.0/apache2/conf.d/zzz-evolinux-custom.ini php_fpm_defaults_ini_file: /etc/php/7.0/fpm/conf.d/z-evolinux-defaults.ini php_fpm_custom_ini_file: /etc/php/7.0/fpm/conf.d/zzz-evolinux-custom.ini - php_fpm_defaults_conf_file: /etc/php/7.0/fpm/pool.d/z-evolinux-defaults.conf - php_fpm_custom_conf_file: /etc/php/7.0/fpm/pool.d/zzz-evolinux-custom.conf + php_fpm_debian_default_pool_file: /etc/php/7.0/fpm/pool.d/www.conf + php_fpm_default_pool_file: /etc/php/7.0/fpm/pool.d/www-evolinux-defaults.conf + php_fpm_default_pool_custom_file: /etc/php/7.0/fpm/pool.d/www-evolinux-zcustom.conf + php_fpm_default_pool_socket: /var/run/php/php7.0-fpm.sock php_fpm_service_name: php7.0-fpm # Packages @@ -22,9 +24,9 @@ - php-intl - php-imap - php-ldap - - php-mysql - # php-mcrypt is no longer packaged for PHP 7.2 + - "{{ php_modules_mysqlnd | bool | ternary('php-mysqlnd','php-mysql') }}" - php-pgsql + # php-mcrypt is no longer packaged for PHP 7.2 - php-gettext - php-curl - php-ssh2 @@ -37,26 +39,23 @@ - name: "Install PHP packages (Debian 9 or later)" apt: - name: '{{ item }}' + name: '{{ php_stretch_packages }}' state: present - with_items: "{{ php_stretch_packages }}" - name: "Install mod_php packages (Debian 9 or later)" apt: - name: '{{ item }}' + name: + - libapache2-mod-php + - php state: present - with_items: - - libapache2-mod-php - - php when: php_apache_enable - name: "Install PHP FPM packages (Debian 9 or later)" apt: - name: '{{ item }}' + name: + - php-fpm + - php state: present - with_items: - - php-fpm - - php when: php_fpm_enable # Configuration @@ -70,6 +69,7 @@ - /etc/php/7.0 - include: config_cli.yml + - name: Enforce permissions on PHP cli directory file: dest: /etc/php/7.0/cli @@ -77,6 +77,7 @@ - include: config_fpm.yml when: php_fpm_enable + - name: Enforce permissions on PHP fpm directory file: dest: /etc/php/7.0/fpm @@ -85,6 +86,7 @@ - include: config_apache.yml when: php_apache_enable + - name: Enforce permissions on PHP apache2 directory file: dest: /etc/php/7.0/apache2 diff --git a/php/tasks/sury_post.yml b/php/tasks/sury_post.yml index e26fbf8d..ecfb13dc 100644 --- a/php/tasks/sury_post.yml +++ b/php/tasks/sury_post.yml @@ -1,52 +1,52 @@ --- -- name: Symlink Evolix CLI config files from 7.3 to 7.0 +- name: Symlink Evolix CLI config files from 7.4 to 7.0 file: src: "{{ item.src }}" dest: "{{ item.dest }}" force: yes state: link with_items: - - { src: "{{ php_cli_defaults_ini_file }}", dest: "/etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini" } - - { src: "{{ php_cli_custom_ini_file }}", dest: "/etc/php/7.3/cli/conf.d/zzz-evolinux-custom.ini" } + - { src: "{{ php_cli_defaults_ini_file }}", dest: "/etc/php/7.4/cli/conf.d/z-evolinux-defaults.ini" } + - { src: "{{ php_cli_custom_ini_file }}", dest: "/etc/php/7.4/cli/conf.d/zzz-evolinux-custom.ini" } -- name: Enforce permissions on PHP 7.3/cli directory +- name: Enforce permissions on PHP 7.4/cli directory file: - dest: /etc/php/7.3/cli + dest: /etc/php/7.4/cli mode: "0755" -- name: Symlink Evolix Apache config files from 7.3 to 7.0 +- name: Symlink Evolix Apache config files from 7.4 to 7.0 file: src: "{{ item.src }}" dest: "{{ item.dest }}" force: yes state: link with_items: - - { src: "{{ php_apache_defaults_ini_file }}", dest: "/etc/php/7.3/apache2/conf.d/z-evolinux-defaults.ini" } - - { src: "{{ php_apache_custom_ini_file }}", dest: "/etc/php/7.3/apache2/conf.d/zzz-evolinux-custom.ini" } + - { src: "{{ php_apache_defaults_ini_file }}", dest: "/etc/php/7.4/apache2/conf.d/z-evolinux-defaults.ini" } + - { src: "{{ php_apache_custom_ini_file }}", dest: "/etc/php/7.4/apache2/conf.d/zzz-evolinux-custom.ini" } when: php_apache_enable -- name: Enforce permissions on PHP 7.3/cli directory +- name: Enforce permissions on PHP 7.4/cli directory file: - dest: /etc/php/7.3/apache2 + dest: /etc/php/7.4/apache2 mode: "0755" when: php_apache_enable -- name: Symlink Evolix FPM config files from 7.3 to 7.0 +- name: Symlink Evolix FPM config files from 7.4 to 7.0 file: src: "{{ item.src }}" dest: "{{ item.dest }}" force: yes state: link with_items: - - { src: "{{ php_fpm_defaults_ini_file }}", dest: "/etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini" } - - { src: "{{ php_fpm_custom_ini_file }}", dest: "/etc/php/7.3/fpm/conf.d/zzz-evolinux-custom.ini" } - - { src: "{{ php_fpm_defaults_conf_file }}", dest: "/etc/php/7.3/fpm/pool.d/z-evolinux-defaults.conf" } - - { src: "{{ php_fpm_custom_conf_file }}", dest: "/etc/php/7.3/fpm/pool.d/zzz-evolinux-custom.conf" } + - { src: "{{ php_fpm_defaults_ini_file }}", dest: "/etc/php/7.4/fpm/conf.d/z-evolinux-defaults.ini" } + - { src: "{{ php_fpm_custom_ini_file }}", dest: "/etc/php/7.4/fpm/conf.d/zzz-evolinux-custom.ini" } + - { src: "{{ php_fpm_defaults_conf_file }}", dest: "/etc/php/7.4/fpm/pool.d/z-evolinux-defaults.conf" } + - { src: "{{ php_fpm_custom_conf_file }}", dest: "/etc/php/7.4/fpm/pool.d/zzz-evolinux-custom.conf" } when: php_fpm_enable -- name: Enforce permissions on PHP 7.3/cli directory +- name: Enforce permissions on PHP 7.4/cli directory file: - dest: /etc/php/7.3/fpm + dest: /etc/php/7.4/fpm mode: "0755" when: php_fpm_enable diff --git a/php/tasks/sury_pre.yml b/php/tasks/sury_pre.yml index 9ab816ab..45d5d005 100644 --- a/php/tasks/sury_pre.yml +++ b/php/tasks/sury_pre.yml @@ -18,7 +18,7 @@ - name: Setup deb.sury.org repository - Add source list apt_repository: - repo: "deb https://packages.sury.org/php/ stretch main" + repo: "deb https://packages.sury.org/php/ {{ ansible_distribution_release }} main" filename: sury state: present diff --git a/postfix/meta/main.yml b/postfix/meta/main.yml index c86b3d0b..fe59228a 100644 --- a/postfix/meta/main.yml +++ b/postfix/meta/main.yml @@ -14,8 +14,8 @@ galaxy_info: - jessie dependencies: - - { role: ldap, ldap_schema: 'cn4evolix.ldif', when: postfix_packmail == True } - - { role: spamassasin, when: postfix_packmail == True } - - { role: clamav, when: postfix_packmail == True } - - { role: opendkim, when: postfix_packmail == True } - - { role: dovecot, when: postfix_packmail == True } + - { role: evolix/ldap, ldap_schema: 'cn4evolix.ldif', when: postfix_packmail == True } + - { role: evolix/spamassasin, when: postfix_packmail == True } + - { role: evolix/clamav, when: postfix_packmail == True } + - { role: evolix/opendkim, when: postfix_packmail == True } + - { role: evolix/dovecot, when: postfix_packmail == True } diff --git a/postfix/tasks/minimal.yml b/postfix/tasks/minimal.yml index d1e4b4c5..ad666532 100644 --- a/postfix/tasks/minimal.yml +++ b/postfix/tasks/minimal.yml @@ -1,10 +1,8 @@ --- - name: ensure packages are installed apt: - name: '{{ item }}' + name: postfix state: present - with_items: - - postfix tags: - postfix @@ -17,8 +15,6 @@ mode: "0644" force: yes notify: restart postfix - when: postfix_force_main_cf == True or - postfix_maincf_md5_jessie in default_main_cf.stdout or - postfix_maincf_md5_stretch in default_main_cf.stdout + when: postfix_force_main_cf or postfix_maincf_md5_jessie in default_main_cf.stdout or postfix_maincf_md5_stretch in default_main_cf.stdout tags: - postfix diff --git a/postfix/tasks/packmail.yml b/postfix/tasks/packmail.yml index 67853643..f6900639 100644 --- a/postfix/tasks/packmail.yml +++ b/postfix/tasks/packmail.yml @@ -1,13 +1,12 @@ --- - name: ensure packages are installed apt: - name: '{{ item }}' + name: + - postfix + - postfix-ldap + - postfix-policyd-spf-python + - mailgraph state: present - with_items: - - postfix - - postfix-ldap - - postfix-policyd-spf-python - - mailgraph tags: - postfix @@ -20,9 +19,7 @@ mode: "0644" force: yes notify: restart postfix - when: postfix_force_main_cf == True or - postfix_maincf_md5_jessie in default_main_cf.stdout or - postfix_maincf_md5_stretch in default_main_cf.stdout + when: postfix_force_main_cf or postfix_maincf_md5_jessie in default_main_cf.stdout or postfix_maincf_md5_stretch in default_main_cf.stdout tags: - postfix @@ -88,7 +85,7 @@ - postfix - include_role: - name: remount-usr + name: evolix/remount-usr tags: - postfix @@ -100,6 +97,12 @@ tags: - postfix +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" + failed_when: False + changed_when: False + register: is_cron_installed + - name: enable spam.sh cron lineinfile: dest: /etc/cron.d/spam @@ -107,6 +110,7 @@ create: yes state: present mode: "0640" + when: is_cron_installed.rc == 0 tags: - postfix diff --git a/postgresql/defaults/main.yml b/postgresql/defaults/main.yml index 6ee57747..c81ff575 100644 --- a/postgresql/defaults/main.yml +++ b/postgresql/defaults/main.yml @@ -10,3 +10,9 @@ postgresql_effective_cache_size: "{{ (ansible_memtotal_mb * 0.5) | int }}MB" # PostgreSQL version postgresql_version: '9.6' + +# Set locales +locales_default: fr_FR.UTF-8 + +# PostGIS +postgresql_install_postgis: False diff --git a/postgresql/files/logrotate_postgresql b/postgresql/files/logrotate_postgresql index 4fef188d..a9306aa3 100644 --- a/postgresql/files/logrotate_postgresql +++ b/postgresql/files/logrotate_postgresql @@ -1,7 +1,11 @@ -/var/log/postgresql.log { - weekly +/var/log/postgresql/*.log { + daily + rotate 10 + copytruncate + nodelaycompress + compress + notifempty missingok - rotate 8 - create 640 root adm + su root root } diff --git a/postgresql/handlers/main.yml b/postgresql/handlers/main.yml index fab0c614..5275b6a1 100644 --- a/postgresql/handlers/main.yml +++ b/postgresql/handlers/main.yml @@ -10,9 +10,16 @@ state: restarted - name: restart postgresql - service: + systemd: name: postgresql state: restarted + daemon_reload: yes + +- name: reload systemd + command: systemctl daemon-reload - name: Restart minifirewall command: /etc/init.d/minifirewall restart + +- name: reconfigure locales + command: dpkg-reconfigure -f noninteractive locales diff --git a/postgresql/tasks/config.yml b/postgresql/tasks/config.yml index 128812af..dc3fc1b1 100644 --- a/postgresql/tasks/config.yml +++ b/postgresql/tasks/config.yml @@ -1,4 +1,13 @@ --- +- name: Override PostgreSQL systemd unit + template: + src: postgresql.service.j2 + dest: /etc/systemd/system/multi-user.target.wants/postgresql.service + force: yes + notify: + - reload systemd + - restart postgresql + - name: Allow conf.d/*.conf files to be included in PostgreSQL configuration lineinfile: name: "/etc/postgresql/{{postgresql_version}}/main/postgresql.conf" @@ -16,7 +25,7 @@ - name: Copy PostgreSQL config file template: src: postgresql.conf.j2 - dest: "/etc/postgresql/{{postgresql_version}}/main/conf.d/evolinux.conf" + dest: "/etc/postgresql/{{postgresql_version}}/main/conf.d/zz-evolinux.conf" owner: postgres group: postgres mode: "0644" diff --git a/postgresql/tasks/locales.yml b/postgresql/tasks/locales.yml new file mode 100644 index 00000000..89687a7c --- /dev/null +++ b/postgresql/tasks/locales.yml @@ -0,0 +1,19 @@ +--- +- include_role: + name: evolix/remount-usr + +- name: select locales to be generated + locale_gen: + name: "{{ item }}" + state: present + with_items: + - "fr_FR.UTF-8" + become: yes + notify: reconfigure locales + +- name: set default locale + lineinfile: + dest: "/etc/default/locale" + regexp: "^LANG=" + line: "LANG={{ locales_default }}" + notify: reconfigure locales diff --git a/postgresql/tasks/logrotate.yml b/postgresql/tasks/logrotate.yml index e77347d8..f67f407a 100644 --- a/postgresql/tasks/logrotate.yml +++ b/postgresql/tasks/logrotate.yml @@ -2,5 +2,5 @@ - name: logrotate configuration copy: src: logrotate_postgresql - dest: /etc/logrotate.d/postgresql + dest: /etc/logrotate.d/postgresql-common force: no diff --git a/postgresql/tasks/main.yml b/postgresql/tasks/main.yml index 3bc1f904..292849a0 100644 --- a/postgresql/tasks/main.yml +++ b/postgresql/tasks/main.yml @@ -1,10 +1,14 @@ --- +- include: locales.yml - include: packages_jessie.yml when: ansible_distribution_release == "jessie" - include: packages_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '=') + +- include: packages_buster.yml + when: ansible_distribution_major_version is version('10', '>=') - include: config.yml @@ -13,3 +17,6 @@ - include: munin.yml - include: logrotate.yml + +- include: postgis.yml + when: postgresql_install_postgis diff --git a/postgresql/tasks/nrpe.yml b/postgresql/tasks/nrpe.yml index 968f2fcc..8ce178e9 100644 --- a/postgresql/tasks/nrpe.yml +++ b/postgresql/tasks/nrpe.yml @@ -26,6 +26,7 @@ postgresql_user: name: nrpe password: '{{ postgresql_nrpe_password.stdout }}' + encrypted: yes no_password_changes: yes register: postgresql_create_nrpe_user when: nrpe_evolix_config.stat.exists diff --git a/postgresql/tasks/packages_buster.yml b/postgresql/tasks/packages_buster.yml new file mode 100644 index 00000000..3f45e84c --- /dev/null +++ b/postgresql/tasks/packages_buster.yml @@ -0,0 +1,11 @@ +--- + +- include: pgdg-repo.yml + when: postgresql_version != '11' + +- name: Install postgresql package + apt: + name: + - postgresql + - pgtop + - libdbd-pg-perl diff --git a/postgresql/tasks/packages_stretch.yml b/postgresql/tasks/packages_stretch.yml index 334a1dff..d6a3aa5e 100644 --- a/postgresql/tasks/packages_stretch.yml +++ b/postgresql/tasks/packages_stretch.yml @@ -1,9 +1,11 @@ --- +- include: pgdg-repo.yml + when: postgresql_version != '9.6' + - name: Install postgresql package apt: - name: '{{item}}' - with_items: - - postgresql - - ptop - - libdbd-pg-perl + name: + - postgresql + - ptop + - libdbd-pg-perl diff --git a/postgresql/tasks/postgis.yml b/postgresql/tasks/postgis.yml new file mode 100644 index 00000000..0c18cb51 --- /dev/null +++ b/postgresql/tasks/postgis.yml @@ -0,0 +1,7 @@ +--- +- name: Install PostGIS extention + apt: + name: + - postgis + - "postgresql-{{ postgresql_version }}-postgis-2.5" + - "postgresql-{{ postgresql_version }}-postgis-2.5-scripts" diff --git a/postgresql/templates/postgresql.conf.j2 b/postgresql/templates/postgresql.conf.j2 index ebb6dde2..25597519 100644 --- a/postgresql/templates/postgresql.conf.j2 +++ b/postgresql/templates/postgresql.conf.j2 @@ -3,7 +3,7 @@ shared_buffers = {{ postgresql_shared_buffers }} work_mem = {{ postgresql_work_mem }} #shared_preload_libraries = 'pg_stat_statements' #synchronous_commit = off -{% if postgresql_version | version_compare('9.5', '<') %} +{% if postgresql_version is version('9.5', '<') %} checkpoint_segments = 30 {% else %} max_wal_size = 15GB diff --git a/postgresql/templates/postgresql.service.j2 b/postgresql/templates/postgresql.service.j2 new file mode 100644 index 00000000..b5bd2730 --- /dev/null +++ b/postgresql/templates/postgresql.service.j2 @@ -0,0 +1,19 @@ +# systemd service for managing all PostgreSQL clusters on the system. This +# service is actually a systemd target, but we are using a service since +# targets cannot be reloaded. + +[Unit] +Description=PostgreSQL RDBMS + +[Service] +OOMScoreAdjust=-1000 +Environment=PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj +Environment=PG_MASTER_OOM_SCORE_ADJ=-1000 +Environment=PG_CHILD_OOM_SCORE_ADJ=0 +Type=oneshot +ExecStart=/bin/true +ExecReload=/bin/true +RemainAfterExit=on + +[Install] +WantedBy=multi-user.target diff --git a/rabbitmq/meta/main.yml b/rabbitmq/meta/main.yml index 3d60006f..a5c7d425 100644 --- a/rabbitmq/meta/main.yml +++ b/rabbitmq/meta/main.yml @@ -12,6 +12,7 @@ galaxy_info: - name: Debian versions: - jessie + - buster dependencies: [] # List your role dependencies here, one per line. diff --git a/rabbitmq/tasks/main.yml b/rabbitmq/tasks/main.yml index b251276d..c8e49407 100644 --- a/rabbitmq/tasks/main.yml +++ b/rabbitmq/tasks/main.yml @@ -1,9 +1,7 @@ - name: Install packages apt: - name: "{{ item }}" + name: rabbitmq-server state: present - with_items: - - rabbitmq-server - name: Create rabbitmq-env.conf copy: diff --git a/rabbitmq/tasks/munin.yml b/rabbitmq/tasks/munin.yml index 1b410d37..cb872391 100644 --- a/rabbitmq/tasks/munin.yml +++ b/rabbitmq/tasks/munin.yml @@ -1,7 +1,7 @@ --- - include_role: - name: remount-usr + name: evolix/remount-usr tags: - rabbitmq - munin diff --git a/rabbitmq/tasks/nrpe.yml b/rabbitmq/tasks/nrpe.yml index 02c7c97b..c4c700df 100644 --- a/rabbitmq/tasks/nrpe.yml +++ b/rabbitmq/tasks/nrpe.yml @@ -6,7 +6,7 @@ state: present - include_role: - name: remount-usr + name: evolix/remount-usr # https://raw.githubusercontent.com/CaptPhunkosis/check_rabbitmq/master/check_rabbitmq - name: check_rabbitmq is installed diff --git a/rbenv/defaults/main.yml b/rbenv/defaults/main.yml index 533834cd..0645f476 100644 --- a/rbenv/defaults/main.yml +++ b/rbenv/defaults/main.yml @@ -1,6 +1,6 @@ --- rbenv_version: v1.1.2 -rbenv_ruby_version: 2.6.3 +rbenv_ruby_version: 2.7.0 rbenv_root: "~/.rbenv" rbenv_repo: "https://github.com/rbenv/rbenv.git" rbenv_plugins: diff --git a/rbenv/tasks/main.yml b/rbenv/tasks/main.yml index 17f95692..28f25481 100644 --- a/rbenv/tasks/main.yml +++ b/rbenv/tasks/main.yml @@ -2,19 +2,18 @@ - name: "Rbenv dependencies are installed" apt: - name: '{{ item }}' + name: + - build-essential + - git + - libcurl4-openssl-dev + - libffi-dev + - libreadline-dev + - libssl-dev + - libxml2-dev + - libxslt1-dev + - zlib1g-dev + - pkg-config state: present - with_items: - - build-essential - - git - - libcurl4-openssl-dev - - libffi-dev - - libreadline-dev - - libssl-dev - - libxml2-dev - - libxslt1-dev - - zlib1g-dev - - pkg-config tags: - rbenv - packages diff --git a/redis/README.md b/redis/README.md index 5a7168b7..9b43635b 100644 --- a/redis/README.md +++ b/redis/README.md @@ -12,13 +12,13 @@ Everything is in the `tasks/main.yml` file. Main variables are : -* `redis_daemon`: name of the process ; -* `redis_conf_path`: config file location ; +* `redis_conf_dir`: config directory ; * `redis_port`: listening TCP port ; * `redis_bind_interface`: listening IP address ; * `redis_password`: password for redis. Empty means no password ; -* `redis_unixsocket`: Unix socket ; -* `redis_loglevel`: log verbosity ; -* `redis_logfile`: log file location. +* `redis_socket_dir`: Unix socket directory ; +* `redis_log_level`: log verbosity ; +* `redis_log_dir`: log file directory. +* `redis_maxmemory`: max memory (0 for no limit) The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/redis/defaults/main.yml b/redis/defaults/main.yml index cb8d451d..6fc0b4c3 100644 --- a/redis/defaults/main.yml +++ b/redis/defaults/main.yml @@ -1,12 +1,17 @@ --- -redis_daemon: redis-server -redis_conf_path: /etc/redis/redis.conf +redis_systemd_name: redis-server + +redis_conf_dir_prefix: /etc/redis redis_port: 6379 redis_bind_interface: 127.0.0.1 -redis_unixsocket: '/var/run/redis/redis.sock' -redis_unixsocketperm: 770 -redis_pidfile: "/var/run/redis/{{ redis_daemon }}.pid" + +redis_socket_enabled: True +redis_socket_dir_prefix: '/var/run/redis' +redis_socket_perms: 770 + +redis_pid_dir_prefix: "/var/run/redis" + redis_timeout: 300 # for client authorization @@ -14,8 +19,8 @@ redis_password: NULL # for slave authorization on master redis_password_master: "{{ redis_password }}" -redis_loglevel: "notice" -redis_logfile: /var/log/redis/redis-server.log +redis_log_dir_prefix: /var/log/redis +redis_log_level: "notice" redis_databases: 16 @@ -25,19 +30,21 @@ redis_save: - 300 10 - 60 10000 -redis_rdbcompression: "yes" -redis_dbfilename: dump.rdb -redis_dbdir: /var/lib/redis +redis_rdbcompression: True +redis_data_dir_prefix: /var/lib/redis +redis_data_file: dump.rdb + +redis_maxclients: 10000 redis_maxmemory: 0 redis_maxmemory_policy: "noeviction" redis_maxmemory_samples: 5 -redis_appendonly: "no" +redis_appendonly: False redis_appendfsync: "everysec" # Supported only in Redis 3.2+ -redis_protected_mode: "yes" +redis_protected_mode: True # Add extra include files for local configuration/overrides. redis_includes: [] @@ -46,3 +53,10 @@ redis_restart_if_needed: True redis_restart_force: False redis_disabled_commands: [] + +redis_sentinel_install: False + +redis_default_server_disabled: False + +general_alert_email: "root@localhost" +log2mail_alert_email: Null diff --git a/redis/files/check_redis.pl b/redis/files/check_redis.pl new file mode 100755 index 00000000..1a500b4b --- /dev/null +++ b/redis/files/check_redis.pl @@ -0,0 +1,2914 @@ +#!/usr/bin/perl -w +# +# ============================== SUMMARY ===================================== +# +# Program : check_redis.pl +# Version : 0.73 +# Date : Mar 23, 2013 +# Author : William Leibzon - william@leibzon.org +# Licence : GPL - summary below, full text at http://www.fsf.org/licenses/gpl.txt +# +# =========================== PROGRAM LICENSE ================================= +# +# 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. +# +# ===================== INFORMATION ABOUT THIS PLUGIN ========================= +# +# This is Redis Server Check plugin. It gets stats variables and allows to set +# thresholds on their value or their rate of change. It can measure response time, +# hitrate, memory utilization, check replication sync and more. It can also test +# data in a specified key (if necessary doing average or sum on range). +# +# Plugin returns stats variables as performance data for further nagios 2.0 +# post-processing, you can find graph templates for PNP4Nagios at: +# http://william.leibzon.org/nagios/ +# +# This program is written and maintained by: +# William Leibzon - william(at)leibzon.org +# +# ============================= SETUP NOTES ==================================== +# +# Make sure to install Redis perl library from CPAN first. +# +# Next for help and to see what parameters this plugin accepts do: +# ./check_redis.pl --help +# +# This plugin checks Redis NoSQL database status variables, measures its response +# time and if specified allows to set thresholds on one or more key data. You can +# set thresholds for data in stats variables and some of them are also conveniently +# available as long options with special threshold syntax. Plugin also calculates +# statistics such as Hitrate (calculated as rate of change of hits/misses) and +# memory use and can check replication delay. +# +# All variables can be returned as performance data for graphing and pnp4nagios +# template should be available with this plugin on the site you downloaded it from. + +# 1. Connection Parameters +# +# The connection parameters are "-H hostname", "-p port", "-D database" and +# "-C password_file" or "-x password". Specifying hostname is required, if you +# run locally specify it as -H 127.0.0.1. Everything else is optional and rarely +# needed. Default port is 6337. Database name (usually a numeric id) is probably +# only needed if you use --query option. Password can be passed on a command +# line with -x but its safer to read read it from a file or change in the code +# itself if you do use authentication. +# +# 2. Response Time, HitRate, Memory Utilization, Replication Delay +# +# To get response time you use "-T" or "--response_time=" option. By itself +# it will cause output of response time at the status line. You can also use +# it as "-T warn,crit" to specify warning and critical thresholds. +# +# To get hitrate the option is "-R" or "--hitrate=". If previous performance +# data is not feed to plugin (-P option, see below) the plugin calculates +# it as total hitrate over life of redis process. If -P is specified and +# previous performance data is fed back, the data is based on real hitrate +# (which can show spikes and downs) with lifelong info also given in paranthesis +# The data is based on keyspace_hits and keyspace_misses stats variables. +# As with -T you can specify -R by itself or with thresholds as -R warn,crit +# +# Memory utilization is percent of real memory used by Redis out of total +# memory on the system. To be able to calculate it plugin needs to known +# amount of memory your system has which you specify with "-M" or "--total_memory=" +# option. Memory utilization option itself is lower "-m" or "--memory_utilization=" +# and you can specify threshold for it as "-m warn,crit" +# +# Replication delay threshold option "-R" or "--replication_delay=" is used +# to check replication with data from "master_last_io_seconds_ago" stats and +# valid only on slave servers. Other variables maybe checked for this later +# with more complex functionality, so it was chosen to do this as separate +# option rather than directing people to check that variable. +# +# 3. Checks on Redis Status Variables +# +# All status variables from redis can be checked with the plugin. For some +# status variables separate long option is provided to specify threshold. +# i.e. --connected_clients= +# +# This is a new alternative to specifying all variables together with -a +# (--variables) option. For example: +# -a connected_clients,blocked_clients +# When you do above results are included in status output line and you +# are required to specify thresholds with -w or --warn and -c or --crit +# with exactly number of thresholds as a number of variables specified +# in -a. If you simply want variable values on status line without specifying +# any threshold, use ~ in place of threshold value or skip value but specify +# all appropriate commas. For example: +# -a connected_clients,blocked_clients -w ~,~ -c ~,~ +# OR -a connected_clients,blocked_clients -w , -c , +# +# If you use new syntax with a long option for specific stats variables, you +# can specify list of one or more threshold specifiers which can be any of: +# NAME: - Overrides name for this variable for use in status and PERF output +# PATTERN: - Regular Expression that allows to match multiple data results +# WARN:threshold - warning alert threshold +# CRIT:threshold - critical alert threshold +# Threshold is a value (usually numeric) which may have the following prefix: +# > - warn if data is above this value (default for numeric values) +# < - warn if data is below this value (must be followed by number) +# = - warn if data is equal to this value (default for non-numeric values) +# ! - warn if data is not equal to this value +# Threshold can also be specified as a range in two forms: +# num1:num2 - warn if data is outside range i.e. if datanum2 +# \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 +# ABSENT:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if data is absent +# ZERO:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if result is 0 +# DISPLAY:YES|NO - Specifies if data should be included in nagios status line output +# PERF:YES|NO - Output in performance data or not (always YES if -F option is used) +# UOM: - Unit Of Measurement symbol to add to perf data - 'c','%','s','B' +# This is used by programs that graph perf data such as PNP +# +# These can be specified in any order separated by ",". For example: +# --connected_clients=CRIT:>100,WARN:>50,ABSENT:CRITICAL,ZERO:OK,DISPLAY:YES,PERF:YES +# +# Variables that are not known to plugin and don't have specific long option (or even if +# they do) can be specified using general long option --check or --option or -o +# (all are aliases for same option): +# --check=NAME:connected_clients,CRIT:>100,WARN:>50,ABSENT:CRITICAL,DISPLAY:YES,PERF:YES +# +# Then NAME is used to specify what to match and multiple data vars maybe matched +# with PATTERN regex option (and please only use PATTERN with --check and not confuse +# plugin by using it in a named long option). Either NAME or PATTERN are required. +# +# 4. Calculating and using Rate of Change for Variables +# +# If you want to check rate of change rather than actual value you can do this +# by specifying it as '&variable' such as "&total_connections_received" or +# as "variable_rate" which is "total_connections_received_rate" and is similar +# to 'connected_clients' variable. By default it would be reported in the output +# as 'variable_rate' though '&variable' is a format used internally by plugin. +# +# As an alternative you can specify how to label these with --rate_label +# option where you can specify prefix and/or suffix. For example '--rate_label=dt_' +# would have the output being "dt_total_connections_received' where as +# '--rate_label=,_rate' is plugin default giving 'total_connections_received_rate'. +# You can use these names with -a and -A such as: +# --rate_label=,_rate -a total_connections_received_rate -w 1000 -c ~ +# Note that --rate_label will not work with new variable-named options, the +# only way to change default if you use that is to modify code and change +# $o_rprefix and $o_rsuffix variables default values. +# +# Now in order to be able to calculate rate of change, the plugin needs to +# know values of the variables from when it was run the last time. This +# is done by feeding it previous performance data with a -P option. +# In commands.cfg this would be specified as: +# -P "$SERVICEPERFDATA$" +# And don't forget the quotes, in this case they are not just for documentation. +# +# 5. Threshold Specification +# +# The plugin fully supports Nagios plug-in specification for specifying thresholds: +# http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT +# +# And it supports an easier format with the following one-letter prefix modifiers: +# >value : issue alert if data is above this value (default for numeric value) +# and < are interpreted by shell you may need to specify this in quotes) +# There are also two specifications of range formats as with other nagios plugins: +# number1:number2 issue alert if data is OUTSIDE of range [number1..number2] +# i.e. alert if data<$number1 or data>$number2 +# @number1:number2 issue alert if data is WITHIN range [number1..number2] +# i.e. alert if data>=$number and $data<=$number2 +# +# The plugin will attempt to check that WARNING value is less than CRITICAL +# (or greater for <). A special prefix modifier '^' can be used to disable these +# checks. A quick example of such special use is '--warn=^<100 --crit=>200' which +# means warning alert if value is < 100 and critical alert if its greater than 200. +# +# 6. Performance Data +# +# With '-f' option values of all variables you specified in -a as well as +# response time from -T (response time), +# hitrate from -R, +# memory utilization from -m +# and other data are reported back out as performance data for Nagios graphing programs. +# +# You may also directly specify which variables are to be return as performance data +# with '-A' option. If you use '-A' by itself and not specify any variables or use +# special value of '*' (as in '-A *') the plugin will output all variables which is useful +# for finding what data you can check with this plugin. +# +# The plugin will output threshold values as part of performance data as specified at +# http://nagiosplug.sourceforge.net/developer-guidelines.html#AEN201 +# And don't worry about using non-standard >,<,=,~ prefixes, all of that would get +# converted into nagios threshold format for performance output +# +# The plugin is smart enough to add 'c' suffix for known COUNTER variables to +# values in performance data. Known variables are specified in an array you can +# find at the top of the code (further below) and plugin author does not claim +# to have identified all variables correctly. Please email if you find an error +# or want to add more variables. +# +# As noted above performance data is also used to calculate rate of change +# by feeding it back with -P option. In that regard even if you did not specify +# -f or -A but you have specified &variable, its actual data would be sent out +# in performance output. Additionally last time plugin was run is also in +# performance data as special _ptime variable. +# +# 7. Query Option and setting thresholds for data in Redis Database +# +# With -q (--query) option the plugin can retrieve data from Redis database +# which become new variables you can then check thresholds on. Currently it +# supports getting single key values with GET and getting range or values (or +# everything in list) with LRANGE and finding their Average or Min or Max or Sum. +# The option maybe repeated more than once. The format for this option is: +# +# -q, --query=query_type,key[:varname]<,list of threshold specifiers> +# +# query_type is one of: +# GET - get one string value +# LLEN - returns number of items in a list +# LRANGE:AVG:start:end - retrieve list and average results +# LRANGE:SUM:start:end - retrieve list and sum results +# LRANGE:MIN:start:end - retrieve list and return minimum +# LRANGE:MAX:start:end - retrieve list and return maximum +# HLEN - returns number of items in a hash [TODO] +# HGET:name - get specific hash key 'name' [TODO] +# HEXISTS:name - returns 0 or 1 depending on if specified hash key 'name' exists [TODO] +# SLEN - returns number of items in a set [TODO, SCARD redis opp] +# SEXISTS:name - returns 0 or 1 depending on if set member 'name' exists [SISMEMBER, TODO] +# ZLEN - returns number of items in a sorted set [TODO, ZCARD redis opp] +# ZCOUNT:min:max - counts number of items in sorted set with scores within the given values +# ZRANGE:AVG:min:max - retrieve sorted set members from min to max and average results +# ZRANGE:SUM:min:max - retrieve sorted set members from min to max and sum results +# ZRANGE:MIN:min:max - retrieve sorted set members from min to max list and return minimum +# ZRANGE:MAX:min:max- retrieve sorted set members from min to max and return maximum +# For LRANGE if you do not specify start and end, then start will be 0 and end +# is last value in the list pointed to by this key (found by using llen). +# +# Key is the Redis key name to be retrieved and optionally you can add ":varname" +# after it which specifies what to name plugin variable based on this data - +# based on what you specify here is how it will be displayed in the status +# line and performance data, default is same as Redis key name. +# +# After these key name you specify list of thresholds in the same format as +# variable-based long options described in section 3. Again the list of the +# possible specifiers are: +# WARN:threshold +# CRIT:threshold +# ABSENT:OK|WARNING|CRITICAL|UNKNOWN - what to do if data is not available +# ZERO:OK|WARNING|CRIICAL|UNKNOWN - what to do if data is 0 (rarely needed) +# DISPLAY:YES|NO - display on status line or not (default YES) +# PERF:YES|NO - output in perf data or not +# +# You can also optionally use -a, -w and -c to check data from the query instead +# of specifying thresholds as part of query option itself And remember that you if +# you need to check multiple keys you just repeat --query option more than once. +# +# 8. Example of Nagios Config Definitions +# +# Sample command and service definitions are below: +# +# define command { +# command_name check_redis_new +# command_line $USER1$/check_redis.pl -H $HOSTADDRESS$ -p $ARG1$ -T $ARG2$ -R -A -M $_HOSTSYSTEM_MEMORY$ -m $ARG3$ -a $ARG4$ -w $ARG5$ -c $ARG6$ -f -P "$SERVICEPERFDATA$" +# } +# +# Arguments and thresholds are: +# $ARG1 : Port +# $ARG2 : response time thresholds +# $ARG3 : memory utilization thresholds +# $ARG4 : additional variables to be checked +# $ARG5 : warning thresholds for those variables +# $ARG6 : critical thresholds for those variables +# +# define service { +# use prod-service +# hostgroups redishosts +# service_description Redis +# check_command check_redis_new!6379!"1,2"!"80,90"!blocked_clients,connected_clients!50,~!100,~ +# } +# +# define host { +# use prod-server +# host_name redis.mynetwork +# address redis.mynetwork +# alias Redis Stat Server +# hostgroups linux,redishosts +# _SYSTEM_MEMORY '8G' +# } +# +# Example of command-line use: +# /usr/lib/nagios/plugins/check_redis.pl -H localhost -a 'connected_clients,blocked_clients' -w ~,~ -c ~,~ -m -M 4G -A -R -T -f -v +# +# In above the -v option means "verbose" and with it plugin will output some debugging information +# about what it is doing. The option is not intended to be used when plugin is called from nagios itself. +# +# Example of using query and variable-based long options with debug enabled as well (-v): +# +# ./check_redis.pl -H localhost -p 6379 -D 1 --query LRANGE:AVG:0:,MyColumn1:Q1,ABSENT:WARNING,WARN:300,CRIT:500,DISPLAY:YES,PERF:NO +# --query GET,MyKey:K1,ABSENT:CRITICAL "--connected_clients=WARN:<2,CRIT:>100,ZERO:OK,ABSENT:WARNING,DISPLAY:YES,PERF:YES" +# +# ======================= VERSION HISTORY and TODO ================================ +# +# The plugins is written by reusing code my check_memcached.pl which itself is based +# on check_mysqld.pl. check_mysqld.pl has history going back to 2004. +# +# [0.4 - Mar 2012] First version of the code based on check_mysqld.pl 0.93 +# and check_memcached.pl 0.6. Internal work, not released. +# Version 0.4 because its based on a well developed code base +# [0.41 - Apr 15, 2012] Added list of variables array and perf_ok regex. +# Still testing internally and not released yet. +# [0.42 - Apr 28, 2012] Added total_keys, total_expires, nice uptime_info +# and memory utilization +# [0.43 - May 31, 2012] Release candidate. More documentation added +# replacing check_memcached examples. Bugs fixed. +# Made "_rate" as default rate variables suffix in +# place of &delta. Changed -D option to -r. +# +# [0.5 - Jun 01, 2012] First official release will start with version 0.5 +# Documentation changes, but no code updates. +# [0.51 - Jun 16, 2012] Added support to specify filename to '-v' option +# for debug output and '--debug' as alias to '--verbose' +# [0.52 - Jul 10, 2012] Patch by Jon Schulz to support credentials with -C +# (credentials file) and addition by me to support +# password as command argument. +# [0.53 - Jul 15, 2012] Adding special option to do query on one redis key and +# and do threshold checking of results if its numeric +# +# [0.6 - Jul 17, 2012] Rewrote parts of thresholds checking code and moved code +# that checks and parses thresholds from main into separate +# functions that are to become part of plugin library. +# Added support for variable thresholds specified as: +# option=WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL,ZERO:.. +# which are to be used for stats-variable based long options such as +# --connected_clients=WARN:threshold,CRIT:threshold +# and added DISPLAY:YES|NO and PERF specifiers for above too. +# Added -D option to specify database needed for --query +# [0.61 - Aug 03, 2012] Added more types of key query for lists, sets, hashes +# and options to find number of elements in a list/set/hash. +# New options added are: +# LLEN,HLEN,SLEN,ZLEN,HGET,HEXISTS,SEXISTS,ZRANGE +# +# [0.7 - Aug 28, 2012] A lot of internal rewrites in the library. Its now not just a +# a set of functions, but a proper object library with internal +# variables hidden from outside. Support has also been added for +# regex matching with PATTERN specifier and for generalized +# --check option that can be used where specific long option is +# not available. For use with that option also added UOM specifier. +# Also added checkin 'master_last_io_seconds_ago' (when link is down) +# for when replication_delay info is requested. +# [0.71 - Sep 03, 2012] Fixed bug in a new library related to when data is missing +# [0.72 - Oct 05, 2012] Fixed bug reported by Matt McMillan in specified memory size +# when KB are used. Fixed bugs in adding performance data that +# results in keyspace_hits, keyspace_misses, memory_utilization +# having double 'c' or '%' in perfdata. Added contributors section. +# [0.73 - Mar 23, 2013] Fixed bug in parse_threshold function of embedded library +# +# TODO or consider for future: +# +# 1. Library Enhancements (will apply to multiple plugins that share common code) +# (a) Add '--extra-opts' to allow to read options from a file as specified +# at http://nagiosplugins.org/extra-opts. This is TODO for all my plugins +# (b) [DONE] +# In plans are to allow long options to specify thresholds for known variables. +# These would mean you specify '--connected_clients' in similar way to '--hitrate' +# Internally these would be converged into -A, -w, -c as appropriate and used +# together with these options. So in practice it will now allow to get any data +# just a different way to specify options for this plugin. +# (c) Allow regex when selecting variable name(s) with -a, this will be enabled with +# a special option and not be default +# [DONE] +# +# 2. REDIS Specific +# (a) Add option to check from master that slave is connected and working. +# (b) Look into replication delay from master and how it can be done. Look +# for into on replication_delay from slave as well +# (c) How to better calculate memory utilization and get max memory available +# without directly specifying it +# (d) Maybe special options to measure cpu use and set thresholds +# +# Others are welcome recommend a new feature to be added here. If so please email to +# william@leibzon.org. +# And don't worry, I'm not a company with some hidden agenda to use your idea +# but an actual person who you can easily get hold of by email, find on forums +# and on Nagios conferences. More info on my nagios work is at: +# http://william.leibzon.org/nagios/ +# Above site should also have PNP4Nagios template for this and other plugins. +# +# ============================ LIST OF CONTRIBUTORS =============================== +# +# The following individuals have contributed code, patches, bug fixes and ideas to +# this plugin (listed in last-name alphabetical order): +# +# William Leibzon +# Matthew Litwin +# Matt McMillan +# Jon Schulz +# M Spiegle +# +# ============================ START OF PROGRAM CODE ============================= + +use strict; +use IO::Socket; +use Time::HiRes; +use Text::ParseWords; +use Getopt::Long qw(:config no_ignore_case); +use Redis; + +# default hostname, port, database, user and password, see NOTES above +my $HOSTNAME= 'localhost'; +my $PORT= 6379; +my $PASSWORD= undef; +my $DATABASE= undef; + +# Add path to additional libraries if necessary +use lib '/usr/lib/nagios/plugins'; +our $TIMEOUT; +our %ERRORS; +eval 'use utils qw(%ERRORS $TIMEOUT)'; +if ($@) { + $TIMEOUT = 20; + %ERRORS = ('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4); +} + +my $Version='0.73'; + +# This is a list of known stat and info variables including variables added by plugin, +# used in order to designate COUNTER variables with 'c' in perfout for graphing programs +# The format is: +# VAR_NAME => [ TYPE, PerfSuffix, DESCRIPTION] +# If option has description, the variable will also become available as a long option so for example +# you can specify "--connected_clients=WARN,CRIT" instead of specifying "-a connected_clients -w WARN -c CRIT' +my %KNOWN_STATUS_VARS = ( + 'memory_utilization' => [ 'status', 'GAUGE', '%' ], # calculated by plugin + 'redis_version' => [ 'status', 'VERSION', '' ], # version string variable + 'response_time' => [ 'status','GAUGE', 's' ], # measured by plugin + 'hitrate' => [ 'status', 'GAUGE', '%' ], # calculated by plugin + 'total_keys' => [ 'status','GAUGE', '', 'Total Number of Keys on the Server' ], + 'total_expires' => [ 'status','GAUGE', '', 'Number of Expired Keys for All DBs' ], + 'last_save_time' => [ 'status', 'GAUGE', 's' ], + 'bgsave_in_progress' => [ 'status', 'BOOLEAN', '' ], + 'vm_enabled' => [ 'status', 'BOOLEAN', '' ], + 'uptime_in_seconds' => [ 'status', 'COUNTER', 'c' ], + 'total_connections_received' => [ 'status', 'COUNTER', 'c', 'Total Connections Received' ], + 'used_memory_rss' => [ 'status', 'GAUGE', 'B', 'Resident Set Size, Used Memory in Bytes' ], # RSS - Resident Set Size + 'used_cpu_sys' => [ 'status', 'GAUGE', '', 'Main Process Used System CPU' ], + 'redis_git_dirty' => [ 'status', 'BOOLEAN', '', 'Git Dirty Set Bit' ], + 'loading' => [ 'status', 'BOOLEAN', '' ], + 'latest_fork_usec' => [ 'status', 'GAUGE', '' ], + 'connected_clients' => [ 'status', 'GAUGE', '', 'Total Number of Connected Clients' ], + 'used_memory_peak_human' => [ 'status', 'GAUGE', '' ], + 'mem_allocator' => [ 'status', 'TEXTINFO', '' ], + 'uptime_in_days' => [ 'status', 'COUNTER', 'c', 'Total Uptime in Days' ], + 'keyspace_hits' => [ 'status', 'COUNTER', 'c', 'Total Keyspace Hits' ], + 'client_biggest_input_buf' => [ 'status', 'GAUGE', '' ], + 'gcc_version' => [ 'status', 'TEXTINFO', '' ], + 'changes_since_last_save' => [ 'status', 'COUNTER', 'c' ], + 'arch_bits' => [ 'status', 'TEXTINFO', '' ], + 'lru_clock' => [ 'status', 'GAUGE', '' ], # LRU is page replacement algorithm (least recently used), I'm unsure what this represents though + 'role' => [ 'status', 'SETTING', '' ], + 'multiplexing_api' => [ 'status', 'SETTING' , '' ], + 'slave' => [ 'status', 'TEXTDATA', '' ], + 'pubsub_channels' => [ 'status', 'GAUGE', '', 'Number of Pubsub Channels' ], + 'redis_git_sha1' => [ 'status', 'TEXTDATA', '' ], + 'used_cpu_user_children' => [ 'status', 'GAUGE', '', 'Child Processes Used User CPU' ], + 'process_id' => [ 'status', 'GAUGE', '' ], + 'used_memory_human' => [ 'status', 'GAUGE', '' ], + 'keyspace_misses' => [ 'status', 'COUNTER', 'c', 'Keyspace Misses' ], + 'used_cpu_user' => [ 'status', 'GAUGE', '', 'Main Process Used User CPU' ], + 'total_commands_processed' => [ 'status', 'COUNTER', 'c', 'Total Number of Commands Processed from Start' ], + 'mem_fragmentation_ratio' => [ 'status', 'GAUGE', '', 'Memory Fragmentation Ratio' ], + 'client_longest_output_list' => [ 'status', 'GAUGE', '' ], + 'blocked_clients' => [ 'status', 'GAUGE', '', 'Number of Currently Blocked Clients' ], + 'aof_enabled' => [ 'status', 'BOOLEAN', '' ], + 'evicted_keys' => [ 'status', 'COUNTER', 'c', 'Total Number of Evicted Keys' ], + 'bgrewriteaof_in_progress' => [ 'status','BOOLEAN', '' ], + 'expired_keys' => [ 'status', 'COUNTER', 'c', 'Total Number of Expired Keys' ], + 'used_memory_peak' => [ 'status', 'GAUGE', 'B' ], + 'connected_slaves' => [ 'status', 'GAUGE', '', 'Number of Connected Slaves' ], + 'used_cpu_sys_children' => [ 'status', 'GAUGE', '', 'Child Processed Used System CPU' ], + 'master_host' => [ 'status', 'TEXTINFO', '' ], + 'master_port' => [ 'status', 'TEXTINFO', '' ], + 'master_link_status' => [ 'status', 'TEXTINFO', '' ], + 'slave0' => [ 'status', 'TEXTINFO', '' ], + 'slave1' => [ 'status', 'TEXTINFO', '' ], + 'slave2' => [ 'status', 'TEXTINFO', '' ], + 'slave3' => [ 'status', 'TEXTINFO', '' ], + ); + +# Here you can also specify which variables should go into perf data, +# For right now it is 'GAUGE', 'COUNTER', 'DATA' (but not 'TEXTDATA'), and 'BOOLEAN' +# you may want to remove BOOLEAN if you don't want too much data +my $PERF_OK_STATUS_REGEX = 'GAUGE|COUNTER|^DATA$|BOOLEAN'; + +# ============= MAIN PROGRAM CODE - DO NOT MODIFY BELOW THIS LINE ============== + +my $o_host= undef; # hostname +my $o_port= undef; # port +my $o_pwfile= undef; # password file +my $o_password= undef; # password as parameter +my $o_database= undef; # database name (usually a number) +my $o_help= undef; # help option +my $o_verb= undef; # verbose mode +my $o_version= undef; # version info option +my $o_variables=undef; # list of variables for warn and critical +my $o_perfvars= undef; # list of variables to include in performance data +my $o_warn= undef; # warning level option +my $o_crit= undef; # Critical level option +my $o_perf= undef; # Performance data option +my @o_check= (); # General check option that maybe repeated more than once +my $o_timeout= undef; # Timeout to use - note that normally timeout is from nagios +my $o_timecheck=undef; # threshold spec for connection time +my $o_memutilization=undef; # threshold spec for memory utilization% +my $o_totalmemory=undef; # total memory on a system +my $o_hitrate= undef; # threshold spec for hitrate% +my $o_repdelay=undef; # replication delay time +my @o_querykey=(); # query this key, this option maybe repeated so its an array +my $o_prevperf= undef; # performance data given with $SERVICEPERFDATA$ macro +my $o_prevtime= undef; # previous time plugin was run $LASTSERVICECHECK$ macro +my $o_ratelabel=undef; # prefix and suffix for creating rate variables +my $o_rsuffix='_rate'; # default suffix +my $o_rprefix=''; + +## Additional global variables +my $redis= undef; # DB connection object +my @query=(); # array of queries with each entry being keyed hash of processedoption data on howto query + + +sub p_version { print "check_redis.pl version : $Version\n"; } + +sub print_usage_line { + print "Usage: $0 [-v [debugfilename]] -H [-p ] [-x password | -C credentials_file] [-D ] [-a -w -c ] [-A ] [-T [conntime_warn,conntime_crit]] [-R [hitrate_warn,hitrate_crit]] [-m [mem_utilization_warn,mem_utilization_crit] [-M [B|K|M|G]]] [-r replication_delay_time_warn,replication_delay_time_crit] [-f] [-T ] [-V] [-P ] [-q (GET|LLEN|HLEN|SLEN|ZLEN|HGET:name|HEXISTS:name|SEXISTS:name|LRANGE:(AVG|SUM|MIN|MAX):start:end|ZRANGE:(AVG|SUM|MIN|MAX):start:end),query_type,query_key_name[:data_name][,ABSENT:WARNING|CRITICAL][,WARN:threshold,CRIT:threshold]] [-o ]\n"; +} + +sub print_usage { + print_usage_line(); + print "For more details on options do: $0 --help\n"; +} + +sub help { + my $nlib = shift; + + print "Redis Check for Nagios version ",$Version,"\n"; + print " by William Leibzon - william(at)leibzon.org\n\n"; + print "This is redis monitoring plugin to check its stats variables, replication, response time\n"; + print "hitrate, memory utilization and other info. The plugin can also query and test key data\n"; + print "against specified thresholds. All data is available as performance output for graphing.\n\n"; + print_usage_line(); + print "\n"; + print < - warn if data is above this value (default for numeric values) + < - warn if data is below this value (must be followed by number) + = - warn if data is equal to this value (default for non-numeric values) + ! - warn if data is not equal to this value + ~ - do not check this data (must not be followed by number or ':') + ^ - for numeric values this disables check that warning < critical + Threshold values can also be specified as range in two forms: + num1:num2 - warn if data is outside range i.e. if datanum2 + \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 + -c, --crit=STR[,STR[,STR[..]]] + This option can only be used if '--variables' (or '-a') option above + is used and number of values listed here must exactly match number of + variables specified with '-a'. The values specify critical threshold + for when Nagios should send CRITICAL alert. The format is exactly same + as with -w option except no '^' prefix. + +Performance Data Processing Options: + -f, --perfparse + This should only be used with '-a' and causes variable data not only as part of + main status line but also as perfparse compatible output (for graphing, etc). + -A, --perfvars=[STRING[,STRING[,STRING...]]] + This allows to list variables which values will go only into perfparse + output (and not for threshold checking). The option by itself (emply value) + is same as a special value '*' and specify to output all variables. + -P, --prev_perfdata + Previous performance data (normally put '-P \$SERVICEPERFDATA\$' in nagios + command definition). This is used to calculate rate of change for counter + statistics variables and for proper calculation of hitrate. + --rate_label=[PREFIX_STRING[,SUFFIX_STRING]] + Prefix or Suffix label used to create a new variable which has rate of change + of another base variable. You can specify PREFIX or SUFFIX or both. Default + if not specified is suffix '_rate' i.e. --rate_label=,_rate + +Key Data Query Option (maybe repeated more than once): + -q, --query=query_type,key[:varname][,ABSENT:OK|WARNING|CRITICAL,WARN:threshold,CRIT:threshold] + query_type is one of: + GET - get one data value + LLEN - number of items in a list + LRANGE:AVG:start:end - retrieve list and average results + LRANGE:SUM:start:end - retrieve list and sum results + LRANGE:MIN:start:end - retrieve list and return minimum + LRANGE:MAX:start:end - retrieve list and return maximum + HLEN - returns number of items in a hash + HGET:name - get specific hash key 'name' + HEXISTS:name - returns 0 or 1 depending on if specified hash key 'name' exists + SLEN - returns number of items in a set + SEXISTS:name - returns 0 or 1 depending on if set member 'name' exists + ZLEN - returns number of items in a sorted set + ZCOUNT:min:max - counts items in sorted set with scores within the given values + ZRANGE:AVG:min:max - retrieve sorted set members from min to max and average results + ZRANGE:SUM:min:max - retrieve sorted set members from min to max and sum results + ZRANGE:MIN:min:max - retrieve sorted set members from min to max list and return minimum + ZRANGE:MAX:min:max - retrieve sorted set memers from min to max and return maximum + + Option specifies key to query and optional variable name to assign the results to after : + (if not specified it would be same as key). If key is not available the plugin can issue + either warning or critical alert depending on what you specified after ABSENT. + Numeric results are calculated for ranges and can be checked with specified thresholds + or you can do it together with standard with redis stats variables and -a option. + +General Check Option (all 3 forms equivalent, can be repated more than once): + -o , --option=, --check= + where specifiers are separated by , and must include NAME or PATTERN: + NAME: - Default name for this variable as you'd have specified with -v + PATTERN: - Regular Expression that allows to match multiple data results + WARN:threshold - warning alert threshold + CRIT:threshold - critical alert threshold + Threshold is a value (usually numeric) which may have the following prefix: + > - warn if data is above this value (default for numeric values) + < - warn if data is below this value (must be followed by number) + = - warn if data is equal to this value (default for non-numeric values) + ! - warn if data is not equal to this value + Threshold can also be specified as a range in two forms: + num1:num2 - warn if data is outside range i.e. if datanum2 + \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 + ABSENT:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if data is absent + ZERO:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if result is 0 + DISPLAY:YES|NO - Specifies if data should be included in nagios status line output + PERF:YES|NO - Output results as performance data or not (always YES if asked for rate) + UOM: - Unit Of Measurement symbol to add to perf data - 'c','%','s','B' + +Measured/Calculated Data: + -T, --response_time=[WARN,CRIT] + If this is used as just -T the plugin will measure and output connection + response time in seconds. With -f this would also be provided on perf variables. + You can also specify values for this parameter, these are interprted as + WARNING and CRITICAL thresholds (separated by ','). + -R, --hitrate=[WARN,CRIT] + Calculates Hitrate %: cache_miss/(cache_hits+cache_miss). If this is used + as just -R then this info just goes to output line. With '-R -f' these + go as performance data. You can also specify values for this parameter, + these are interprted as WARNING and CRITICAL thresholds (separated by ','). + The format for WARN and CRIT is same as what you would use in -w and -c. + -m, --memory_utilization=[WARN,CRIT] + This calculates percent of total memory on system used by redis, which is + utilization=redis_memory_rss/total_memory*100. + Total_memory on server must be specified with -M since Redis does not report + it and can use maximum memory unless you enabled virtual memory and set a limit + (I plan to test this case and see if it gets reported then). + If you specify -m by itself, the plugin will just output this info, + with '-f' it will also include this in performance data. You can also specify + parameter values which are interpreted as WARNING and CRITICAL thresholds. + -M, --total_memory=NUM[B|K|M|G] + Amount of memory on a system for memory utilization calculations above. + If it does not end with K,M,G then its assumed to be B (bytes) + -r, --replication_delay=WARN,CRIT + Allows to set threshold on replication delay info. Only valid if this is a slave! + The threshold value is in seconds and fractions are acceptable. + +EOT + + if (defined($nlib) && $nlib->{'enable_long_options'} == 1) { + my $long_opt_help = $nlib->additional_options_help(); + if ($long_opt_help) { + print "Stats Variable Options (this is alternative to specifying them as list with -a):\n"; + print $long_opt_help; + print "\n"; + } + } +} + +############################ START OF THE LIBRARY FUNCTIONS ##################################### +# +# THIS IS WORK IN PROGRESS, THE LIBRARY HAS NOT BEEN RELEASED YET AND INTERFACES MAY CHANGE +# +# ====================================== SUMMARY ================================================ +# +# Name : Naglio Perl Library For Developing Nagios Plugins +# Version : 0.2 +# Date : Aug 28, 2012 +# Author : William Leibzon - william@leibzon.org +# Licence : LGPL - full text at http://www.fsf.org/licenses/lgpl.txt +# +# ============================= LIBRARY HISTORY AND VERSIONS ==================================== +# +# Note: you may safely skip this section if you're looking at documentation about this library or plugin +# +# [2006-2008] The history of this library goes back to plugins such as check_snmp_temperature.pl, +# check_mysqld,pl and others released as early as 2006 with common functions to +# support prefixes "<,>,=,!" for specifying thresholds and checking data against +# these thresholds. Several of my plugins had common architecture supporting multiple +# variables or attributes to be checked using -a/--attributes/--variables option and +# --warn and --crit options with list of thresholds for these attributes and --perfvars +# specifying variables whose data would only go as PERFOUT for graphing. +# +# [2008-2011] Threshold parsing and check code had been rewritten and support added for specifying +# range per plugin guidelines: http://nagiosplug.sourceforge.net/developer-guidelines.html +# Internal structures had been changing and becoming more complex to various cases. +# In 2010-2012 plugins started to get support for ;warn;crit output of thresholds in perf, +# as specified in the guidelines. +# +# [Early 2012] Code from check_memcached had been used as a base for check_memcached and then +# check_redis plugins with some of the latest threshold code from check_netstat +# with more updates. Starting with check_redis the code from check_options() and +# from main part of plugin that was very similar across my plugins were separated +# into their own functions. KNOWN_STATS_VARS array was introduced as well to be +# able to properly add UOM symbol ('c', '%', 's', 'ms', 'B', 'KB') to perfout. +# check_memcached and check_redis also included support for calculating rate of +# variables in a similar way to how its been done in check_snmp_netint +# +# [0.1 - July 17, 2012] In 0.6 release of check_redis.pl support had been added for long options +# with special threshold line syntax: +# --option=WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,DISPLAY:YES|NO,PERF:YES|NO +# This was extension from just doing --option=WARN,CRIT to have a more universal +# and extendable way to specify and alike parameters for checking. check_redis 0.6 +# also introduced support automatically adding long options with above syntax based +# on description in KNOWN_STATS_VARS. The functions for the library were all separated +# into their own section of the code. When inported to check_memcached global variables +# were added to that section and accessor functions written for some of them. +# This is considered 0.1 version of the library +# +# [0.2 - Aug 28, 2012] In August the library code in check_memcached had been re-written from +# just functions to object-oriented perl interface. All variables were hidden from +# direct access with accessor functions written. Documentation header had been added +# to each library function and the header for the library itself. This was major work +# taking over a week to do although functions and mainly sllllame as in 0.1. They are +# not stabilized and so library is only to be included within plugins. Support was +# also added for regex matching with PATTERN option spec. Also added NAME spec. +# License changed to LGPL from GPL for this code. +# [0.21 - Sep 3, 2012] Fix bug in handling absent data +# [0.22 - Mar 23, 2013] Fix bug in parse_threshold functon +# +# ================================== LIBRARY TODO ================================================= +# +# (a) Add library function to support '--extra-opts' to read plugin options from a file +# This is being to be compatible with http://nagiosplugins.org/extra-opts +# (b) Support regex matching and allowing multiple data for same threshold definition. +# [DONE] +# (c) Support for expressions in places of numeric values for thresholds. The idea is to allow +# to refer to another variable or to special macro. I know at least one person has extended +# my check_mysqld to support using mysql variables (not same as status data) for thresholds. +# I also previouslyhad planned such support with experimental check_snmp_attributes plugin +# library/base. The idea was also floated around on nagios-devel list. +# (d) Support specifying variables as expressions. This is straight out of check_snmp_atributes +# and maybe part of it can be reused for this +# (e) Add common SNMP functions into library as so many of my plugins use it# +# (f) Add more functions to make this library easier to use and stabilize its interfaces. +# Port my plugins to this library. +# (f) Add support for functions in Nagios-Plugins perl library. While its interfaces are +# different, I believe, it'd be possible to add "shim" code to support them too. +# (h) Write proper Perl-style documentation as well as web documentation (much of above maybe +# moved to web documentation) and move library to separate GITHUB project. Release it. +# (i) Port this library to Python and write one or two example plugins +# +# ================================================================================================ +{ +package Naglio; +use fields qw(); +use Text::ParseWords; + +my %ERRORS = ('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4); +my $DEFAULT_PERF_OK_STATUS_REGEX = 'GAUGE|COUNTER|^DATA$|BOOLEAN'; + +# @DESCRIPTION : Library object constructor +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : Hash array of named config settings. All parameters are optiona. Currently supported are: +# plugin_name => string - short name of the plugin +# plugin_description => string - plugin longer description +# plugin_authors => string - list of plugin authors +# knownStatsVars => reference to hash - hash array defining known variables, what type they are, their description +# usage_function => &ref - function that would display helpful text in case of error with options for this plugin +# verbose => 1 or "" or "filename" - set to 1 or "" if verbose/debug or to filename to send data to (may not be called "0" or "1") +# output_comparison_symbols => 0 or 1 - 1 means library output in case threshold is met can use "<", ">", "=" +# 0 means output is something like "less than or equal", "more than", etc. +# all_variables_perf => 0 or 1 - 1 means data for all variables would go to PERF. This is what '-A *' or just -A do +# enable_long_options => 0 or 1 - 1 enables long options generated based on knownStatsVars. This is automatically enabled (from 0 +# to 1) when plugin references additional_options_list() unless this is set to -1 at library init +# enable_rate_of_change => 0 or 1 - enables support for calculating rate of change based on previously saved data, default is 1 +# enable_regex_match => 0 or 1 - when set to 1 each threshold-specified var name is treated as regex and can match +# to multiple collected data. this can also be enabled per-variable with PATTERN spec +# @RETURNS : Reference representing object instance of this library +# @PRIVACY & USE : PUBLIC, To be used when initializing the library +sub lib_init { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my %other_args = @_; + + # These used to be global variables, now these are object local variables in self with accessor + my @allVars = (); # all variables after options processing + my @perfVars = (); # performance variables list [renamed from @o_perfVarsL in earlier code] + my %thresholds=(); # hash array of thresholds for above variables, [this replaced @o_warnL and @o_critL in earlier code] + my %dataresults= (); # This is where data is loaded. It is a hash with variable names as keys and array array for value: + # $dataresults{$var}[0] - undef of value of this variable + # $dataresults{$var}[1] - 0 if variable not printed out to status line yet, 1 or more otherwise + # $dataresults{$var}[2] - 0 if variable data not yet put into PERF output, -1 if PERF output is preset, 1 after output + # $dataresults{$var}[3] - string, '' to start with, holds ready performance data output for this variable + # $dataresults{$var}[4] - only for regex matches. name of match var (which should be key in thresholds), otherwise undef + my %dataVars = (); # keys are variables from allVars and perfVars, values is array of data that matched i.e. keys in dataresults + my @ar_warnLv = (); # used during options processing + my @ar_critLv = (); # used during options processing + my @ar_varsL= (); # used during options processing + my @prev_time= (); # timestamps if more then one set of previois performance data + + my $self = { # library and nagios versions + _NaglioLibraryVersion => 0.2, # this library's version + _NagiosVersion => 3, # assume nagios core 3.x unless known otherwise + # library internal data structures + _allVars => \@allVars, + _perfVars => \@perfVars, + _thresholds => \%thresholds, + _dataresults => \%dataresults, + _datavars => \%dataVars, + _ar_warnLv => \@ar_warnLv, + _ar_critLv => \@ar_critLv, + _ar_varsL => \@ar_varsL, + _prevTime => \@prev_time, + _prevPerf => {}, # array that is populated with previous performance data + _checkTime => undef, # time when data was last checked + _statuscode => "OK", # final status code + _statusinfo => "", # if there is an error, this has human info about what it is + _statusdata => "", # if there is no error but we want some data in status line, this var gets it + _perfdata => "", # this variable collects performance data line + _saveddata => "", # collects saved data (for next plugin re-run, not implimented yet) + _init_args => \%other_args, + # copy of data from plugin option variables + o_variables => undef, # List of variables for warn and critical checks + o_crit => undef, # Comma-separated list of critical thresholds for each checked variable + o_warn => undef, # Comma-separated list of warning thresholds for each checked variable + o_perf => undef, # defined or undef. perf option means all data from variables also goes as PERFDATA + o_perfvars => undef, # List of variables only for PERFDATA + o_prevperf => undef, # previously saved performance data coming from $SERVICEPERFDATA$ macro + # library special input variables (similar to options) + o_rprefix => '', # prefix used to distinguish rate variables + o_rsuffix => '_rate', # suffix used to distinguish rate variables + knownStatusVars => {}, # Special HASH ARRAY with names and description of known variables + perfOKStatusRegex => $DEFAULT_PERF_OK_STATUS_REGEX, + verbose => 0, # verbose, same as debug, same as o_verb + plugin_name => '', # next 3 parameters are variables are currently not used + plugin_description => '', # but its still better if these are provided + plugin_authors => '', # in the future these maybe used for help & usage functions + # library setting variables + debug_file => "", # instead of setting file name in verbose, can also set it here + output_comparison_symbols => 1, # should plugin output >,<.=,! for threshold match + # if 0, it will say it in human form, i.e. "less" + all_variables_perf => 0, # should we all variables go to PERF (even those not listed in o_variables and o_perfvars) + # this is the option set to 1 when --perfvars '*' is used + enable_long_options => 0, # enable support for long options generated based on knownStatusVars description + enable_rate_of_change => 1, # enables support for calculatin rate of chane and for rate of change long options + enable_regex_match => 0, # 0 is not enabled, 1 means variables in o_variables and o_perfvars are considered regex to match actual data + # a value of 2 means its enabled, but for options with PATTERN specifier (this is not configurale value) + }; + + # bless to create an object + bless $self, $class; + + # deal with arguments that maybe passed to library when initalizing + if (exists($other_args{'KNOWN_STATUS_VARS'})) { + $self->{'knownStatusVars'} = $other_args{'KNOWN_STATUS_VARS'}; + } + $self->{'plugin_name'} = $other_args{'plugin_name'} if exists($other_args{'plugin_name'}); + $self->{'plugin_description'} = $other_args{'plugin_description'} if exists($other_args{'plugin_description'}); + $self->{'plugin_authors'} = $other_args{'plugin_authors'} if exists($other_args{'plugin_authors'}); + $self->{'usage_function'} = $other_args{'usage_gunction'} if exists($other_args{'usage_function'}); + $self->configure(%other_args); + + # return self object + return $self; +} + +# This is just an alias for object constructor lib_init function +sub new { + return lib_init(@_); +} + +# @DESCRIPTION : Allows to confiure some settings after initialization (all these can also be done as part of lib_init) +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : Hash array of named config settings. All parameters are optiona. Currently supported are: +# verbose => 1 or "" or "filename" - set to 1 or "" if verbose/debug or to filename to send data to (may not be called "0" or "1") +# output_comparison_symbols => 0 or 1 - 1 means library output in case threshold is met can use "<", ">", "=" +# 0 means output is something like "less than or equal", "more than", etc. +# all_variables_perf => 0 or 1 - 1 means data for all variables would go to PERF. This is what '-A *' or just -A do +# enable_long_options => 0 or 1 - 1 enables long options generated based on knownStatsVars. This is automatically enabled (from 0 +# to 1) when plugin references additional_options_list() unless this is set to -1 at library init +# enable_rate_of_change => 0 or 1 - enables support for calculating rate of change based on previously saved data, default is 1 +# enable_regex_match => 0 or 1 - when set to 1 each threshold-specified var name is treated as regex and can match +# to multiple collected data. this can also be enabled per-variable with PATTERN spec +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function. +sub configure { + my $self = shift; + my %args = @_; + + if (exists($args{'verbose'}) || exists($args{'debug'})) { + $self->{'verbose'} = 1; + if (exists($args{'verbose'}) && $args{'verbose'}) { + $self->{'debug_file'} = $args{'verbose'}; + } + if (exists($args{'debug_log_filename'})) { + $self->{'debug_file'} = $args{'debug_log_filename'}; + } + } + $self->{'all_variables_perf'} = $args{'all_variables_perf'} if exists($args{'all_variables_perf'}); + $self->{'enable_long_options'} = $args{'enable_long_options'} if exists($args{'enable_long_options'}); + $self->{'enable_rate_of_change'} = $args{'enable_rate_of_change'} if exists($args{'enable_rate_of_change'}); + $self->{'enable_regex_match'} = 1 if exists($args{'enable_regex_match'}) && $args{'enable_regex_match'}!=0; + $self->{'output_comparison_symbols'} = $args{'output_comparison_symbols'} if exists($args{'output_comparison_symbols'}); +} + +# @DESCRIPTION : Allows functions to take be used both directly and as object referenced functions +# In the 2nd case they get $self as 1st argument, in 1st they don't. this just adds +# $self if its if its not there so their argument list is known. +# Functions that allow both should still check if $self is defined +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : arbitrary list of arguments +# @RETURNS : arbitrary list of arguments with 1st being object hash or undef +# @PRIVACY & USE : PRIVATE +sub _self_args { + return @_ if ref($_[0]) && exists($_[0]->{'_NaglioLibraryVersion'}); + unshift @_,undef; + return @_; +} + +# @DESCRIPTION : Sets function to be called to display help text on using plugin in case of error +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : reference to usage function +# @RETURNS : nothing +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function : +sub set_usage_function { + my ($self, $usage_function) = @_; + $self->{'usage_function'} = $usage_function; +} + +# @DESCRIPTION : Usage function. For right now it just calls usage function given as a parameter +# In the future if it is not available, it'll print something standard. +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : nothing +# @PRIVACY & USE : PUBLIC, But primary for internal use. Must be used as an object instance function. +sub usage { + my $self = shift; + if (defined($self) && defined($self->{'usage_function'})) { &{$self->{'usage_function'}}(); } +} + +# @DESCRIPTION : This function converts uptime in seconds to nice & short output format +# @LAST_CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - uptime in seconds +# @RETURNS : string of uptime for human consumption +# @PRIVACY & USE : PUBLIC, Maybe used directly or as object instance function : +sub uptime_info { + my ($self,$uptime_seconds) = _self_args(@_); + my $upinfo = ""; + my ($secs,$mins,$hrs,$days) = (undef,undef,undef,undef); + + sub div_mod { return int( $_[0]/$_[1]) , ($_[0] % $_[1]); } + + ($mins,$secs) = div_mod($uptime_seconds,60); + ($hrs,$mins) = div_mod($mins,60); + ($days,$hrs) = div_mod($hrs,24); + $upinfo .= "$days days" if $days>0; + $upinfo .= (($upinfo ne '')?' ':'').$hrs." hours" if $hrs>0; + $upinfo .= (($upinfo ne '')?' ':'').$mins." minutes" if $mins>0 && ($days==0 || $hrs==0); + $upinfo .= (($upinfo ne '')?' ':'').$secs." seconds" if $secs>0 && $days==0 && $hrs==0; + return $upinfo; +} + +# @DESCRIPTION : If debug / verbose option is set, function prints its input out or to debug file +# @LAST_CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - string of debug text +# @RETURNS : nothing +# @PRIVACY & USE : PUBLIC, Maybe used directly or as object instance function +sub verb { + my ($self,$in) = _self_args(@_); + my $debug_file_name = ""; + + if (defined($o_verb) || (defined($self) && defined($self->{'verbose'}) && $self->{'verbose'} ne 0)) { + $debug_file_name = $self->{'debug_file'} if defined($self) && $self->{'debug_file'} ne ""; + $debug_file_name = $self->{'verbose'} if $debug_file_name ne "" && defined($self) && + ($self->{'verbose'} ne 0 && $self->{'verbose'} ne 1 && $self->{'verbose'} ne ''); + $debug_file_name = $o_verb if $debug_file_name ne "" && defined($o_verb) && $o_verb ne ""; + if ($debug_file_name ne "") { + if (!open (DEBUGFILE, ">>$debug_file_name")) { + print $in, "\n"; + } + else { + print DEBUGFILE $in,"\n"; + close DEBUGFILE; + } + } + else { + print $in, "\n"; + } + } +} + +# @DESCRIPTION : Check of string is a a number supporting integers, negative, decimal floats +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - string of text to be checked +# @RETURNS : 1 if its a number, 0 if its not a number +# @PRIVACY & USE : PUBLIC, To be used statically and not as an object instance reference +sub isnum { + my $num = shift; + if (defined($num) && $num =~ /^[-|+]?((\d+\.?\d*)|(^\.\d+))$/ ) { return 1 ;} + return 0; +} + +# @DESCRIPTION : Check of string is a a number supporting integers, negative, decimal floats +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - string of text to be checked +# @RETURNS : 1 if its a number, 0 if its not a number +# @PRIVACY & USE : PUBLIC, To be used statically and not as an object instance function +sub trim { + my $string = shift; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} + +# @DESCRIPTION : Takes as input string from PERF or SAVED data from previous plugin invocation +# which should contain space-separated list of var=data pairs. The string is +# parsed and it returns back hash array of var=>data pairs. +# - Function written in 2007 for check_snmp_netint, first release 06/01/07 +# - Modified to use quotewords as suggested by Nicholas Scott, release of 05/20/12 +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - string of text passed from SERVICEPERFDATA OR SERVICESAVEDDATA MACRO +# @RETURNS : hash array (see description) +# @PRIVACY & USE : PUBLIC, Maybe used directly or as object instance function +# TODO: double-check this works when there are no single quotes as check_snmp_netint always did quotes +sub process_perf { + my ($self,$in) = _self_args(@_); + my %pdh; + my ($nm,$dt); + use Text::ParseWords; + foreach (quotewords('\s+',1,$in)) { + if (/(.*)=(.*)/) { + ($nm,$dt)=($1,$2); + if (defined($self)) { $self->verb("prev_perf: $nm = $dt"); } + else { verb("prev_perf: $nm = $dt"); } + # in some of my plugins time_ is to profile execution time for part of plugin + # $pdh{$nm}=$dt if $nm !~ /^time_/; + $pdh{$nm}=$dt; + $pdh{$nm}=$1 if $dt =~ /(\d+)[csB%]/; # 'c' or 's' or B or % maybe have been added + # support for more than one set of previously cached performance data + # push @prev_time,$1 if $nm =~ /.*\.(\d+)/ && (!defined($prev_time[0]) || $prev_time[0] ne $1); + } + } + return %pdh; +} + +# @DESCRIPTION : Converts variables with white-spaces with per-name enclosed with '' +# @LAST CHANGED : 08-24-12 by WL +# @INPUT : ARG1 - varible name +# @RETURNS : name for perf-out output +# @PRIVACY & USE : PUBLIC, but its use should be limited. To be used statically and not as an object instance function +sub perf_name { + my $in = shift; + my $out = $in; + $out =~ s/'\/\(\)/_/g; #' get rid of special characters in performance description name + if ($in !~ /\s/ && $in eq $out) { + return $in; + } + return "'".$out."'"; +} + +# @DESCRIPTION : Determines appropriate output name (for STATUS and PERF) taking into account +# rate variales prefix/suffix and 'NAME' override in long thresholds line specification +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name (variable as found in dataresults) +# @RETURNS : name for output +# @PRIVACY & USE : PUBLIC, but its use should be limited. To be as an object instance function, +sub out_name { + my ($self,$dname) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self-> {'_dataresults'}; + my $vr = $self->data2varname($dname,1); + my $name_out; + + if (defined($vr) && exists($thresholds->{$vr}{'NAME'})) { + if (exists($thresholds->{$vr}{'PATTERN'}) || $self->{'enable_regex_match'} == 1) { + $thresholds->{$vr}{'NAMES_INDEX'} = {} if !exists($thresholds->{$vr}{'NAMES_INDEX'}); + if (!exists($thresholds->{$vr}{'NAMES_INDEX'}{$dname})) { + my $ncount = scalar(keys %{$thresholds->{$vr}{'NAMES_INDEX'}}); + $ncount++; + $thresholds->{$vr}{'NAMES_INDEX'}{$dname} = $ncount; + } + $name_out = $thresholds->{$vr}{'NAME'} .'_'. $thresholds->{$vr}{'NAMES_INDEX'}{$dname}; + } + else { + $name_out = $thresholds->{$vr}{'NAME'}; + } + } + else { + # this is for output of rate variables which name internally start with & + if ($dname =~ /^&(.*)/) { + $name_out = $self->{'o_rprefix'}.$1.$self->{'o_rsuffix'}; + } + else { + $name_out = $dname; + } + } + return $name_out; +} + +# @DESCRIPTION : Builds statusline. Adds info on error conditions that would preceed status data. +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - string argument for status info +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, but its direct use is discouraged. Must be used as an object instance function +sub addto_statusinfo_output { + my ($self, $var, $sline) = @_; + $self->{'_statusinfo'} .= ", " if $self->{'_statusinfo'}; + $self->{'_statusinfo'} .= trim($sline); + $self->{'_dataresults'}{$var}[1]++; +} + +# @DESCRIPTION : Accessor function for statusinfo +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : statusinfo (error conditions and messages) string +# @PRIVACY & USE : PUBLIC. Must be used as an object instance function +sub statusinfo { + my $self = shift; + if (defined($self) && defined($self->{'_statusinfo'})) { + return $self->{'_statusinfo'}; + } + return undef; +} + +# @DESCRIPTION : Builds Statuline. Adds variable data for status line output in non-error condition. +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - formatted for human consumption text of collected data for this variable +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, but its direct use is discouraged. Must be used as an object instance function +sub addto_statusdata_output { + my ($self,$dvar,$data) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self -> {'_dataresults'}; + my $avar = $self->data2varname($dvar,1); + + # $self->verb("debug: addto_statusdata_output - dvar is $dvar and avar is $avar"); + if ((!exists($thresholds->{$avar}{'DISPLAY'}) || $thresholds->{$avar}{'DISPLAY'} eq 'YES') && + (!exists($dataresults->{$dvar}[1]) || $dataresults->{$dvar}[1] == 0)) { + $self->{'_statusdata'} .= ", " if $self->{'_statusdata'}; + if (defined($data)) { + $self->{'_statusdata'} .= trim($data); + } + elsif (exists($dataresults->{$dvar}[0])) { + $self->{'_statusdata'} .= $self->out_name($dvar) ." is ".$dataresults->{$dvar}[0]; + } + $dataresults->{$dvar}[1]++; + } +} + +# @DESCRIPTION : Accessor function for statusdata +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : statusdata string (non-error data from some variables) +# @PRIVACY & USE : PUBLIC. Must be used as an object instance function +sub statusdata { + my $self = shift; + if (defined($self) && defined($self->{'_statusdata'})) { + return $self->{'_statusdata'}; + } + return undef; +} + +# @DESCRIPTION : This function sets text or data for data variable PERFORMANCE output +# (;warn;crit would be added to it later if thresholds were set for this variable) +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - either "var=data" text or just "data" (in which case var= is prepended to it) +# ARG3 - UOM symol ('c' for continous, '%' for percent, 's' for seconds) to added after data +# if undef then it is looked up in known variables and if one is present there, its used +# ARG4 - one of: "REPLACE" - if existing preset perfdata is present, it would be replaced with ARG2 +# "ADD" - if existing preset perfdata is there, ARG2 string would be added to it (DEFAULT) +# "IFNOTSET - only set perfdata to ARG2 if it is empty, otherwise keep existing +# @RETURNS : nothing (future: 0 on success, -1 on error) +# @PRIVACY & USE : PUBLIC, but its use should be limited to custom variables added by plugins to data +# Must be used as an object instance function +sub set_perfdata { + my ($self,$avar,$adata,$unit,$opt) = @_; + my $dataresults = $self->{'_dataresults'}; + my $thresholds = $self->{'_thresholds'}; + my $known_vars = $self->{'knownStatusVars'}; + my $bdata = $adata; + my $vr = undef; + + # default operation is ADD + if (!defined($opt)) { + $opt = "ADD"; + } + else { + $opt = uc $opt; + } + if (defined($adata)) { + # if only data wthout "var=" create proper perf line + $bdata = perf_name($self->out_name($avar)).'='.$adata if $adata !~ /=/; + if (defined($unit)) { + $bdata .= $unit; + } + else { + # appending UOM is done here + $vr = $self->data2varname($avar,1); + if (defined($vr)) { + if (exists($thresholds->{$vr}{'UOM'})) { + $bdata .= $thresholds->{$vr}{'UOM'}; + } + elsif (exists($known_vars->{$vr}[2])) { + $bdata .= $known_vars->{$vr}[2]; + } + } + } + # preset perfdata in dataresults array + $dataresults->{$avar}=[undef,0,0,''] if !defined($dataresults->{$avar}); + $dataresults->{$avar}[2]=-1; + if ($opt eq "REPLACE" || !exists($dataresults->{$avar}[3]) || $dataresults->{$avar}[3] eq '') { + $dataresults->{$avar}[3]=$bdata; + } + elsif (exists($dataresults->{$avar}[3]) && $dataresults->{$avar}[3] ne '' && $opt eq "ADD") { + $dataresults->{$avar}[3].=$adata; + } + } +} + +# @DESCRIPTION : This function is used when building performance output +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - optional data argument, if not present variable's dataresults are used +# ARG3 - one of: "REPLACE" - if existing preset perfdata is present, it would be replaced with ARG2 +# "ADD" - if existing preset perfdata is there, ARG2 string would be added to it +# "IFNOTSET - only set perfdata to ARG2 if it is empty, otherwise keep existing (DEFAULT) +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, but its direct use is discouraged. Must be used as an object instance function +sub addto_perfdata_output { + my ($self,$avar,$adata, $opt) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self-> {'_dataresults'}; + my $vr = undef; + + if (!defined($opt)) { + $opt = "IFNOTSET"; + } + else { + $opt = uc $opt; + } + $vr = $self->data2varname($avar,1); + if (defined($avar) && defined($vr) && + (!exists($thresholds->{$vr}{'PERF'}) || $thresholds->{$vr}{'PERF'} eq 'YES') && + (!defined($dataresults->{$avar}[2]) || $dataresults->{$avar}[2] < 1)) { + my $bdata = ''; + if (defined($adata)) { + $bdata .= trim($adata); + } + # this is how most perfdata gets added + elsif (defined($dataresults->{$avar}[0])) { + $bdata .= perf_name($self->out_name($avar)) .'='. $dataresults->{$avar}[0]; + } + # this would use existing preset data now if it was present due to default + # setting UOM from KNOWN_STATUS_VARS array is now in set_perfdata if 3rd arg is undef + $self->set_perfdata($avar,$bdata,undef,$opt); + # now we actually add to perfdata from [3] of dataresults + if (exists($dataresults->{$avar}[3]) && $dataresults->{$avar}[3] ne '') { + $bdata = trim($dataresults->{$avar}[3]); + $self->{'_perfdata'} .= " " if $self->{'_perfdata'}; + $self->{'_perfdata'} .= $bdata; + $dataresults->{$avar}[2]=0 if $dataresults->{$avar}[2] < 0; + $dataresults->{$avar}[2]++; + } + } +} + +# @DESCRIPTION : Accessor function for map from data collected to variable names specified in options and thresholds +# @LAST CHANGED : 08-22-13 by WL +# @INPUT : ARG1 - data variable name +# ARG2 - if undef or 0 return undef if no match for ARG1 found, if 1 return ARG1 +# @RETURNS : string of variable name as was specified with --variables or --thresholds +# @PRIVACY & USE : PUBLIC. Must be used as an object instance function +sub data2varname { + my ($self,$dname,$ropt) = @_; + my $dataresults = $self->{'_dataresults'}; + + return $dataresults->{$dname}[4] if defined($self) && defined($dataresults->{$dname}[4]); + return $dname if defined($ropt) && $ropt eq 1; + return undef; +} + +# @DESCRIPTION : Sets list and info on known variables and regex for acceptable data types. +# This function maybe called more than once. If called again, new vars in subsequent +# calls are added to existing ones and existing vars are replaced if they are there again. +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : ARG1 - ref to hash array of known vars. Keys are variable names. Data is an array. Example is: +# 'version' => [ 'misc', 'VERSION', '' ], +# 'utilization' => [ 'misc', 'GAUGE', '%' ], +# 'cmd_get' => [ 'misc', 'COUNTER', 'c', "Total Number of Get Commands from Start" ], +# The array elements are: +# 1st - string of source for this variable. not used by the library at all, but maybe used by code getting the data +# 2nd - type of data in a variable. May be "GAUGE", "VERSION", "COUNTER", "BOOLEAN", "TEXTINFO", "TEXTDATA", "SETTING" +# 3rd - either empty or one-character UOM to be added to perforance data - 'c' for continous, '%' percent, 's' seconds +# 4th - either empty or a description of this variable. If not empty, the variable becomes long-option and this is help text +# ARG2 - regex of acceptable types of data for performance output. Anything else is ignored (i.e. no no output to perf), but +# is still available for threshold checks. if this is undef, then default of 'GAUGE|COUNTER|^DATA$|BOOLEAN' is used +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as object instance function +sub set_knownvars { + my ($self, $known_vars_in, $vartypes_regex_in) = @_; + my $known_vars = $self->{'knownStatusVars'}; + + if (defined($known_vars_in)) { + foreach (keys %{$known_vars_in}) { + $known_vars->{$_} = $known_vars_in->{$_}; + } + } + if (defined($vartypes_regex_in)) { + $self->{'perfOKStatusRegex'} = $vartypes_regex_in; + } + else { + $self->{'perfOKStatusRegex'} = $DEFAULT_PERF_OK_STATUS_REGEX; + } +} + +# @DESCRIPTION : Adds known variables definition one at a time +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - string of source for this variable. not used by the library at all, but maybe used by code getting the data +# ARG3 - type of data in a variable. May be "GAUGE", "VERSION", "COUNTER", "BOOLEAN", "TEXTINFO", "TEXTDATA", "SETTING" +# ARG4 - either empty or one-character UOM symbol to be added to perforance data - 'c' for continous, '%' percent, 's' seconds +# ARG5 - either empty or a description of this variable. If not empty, the variable becomes long-option and this is help text +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as object instance function +sub add_knownvar { + my ($self, $varname, $source, $type, $unit, $description) = @_; + my $temp = { $varname => [ $source, $type, $unit, $description] }; + $self->set_knownvars($temp,undef); +} + +# @DESCRIPTION : This function is used for checking data values against critical and warning thresholds +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - variable name (used for text output in case it falls within threshold) +# ARG2 - data to be checked +# ARG3 - threshold to be checked, internal structure returned by parse_threshold() +# @RETURNS : Returns "" (empty string) if data is not within threshold range +# and text message for status line out about how data is within range otherwise +# @PRIVACY & USE : PUBLIC. Maybe used directly or as an object instance function +sub check_threshold { + my ($self,$attrib,$data,$th_array) = _self_args(@_); + my $mod = $th_array->[0]; + my $lv1 = $th_array->[1]; + my $lv2 = $th_array->[2]; + my $issymb = 1; + $issymb = 0 if defined($self) && $self->{'output_comparison_symbols'} eq 0; + + # verb("debug check_threshold: $mod : ".(defined($lv1)?$lv1:'')." : ".(defined($lv2)?$lv2:'')); + return "" if !defined($lv1) || ($mod eq '' && $lv1 eq ''); + return " " . $attrib . " is " . $data . ( ($issymb==1)?' = ':' equal to ' ). $lv1 if $mod eq '=' && $data eq $lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' != ':' not equal to ' ). $lv1 if $mod eq '!' && $data ne $lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' > ':' more than ' ) . $lv1 if $mod eq '>' && $data>$lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' > ':' more than ' ) . $lv2 if $mod eq ':' && $data>$lv2; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' >= ':' more than or equal to ' ) . $lv1 if $mod eq '>=' && $data>=$lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' < ':' less than ' ). $lv1 if ($mod eq '<' || $mod eq ':') && $data<$lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' <= ':' less than or equal to ' ) . $lv1 if $mod eq '<=' && $data<=$lv1; + return " " . $attrib . " is " . $data . " in range $lv1..$lv2" if $mod eq '@' && $data>=$lv1 && $data<=$lv2; + return ""; +} + +# @DESCRIPTION : This function is called to parse threshold string +# @LAST CHANGED : 03-23-13 by WL +# (the code in this function can be traced back to late 2006. It has not much changed from 2008) +# @INPUT : ARG1 - String for one variable WARN or CRIT threshold which can be as follows: +# data - warn if data is above this value if numeric data, or equal for non-numeric +# >data - warn if data is above this value (default for numeric values) +# num2 +# \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 +# @RETURNS : Returns reference to a hash array, this library's structure for holding processed threshold spec +# @PRIVACY & USE : PUBLIC. Maybe used directly or as an object instance function +sub parse_threshold { + my ($self,$thin) = _self_args(@_); + + # link to an array that holds processed threshold data + # array: 1st is type of check, 2nd is threshold value or value1 in range, 3rd is value2 in range, + # 4th is extra options such as ^, 5th is nagios spec string representation for perf out + my $th_array = [ '', undef, undef, '', '' ]; + my $th = $thin; + my $at = ''; + + $at = $1 if $th =~ s/^(\^?[@|>|<|=|!]?~?)//; # check mostly for my own threshold format + $th_array->[3]='^' if $at =~ s/\^//; # deal with ^ option + $at =~ s/~//; # ignore ~ if it was entered + if ($th =~ /^\:([-|+]?\d+\.?\d*)/) { # :number format per nagios spec + $th_array->[1]=$1; + $th_array->[0]=($at !~ /@/)?'>':'<='; + $th_array->[5]=($at !~ /@/)?('~:'.$th_array->[1]):($th_array->[1].':'); + } + elsif ($th =~ /([-|+]?\d+\.?\d*)\:$/) { # number: format per nagios spec + $th_array->[1]=$1; + $th_array->[0]=($at !~ /@/)?'<':'>='; + $th_array->[5]=($at !~ /@/)?'':'@'; + $th_array->[5].=$th_array->[1].':'; + } + elsif ($th =~ /([-|+]?\d+\.?\d*)\:([-|+]?\d+\.?\d*)/) { # nagios range format + $th_array->[1]=$1; + $th_array->[2]=$2; + if ($th_array->[1] > $th_array->[2]) { + print "Incorrect format in '$thin' - in range specification first number must be smaller then 2nd\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + $th_array->[0]=($at !~ /@/)?':':'@'; + $th_array->[5]=($at !~ /@/)?'':'@'; + $th_array->[5].=$th_array->[1].':'.$th_array->[2]; + } + if (!defined($th_array->[1])) { # my own format (<,>,=,!) + $th_array->[0] = ($at eq '@')?'<=':$at; + $th_array->[1] = $th; + $th_array->[5] = '~:'.$th_array->[1] if ($th_array->[0] eq '>' || $th_array->[0] eq '>='); + $th_array->[5] = $th_array->[1].':' if ($th_array->[0] eq '<' || $th_array->[0] eq '<='); + $th_array->[5] = '@'.$th_array->[1].':'.$th_array->[1] if $th_array->[0] eq '='; + $th_array->[5] = $th_array->[1].':'.$th_array->[1] if $th_array->[0] eq '!'; + } + if ($th_array->[0] =~ /[>|<]/ && !isnum($th_array->[1])) { + print "Numeric value required when '>' or '<' are used !\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + # verb("debug parse_threshold: $th_array->[0] and $th_array->[1]"); + $th_array->[0] = '=' if !$th_array->[0] && !isnum($th_array->[1]) && $th_array->[1] ne ''; + if (!$th_array->[0] && isnum($th_array->[1])) { # this is just the number by itself, becomes 0:number check per nagios guidelines + $th_array->[2]=$th_array->[1]; + $th_array->[1]=0; + $th_array->[0]=':'; + $th_array->[5]=$th_array->[2]; + } + return $th_array; +} + +# @DESCRIPTION : this function checks that for numeric data warn threshold is within range of critical +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - warhing threshold structure (reference to hash array) +# ARG2 - critical threshold structure (reference to hash array) +# @RETURNS : Returns 1 if warning does not fall within critical (there is an error) +# Returns 0 if everything is ok and warning is within critical +# @PRIVACY & USE : PUBLIC, but its use is discouraged. Maybe used directly or as an object instance function. +sub threshold_specok { + my ($self, $warn_thar,$crit_thar) = _self_args(@_); + + return 1 if defined($warn_thar) && defined($warn_thar->[1]) && + defined($crit_thar) && defined($crit_thar->[1]) && + isnum($warn_thar->[1]) && isnum($crit_thar->[1]) && + $warn_thar->[0] eq $crit_thar->[0] && + (!defined($warn_thar->[3]) || $warn_thar->[3] !~ /\^/) && + (!defined($crit_thar->[3]) || $crit_thar->[3] !~ /\^/) && + (($warn_thar->[1]>$crit_thar->[1] && ($warn_thar->[0] =~ />/ || $warn_thar->[0] eq '@')) || + ($warn_thar->[1]<$crit_thar->[1] && ($warn_thar->[0] =~ /[0] eq ':')) || + ($warn_thar->[0] eq ':' && $warn_thar->[2]>=$crit_thar->[2]) || + ($warn_thar->[0] eq '@' && $warn_thar->[2]<=$crit_thar->[2])); + return 0; # return with 0 means specs check out and are ok +} + +# @DESCRIPTION : this compares var names from data to names given as plugin options treating them regex +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - the name to search for +# @RETURNS : Keyname for what first one that matched from _thresholds +# Undef if nothing matched +# @PRIVACY & USE : PUBLIC, but its direct use should be rare. Must be used as an object instance function. +sub var_pattern_match { + my ($self, $name) = @_; + my $thresholds = $self->{'_thresholds'}; + my $allvars = $self->{'_allVars'}; + my $is_regex_match = $self->{'enable_regex_match'}; + my $v; + my $pattern; + + foreach $v (@{$allvars}) { + $pattern=''; + if ($is_regex_match eq 1 && !defined($thresholds->{$v}{'PATTERN'})) { + $pattern=$v; + } + elsif ($is_regex_match ne 0 && defined($thresholds->{$v}{'PATTERN'})) { + $pattern = $thresholds->{$v}{'PATTERN'}; + } + if ($pattern ne '' && $name =~ /$pattern/) { + $self->verb("Data name '".$name."' matches pattern '".$pattern."'"); + return $v; + } + } + return undef; +} + +# @DESCRIPTION : This function adds data results +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - name of data variable +# ARG2 - data for this variable +# ARG3 - name of checked variable/parameter corresponding to this data variable +# default undef, assumed to be same as ARG1 +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub add_data { + my ($self, $dnam, $dval, $anam) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self-> {'_dataresults'}; + my $datavars = $self -> {'_datavars'}; + my $perfVars = $self->{'_perfVars'}; + + # determine what plugin options-specified var & threshold this data corresponds to + if (!defined($anam)) { + if ($self->{'enable_regex_match'} == 0) { + $anam = $dnam; + } + else { + $anam = $self->var_pattern_match($dnam); + $anam = $dnam if !defined($anam); + } + } + # set dataresults + if (exists($dataresults->{$dnam})) { + $dataresults->{$dnam}[0] = $dval; + $dataresults->{$dnam}[4] = $anam if defined($anam); + } + else { + $dataresults->{$dnam} = [$dval, 0, 0, '', $anam]; + } + # reverse map array + $datavars->{$anam} = [] if !exists($datavars->{$anam}); + push @{$datavars->{$anam}}, $dnam; + # setperf if all variables go to perf + if ($self->{'all_variables_perf'} == 1) { + $thresholds->{$anam}={} if !exists($thresholds->{$anam}); + $thresholds->{$anam}{'PERF_DATALIST'} = [] if !exists($thresholds->{$anam}{'PERF_DATALIST'}); + push @{$thresholds->{$anam}{'PERF_DATALIST'}}, $dnam; + if (!defined($thresholds->{$anam}{'PERF'})) { + push @{$perfVars}, $anam; + $thresholds->{$anam}{'PERF'} = 'YES'; + } + } +} + +# @DESCRIPTION : Accessor function that gets variable data +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - name of data variable +# @RETURNS : undef if variable does not exist and data otherwise +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub vardata { + my ($self,$dnam) = @_; + my $dataresults = $self->{'_dataresults'}; + return undef if !exists($dataresults->{$dnam}); + return $dataresults->{$dnam}[0]; +} + +# @DESCRIPTION : This function parses "WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN" combined threshold string +# Parsing of actual threshold i.e. what is after WARN, CRIT is done by parse_threshold() function +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - String containing threshold line like "WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN" +# Acceptable comma-separated parts threshold specifiers are: +# WARN: - warning threshold +# CRIT: - critical threshold +# ABSENT:OK|WARNING|CRITICAL|UNKNOWN - nagios exit code if data for this variable is not found +# ZERO:OK|WARNING|CRITICAL|UNKNOWN - nagios exit code if data is 0 +# DISPLAY:YES|NO - output data in plugin status line +# PERF:YES|NO - output data as plugin performance data +# SAVED:YES|NO - put results in saved data (this really should not be set manually) +# PATTERN: - enables regex match allowing more than one real data name to match this threshold +# NAME: - overrides output status and perf name for this variable +# UOM: - unit of measurement symbol to add to perf +# @RETURNS : Returns reference to a hash array, a library's structure for holding processed MULTI-THRESHOLD spec +# Note that this is MULTI-THRESHOLD hash structure, it itself contains threshold hashes returned by parse_threshold() +# @PRIVACY & USE : PUBLIC, but its use is discouraged. Maybe used directly or as an object instance function. +sub parse_thresholds_list { + my ($self,$in) = _self_args(@_); + my $thres = {}; + my @tin = undef; + my $t = undef; + my $t2 = undef; + + @tin = split(',', $in); + $t = uc $tin[0] if exists($tin[0]); + # old format with =warn,crit thresolds without specifying which one + if (defined($t) && $t !~ /^WARN/ && $t !~ /^CRIT/ && $t !~ /^ABSENT/ && $t !~ /^ZERO/ && + $t !~ /^DISPLAY/ && $t !~ /^PERF/ && $t !~ /^SAVED/ && + $t !~ /^PATTERN/ && $t !~ /^NAME/ && $t !~ /^UOM/) { + if (scalar(@tin)==2) { + if (defined($self)) { + $thres->{'WARN'} = $self->parse_threshold($tin[0]); + $thres->{'CRIT'} = $self->parse_threshold($tin[1]); + } + else { + $thres->{'WARN'} = parse_threshold($tin[0]); + $thres->{'CRIT'} = parse_threshold($tin[1]); + } + } + else { + print "Can not parse. Unknown threshold specification: $in\n"; + print "Threshold line should be either both warning and critical thresholds separated by ',' or \n"; + print "new format of: WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN\n"; + print "which allows to specify all 3 (CRIT,WARN,ABSENT) or any one of them in any order\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + # new format with prefix specifying if its WARN or CRIT and support of ABSENT + else { + foreach $t (@tin) { + $t2 = uc $t; + if ($t2 =~ /^WARN\:(.*)/) { + if (defined($self)) { + $thres->{'WARN'} = $self->parse_threshold($1); + } + else { + $thres->{'WARN'} = parse_threshold($1); + } + } + elsif ($t2 =~ /^CRIT\:(.*)/) { + if (defined($self)) { + $thres->{'CRIT'} = $self->parse_threshold($1); + } + else { + $thres->{'CRIT'} = parse_threshold($1); + } + } + elsif ($t2 =~ /^ABSENT\:(.*)/) { + my $val = $1; + if (defined($ERRORS{$val})) { + $thres->{'ABSENT'} = $val; + } + else { + print "Invalid value $val after ABSENT. Acceptable values are: OK, WARNING, CRITICAL, UNKNOWN\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t2 =~ /^ZERO\:(.*)/) { + my $val = $1; + if (exists($ERRORS{$val})) { + $thres->{'ZERO'} = $val; + } + else { + print "Invalid value $val after ZERO. Acceptable values are: OK, WARNING, CRITICAL, UNKNOWN\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t2 =~ /^DISPLAY\:(.*)/) { + if ($1 eq 'YES' || $1 eq 'NO') { + $thres->{'DISPLAY'} = $1; + } + else { + print "Invalid value $1 after DISPLAY. Specify this as YES or NO.\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t2 =~ /^PERF\:(.*)/) { + if ($1 eq 'YES' || $1 eq 'NO') { + $thres->{'PERF'} = $1; + } + else { + print "Invalid value $1 after PERF. Specify this as YES or NO.\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t =~ /^PATTERN\:(.*)/i) { + $thres->{'PATTERN'} = $1; + $self->{'enable_regex_match'} = 2 if defined($self) && $self->{'enable_regex_match'} eq 0; + } + elsif ($t =~ /^NAME\:(.*)/i) { + $thres->{'NAME'} = $1; + } + elsif ($t =~ /^UOM\:(.*)/i) { + $thres->{'UOM'} = $1; + } + else { + print "Can not parse. Unknown threshold specification: $_\n"; + print "Threshold line should be WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,ZERO:OK|WARNING|CRITICAL|UNKNOWN\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + } + if (exists($thres->{'WARN'}) && exists($thres->{'CRIT'})) { + my $check_warncrit = 0; + if (defined($self)) { + $check_warncrit = $self->threshold_specok($thres->{'WARN'},$thres->{'CRIT'}); + } + else { + $check_warncrit = threshold_specok($thres->{'WARN'},$thres->{'CRIT'}); + } + if ($check_warncrit) { + print "All numeric warning values must be less then critical (or greater then when '<' is used)\n"; + print "Note: to override this check prefix warning value with ^\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + return $thres; +} + +# @DESCRIPTION : Adds variable to those whose thresholds would be checked +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - name of the data variable +# ARG2 - either: +# 1) ref to combined thresholds hash array i.e. { 'WARN' => threshold array, 'CRIT' => threshold array, ABSENT => ... } +# such hash array is returned by by parse_thresholds_list function +# -- OR -- +# 2) a tet string with a list of thresholds in the format +# WARN:threshold,CRIT:thresholod,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,ZERO:WARNING|CRITICAL|UNKNOWN,PATTERN:pattern,NAME:name +# which would get parsed y parse_thresholds_list function into ref array +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Recommend function for adding thresholds. Must be used as an object instance function +sub add_thresholds { + my ($self,$var,$th_in) = @_; + my $th; + if (ref($th_in) && (exists($th_in->{'WARN'}) || exists($th_in->{'CRIT'}) || exists($th_in->{'DISPLAY'}) || + exists($th_in->{'PERF'}) || exists($th_in->{'SAVED'}) || exists($th_in->{'ABSENT'}) || + exists($th_in->{'ZERO'}) || exists($th_in->{'PATTERN'}))) { + $th = $th_in; + } + else { + $th = $self->parse_thresholds_list($th_in); + } + if (!defined($var)) { + if (defined($th->{'NAME'})) { + $var = $th->{'NAME'}; + } + elsif (defined($th->{'PATTERN'})) { + $var = $th->{'PATTERN'}; + } + else { + print "Can not parse. No name or pattern in threshold: $th_in\n"; + print "Specify threshold line as: NAME:name,PATTERN:regex,WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,ZERO:OK|WARNING|CRITICAL|UNKNOWN\n"; + $self->usage(); + exit $ERRORS{"UNKNOWN"}; + } + } + push @{$self->{'_allVars'}}, $var if !exists($self->{'_thresholds'}{$var}); + $self->{'_thresholds'}{$var}=$th; +} + +# @DESCRIPTION : Accessor function for thresholds and related variable settings on what and how to check +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - name of data variable +# ARG2 - name of the threshold or related data setting to return +# This can be: "WARN", "CRIT", "ABSENT", "ZERO", "DISPLAY", "PERF" +# @RETURNS : undef if variable does not exist +# if variable exists and "WARN" or "CRIT" thresholds are requested, it returns asociated +# threshold hash array structure for named threshold of the type returned by parse_threshold() +# for ABSENT, ZERO, DISPLAY, PERF and other, it returns a string for this check setting +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub get_threshold { + my ($self,$var,$thname) = @_; + return undef if !exists($self->{'_thresholds'}{$var}) || !exists($self->{'_thresholds'}{$var}{$thname}); + return $self->{'_thresholds'}{$var}{$thname}; +} + +# @DESCRIPTION : Modifier function for thresholds and related variable settings on how to check and display results +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - name of data variable +# ARG2 - type of the threshold or related data setting +# This can be: "WARN", "CRIT", "ABSENT", "ZERO", "DISPLAY", "PERF" +# ARG3 - what to set this to, for "WARN" and "CRIT" this must be hash array returned by parse_threshold() +# @RETURNS : 0 if type you want to set is not one of "WARN", "CRIT", "ZERO" or other acceptable settings +# 1 on success +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub set_threshold { + my ($self,$var,$thname,$thdata) = @_; + if ($thname ne 'WARN' && $thname ne 'CRIT' && $thname ne 'ZERO' && $thname ne 'PATTERN' && $thname ne 'NAME' && + $thname ne 'ABSENT' && $thname ne 'PERF' && $thname ne 'DISPLAY' && $thname ne 'SAVED' && $thname ne 'UOM') { + return 0; + } + $self->{'_thresholds'}{$var}={} if !exists($self->{'_thresholds'}{$var}); + $self->{'_thresholds'}{$var}{$thname}=$thdata; + return 1; +} + +# @DESCRIPTION : Returns list variables for GetOptions(..) that are long-options based on known/defined variable +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : none +# @RETURNS : Array of additional options based on KNOWN_STATS_VARS +# @PRIVACY & USE : PUBLIC, Special use case with GetOpt::Long. Must be used as an object instance function +sub additional_options_list { + my $self = shift; + + my $known_vars = $self->{'knownStatusVars'}; + my ($o_rprefix, $o_rsuffix, $v, $v2) = ('','','',''); + $o_rprefix = $self->{'o_rprefix'} if defined($self->{'o_rprefix'}); + $o_rsuffix = $self->{'o_rsuffix'} if defined($self->{'o_rsuffix'}); + my @VarOptions = (); + + if ($self->{'enable_long_options'} != -1) { + if (defined($self) && defined($known_vars)) { + foreach $v (keys %{$known_vars}) { + if (exists($known_vars->{$v}[3]) && $known_vars->{$v}[3] ne '') { + push @VarOptions,$v."=s"; + if ($self->{'enable_rate_of_change'} eq 1 && $known_vars->{$v}[1] eq 'COUNTER' && ($o_rprefix ne '' || $o_rsuffix ne '')) { + $v2 = $o_rprefix.$v.$o_rsuffix; + push @VarOptions,$v2."=s" + } + } + } + } + } + if (scalar(@VarOptions)>0) { + $self->{'enable_long_options'} = 1; + } + return @VarOptions; +} + +# @DESCRIPTION : Prints out help for generated long options +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : none +# @RETURNS : a string of text for help output +# @PRIVACY & USE : PUBLIC, Special use case with GetOpt::Long. Must be used as an object instance function +sub additional_options_help { + my $self = shift; + my $vname; + my $vname2; + my $counter = 0; + my $known_vars = $self->{'knownStatusVars'}; + + if ($self->{'enable_long_options'} != 1) { return ''; } + + my $out=" These options are all --long_name= + where specifiers are one or more of: + WARN:threshold - warning alert threshold + CRIT:threshold - critical alert threshold + Threshold is a value (usually numeric) which may have the following prefix: + > - warn if data is above this value (default for numeric values) + < - warn if data is below this value (must be followed by number) + = - warn if data is equal to this value (default for non-numeric values) + ! - warn if data is not equal to this value + Threshold can also be specified as a range in two forms: + num1:num2 - warn if data is outside range i.e. if datanum2 + \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 + ABSENT:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if data is absent + ZERO:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if result is 0 + DISPLAY:YES|NO - Specifies if data should be included in nagios status line output + PERF:YES|NO - Output results as performance data or not (always YES if asked for rate) + NAME: - Change the name to in status and PERF output\n\n"; + + # add more options based on KNOWN_STATUS_VARS array + foreach $vname (keys(%{$known_vars})) { + if (exists($known_vars->{$vname}[3])) { + $counter++; + $out .= ' --'.$vname."=WARN:threshold,CRIT:threshold,\n"; + $out .= " ".$known_vars->{$vname}[3]."\n"; + if ($known_vars->{$vname}[1] eq 'COUNTER' && $self->{'enable_rate_of_change'} eq 1) { + $vname2=$o_rprefix.$vname.$o_rsuffix; + $out .= ' --'.$vname2."=WARN:threshold,CRIT:threshold,\n"; + $out .= " Rate of Change of ".$known_vars->{$vname}[3]."\n"; + } + } + } + if ($counter>0) { return $out; } + return ""; +} + +# @DESCRIPTION : Processes standard options parsing out of them variables to be checked +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - Options data hash from GetOpt::Long +# ARG2 - option --verbose or -v or --debug : undef normally and "" or filename if debug enabled +# ARG3 - option --variables or -a in WL's plugins : comma-separated list of variables to check +# ARG4 - option --warn or -w : comma-separated warning thresholds for variables in ARG3 +# ARG5 - option --crit or -c : comma-separated critical thresholds for variables in ARG3 +# ARG6 - option --perf or -f in WL's plugin: all regular variables should also go to perf data +# ARG7 - option --perfvars or -A in WL's plugins: command-separated list of variables whose data goes to PERF output +# ARG8 - prefix to distinguish rate variables, maybe "" but usually this is "rate_" +# ARG9 - suffix to distinguish rate variables, only if ARG7 is "", otherwise optional and absent +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be used shortly after GetOptions. Must be used as an object instance function +sub options_startprocessing { + my ($self, $Options, $o_verb, $o_variables, $o_warn, $o_crit, $o_perf, $o_perfvars, $o_rprefix, $o_rsuffix) = @_; + + # Copy input parameters to object hash array, set them if not present + $o_rprefix="" if !defined($o_rprefix); + $o_rsuffix="" if !defined($o_rsuffix); + $o_crit="" if !defined($o_crit); + $o_warn="" if !defined($o_warn); + $o_variables="" if !defined($o_variables); + $self->{'o_variables'} = $o_variables; + $self->{'o_perfvars'} = $o_perfvars; + $self->{'o_crit'} = $o_crit; + $self->{'o_warn'} = $o_warn; + $self->{'o_perf'} = $o_perf; + $self->{'o_rprefix'} = $o_rprefix; + $self->{'o_rsuffix'} = $o_rsuffix; + $self->{'verbose'} = $o_verb if defined($o_verb); + # start processing + my $perfVars = $self->{'_perfVars'}; + my $ar_varsL = $self->{'_ar_varsL'}; + my $ar_critLv = $self->{'_ar_critLv'}; + my $ar_warnLv = $self->{'_ar_warnLv'}; + my $known_vars = $self->{'knownStatusVars'}; + $o_rprefix = lc $o_rprefix; + $o_rsuffix = lc $o_rsuffix; + # process o_perfvars option + if (defined($o_perfvars)) { + @{$perfVars} = split( /,/ , lc $o_perfvars ); + if (scalar(@{$perfVars})==0) { + $o_perfvars='*'; + $self->{'o_perfvars'}='*'; + } + if ($o_perfvars eq '*') { + $self->{'all_variables_perf'} = 1; + } + else { + # below loop converts rate variables to internal representation + for (my $i=0; $i[$i] = '&'.$1 if $perfVars->[$i] =~ /^$o_rprefix(.*)$o_rsuffix$/; + } + } + } + if (defined($o_warn) || defined($o_crit) || defined($o_variables)) { + if (defined($o_variables)) { + @{$ar_varsL}=split( /,/ , lc $o_variables ); + if (defined($o_warn)) { + $o_warn.="~" if $o_warn =~ /,$/; + @{$ar_warnLv}=split( /,/ , lc $o_warn ); + } + if (defined($o_crit)) { + $o_crit.="~" if $o_crit =~ /,$/; + @{$ar_critLv}=split( /,/ , lc $o_crit ); + } + } + else { + print "Specifying warning or critical thresholds requires specifying list of variables to be checked\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + # this is a special loop to check stats-variables options such as "connected_clients=WARN:warning,CRIT:critical" + # which are specified as long options (new extended threshold line spec introduced in check_redis and check_memcached) + my ($vname,$vname2) = (undef,undef); + foreach $vname (keys(%{$known_vars})) { + $vname2=$o_rprefix.$vname.$o_rsuffix; + if (exists($known_vars->{$vname}[3])) { + if (exists($Options->{$vname})) { + $self->verb("Option $vname found with spec parameter: ".$Options->{$vname}); + $self->add_thresholds($vname,$Options->{$vname}); + } + if (exists($Options->{$vname2})) { + $self->verb("Rate option $vname2 found with spec parameter: ".$Options->{$vname2}); + $self->add_thresholds('&'.$vname,$Options->{$vname2}); + } + } + } + $self->{'_called_options_startprocessing'}=1; +} + +# @DESCRIPTION : Internal function. Parses and sets thresholds for given list of variables after all options have been processed +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PRIVATE, Must be used as an object instance function +sub _options_setthresholds { + my $self = shift; + + my $perfVars = $self->{'_perfVars'}; + my $ar_varsL = $self->{'_ar_varsL'}; + my $ar_critLv = $self->{'_ar_critLv'}; + my $ar_warnLv = $self->{'_ar_warnLv'}; + my $known_vars = $self->{'knownStatusVars'}; + my $thresholds = $self->{'_thresholds'}; + my ($o_rprefix, $o_rsuffix) = ("", ""); + $o_rprefix = $self->{'o_rprefix'} if exists($self->{'o_rprefix'}); + $o_rsuffix = $self->{'o_rsuffix'} if exists($self->{'o_rsuffix'}); + + if (scalar(@{$ar_warnLv})!=scalar(@{$ar_varsL}) || scalar(@{$ar_critLv})!=scalar(@{$ar_varsL})) { + printf "Number of specified warning levels (%d) and critical levels (%d) must be equal to the number of attributes specified at '-a' (%d). If you need to ignore some attribute do it as ',,'\n", scalar(@{$ar_warnLv}), scalar(@{$ar_critLv}), scalar(@{$ar_varsL}); + $self->verb("Warning Levels: ".join(",",@{$ar_warnLv})); + $self->verb("Critical Levels: ".join(",",@{$ar_critLv})); + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + for (my $i=0; $i[$i] = '&'.$1 if $ar_varsL->[$i] =~ /^$o_rprefix(.*)$o_rsuffix$/; + if ($ar_varsL->[$i] =~ /^&(.*)/) { + if (!defined($self->{'o_prevperf'})) { + print "Calculating rate variable such as ".$ar_varsL->[$i]." requires previous performance data. Please add '-P \$SERVICEPERFDATA\$' to your nagios command line.\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + if (defined($known_vars->{$1}) && $known_vars->{$1}[0] ne 'COUNTER') { + print "$1 is not a COUNTER variable for which rate of change should be calculated\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + if (!exists($thresholds->{$ar_varsL->[$i]})) { + my $warn = $self->parse_threshold($ar_warnLv->[$i]); + my $crit = $self->parse_threshold($ar_critLv->[$i]); + if ($self->threshold_specok($warn,$crit)) { + print "All numeric warning values must be less then critical (or greater then when '<' is used)\n"; + print "Note: to override this check prefix warning value with ^\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + $self->add_thresholds($ar_varsL->[$i], {'WARN'=>$warn,'CRIT'=>$crit} ); + } + } +} + +# @DESCRIPTION : Internal helper function. Finds time when previous performance data was calculated/saved at +# @DEVNOTE : Right now this library and function only supports one previous performance data set, +# but check_snmp_netint plugin supports multiple sets and there the code is more complex, +# As this function originated there, that code is commented out right now. +# @LAST CHANGED : 08-21-12 by WL +# @INPUT : ARG1 - reference to previous performance data hash array. It looks for _ptime variable there. +# ARG2 - string with previous performance time in unix seconds. This may come from separate plugin option. +# @RETURNS : Time in unix seconds frm 1970 or undef if it was not located +# @PRIVACY & USE : PRIVATE, Maybe used directly or as an object instance function. +sub _set_prevtime { + my ($self,$prevperf,$o_prevtime) = _self_args(@_); + my $perfcheck_time; + + if (defined($o_prevtime)) { + # push @prev_time, $o_prevtime; + # $prev_perf{ptime}=$o_prevtime; + $perfcheck_time=$o_prevtime; + } + elsif (defined($prevperf) && defined($prevperf->{'_ptime'})) { + # push @prev_time, $prev_perf{ptime}; + $perfcheck_time=$prevperf->{'_ptime'}; + } + else { + # @prev_time=(); + $perfcheck_time=undef; + } + # numeric sort for timestamp array (this is from lowest time to highiest, i.e. to latest) + # my %ptimes=(); + # $ptimes{$_}=$_ foreach @prev_time; + # @prev_time = sort { $a <=> $b } keys(%ptimes); + return $perfcheck_time; +} + +# @DESCRIPTION : Processes standard options, setting up thresholds based on options that are to be checked +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after plugin finished processing its own custom options. Must be used as an object instance function +sub options_finishprocessing { + my $self = shift; + + if (!exists($self->{'_called_options_finishprocessing'})) { + # process previous performance data + my $prevperf = $self->{'_prevPerf'}; + if (defined($self->{'o_prevperf'})) { + if (defined($self->{'o_perf'}) || defined($self->{'o_perfvars'})) { + %{$prevperf}=$self->process_perf($self->{'o_prevperf'}); + $self->{'_perfcheck_time'} = $self->_set_prevtime($prevperf,$self->{'o_prevtime'}); + } + else { + print "--prevperf can only be used with --perf or --perfvars options\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + # set thresholds + $self->_options_setthresholds(); + # prepare data results arrays + my $dataresults = $self->{'_dataresults'}; + my $thresholds = $self->{'_thresholds'}; + $dataresults->{$_} = [undef, 0, 0] foreach(@{$self->{'_allVars'}}); + if (defined($self->{'_perfVars'})) { + foreach(@{$self->{'_perfVars'}}) { + $dataresults->{$_} = [undef, 0, 0] if !exists($dataresults->{$_}); + $thresholds->{$_} = {} if !exists($thresholds->{$_}); + $thresholds->{$_}{'PERF'} = 'YES'; + } + } + # mark as having finished + $self->{'_called_options_finishprocessing'}=1; + } +} + +# @DESCRIPTION : Accessor function for previously saved perfdata +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : ARG1 - varname +# @RETURNS : value of that variable on previous plugin run, undef if not known +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub prev_perf { + my ($self,$var) = @_; + if (defined($self) && defined($self->{'_prevPerf'}{$var})) { + return $self->{'_prevPerf'}{$var}; + } + return undef; +} + +# @DESCRIPTION : Accessor function for exit status code +# @LAST CHANGED : 08-21-12 by WL +# @INPUT : none +# @RETURNS : current expected exit status code +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub statuscode { + my $self = shift; + return $self->{'_statuscode'}; +} + +# @DESCRIPTION : Sets plugin exist status +# @LAST CHANGED : 08-21-12 by WL +# @INPUT : status code string - one of "WARNING", "CRITICAL", "UNKNOWN". +# @RETURNS : 0 on success, 1 if this status code is below level that plugin would exit with and as such it was not set +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub set_statuscode { + my ($self,$newcode) = @_; + + if ($newcode eq 'UNKNOWN') { + $self->{'_statuscode'} = 'UNKNOWN'; + return 0; + } + if ($self->{'_statuscode'} eq 'UNKNOWN') { return 1; } + elsif ($self->{'_statuscode'} eq 'CRITICAL') { + if ($newcode eq 'CRITICAL') { return 0;} + else { return 1; } + } + elsif ($self->{'_statuscode'} eq 'WARNING') { + if ($newcode eq 'CRITICAL') { + $self->{'_statuscode'} ='CRITICAL'; + return 0; + } + elsif ($newcode eq 'WARNING') { return 0; } + else { return 1; } + } + elsif ($self->{'_statuscode'} eq 'OK') { + if ($newcode eq 'CRITICAL' || $newcode eq 'WARNING') { + $self->{'_statuscode'} = $newcode; + return 0; + } + else { return 1; } + } + else { + printf "SYSTEM ERROR: status code $newcode not supported"; + exit $ERRORS{'UNKNOWN'}; + } + return 1; # should never get here +} + +# @DESCRIPTION : This function is called closer to end of the code after plugin retrieved data and +# assigned values to variables. This function checks variables against all thresholds. +# It prepares statusdata and statusinfo and exitcode. +# @LAST CHANGED : 09-03-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after variables have values. Must be used as an object instance function +sub main_checkvars { + my $self = shift; + + $self->options_finishprocessing() if !exists($self->{'_called_options_finshprocessing'}); + if (exists($self->{'_called_main_checkvars'})) { return; } + + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self->{'_dataresults'}; + my $allVars = $self->{'_allVars'}; + my $datavars = $self->{'_datavars'}; + + my ($dvar,$avar,$aname,$perf_str,$chk)=(undef,undef,undef,undef,undef); + + # main loop to check for warning & critical thresholds + for (my $i=0;$i[$i]; + if (!defined($datavars->{$avar}) || scalar(@{$datavars->{$avar}})==0) { + if (defined($thresholds->{$avar}{'ABSENT'})) { + $self->set_statuscode($thresholds->{$avar}{'ABSENT'}); + } + else { + $self->set_statuscode("CRITICAL"); + } + $aname = $self->out_name($avar); + $self->addto_statusinfo_output($avar, "$aname data is missing"); + } + foreach $dvar (@{$datavars->{$avar}}) { + $aname = $self->out_name($dvar); + if (defined($dataresults->{$dvar}[0])) { + # main check + if (defined($avar)) { + if ($dataresults->{$dvar}[0] eq 0 && exists($thresholds->{$avar}{'ZERO'})) { + $self->set_statuscode($thresholds->{$avar}{'ZERO'}); + $self->addto_statusinfo_output($dvar, "$aname is zero") if $self->statuscode() ne 'OK'; + } + else { + $chk=undef; + if (exists($thresholds->{$avar}{'CRIT'})) { + $chk = $self->check_threshold($aname,lc $dataresults->{$dvar}[0], $thresholds->{$avar}{'CRIT'}); + if ($chk) { + $self->set_statuscode("CRITICAL"); + $self->addto_statusinfo_output($dvar,$chk); + } + } + if (exists($thresholds->{$avar}{'WARN'}) && (!defined($chk) || !$chk)) { + $chk = $self->check_threshold($aname,lc $dataresults->{$dvar}[0], $thresholds->{$avar}{'WARN'}); + if ($chk) { + $self->set_statuscode("WARNING"); + $self->addto_statusinfo_output($dvar,$chk); + } + } + } + } + # if we did not output to status line yet, do so + $self->addto_statusdata_output($dvar,$aname." is ".$dataresults->{$dvar}[0]); + + # if we were asked to output performance, prepare it but do not output until later + if ((defined($self->{'o_perf'}) && defined($avar) && !exists($thresholds->{$avar}{'PERF'})) || + (exists($thresholds->{$avar}{'PERF'}) && $thresholds->{$avar}{'PERF'} eq 'YES')) { + $perf_str = perf_name($aname).'='.$dataresults->{$dvar}[0]; + $self->set_perfdata($dvar, $perf_str, undef, "IFNOTSET"); # with undef UOM would get added + $dataresults->{$dvar}[2]=0; # this would clear -1 from preset perf data, making it ready for output + # below is where threshold info gets added to perfdata + if ((exists($thresholds->{$avar}{'WARN'}[5]) && $thresholds->{$avar}{'WARN'}[5] ne '') || + (exists($thresholds->{$avar}{'CRIT'}[5]) && $thresholds->{$avar}{'CRIT'}[5] ne '')) { + $perf_str = ';'; + $perf_str .= $thresholds->{$avar}{'WARN'}[5] if exists($thresholds->{$avar}{'WARN'}[5]) && $thresholds->{$avar}{'WARN'}[5] ne ''; + $perf_str .= ';'.$thresholds->{$avar}{'CRIT'}[5] if exists($thresholds->{$avar}{'CRIT'}[5]) && $thresholds->{$avar}{'CRIT'}[5] ne ''; + $self->set_perfdata($dvar, $perf_str, '', "ADD"); + } + } + } + } + } + $self->{'_called_main_checkvars'}=1; + # $statusinfo=trim($statusinfo); + # $statusdata=trim($statusdata); +} + +# @DESCRIPTION : This function is at the end. It prepares PERFOUT for output collecting all perf variables data +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after variables have values. Must be used as an object instance function +# Calling this function direcly is optional, its automatically called on 1st call to perfdata() +sub main_perfvars { + my $self = shift; + + my $dataresults = $self->{'_dataresults'}; + my $PERF_OK_STATUS_REGEX = $self->{'perfOKStatusRegex'}; + my $perfVars = $self->{'_perfVars'}; + my $known_vars = $self->{'knownStatusVars'}; + my $datavars = $self->{'_datavars'}; + my $avar; + my $dvar; + + $self->main_checkvars() if !exists($self->{'_called_main_checkvars'}); + if (exists($self->{'_called_main_perfvars'})) { return; } + + for (my $i=0;$i[$i]; + if (!defined($datavars->{$avar}) || scalar(@{$datavars->{$avar}})==0) { + $self->verb("Perfvar: $avar selected for PERFOUT but data not available"); + } + else { + foreach $dvar (@{$datavars->{$avar}}) { + if (defined($dataresults->{$dvar}[0])) { + $self->verb("Perfvar: $dvar ($avar) = ".$dataresults->{$dvar}[0]); + if (!defined($known_vars->{$avar}[1]) || $known_vars->{$avar}[1] =~ /$PERF_OK_STATUS_REGEX/ ) { + $self->addto_perfdata_output($dvar); + } + else { + $self->verb(" -- not adding to perfdata because of it is '".$known_vars->{$avar}[1]."' type variable --"); + } + } + else { + $self->verb("Perfvar: $avar selected for PERFOUT but data not defined"); + } + } + } + } + if (defined($self->{'o_prevperf'})) { + $self->addto_perfdata_output('_ptime', "_ptime=".time(), "REPLACE"); + } + foreach $dvar (keys %{$dataresults}) { + if (defined($dataresults->{$dvar}[3]) && $dataresults->{$dvar}[3] ne '') { + $self->verb("Perfvar (Dataresults Loop): $dvar => ".$dataresults->{$dvar}[3]); + $self->addto_perfdata_output($dvar); + } + } + + $self->{'_called_main_perfvars'}=1; + # $perfdata = trim($perfdata); +} + +# @DESCRIPTION : This function should be called at the very very end, it returns perf data output +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : string of perfdata starting with "|" +# @PRIVACY & USE : PUBLIC, To be called during plugin output. Must be used as an object instance function +sub perfdata { + my $self=shift; + + $self->main_perfvars() if !exists($self->{'_called_main_perfvars'}); + my $perfdata = trim($self->{'_perfdata'}); + if ($perfdata ne '') { + return " | " . $perfdata; + } + return ""; +} + +# @DESCRIPTION : This function is called after data is available and calculates rate variables +# based on current and previous (saved in perfdata) values. +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after variables have values. Must be used as an object instance function +sub calculate_ratevars { + my $self = shift; + + my $prev_perf = $self->{'_prevPerf'}; + my $ptime = $self->{'_perfcheck_time'}; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self->{'_dataresults'}; + my $datavars = $self->{'_datavars'}; + my $allVars = $self->{'_allVars'}; + + my ($avar,$dvar,$nvar) = (undef,undef,undef); + my $timenow=time(); + if (defined($self->{'o_prevperf'}) && (defined($self->{'o_perf'}) || defined($self->{'o_perfvars'}))) { + for (my $i=0;$i[$i] =~ /^&(.*)/) { + $avar = $1; + if (defined($datavars->{$avar}) && scalar(@{$datavars->{$avar}})>0) { + foreach $dvar (@{$datavars->{$avar}}) { + $nvar = '&'.$dvar; + # this forces perfdata output if it was not already + if (defined($dataresults->{$dvar}) && $dataresults->{$dvar}[2]<1 && + (!defined($dataresults->{$dvar}[3]) || $dataresults->{$dvar}[3] eq '')) { + $self->set_perfdata($dvar, perf_name($self->out_name($dvar)).'='.$dataresults->{$dvar}[0], undef, "IFNOTSET"); + $self->set_threshold($dvar,'PERF','YES'); + $self->set_threshold($dvar,'SAVED','YES'); # will replace PERF in the future + } + if (defined($prev_perf->{$dvar}) && defined($ptime)) { + $self->add_data($nvar, + sprintf("%.2f",($dataresults->{$dvar}[0]-$prev_perf->{$dvar})/($timenow-$ptime))); + $self->verb("Calculating Rate of Change for $dvar ($avar) : ".$nvar."=". $self->vardata($nvar)); + } + } + } + } + } + } +} + +} +##################################### END OF THE LIBRARY FUNCTIONS ######################################### + +# process --query options (which maybe repeated, that's why loop) +sub option_query { + my $nlib = shift; + + for(my $i=0;$iverb("Processing query key option: $o_querykey[$i]"); + my @ar=split(/,/, $o_querykey[$i]); + # how to query + my @key_querytype = split(':', uc shift @ar); + $nlib->verb("- processing query type specification: ".join(':',@key_querytype)); + $query[$i] = { 'query_type' => $key_querytype[0] }; + if ($key_querytype[0] eq 'GET' || $key_querytype[0] eq 'LLEN' || + $key_querytype[0] eq 'SLEN' || $key_querytype[0] eq 'HLEN' || + $key_querytype[0] eq 'ZLEN') { + if (scalar(@key_querytype)!=1) { + print "Incorrect specification. GET, LLEN, SLEN, HLEN, ZLEN do not have any arguments\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($key_querytype[0] eq 'HGET' || $key_querytype[0] eq 'HEXISTS' || + $key_querytype[0] eq 'SEXISTS') { + if (scalar(@key_querytype)!=2) { + print "Incorrect specification of HGET, HEXISTS or SEXIST. Must include hash or set member name as an argument.\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + $query[$i]{'element_name'} = $key_querytype[1]; + } + elsif ($key_querytype[0] eq 'LRANGE' || $key_querytype[0] eq 'ZRANGE') { + if ($key_querytype[0] eq 'ZRANGE' && scalar(@key_querytype)!=4) { + print "Incorrect specification of ZRANGE. Must include type and start and end (min and max scores).\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + elsif ($key_querytype[0] eq 'LRANGE' && (scalar(@key_querytype)<2 || scalar(@key_querytype)>4)) { + print "Incorrect specification of LRANGE. Must include type and start and end range.\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + elsif ($key_querytype[1] ne 'MAX' && $key_querytype[1] ne 'MIN' && + $key_querytype[1] ne 'AVG' && $key_querytype[1] ne 'SUM') { + print "Invalid LRANGE/ZRANGE type $key_querytype[1]. This must be either MAX or MIN or AVG or SUM\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + $query[$i]{'query_subtype'} = $key_querytype[1]; + $query[$i]{'query_range_start'} = $key_querytype[2] if defined($key_querytype[2]); + $query[$i]{'query_range_end'} = $key_querytype[3] if defined($key_querytype[3]); + } + else { + print "Invalid key query $key_querytype[0]. Currently supported are GET, LLEN, SLEN, HLEN, ZLEN, HGET, HEXISTS, SEXISTS, LRANGE and ZRANGE.\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + # key to query and how to name it + if (scalar(@ar)==0) { + print "Invalid query specification. Missing query key name\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + my ($key_query,$key_name) = split(':', shift @ar); + $key_name = $key_query if !defined($key_name) || ! $key_name; + $nlib->verb("- variable $key_name will receive data from $key_query"); + $query[$i]{'key_query'} = $key_query; + $query[$i]{'key_name'} = $key_name; + # parse thresholds and finish processing assigning values to arrays + my $th = $nlib->parse_thresholds_list(join(',',@ar)); + if (exists($th->{'ABSENT'})) { + $nlib->verb("- ".$th->{'ABSENT'}." alert will be issued if $key_query is not present"); + $query[$i]{'alert'} = $th->{'ABSENT'}; + } + if (exists($th->{'WARN'})) { + $nlib->verb("- warning threshold ".$th->{'WARN'}." set"); + $query[$i]{'warn'} = $th->{'WARN'}; + } + if (exists($th->{'CRIT'})) { + $nlib->verb("- critical threshold ".$th->{'CRIT'}." set"); + $query[$i]{'crit'} = $th->{'CRIT'}; + } + $nlib->add_thresholds($key_name,$th); + } +} + +# sets password, host, port and other data based on options entered +sub options_setaccess { + if (!defined($o_host)) { print "Please specify hostname (-H)\n"; print_usage(); exit $ERRORS{"UNKNOWN"}; } + if (defined($o_pwfile) && $o_pwfile) { + if ($o_password) { + print "use either -x or -C to enter credentials\n"; print_usage(); exit $ERRORS{"UNKNOWN"}; + } + open my $file, '<', $o_pwfile or die $!; + while (<$file>) { + # Match first non-blank line that doesn't start with a comment + if (!($_ =~ /^\s*#/) && $_ =~ /\S+/) { + chomp($PASSWORD = $_); + last; + } + } + close $file; + print 'Password file is empty' and exit $ERRORS{"UNKNOWN"} if !$PASSWORD; + } + if (defined($o_password) && $o_password) { + $PASSWORD = $o_password; + } + $HOSTNAME = $o_host if defined($o_host); + $PORT = $o_port if defined($o_port); + $TIMEOUT = $o_timeout if defined($o_timeout); + $DATABASE = $o_database if defined($o_database); +} + +# parse command line options +sub check_options { + my $opt; + my $nlib = shift; + my %Options = (); + Getopt::Long::Configure("bundling"); + GetOptions(\%Options, + 'v:s' => \$o_verb, 'verbose:s' => \$o_verb, "debug:s" => \$o_verb, + 'h' => \$o_help, 'help' => \$o_help, + 'H:s' => \$o_host, 'hostname:s' => \$o_host, + 'p:i' => \$o_port, 'port:i' => \$o_port, + 'C:s' => \$o_pwfile, 'credentials:s' => \$o_pwfile, + 'x:s' => \$o_password, 'password:s' => \$o_password, + 'D:s' => \$o_database, 'database:s' => \$o_database, + 't:i' => \$o_timeout, 'timeout:i' => \$o_timeout, + 'V' => \$o_version, 'version' => \$o_version, + 'a:s' => \$o_variables, 'variables:s' => \$o_variables, + 'c:s' => \$o_crit, 'critical:s' => \$o_crit, + 'w:s' => \$o_warn, 'warn:s' => \$o_warn, + 'f:s' => \$o_perf, 'perfparse:s' => \$o_perf, + 'A:s' => \$o_perfvars, 'perfvars:s' => \$o_perfvars, + 'T:s' => \$o_timecheck, 'response_time:s' => \$o_timecheck, + 'R:s' => \$o_hitrate, 'hitrate:s' => \$o_hitrate, + 'r:s' => \$o_repdelay, 'replication_delay:s' => \$o_repdelay, + 'P:s' => \$o_prevperf, 'prev_perfdata:s' => \$o_prevperf, + 'E:s' => \$o_prevtime, 'prev_checktime:s'=> \$o_prevtime, + 'm:s' => \$o_memutilization, 'memory_utilization:s' => \$o_memutilization, + 'M:s' => \$o_totalmemory, 'total_memory:s' => \$o_totalmemory, + 'q=s' => \@o_querykey, 'query=s' => \@o_querykey, + 'o=s' => \@o_check, 'check|option=s' => \@o_check, + 'rate_label:s' => \$o_ratelabel, + map { ($_) } $nlib->additional_options_list() + ); + + ($o_rprefix,$o_rsuffix)=split(/,/,$o_ratelabel) if defined($o_ratelabel) && $o_ratelabel ne ''; + + # Standard nagios plugin required options + if (defined($o_help)) { help($nlib); exit $ERRORS{"UNKNOWN"} }; + if (defined($o_version)) { p_version(); exit $ERRORS{"UNKNOWN"} }; + + # now start options processing in the library + $nlib->options_startprocessing(\%Options, $o_verb, $o_variables, $o_warn, $o_crit, $o_perf, $o_perfvars, $o_rprefix, $o_rsuffix); + + # additional variables/options calculated and added by this plugin + if (defined($o_timecheck) && $o_timecheck ne '') { + $nlib->verb("Processing timecheck thresholds: $o_timecheck"); + $nlib->add_thresholds('response_time',$o_timecheck); + } + if (defined($o_hitrate) && $o_hitrate ne '') { + $nlib->verb("Processing hitrate thresholds: $o_hitrate"); + $nlib->add_thresholds('hitrate',$o_hitrate); + $nlib->set_threshold('hitrate','ZERO','OK') if !defined($nlib->get_threshold('hitrate','ZERO')); # except case of hitrate=0, don't remember why I added it + } + if (defined($o_memutilization) && $o_memutilization ne '') { + $nlib->verb("Processing memory utilization thresholds: $o_memutilization"); + $nlib->add_thresholds('memory_utilization',$o_memutilization); + } + if (defined($o_totalmemory)) { + if ($o_totalmemory =~ /^(\d+)B/) { + $o_totalmemory = $1; + } + elsif ($o_totalmemory =~ /^(\d+)K/) { + $o_totalmemory = $1*1024; + } + elsif ($o_totalmemory =~ /^(\d+)M/) { + $o_totalmemory = $1*1024*1024; + } + elsif ($o_totalmemory =~ /^(\d+)G/) { + $o_totalmemory = $1*1024*1024*1024; + } + elsif ($o_totalmemory !~ /^(\d+)$/) { + print "Total memory value $o_totalmemory can not be interpreted\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + } + if (defined($o_repdelay) && $o_repdelay ne '') { + $nlib->verb("Processing replication delay thresholds: $o_repdelay"); + $nlib->add_thresholds('replication_delay',$o_repdelay); + } + + # general check option, allows to specify everything, can be repeated more than once + foreach $opt (@o_check) { + $nlib->verb("Processing general check option: ".$opt); + $nlib->add_thresholds(undef,$opt); + } + + # query option processing + option_query($nlib); + + # finish it up + $nlib->options_finishprocessing(); + options_setaccess(); +} + +# Get the alarm signal (just in case nagios screws up) +$SIG{'ALRM'} = sub { + $redis->quit if defined($redis); + print ("ERROR: Alarm signal (Nagios time-out)\n"); + exit $ERRORS{"UNKNOWN"}; +}; + +########## MAIN ####### + +my $nlib = Naglio->lib_init('plugin_name' => 'check_redis.pl', + 'plugins_authors' => 'William Leibzon', + 'plugin_description' => 'Redis Monitoring Plugin for Nagios', + 'usage_function' => \&print_usage, + 'enable_long_options' => 1, + 'enable_rate_of_change' => 1); +$nlib->set_knownvars(\%KNOWN_STATUS_VARS, $PERF_OK_STATUS_REGEX); + +check_options($nlib); +$nlib->verb("check_redis.pl plugin version ".$Version); + +# Check global timeout if plugin screws up +if (defined($TIMEOUT)) { + $nlib->verb("Alarm at $TIMEOUT"); + alarm($TIMEOUT); +} +else { + $nlib->verb("no timeout defined : $o_timeout + 10"); + alarm ($o_timeout+10); +} + +# some more variables for processing of the results +my $dbversion = ""; +my $vnam; +my $vval; +my %dbs=(); # database-specific info, this is almost unused right now +my %slaves=(); +my $avar; + +# connect using tcp and verify the port is working +my $sock = new IO::Socket::INET( + PeerAddr => $HOSTNAME, + PeerPort => $PORT, + Proto => 'tcp', +); +if (!$sock) { + print "CRITICAL ERROR - Can not connect to '$HOSTNAME' on port $PORT\n"; + exit $ERRORS{'CRITICAL'}; +} +close($sock); + +# now do connection using Redis library +my $start_time; +my $dsn = $HOSTNAME.":".$PORT; +$nlib->verb("connecting to $dsn"); +$start_time = [ Time::HiRes::gettimeofday() ] if defined($o_timecheck); + +$redis = Redis-> new ( server => $dsn, 'debug' => (defined($o_verb))?1:0 ); + +if ($PASSWORD) { + $redis->auth($PASSWORD); +} +if ($DATABASE) { + $redis->select($DATABASE); +} + +if (!$redis) { + print "CRITICAL ERROR - Redis Library - can not connect to '$HOSTNAME' on port $PORT\n"; + exit $ERRORS{'CRITICAL'}; +} + +if (!$redis->ping) { + print "CRITICAL ERROR - Redis Library - can not ping '$HOSTNAME' on port $PORT\n"; + exit $ERRORS{'CRITICAL'}; +} + +# This returns hashref of various statistics/info data +my $stats = $redis->info(); + +# Check specified key if option -q was used +for (my $i=0; $iverb("Getting redis key: ".$query[$i]{'key_query'}); + $result = $redis->get($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'LLEN') { + $nlib->verb("Getting number of items for list with redis key: ".$query[$i]{'key_query'}); + $result = $redis->llen($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'HLEN') { + $nlib->verb("Getting number of items for hash with redis key: ".$query[$i]{'key_query'}); + $result = $redis->hlen($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'SLEN') { + $nlib->verb("Getting number of items for set with redis key: ".$query[$i]{'key_query'}); + $result = $redis->scard($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'ZLEN') { + $nlib->verb("Getting number of items for sorted set with redis key: ".$query[$i]{'key_query'}); + $result = $redis->zcard($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'HGET') { + $nlib->verb("Getting hash member ".$query[$i]{'element_name'}." with redis key: ".$query[$i]{'key_query'}); + $result = $redis->hget($query[$i]{'key_query'},$query[$i]{'element_name'}); + } + elsif ($query[$i]{'query_type'} eq 'HEXISTS') { + $nlib->verb("Checking if there exists hash member ".$query[$i]{'element_name'}." with redis key: ".$query[$i]{'key_query'}); + $result = $redis->hexists($query[$i]{'key_query'},$query[$i]{'element_name'}); + } + elsif ($query[$i]{'query_type'} eq 'SEXISTS') { + $nlib->verb("Checking if there exists set member ".$query[$i]{'element_name'}." with redis key: ".$query[$i]{'key_query'}); + $result = $redis->sismember($query[$i]{'key_query'},$query[$i]{'element_name'}); + } + elsif ($query[$i]{'query_type'} eq 'LRANGE' || $query[$i]{'query_type'} eq 'ZRANGE') { + my $range_start; + my $range_end; + if (defined($query[$i]{'query_range_start'}) && $query[$i]{'query_range_start'} ne '') { + $range_start=$query[$i]{'query_range_start'}; + } + else { + $range_start=0; + } + if (defined($query[$i]{'query_range_end'}) && $query[$i]{'query_range_end'} ne '') { + $range_end= $query[$i]{'query_range_end'}; + } + elsif ($query[$i]{'query_type'} eq 'LRANGE') { + $nlib->verb("Getting (lrange) redis key: ".$query[$i]{'key_query'}); + $range_end = $redis->llen($query[$i]{'key_query'})-1; + } + else { + print "ERROR - can not do ZRANGE if you do not specify mix and max score."; + exit $ERRORS{"UNKNOWN"}; + } + my @list; + if ($query[$i]{'query_type'} eq 'LRANGE') { + @list = $redis->lrange($query[$i]{'key_query'}, $range_start, $range_end); + } + else { + @list = $redis->zrange($query[$i]{'key_query'}, $range_start, $range_end); + } + if (scalar(@list)>0) { + $result=shift @list; + foreach(@list) { + $result+=$_ if $query[$i]{'query_subtype'} eq 'SUM' || $query[$i]{'query_subtype'} eq 'AVG'; + $result=$_ if ($query[$i]{'query_subtype'} eq 'MIN' && $_ < $result) || + ($query[$i]{'query_subtype'} eq 'MAX' && $_ > $result); + } + $result = $result / (scalar(@list)+1) if $query[$i]{'query_subtype'} eq 'AVG'; + } + } + if (defined($result)) { + $query[$i]{'result'} = $result; + $nlib->add_data($query[$i]{'key_name'}, $result); + $nlib->verb("Result of querying ".$query[$i]{'key_query'}." is: $result"); + } + else { + $nlib->verb("could not get results for ".$query[$i]{'key_query'}); + } + # else { + # if (exists($query[$i]{'alert'}) && $query[$i]{'alert'} ne 'OK') { + # $statuscode=$query[$i]{'alert'} if $statuscode ne 'CRITICAL'; + # $statusinfo.=", " if $statusinfo; + # $statusinfo.= "Query on ".$query[$i]{'key_query'}." did not succeed"; + # } + # } +} + +# end redis session +$redis->quit; + +# load stats data into internal hash array +my $total_keys=0; +my $total_expires=0; +foreach $vnam (keys %{$stats}) { + $vval = $stats->{$vnam}; + if (defined($vval)) { + $nlib->verb("Stats Line: $vnam = $vval"); + if (exists($KNOWN_STATUS_VARS{$vnam}) && $KNOWN_STATUS_VARS{$vnam}[1] eq 'VERSION') { + $dbversion .= $vval; + } + elsif ($vnam =~ /^db\d+$/) { + $dbs{$vnam}= {'name'=>$vnam}; + foreach (split(/,/,$vval)) { + my ($k,$d) = split(/=/,$_); + $nlib->add_data($vnam.'_'.$k,$d); + $dbs{$vnam}{$k}=$d; + $nlib->verb(" - stats data added: ".$vnam.'_'.$k.' = '.$d); + $total_keys+=$d if $k eq 'keys' && Naglio::isnum($d); + $total_expires+=$d if $k eq 'expires' && Naglio::isnum($d); + } + } + elsif ($vnam =~ /~slave/) { + # TODO TODO TODO TODO + } + else { + $nlib->add_data($vnam, $vval); + } + } + else { + $nlib->verb("Stats Data: $vnam = NULL"); + } +} +$nlib->verb("Calculated Data: total_keys=".$total_keys); +$nlib->verb("Calculated Data: total_expires=".$total_expires); +$nlib->add_data('total_keys',$total_keys); +$nlib->add_data('total_expires',$total_expires); + +# Response Time +if (defined($o_timecheck)) { + $nlib->add_data('response_time',Time::HiRes::tv_interval($start_time)); + $nlib->addto_statusdata_output('response_time',sprintf("response in %.3fs",$nlib->vardata('response_time'))); + if (defined($o_perf)) { + $nlib->set_perfdata('response_time','response_time='.$nlib->vardata('response_time'),'s'); + } +} + +# calculate rate variables +$nlib->calculate_ratevars(); + +# Hitrate +my $hitrate=0; +my $hits_total=0; +my $hits_hits=undef; +my $hitrate_all=0; +if (defined($o_hitrate) && defined($nlib->vardata('keyspace_hits')) && defined($nlib->vardata('keyspace_misses'))) { + for $avar ('keyspace_hits', 'keyspace_misses') { + if (defined($o_prevperf) && defined($o_perf)) { + $nlib->set_perfdata($avar,$avar."=".$nlib->vardata($avar),'c'); + } + $hits_hits = $nlib->vardata('keyspace_hits') if $avar eq 'keyspace_hits'; + $hits_total += $nlib->vardata($avar); + } + $nlib->verb("Calculating Hitrate : total=".$hits_total." hits=".$hits_hits); + if (defined($hits_hits) && defined($nlib->prev_perf('keyspace_hits')) && defined($nlib->prev_perf('keyspace_misses')) && $hits_hits > $nlib->prev_perf('keyspace_hits')) { + $hitrate_all = $hits_hits/$hits_total*100 if $hits_total!=0; + $hits_hits -= $nlib->prev_perf('keyspace_hits'); + $hits_total -= $nlib->prev_perf('keyspace_misses'); + $hits_total -= $nlib->prev_perf('keyspace_hits'); + verb("Calculating Hitrate. Adjusted based on previous values. total=".$hits_total." hits=".$hits_hits); + } + if (defined($hits_hits)) { + if ($hits_total!=0) { + $hitrate= sprintf("%.4f", $hits_hits/$hits_total*100); + } + $nlib->add_data('hitrate',$hitrate); + my $sdata .= sprintf(" hitrate is %.2f%%", $hitrate); + $sdata .= sprintf(" (%.2f%% from launch)", $hitrate_all) if ($hitrate_all!=0); + $nlib->addto_statusdata_output('hitrate',$sdata); + if (defined($o_perf)) { + $nlib->set_perfdata('hitrate',"hitrate=$hitrate",'%'); + } + } +} + +# Replication Delay +my $repl_delay=0; +if (defined($o_repdelay) && defined($nlib->vardata('master_last_io_seconds_ago')) && defined($nlib->vardata('role'))) { + if ($nlib->vardata('role') eq 'slave') { + $repl_delay = $nlib->vardata('master_link_down_since_seconds'); + if (!defined($repl_delay) || $repl_delay < $nlib->vardata('master_last_io_seconds_ago')) { + $repl_delay = $nlib->vardata('master_last_io_seconds_ago','s'); + } + if (defined($repl_delay) && $repl_delay>=0) { + $nlib->add_data('replication_delay',$repl_delay); + $nlib->addto_statusdata_output('replication_delay',sprintf("replication_delay is %d", $nlib->vardata('replication_delay'))); + if (defined($o_perf)) { + $nlib->set_perfdata('replication_delay',sprintf("replication_delay=%d", $nlib->vardata('replication_delay'))); + } + } + } +} + +# Memory Use Utilization +if (defined($o_memutilization) && defined($nlib->vardata('used_memory_rss'))) { + if (defined($o_totalmemory)) { + $nlib->add_data('memory_utilization',$nlib->vardata('used_memory_rss')/$o_totalmemory*100); + $nlib->verb('memory utilization % : '.$nlib->vardata('memory_utilization').' = '.$nlib->vardata('used_memory_rss').' (used_memory_rss) / '.$o_totalmemory.' * 100'); + } + elsif ($o_memutilization ne '') { + print "ERROR: Can not calculate memory utilization if you do not specify total memory on a system (-M option)\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + if (defined($o_perf) && defined($nlib->vardata('memory_utilization'))) { + $nlib->set_perfdata('memory_utilization',sprintf(" memory_utilization=%.4f", $nlib->vardata('memory_utilization')),'%'); + } + if (defined($nlib->vardata('used_memory_human')) && defined($nlib->vardata('used_memory_peak_human'))) { + my $sdata="memory use is ".$nlib->vardata('used_memory_human')." ("; + $sdata.='peak '.$nlib->vardata('used_memory_peak_human'); + if (defined($nlib->vardata('memory_utilization'))) { + $sdata.= sprintf(", %.2f%% of max", $nlib->vardata('memory_utilization')); + } + if (defined($nlib->vardata('mem_fragmentation_ratio'))) { + $sdata.=", fragmentation ".$nlib->vardata('mem_fragmentation_ratio').'%'; + } + $sdata.=")"; + $nlib->addto_statusdata_output('memory_utilization',$sdata); + } +} + +# Check thresholds in all variables and prepare status and performance data for output +$nlib->main_checkvars(); +$nlib->main_perfvars(); + +# now output the results +print $nlib->statuscode() . ': '.$nlib->statusinfo(); +print " - " if $nlib->statusinfo(); +print "REDIS " . $dbversion . ' on ' . $HOSTNAME. ':'. $PORT; +print ' has '.scalar(keys %dbs).' databases ('.join(',',keys(%dbs)).')'; +print " with $total_keys keys" if $total_keys > 0; +print ', up '.$nlib->uptime_info($nlib->vardata('uptime_in_seconds')) if defined($nlib->vardata('uptime_in_seconds')); +print " - " . $nlib->statusdata() if $nlib->statusdata(); +print $nlib->perfdata(); +print "\n"; + +# end exit +exit $ERRORS{$nlib->statuscode()}; diff --git a/redis/files/check_redis_instances.sh b/redis/files/check_redis_instances.sh new file mode 100644 index 00000000..7821aeb0 --- /dev/null +++ b/redis/files/check_redis_instances.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +# {{ ansible_managed }} + +set -u + +return=0 +nb_crit=0 +nb_warn=0 +nb_ok=0 +nb_unchk=0 +output="" + +packaged_check=/usr/lib/nagios/plugins/check_redis +vendored_check=/usr/local/lib/nagios/plugins/check_redis + +if [ -x $packaged_check ]; then + check_bin=$packaged_check +elif [ -x $vendored_check ]; then + check_bin=$vendored_check +else + echo "UNCHK - can't find check_redis" + exit 3 +fi + +check_server() { + name=$1 + conf_file=$2 + + host=$(config_var "bind" "${conf_file}") + port=$(config_var "port" "${conf_file}") + pass=$(config_var "requirepass" "${conf_file}") + + cmd="${check_bin} -H ${host} -p ${port}" + if [ -n "${pass}" ]; then + cmd="${cmd} -x ${pass}" + fi + result=$($cmd) + ret="${?}" + if [ "${ret}" -ge 2 ]; then + nb_crit=$((nb_crit + 1)) + output="${output}${result}\n" + [ "${return}" -le 2 ] && return=2 + elif [ "${ret}" -ge 1 ]; then + nb_warn=$((nb_warn + 1)) + output="${output}${result}\n" + [ "${return}" -le 1 ] && return=1 + else + nb_ok=$((nb_ok + 1)) + output="${output}${result}\n" + [ "${return}" -le 0 ] && return=0 + fi +} +config_var() { + variable=$1 + file=$2 + test -f "${file}" && grep -E "^${variable}\s+.+$" "${file}" | awk '{ print $2 }' | sed -e "s/^[\"']//" -e "s/[\"']$//" +} + +# default instance +if systemctl is-enabled -q redis-server; then + check_server "default" "/etc/redis/redis.conf" +fi + +# additional instances +conf_files=$(ls -1 /etc/redis-*/redis.conf) +for conf_file in ${conf_files}; do + name=$(dirname "${conf_file}" | sed '{s|/etc/redis-||}') + if systemctl is-enabled -q "redis-server@${name}.service"; then + check_server "${name}" "${conf_file}" + else + nb_unchk=$((nb_unchk + 1)) + output="${output}UNCHK - ${name} (unit is disabled or missing)\n" + fi +done + +[ "${return}" -ge 0 ] && header="OK" +[ "${return}" -ge 1 ] && header="WARNING" +[ "${return}" -ge 2 ] && header="CRITICAL" + +printf "%s - %s UNCHK / %s CRIT / %s WARN / %s OK\n\n" "${header}" "${nb_unchk}" "${nb_crit}" "${nb_warn}" "${nb_ok}" + +printf "%s" "${output}" | grep -E "^CRITICAL" +printf "%s" "${output}" | grep -E "^WARNING" +printf "%s" "${output}" | grep -E "^OK" +printf "%s" "${output}" | grep -E "^UNCHK" + +exit "${return}" diff --git a/redis/files/redis-server@.service b/redis/files/redis-server@.service deleted file mode 100644 index d8f8f5f4..00000000 --- a/redis/files/redis-server@.service +++ /dev/null @@ -1,19 +0,0 @@ -[Unit] -Description=Advanced key-value store -After=network.target - -[Service] -ExecStartPre=/bin/mkdir -m 0755 -p /run/redis/%i -ExecStartPre=/bin/chown redis-%i: /run/redis/%i -PermissionsStartOnly=yes - -Type=forking -ExecStart=/usr/bin/redis-server /etc/redis/redis-%i.conf --unixsocket /run/redis/%i/redis.sock --pidfile /run/redis/%i/redis-server.pid -ExecStop=/usr/bin/redis-cli -s /run/redis/%i/redis.sock shutdown -Restart=always -User=redis-%i -Group=redis-%i -LimitNOFILE=65535 - -[Install] -WantedBy=multi-user.target diff --git a/redis/handlers/main.yml b/redis/handlers/main.yml index c7f66ffb..d85dcbf8 100644 --- a/redis/handlers/main.yml +++ b/redis/handlers/main.yml @@ -1,7 +1,7 @@ --- - name: restart redis systemd: - name: "{{ redis_daemon }}" + name: "{{ redis_systemd_name }}" state: restarted - name: restart redis (noop) @@ -18,3 +18,8 @@ service: name: nagios-nrpe-server state: restarted + +- name: restart log2mail + service: + name: log2mail + state: restarted diff --git a/redis/tasks/default-log2mail.yml b/redis/tasks/default-log2mail.yml new file mode 100644 index 00000000..8614a11d --- /dev/null +++ b/redis/tasks/default-log2mail.yml @@ -0,0 +1,19 @@ +--- + +- name: log2mail config is present + blockinfile: + dest: /etc/log2mail/config/redis.conf + owner: log2mail + group: adm + mode: "0640" + create: yes + marker: "# {mark} ANSIBLE MANAGED RULES FOR DEFAULT INSTANCE" + content: | + file = {{ redis_log_dir }}/redis-server.log + pattern = "Cannot allocate memory" + mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} + template = /etc/log2mail/mail + notify: restart log2mail + tags: + - redis + - log2mail diff --git a/redis/tasks/munin.yml b/redis/tasks/default-munin.yml similarity index 98% rename from redis/tasks/munin.yml rename to redis/tasks/default-munin.yml index 5f2a2dc7..b7edce3a 100644 --- a/redis/tasks/munin.yml +++ b/redis/tasks/default-munin.yml @@ -7,7 +7,7 @@ - redis - include_role: - name: remount-usr + name: evolix/remount-usr tags: - redis diff --git a/redis/tasks/default-server.yml b/redis/tasks/default-server.yml new file mode 100644 index 00000000..08653cfa --- /dev/null +++ b/redis/tasks/default-server.yml @@ -0,0 +1,20 @@ +--- + +- name: Redis is configured. + template: + src: redis.conf.j2 + dest: "{{ redis_conf_dir }}/redis.conf" + mode: "0640" + owner: redis + group: redis + notify: "{{ redis_restart_handler_name }}" + tags: + - redis + +- name: Redis is running and enabled on boot. + systemd: + name: "{{ redis_systemd_name }}" + enabled: yes + state: started + tags: + - redis diff --git a/redis/tasks/instance-log2mail.yml b/redis/tasks/instance-log2mail.yml new file mode 100644 index 00000000..8f853199 --- /dev/null +++ b/redis/tasks/instance-log2mail.yml @@ -0,0 +1,19 @@ +--- + +- name: log2mail config is present + blockinfile: + dest: /etc/log2mail/config/redis.conf + owner: log2mail + group: adm + mode: "0640" + create: yes + marker: "# {mark} ANSIBLE MANAGED RULES FOR INSTANCE {{ redis_instance_name }}" + content: | + file = {{ redis_log_dir }}/redis-server.log + pattern = "Cannot allocate memory" + mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} + template = /etc/log2mail/mail + notify: restart log2mail + tags: + - redis + - log2mail diff --git a/redis/tasks/munin-instances.yml b/redis/tasks/instance-munin.yml similarity index 97% rename from redis/tasks/munin-instances.yml rename to redis/tasks/instance-munin.yml index c8d7cefe..8d0e207c 100644 --- a/redis/tasks/munin-instances.yml +++ b/redis/tasks/instance-munin.yml @@ -7,7 +7,7 @@ - redis - include_role: - name: remount-usr + name: evolix/remount-usr tags: - redis diff --git a/redis/tasks/instance-server.yml b/redis/tasks/instance-server.yml new file mode 100644 index 00000000..e87c6ce9 --- /dev/null +++ b/redis/tasks/instance-server.yml @@ -0,0 +1,135 @@ +--- + +- name: "Instance '{{ redis_instance_name }}' group is present" + group: + name: "redis-{{ redis_instance_name }}" + state: present + system: True + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' user is present" + user: + name: "redis-{{ redis_instance_name }}" + group: "redis-{{ redis_instance_name }}" + state: present + system: True + shell: '/bin/false' + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' config directory is present" + file: + dest: "{{ redis_conf_dir }}" + mode: "0755" + owner: "root" + group: "root" + follow: yes + state: directory + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' config hooks directories are present" + file: + dest: "{{ item }}" + mode: "0755" + owner: "root" + group: "root" + follow: yes + state: directory + with_items: + - "{{ redis_conf_dir }}/redis-server.pre-up.d" + - "{{ redis_conf_dir }}/redis-server.post-up.d" + - "{{ redis_conf_dir }}/redis-server.pre-down.d" + - "{{ redis_conf_dir }}/redis-server.post-down.d" + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '=') + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' hooks examples are present" + command: "cp -a /etc/redis/{{ item }}/00_example {{ redis_conf_dir }}/{{ item }}" + args: + creates: "{{ redis_conf_dir }}/{{ item }}/00_example" + with_items: + - "redis-server.pre-up.d" + - "redis-server.post-up.d" + - "redis-server.pre-down.d" + - "redis-server.post-down.d" + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '=') + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' other directories are present" + file: + dest: "{{ item }}" + mode: "0750" + owner: "redis-{{ redis_instance_name }}" + group: "redis-{{ redis_instance_name }}" + follow: yes + state: directory + with_items: + - "{{ redis_pid_dir }}" + - "{{ redis_socket_dir }}" + - "{{ redis_data_dir }}" + - "{{ redis_log_dir }}" + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' configuration file is present" + template: + src: redis.conf.j2 + dest: "{{ redis_conf_dir }}/redis.conf" + mode: "0640" + owner: redis-{{ redis_instance_name }} + group: redis-{{ redis_instance_name }} + notify: "{{ redis_restart_handler_name }}" + tags: + - redis + +- name: Systemd template for redis instances is installed (Debian 8) + template: + src: 'redis-server@jessie.service.j2' + dest: '/etc/systemd/system/redis-server@.service' + mode: "0644" + owner: "root" + group: "root" + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '<') + tags: + - redis + +- name: Systemd template for redis instances is installed (Debian 9 or later) + template: + src: 'redis-server@stretch.service.j2' + dest: '/etc/systemd/system/redis-server@.service' + mode: "0644" + owner: "root" + group: "root" + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '>=') + tags: + - redis + +- name: "Instance '{{ redis_instance_name }}' systemd unit is enabled and started" + systemd: + name: "{{ redis_systemd_name }}" + enabled: yes + state: started + daemon_reload: yes + tags: + - redis + +- name: Redis SysVinit script is stopped and disabled + service: + name: "redis-server" + enabled: no + state: stopped + when: redis_default_server_disabled + tags: + - redis diff --git a/redis/tasks/instances.yml b/redis/tasks/instances.yml deleted file mode 100644 index fabe6118..00000000 --- a/redis/tasks/instances.yml +++ /dev/null @@ -1,87 +0,0 @@ ---- - -# - name: "Set variables for the instance '{{ redis_instance_name }}'" -# set_fact: -# redis_daemon: "redis-server@{{ redis_instance_name }}" -# redis_conf_path: "/etc/redis/redis-{{ redis_instance_name }}.conf" -# redis_unixsocket: "/var/run/redis/{{ redis_instance_name }}/redis.sock" -# redis_pidfile: "/var/run/redis/{{ redis_instance_name }}/{{ redis_daemon }}.pid" -# redis_logfile: "/var/log/redis/{{ redis_instance_name }}/redis-server.log" -# redis_dbdir: "/var/lib/redis/{{ redis_instance_name }}" -# tags: -# - redis - -- name: Systemd template for redis instances is installed - copy: - src: 'redis-server@.service' - dest: '/etc/systemd/system/' - mode: "0644" - tags: - - redis - -- name: Redis SysVinit script is stopped and disabled - service: - name: "redis-server" - enabled: no - state: stopped - tags: - - redis - -- name: "Redis instance '{{ redis_instance_name }}' configuration file is present" - template: - src: redis.conf.j2 - dest: "{{ redis_conf_path }}" - mode: "0644" - tags: - - redis - -- name: "Redis instance '{{ redis_instance_name }}' group is present" - group: - name: "redis-{{ redis_instance_name }}" - state: present - system: True - tags: - - redis - -- name: "Redis instance '{{ redis_instance_name }}' user is present" - user: - name: "redis-{{ redis_instance_name }}" - group: "redis-{{ redis_instance_name }}" - state: present - system: True - shell: '/bin/false' - tags: - - redis - -- name: "Ensure redis base folders will be accessible for all instances" - file: - dest: "{{ item }}" - state: directory - mode: "0755" - owner: "redis" - group: "redis" - with_items: - - "/var/lib/redis" - - "/var/log/redis" - -- name: "Instances '{{ redis_instance_name }}' directories are present" - file: - dest: "{{ item }}" - state: directory - mode: "0755" - owner: "redis-{{ redis_instance_name }}" - group: "redis-{{ redis_instance_name }}" - with_items: - - "{{ redis_dbdir }}" - - "{{ redis_logfile | dirname }}" - tags: - - redis - -- name: "Redis '{{ redis_instance_name }}' systemd unit is enabled and started" - systemd: - name: "{{ redis_daemon }}" - enabled: yes - state: started - daemon_reload: yes - tags: - - redis diff --git a/redis/tasks/main.yml b/redis/tasks/main.yml index b0dce043..28fda65a 100644 --- a/redis/tasks/main.yml +++ b/redis/tasks/main.yml @@ -5,15 +5,23 @@ - name: Redis is installed. apt: - name: "{{ item }}" + name: + - redis-server + - redis-tools state: present - with_items: - - redis-server - - redis-tools tags: - redis - packages +- name: Redis Sentinel is installed. + apt: + name: "redis-sentinel" + state: present + tags: + - redis + - packages + when: redis_sentinel_install + - name: Get Redis version shell: "redis-server -v | grep -Eo '(v=\\S+)' | cut -d'=' -f 2 | grep -E '^([0-9]|\\.)+$'" changed_when: false @@ -28,34 +36,32 @@ tags: - redis -- include: instances.yml - vars: - redis_daemon: "redis-server@{{ redis_instance_name }}" - redis_conf_path: "/etc/redis/redis-{{ redis_instance_name }}.conf" - redis_unixsocket: "/var/run/redis/{{ redis_instance_name }}/redis.sock" - redis_pidfile: "/var/run/redis/{{ redis_instance_name }}/{{ redis_daemon }}.pid" - redis_logfile: "/var/log/redis/{{ redis_instance_name }}/redis-server.log" - redis_dbdir: "/var/lib/redis/{{ redis_instance_name }}" +- name: set variables for default mode + set_fact: + redis_conf_dir: "{{ redis_conf_dir_prefix }}" + redis_socket_dir: "{{ redis_socket_dir_prefix }}" + redis_pid_dir: "{{ redis_pid_dir_prefix }}" + redis_log_dir: "{{ redis_log_dir_prefix }}" + redis_data_dir: "{{ redis_data_dir_prefix }}" + when: redis_instance_name is not defined + +- name: set variables for instance mode + set_fact: + redis_systemd_name: "redis-server@{{ redis_instance_name }}" + redis_conf_dir: "{{ redis_conf_dir_prefix }}-{{ redis_instance_name }}" + redis_socket_dir: "{{ redis_socket_dir_prefix }}-{{ redis_instance_name }}" + redis_pid_dir: "{{ redis_pid_dir_prefix }}-{{ redis_instance_name }}" + redis_log_dir: "{{ redis_log_dir_prefix }}-{{ redis_instance_name }}" + redis_data_dir: "{{ redis_data_dir_prefix }}-{{ redis_instance_name }}" when: redis_instance_name is defined -- name: Redis is configured. - template: - src: redis.conf.j2 - dest: "{{ redis_conf_path }}" - mode: "0644" - notify: "{{ redis_restart_handler_name }}" +- name: configure Redis for default mode + include: default-server.yml when: redis_instance_name is not defined - tags: - - redis -- name: Redis is running and enabled on boot. - systemd: - name: "{{ redis_daemon }}" - enabled: yes - state: started - when: redis_instance_name is not defined - tags: - - redis +- name: configure Redis for instance mode + include: instance-server.yml + when: redis_instance_name is defined - name: Is Munin installed stat: @@ -65,16 +71,18 @@ - redis - munin -- include: munin.yml +- name: configure Munin for default mode + include: default-munin.yml when: - _munin_installed.stat.exists - _munin_installed.stat.isdir - - redis_instance_name is not defined + - redis_instance_name is undefined tags: - redis - munin -- include: munin-instances.yml +- name: configure Munin for instance mode + include: instance-munin.yml when: - _munin_installed.stat.exists - _munin_installed.stat.isdir @@ -83,6 +91,34 @@ - redis - munin +- name: Is log2mail installed + stat: + path: /etc/log2mail/config + register: _log2mail_installed + tags: + - redis + - log2mail + +- name: configure log2mail for default mode + include: default-log2mail.yml + when: + - _log2mail_installed.stat.exists + - _log2mail_installed.stat.isdir + - redis_instance_name is undefined + tags: + - redis + - log2mail + +- name: configure log2mail for instance mode + include: instance-log2mail.yml + when: + - _log2mail_installed.stat.exists + - _log2mail_installed.stat.isdir + - redis_instance_name is defined + tags: + - redis + - log2mail + - name: is NRPE present ? stat: path: /etc/nagios/nrpe.d/evolix.cfg @@ -92,8 +128,8 @@ - redis - nrpe -- include: nrpe_stretch.yml - when: ansible_distribution_release == "stretch" and nrpe_evolix_config.stat.exists == true +- include: nrpe.yml + when: nrpe_evolix_config.stat.exists tags: - redis - nrpe diff --git a/redis/tasks/nrpe.yml b/redis/tasks/nrpe.yml new file mode 100644 index 00000000..f499ed2f --- /dev/null +++ b/redis/tasks/nrpe.yml @@ -0,0 +1,115 @@ +--- + +- name: Install perl lib-redis (needed by check_redis) + apt: + name: libredis-perl + state: present + tags: + - redis + - nrpe + +- name: install check_redis on Jessie + copy: + src: check_redis.pl + dest: /usr/local/lib/nagios/plugins/check_redis + force: yes + mode: "0755" + owner: root + group: root + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '<') + tags: + - redis + - nrpe + +- name: set the path of check_redis on Jessie + set_fact: + redis_check_redis_path: /usr/local/lib/nagios/plugins/check_redis + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '<') + tags: + - redis + - nrpe + +- name: set the path of check_redis on Stretch and later + set_fact: + redis_check_redis_path: /usr/lib/nagios/plugins/check_redis + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('9', '>=') + tags: + - redis + - nrpe + +- name: sudo without password for nagios + lineinfile: + dest: /etc/sudoers.d/evolinux + regexp: 'check_redis$' + line: 'nagios ALL = NOPASSWD: {{ redis_check_redis_path }}' + insertafter: '^nagios' + validate: "visudo -cf %s" + when: redis_instance_name is defined + tags: + - redis + - nrpe + +- name: Use check_redis for NRPE + replace: + dest: /etc/nagios/nrpe.d/evolix.cfg + regexp: '^command\[check_redis\]=.+' + replace: 'command[check_redis]=sudo {{ redis_check_redis_path }} -H {{ redis_bind_interface }} -p {{ redis_port }}' + when: redis_instance_name is undefined + notify: restart nagios-nrpe-server + tags: + - redis + - nrpe + +- name: sudo without password for nagios + lineinfile: + dest: /etc/sudoers.d/evolinux + regexp: 'check_redis$' + line: 'nagios ALL = NOPASSWD: {{ redis_check_redis_path }}' + insertafter: '^nagios' + validate: "visudo -cf %s" + when: redis_instance_name is undefined + tags: + - redis + - nrpe + +- name: install check_redis_instances + copy: + src: check_redis_instances.sh + dest: /usr/local/lib/nagios/plugins/check_redis_instances + force: yes + mode: "0755" + owner: root + group: root + when: redis_instance_name is defined + tags: + - redis + - nrpe + +- name: Use check_redis_instances for NRPE + replace: + dest: /etc/nagios/nrpe.d/evolix.cfg + regexp: '^command\[check_redis\]=.+' + replace: 'command[check_redis]=sudo /usr/local/lib/nagios/plugins/check_redis_instances' + when: redis_instance_name is defined + notify: restart nagios-nrpe-server + tags: + - redis + - nrpe + +- name: sudo without password for nagios + lineinfile: + dest: /etc/sudoers.d/evolinux + regexp: 'check_redis_instances$' + line: 'nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_redis_instances' + insertafter: '^nagios' + validate: "visudo -cf %s" + when: redis_instance_name is defined + tags: + - redis + - nrpe diff --git a/redis/tasks/nrpe_stretch.yml b/redis/tasks/nrpe_stretch.yml deleted file mode 100644 index 5b547071..00000000 --- a/redis/tasks/nrpe_stretch.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -- name: Install perl lib-redis (needed by check_redis) - apt: - name: libredis-perl - state: present - tags: - - redis - - nrpe - -- name: Replace check_tcp by check_redis for NRPE - replace: - dest: /etc/nagios/nrpe.d/evolix.cfg - regexp: '^command\[check_redis\]=.+' - replace: 'command[check_redis]=/usr/lib/nagios/plugins/check_redis -H 127.0.0.1' - when: redis_instance_name is undefined - notify: restart nagios-nrpe-server - tags: - - redis - - nrpe - -- name: Replace check_tcp or check_redis by check_redis_instances for NRPE - replace: - dest: /etc/nagios/nrpe.d/evolix.cfg - regexp: '^command\[check_redis\]=.+' - replace: 'command[check_redis]=/usr/local/lib/nagios/plugins/check_redis_instances' - when: redis_instance_name is defined - notify: restart nagios-nrpe-server - tags: - - redis - - nrpe diff --git a/redis/templates/redis-server@jessie.service.j2 b/redis/templates/redis-server@jessie.service.j2 new file mode 100644 index 00000000..7d2195d7 --- /dev/null +++ b/redis/templates/redis-server@jessie.service.j2 @@ -0,0 +1,19 @@ +[Unit] +Description=Advanced key-value store +After=network.target + +[Service] +Type=forking +PermissionsStartOnly=True +User=redis-%i +Group=redis-%i +ExecStartPre=/bin/mkdir -p {{ redis_socket_dir_prefix }}-%i {{ redis_pid_dir_prefix }}-%i +ExecStartPre=/bin/chown redis-%i:redis-%i {{ redis_socket_dir_prefix }}-%i {{ redis_pid_dir_prefix }}-%i +ExecStartPre=/bin/chmod 0750 {{ redis_socket_dir_prefix }}-%i {{ redis_pid_dir_prefix }}-%i +ExecStart=/usr/bin/redis-server {{ redis_conf_dir_prefix }}-%i/redis.conf --unixsocket {{ redis_socket_dir_prefix }}-%i/redis.sock --pidfile {{ redis_pid_dir_prefix }}-%i/redis-server.pid +ExecStop=/usr/bin/redis-cli -s {{ redis_socket_dir_prefix }}-%i/redis.sock shutdown +ExecStopPost=/bin/rm -rf {{ redis_socket_dir_prefix }}-%i {{ redis_pid_dir_prefix }}-%i +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/redis/templates/redis-server@stretch.service.j2 b/redis/templates/redis-server@stretch.service.j2 new file mode 100644 index 00000000..ea29da5e --- /dev/null +++ b/redis/templates/redis-server@stretch.service.j2 @@ -0,0 +1,39 @@ +[Unit] +Description=Advanced key-value store +After=network.target + +[Service] +Type=forking +ExecStart=/usr/bin/redis-server {{ redis_conf_dir_prefix }}-%i/redis.conf +PIDFile=/var/run/redis-%i/redis-server.pid +TimeoutStopSec=0 +Restart=always +User=redis-%i +Group=redis-%i +RuntimeDirectory=redis-%i + +ExecStartPre=-/bin/run-parts --verbose {{ redis_conf_dir_prefix }}-%i/redis-server.pre-up.d +ExecStartPost=-/bin/run-parts --verbose {{ redis_conf_dir_prefix }}-%i/redis-server.post-up.d +ExecStop=-/bin/run-parts --verbose {{ redis_conf_dir_prefix }}-%i/redis-server.pre-down.d +ExecStop=/bin/kill -s TERM $MAINPID +ExecStopPost=-/bin/run-parts --verbose {{ redis_conf_dir_prefix }}-%i/redis-server.post-down.d + +UMask=007 +PrivateTmp=yes +LimitNOFILE=65535 +PrivateDevices=yes +ProtectHome={{ redis_data_dir_prefix | match('/home') | ternary('no', 'yes') }} +ReadOnlyDirectories=/ +ReadWriteDirectories=-{{ redis_data_dir_prefix }}-%i +ReadWriteDirectories=-{{ redis_log_dir_prefix }}-%i +ReadWriteDirectories=-{{ redis_pid_dir_prefix }}-%i +ReadWriteDirectories=-{{ redis_socket_dir_prefix }}-%i +CapabilityBoundingSet=~CAP_SYS_PTRACE + +# redis-server writes its own config file when in cluster mode so we allow +# writing there (NB. ProtectSystem=true over ProtectSystem=full) +ProtectSystem=true +ReadWriteDirectories=-{{ redis_conf_dir_prefix }}-%i + +[Install] +WantedBy=multi-user.target diff --git a/redis/templates/redis.conf.j2 b/redis/templates/redis.conf.j2 index f2f17c70..b10a11b9 100644 --- a/redis/templates/redis.conf.j2 +++ b/redis/templates/redis.conf.j2 @@ -1,11 +1,11 @@ daemonize yes -pidfile {{ redis_pidfile }} +pidfile {{ redis_pid_dir }}/redis-server.pid port {{ redis_port }} bind {{ redis_bind_interface }} -{% if redis_unixsocket %} -unixsocket {{ redis_unixsocket }} -unixsocketperm {{ redis_unixsocketperm }} +{% if redis_socket_enabled %} +unixsocket {{ redis_socket_dir }}/redis.sock +unixsocketperm {{ redis_socket_perms }} {% endif %} {% if redis_password %} @@ -17,8 +17,8 @@ masterauth {{ redis_password_master }} timeout {{ redis_timeout }} -loglevel {{ redis_loglevel }} -logfile {{ redis_logfile }} +loglevel {{ redis_log_level }} +logfile {{ redis_log_dir }}/redis-server.log # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. @@ -32,15 +32,17 @@ databases {{ redis_databases }} save {{ save }} {% endfor %} -rdbcompression {{ redis_rdbcompression }} -dbfilename {{ redis_dbfilename }} -dir {{ redis_dbdir }} +rdbcompression {{ redis_rdbcompression | bool | ternary('yes','no') }} +dbfilename {{ redis_data_file }} +dir {{ redis_data_dir }} -{% if redis_installed_version | version_compare('3.2', '>=') %} -protected-mode {{ redis_protected_mode }} +{% if redis_installed_version is version('3.2', '>=') %} +protected-mode {{ redis_protected_mode | bool | ternary('yes','no') }} {% endif %} -# maxclients 128 +{% if redis_maxclients %} +maxclients {{ redis_maxclients }} +{% endif %} {% if redis_maxmemory %} maxmemory {{ redis_maxmemory }} @@ -48,7 +50,7 @@ maxmemory-policy {{ redis_maxmemory_policy }} maxmemory-samples {{ redis_maxmemory_samples }} {% endif %} -appendonly {{ redis_appendonly }} +appendonly {{ redis_appendonly | bool | ternary('yes','no') }} appendfsync {{ redis_appendfsync }} no-appendfsync-on-rewrite no diff --git a/redmine/meta/main.yml b/redmine/meta/main.yml index 72b1bd7b..ffa48751 100644 --- a/redmine/meta/main.yml +++ b/redmine/meta/main.yml @@ -1,3 +1,3 @@ --- dependencies: - - nginx + - { role: evolix/nginx } diff --git a/redmine/tasks/main.yml b/redmine/tasks/main.yml index 2b7fd225..eb5c5915 100644 --- a/redmine/tasks/main.yml +++ b/redmine/tasks/main.yml @@ -3,7 +3,7 @@ - include: syslog.yml - include: user.yml - include_role: - name: rbenv + name: evolix/rbenv vars: - username: "{{ redmine_user }}" - include: config.yml diff --git a/redmine/tasks/packages.yml b/redmine/tasks/packages.yml index 2ac00fe9..b2be8faa 100644 --- a/redmine/tasks/packages.yml +++ b/redmine/tasks/packages.yml @@ -1,21 +1,20 @@ --- - name: Install dependancy apt: - name: "{{ item }}" + name: + - libpam-systemd + - imagemagick + - git-core + - git-svn + - gcc + - build-essential + - libxml2-dev + - libxslt1-dev + - libssl-dev + - libmagickwand-dev + - libmagickcore-dev + - libmariadbclient-dev + - python-mysqldb state: present - with_items: - - libpam-systemd - - imagemagick - - git-core - - git-svn - - gcc - - build-essential - - libxml2-dev - - libxslt1-dev - - libssl-dev - - libmagickwand-dev - - libmagickcore-dev - - libmariadbclient-dev - - python-mysqldb tags: - redmine diff --git a/remount-usr/README.md b/remount-usr/README.md index 8457200c..d1f2691e 100644 --- a/remount-usr/README.md +++ b/remount-usr/README.md @@ -9,5 +9,5 @@ Include this role in task before write on /usr partition (eg. copy a file) : ~~~ - include_role: - name: remount-usr + name: evolix/remount-usr ~~~ diff --git a/spamassasin/meta/main.yml b/spamassasin/meta/main.yml index 510b6855..ae652cf8 100644 --- a/spamassasin/meta/main.yml +++ b/spamassasin/meta/main.yml @@ -1,3 +1,3 @@ --- dependencies: - - { role: amavis } + - { role: evolix/amavis } diff --git a/spamassasin/tasks/main.yml b/spamassasin/tasks/main.yml index 3d2efe0f..53c6725b 100644 --- a/spamassasin/tasks/main.yml +++ b/spamassasin/tasks/main.yml @@ -1,13 +1,12 @@ --- - name: install SpamAssasin apt: - name: "{{ item }}" + name: + - spamassassin + - evomaintenance state: present - with_items: - - spamassassin - - evomaintenance tags: - - spamassassin + - spamassassin - name: configure SpamAssasin copy: @@ -16,7 +15,7 @@ mode: "0644" notify: restart spamassassin tags: - - spamassassin + - spamassassin - name: enable SpamAssasin replace: @@ -25,7 +24,7 @@ replace: 'ENABLED=1' notify: restart spamassassin tags: - - spamassassin + - spamassassin - name: add amavis user to debian-spamd group user: @@ -33,7 +32,7 @@ groups: debian-spamd append: yes tags: - - spamassassin + - spamassassin - name: fix right on /var/lib/spamassassin file: @@ -41,12 +40,12 @@ state: directory mode: "0750" tags: - - spamassassin + - spamassassin - include_role: - name: remount-usr + name: evolix/remount-usr tags: - - spamassassin + - spamassassin - name: copy sa-update.sh script copy: @@ -54,8 +53,14 @@ dest: /usr/share/scripts/sa-update.sh mode: "0750" tags: - - spamassassin + - spamassassin +- name: Check if cron is installed + shell: "dpkg -l cron 2> /dev/null | grep -q -E '^(i|h)i'" + failed_when: False + changed_when: False + register: is_cron_installed + - name: enable sa-update.sh cron lineinfile: dest: /etc/cron.d/sa-update @@ -63,14 +68,15 @@ create: yes state: present mode: "0640" + when: is_cron_installed.rc == 0 tags: - - spamassassin + - spamassassin - name: update SpamAssasin's rules command: "/usr/share/scripts/sa-update.sh" changed_when: false tags: - - spamassassin + - spamassassin - name: ensure SpamAssasin is started and enabled systemd: @@ -78,4 +84,4 @@ state: started enabled: True tags: - - spamassassin + - spamassassin diff --git a/squid/README.md b/squid/README.md index a2d5c29f..8811a91f 100644 --- a/squid/README.md +++ b/squid/README.md @@ -18,3 +18,6 @@ A blank file is created at `/etc/squid3/whitelist-custom.conf` to add addresses The full list of variables (with default values) can be found in `defaults/main.yml`. + +**Warning** : if squid has been installed with `squid_localproxy_enable: False`, it can't be simply switched to `True` and re-run. +You have to purge the squid package, remove the configuration `rm -rf /etc/squid* /etc/default/squid*` and then re-run the playbook. diff --git a/squid/files/evolinux-defaults.conf b/squid/files/evolinux-defaults.conf index ef11ea69..26bc7b27 100644 --- a/squid/files/evolinux-defaults.conf +++ b/squid/files/evolinux-defaults.conf @@ -2,6 +2,7 @@ http_port 127.0.0.1:3128 coredump_dir /var/spool/squid max_filedescriptors 4096 +shutdown_lifetime 0 seconds acl SSL_ports port 443 acl Safe_ports port 80 # http acl Safe_ports port 21 # ftp diff --git a/squid/files/systemd-override.conf b/squid/files/systemd-override.conf new file mode 100644 index 00000000..eab76a17 --- /dev/null +++ b/squid/files/systemd-override.conf @@ -0,0 +1,4 @@ +# systemd override for Squid +[Service] +ExecStart= +ExecStart=/usr/sbin/squid -sYC -f /etc/squid/evolinux-defaults.conf diff --git a/squid/tasks/main.yml b/squid/tasks/main.yml index 17429954..68f721f8 100644 --- a/squid/tasks/main.yml +++ b/squid/tasks/main.yml @@ -3,7 +3,7 @@ - fail: msg: only compatible with Debian >= 8 when: - - ansible_distribution != "Debian" or ansible_distribution_major_version | version_compare('8', '<') + - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') - name: "Set squid name (jessie)" set_fact: @@ -13,21 +13,20 @@ - name: "Set squid name (Debian 9 or later)" set_fact: squid_daemon_name: squid - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: "Install Squid packages" apt: - name: '{{ item }}' + name: + - "{{ squid_daemon_name }}" + - squidclient state: present - with_items: - - "{{ squid_daemon_name }}" - - squidclient - name: "Set alternative config file (Debian 9 or later)" copy: src: default_squid dest: /etc/default/squid - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: "squid.conf is present (jessie)" template: @@ -49,14 +48,14 @@ src: evolinux-defaults.conf dest: /etc/squid/evolinux-defaults.conf notify: "restart squid" - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: "evolinux defaults whitelist (Debian 9 or later)" copy: src: evolinux-whitelist-defaults.conf dest: /etc/squid/evolinux-whitelist-defaults.conf notify: "reload squid" - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom whitelist (Debian 9 or later)" copy: @@ -64,7 +63,7 @@ content: | # Put customized values here. force: no - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: "evolinux acl for local proxy (Debian 9 or later)" template: @@ -72,7 +71,7 @@ dest: /etc/squid/evolinux-acl.conf force: no notify: "reload squid" - when: squid_localproxy_enable and ansible_distribution_major_version | version_compare('9', '>=') + when: squid_localproxy_enable and ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom acl (Debian 9 or later)" copy: @@ -80,7 +79,7 @@ content: | # Put customized values here. force: no - when: squid_localproxy_enable == False and ansible_distribution_major_version | version_compare('9', '>=') + when: squid_localproxy_enable == False and ansible_distribution_major_version is version('9', '>=') - name: "evolinux http_access for local proxy (Debian 9 or later)" copy: @@ -88,7 +87,7 @@ dest: /etc/squid/evolinux-httpaccess.conf force: no notify: "reload squid" - when: squid_localproxy_enable and ansible_distribution_major_version | version_compare('9', '>=') + when: squid_localproxy_enable and ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom http_access (Debian 9 or later)" copy: @@ -96,7 +95,7 @@ content: | # Put customized values here. force: no - when: squid_localproxy_enable == False and ansible_distribution_major_version | version_compare('9', '>=') + when: squid_localproxy_enable == False and ansible_distribution_major_version is version('9', '>=') - name: "evolinux overrides for local proxy (Debian 9 or later)" template: @@ -104,7 +103,7 @@ dest: /etc/squid/evolinux-custom.conf force: no notify: "reload squid" - when: squid_localproxy_enable and ansible_distribution_major_version | version_compare('9', '>=') + when: squid_localproxy_enable and ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom overrides (Debian 9 or later)" copy: @@ -112,7 +111,7 @@ content: | # Put customized values here. force: no - when: squid_localproxy_enable == False and ansible_distribution_major_version | version_compare('9', '>=') + when: squid_localproxy_enable == False and ansible_distribution_major_version is version('9', '>=') - name: add some URL in whitelist (Debian 8) lineinfile: @@ -132,13 +131,16 @@ state: present with_items: '{{ squid_whitelist_items }}' notify: "reload squid" - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') + +- include: systemd.yml + when: ansible_distribution_major_version is version('10', '>=') - include: logrotate_jessie.yml when: ansible_distribution_release == "jessie" - include: logrotate_stretch.yml - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - include: minifirewall.yml diff --git a/squid/tasks/systemd.yml b/squid/tasks/systemd.yml new file mode 100644 index 00000000..4d06fa5d --- /dev/null +++ b/squid/tasks/systemd.yml @@ -0,0 +1,32 @@ +--- + +- name: Look for existing systemd unit + command: systemctl -q is-active squid.service + changed_when: False + failed_when: False + check_mode: no + register: _squid_systemd_active + +- name: Squid systemd overrides directory exists + file: + dest: /etc/systemd/system/squid.service.d/ + state: directory + owner: root + group: root + mode: "0755" + +- name: "Squid systemd unit service is present" + copy: + src: systemd-override.conf + dest: /etc/systemd/system/squid.service.d/override.conf + force: yes + register: _squid_systemd_override + +- name: "Systemd daemon is reloaded and Squid restarted" + systemd: + name: squid + state: restarted + daemon_reload: yes + when: + - _squid_systemd_active.rc == 0 + - _squid_systemd_override.changed diff --git a/tomcat-instance/meta/main.yml b/tomcat-instance/meta/main.yml index 1197f736..692e2d44 100644 --- a/tomcat-instance/meta/main.yml +++ b/tomcat-instance/meta/main.yml @@ -15,4 +15,4 @@ galaxy_info: - jessie dependencies: -- tomcat + - { role: evolix/tomcat } diff --git a/tomcat-instance/tasks/user.yml b/tomcat-instance/tasks/user.yml index f60a05ac..a4a7bcb2 100644 --- a/tomcat-instance/tasks/user.yml +++ b/tomcat-instance/tasks/user.yml @@ -37,7 +37,7 @@ - name: Run newaliases command: newaliases - when: tomcat_instance_mail_alias | changed + when: tomcat_instance_mail_alias is changed - name: Enable sudo right lineinfile: diff --git a/tomcat/defaults/main.yml b/tomcat/defaults/main.yml index 74440548..e555e2cd 100644 --- a/tomcat/defaults/main.yml +++ b/tomcat/defaults/main.yml @@ -1,3 +1,2 @@ --- tomcat_instance_root: '/srv/tomcat' -tomat_version: 8 diff --git a/tomcat/tasks/nagios.yml b/tomcat/tasks/nagios.yml index 69c99810..1eb297cf 100644 --- a/tomcat/tasks/nagios.yml +++ b/tomcat/tasks/nagios.yml @@ -5,7 +5,7 @@ state: present - include_role: - name: remount-usr + name: evolix/remount-usr - name: Create Nagios plugins dir file: diff --git a/tomcat/tasks/packages.yml b/tomcat/tasks/packages.yml index 29307736..1d26f176 100644 --- a/tomcat/tasks/packages.yml +++ b/tomcat/tasks/packages.yml @@ -1,12 +1,33 @@ --- + +- name: Set Tomcat version to 7 on Debian 8 if missing + set_fact: + tomcat_version: 7 + when: + - ansible_distribution_release == "jessie" + - tomcat_version is not defined + +- name: Set Tomcat version to 8 on Debian 9 if missing + set_fact: + tomcat_version: 8 + when: + - ansible_distribution_release == "stretch" + - tomcat_version is not defined + +- name: Set Tomcat version to 9 on Debian 10 if missing + set_fact: + tomcat_version: 9 + when: + - ansible_distribution_release == "buster" + - tomcat_version is not defined + - name: Install packages apt: - name: "{{ item }}" + name: + - "tomcat{{ tomcat_version }}" + - "tomcat{{ tomcat_version }}-user" + - "libpam-systemd" state: present - with_items: - - "tomcat{{ tomcat_version }}" - - "tomcat{{ tomcat_version }}-user" - - "libpam-systemd" - name: Create tomcat root dir file: diff --git a/varnish/tasks/main.yml b/varnish/tasks/main.yml index 72884655..c55218ef 100644 --- a/varnish/tasks/main.yml +++ b/varnish/tasks/main.yml @@ -18,13 +18,14 @@ tags: - varnish -- name: Copy Custom Varnish ExecReload script +- name: Copy Custom Varnish ExecReload script (Debian <=9) copy: src: "reload-vcl.sh" dest: "/etc/varnish/reload-vcl.sh" mode: "0700" owner: root group: root + when: ansible_distribution_major_version is version('9', '<=') notify: reload varnish tags: - varnish diff --git a/varnish/tasks/munin.yml b/varnish/tasks/munin.yml index 552c8a34..6e307c49 100644 --- a/varnish/tasks/munin.yml +++ b/varnish/tasks/munin.yml @@ -5,7 +5,7 @@ tags: varnish - include_role: - name: remount-usr + name: evolix/remount-usr tags: varnish - name: Create plugin directory diff --git a/vrrpd/tasks/main.yml b/vrrpd/tasks/main.yml index b4f9b118..b6f4e7f5 100644 --- a/vrrpd/tasks/main.yml +++ b/vrrpd/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Install Evolix public repositry include_role: - name: apt + name: evolix/apt tasks_from: evolix_public.yml - name: Install vrrpd packages diff --git a/webapps/evoadmin-web/README.md b/webapps/evoadmin-web/README.md new file mode 100644 index 00000000..977764ad --- /dev/null +++ b/webapps/evoadmin-web/README.md @@ -0,0 +1,46 @@ +# Set custom web-add.conf file +- "templates/evoadmin-web/web-add.{{ inventory_hostname }}.conf.j2" +- "templates/evoadmin-web/web-add.{{ host_group }}.conf.j2" +- "templates/evoadmin-web/web-add.conf.j2" +And force it to update: + evoadmin_add_conf_force: True + +# Set custom web-mail.tpl +- "templates/evoadmin-web/web-mail.{{ inventory_hostname }}.tpl.j2" +- "templates/evoadmin-web/web-mail.{{ host_group }}.tpl.j2" +- "templates/evoadmin-web/web-mail.tpl.j2" +And force it to update: + evoadmin_mail_tpl_force: True + +# Set custom evoadmin.conf VHost +- "templates/evoadmin-web/evoadmin.{{ inventory_hostname }}.conf.j2" +- "templates/evoadmin-web/evoadmin.{{ host_group }}.conf.j2" +- "templates/evoadmin-web/evoadmin.conf.j2" +And force it to update: + evoadmin_force_vhost: True + +# Set custom config.local.php +- "templates/evoadmin-web/config.local.{{ inventory_hostname }}.php.j2" +- "templates/evoadmin-web/config.local.{{ host_group }}.php.j2" +- "templates/evoadmin-web/config.local.php.j2" +And force it to update: + evoadmin_config_local_php_force: True + +# Set evoadmin-web sudoers file +- "templates/evoadmin-web/sudoers.{{ inventory_hostname }}.j2" +- "templates/evoadmin-web/sudoers.{{ host_group }}.j2" +- "templates/evoadmin-web/sudoers.j2" +- "sudoers.j2" +And force it to update: + evoadmin_sudoers_conf_force: True + +# Set evoadmin-web sudoers file +evoadmin_htpasswd: True + +Overwrite its template: +- "templates/evoadmin-web/htpasswd.{{ inventory_hostname }}.j2" +- "templates/evoadmin-web/htpasswd.{{ host_group }}.j2" +- "templates/evoadmin-web/htpasswd.j2" +- "htpasswd.j2" +And force it to update: + evoadmin_htpasswd_force: True diff --git a/webapps/evoadmin-web/defaults/main.yml b/webapps/evoadmin-web/defaults/main.yml index d0aa6eba..5b28a67c 100644 --- a/webapps/evoadmin-web/defaults/main.yml +++ b/webapps/evoadmin-web/defaults/main.yml @@ -10,11 +10,19 @@ evoadmin_scripts_dir: /usr/share/scripts/evoadmin evoadmin_host: "evoadmin.{{ ansible_fqdn }}" evoadmin_username: evoadmin +evoadmin_multiphp_versions: [] evoadmin_enable_vhost: True -evoadmin_multi_php: False + +evoadmin_force_vhost: False +evoadmin_config_local_php_force: False +evoadmin_sudoers_conf_force: False +evoadmin_add_conf_force: False +evoadmin_mail_tpl_force: True +evoadmin_htpasswd_force: False +evoadmin_htpasswd: False evoadmin_tpl_servername: "{{ ansible_fqdn }}" -evoadmin_tpl_address: "{{ ansible_default_ipv4.address }}" +evoadmin_tpl_address: "{{ ansible_default_ipv4.address }}" evoadmin_tpl_phpmyadmin_url: Null evoadmin_tpl_cgi_suffix: Null evoadmin_tpl_signature: evoadmin diff --git a/webapps/evoadmin-web/files/phpContainer b/webapps/evoadmin-web/files/phpContainer deleted file mode 100644 index 9d8a6e28..00000000 --- a/webapps/evoadmin-web/files/phpContainer +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -evolixContainerVersion=$(grep SetHandler /etc/apache2/sites-enabled/$LOGNAME.conf 2>/dev/null | grep -m 1 -o 'fpm[0-9][0-9]' | head -n 1 | sed 's/php//g' | sed 's/fpm//g') -if [ "$evolixContainerVersion" != "" ]; then - lxc-attach -n php$evolixContainerVersion -- su - $LOGNAME -c "cd \"${PWD@E}\" && php ${*@Q}" -else - # TODO: fallback? - # command php $* - echo "could not determine \$evolixContainerVersion" - exit 1 -fi diff --git a/webapps/evoadmin-web/meta/main.yml b/webapps/evoadmin-web/meta/main.yml index 3fbe0627..cf2ad4e5 100644 --- a/webapps/evoadmin-web/meta/main.yml +++ b/webapps/evoadmin-web/meta/main.yml @@ -1,3 +1,4 @@ +--- galaxy_info: author: Evolix description: Installation of evoadmin-web @@ -9,10 +10,10 @@ galaxy_info: min_ansible_version: 2.2 platforms: - - name: Debian - versions: - - jessie - - stretch + - name: Debian + versions: + - jessie + - stretch dependencies: - - proftpd + - { role: evolix/proftpd } diff --git a/webapps/evoadmin-web/tasks/config.yml b/webapps/evoadmin-web/tasks/config.yml index 3788f5b3..689a217e 100644 --- a/webapps/evoadmin-web/tasks/config.yml +++ b/webapps/evoadmin-web/tasks/config.yml @@ -3,22 +3,29 @@ - name: "Create /etc/evolinux" file: dest: "/etc/evolinux" - recurse: yes + recurse: True state: directory - name: Configure web-add config file template: - src: web-add.conf.j2 + src: "{{ item }}" dest: /etc/evolinux/web-add.conf + force: "{{ evoadmin_add_conf_force }}" + with_first_found: + - "templates/evoadmin-web/web-add.{{ inventory_hostname }}.conf.j2" + - "templates/evoadmin-web/web-add.{{ host_group }}.conf.j2" + - "templates/evoadmin-web/web-add.conf.j2" + - "web-add.conf.j2" + register: evoadmin_add_conf_template - name: Configure web-add template file for mail template: - src: web-mail.tpl.j2 + src: "{{ item }}" dest: "{{ evoadmin_scripts_dir }}/web-mail.tpl" - -- name: Copy multi php-cli script - copy: - src: phpContainer - dest: /usr/local/bin/phpContainer - mode: 0700 - when: evoadmin_multi_php == True + force: "{{ evoadmin_mail_tpl_force }}" + with_first_found: + - "templates/evoadmin-web/web-mail.{{ inventory_hostname }}.tpl.j2" + - "templates/evoadmin-web/web-mail.{{ host_group }}.tpl.j2" + - "templates/evoadmin-web/web-mail.tpl.j2" + - "web-mail.tpl.j2" + register: evoadmin_mail_tpl_template diff --git a/webapps/evoadmin-web/tasks/ftp.yml b/webapps/evoadmin-web/tasks/ftp.yml index d78d50ff..98f275ff 100644 --- a/webapps/evoadmin-web/tasks/ftp.yml +++ b/webapps/evoadmin-web/tasks/ftp.yml @@ -7,6 +7,6 @@ - name: Patch ProFTPd config file patch: - remote_src: no + remote_src: False src: ftp/evolinux.conf.diff dest: /etc/proftpd/conf.d/z-evolinux.conf diff --git a/webapps/evoadmin-web/tasks/packages.yml b/webapps/evoadmin-web/tasks/packages.yml index 79d1e312..7d3f6051 100644 --- a/webapps/evoadmin-web/tasks/packages.yml +++ b/webapps/evoadmin-web/tasks/packages.yml @@ -1,22 +1,39 @@ --- - include_role: - name: apt + name: evolix/apt tasks_from: evolix_public.yml -- name: Install PHP packages +- name: Install PHP packages (Debian 10 and later) apt: - name: '{{ item }}' + name: php-pear state: present - with_items: - - php-pear - - php-log + when: ansible_distribution_major_version is version('10', '>=') -- name: Install PHP5 packages +# /!\ Warning, this is a temporary hack +- include_role: + name: evolix/remount-usr + +# /!\ Warning, this is a temporary hack +- name: Install PHP packages from sid (Debian 10 and later) apt: - name: '{{ item }}' + deb: '{{ item }}' state: present - allow_unauthenticated: yes with_items: - - php5-pam + - 'http://mirror.evolix.org/debian/pool/main/p/php-log/php-log_1.12.9-2_all.deb' + when: ansible_distribution_major_version is version('10', '>=') + +- name: Install PHP packages (stretch) + apt: + name: + - php-pear + - php-log + state: present + when: ansible_distribution_release == "stretch" + +- name: Install PHP5 packages (jessie) + apt: + name: php5-pam + state: present + allow_unauthenticated: True when: ansible_distribution_release == "jessie" diff --git a/webapps/evoadmin-web/tasks/user.yml b/webapps/evoadmin-web/tasks/user.yml index 460dc28e..5aa6c29c 100644 --- a/webapps/evoadmin-web/tasks/user.yml +++ b/webapps/evoadmin-web/tasks/user.yml @@ -4,7 +4,7 @@ user: name: evoadmin comment: "Evoadmin Web Account" - home: "{{ evoadmin_home_dir}}" + home: "{{ evoadmin_home_dir }}" password: "!" - name: Create www-evoadmin group @@ -16,13 +16,13 @@ user: name: www-evoadmin groups: shadow - append: yes + append: True when: ansible_distribution_release == "jessie" - name: "Create www-evoadmin (Debian 9 or later)" user: name: www-evoadmin - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '>=') - name: Is /etc/aliases present? stat: @@ -36,8 +36,10 @@ regexp: "{{ item.regexp }}" state: present with_items: - - { line: 'evoadmin: root', regexp: '^evoadmin:'} - - { line: 'www-evoadmin: root', regexp: '^www-evoadmin:'} + - line: 'evoadmin: root' + regexp: '^evoadmin:' + - line: 'www-evoadmin: root' + regexp: '^www-evoadmin:' notify: "newaliases" when: etc_aliases.stat.exists @@ -51,7 +53,7 @@ repo: https://forge.evolix.org/evoadmin-web.git dest: "{{ evoadmin_document_root }}" version: jessie - update: no + update: False when: ansible_distribution_release == "jessie" - name: "Clone evoadmin repository (Debian 9 or later)" @@ -59,15 +61,15 @@ repo: https://forge.evolix.org/evoadmin-web.git dest: "{{ evoadmin_document_root }}" version: master - update: yes - when: ansible_distribution_major_version | version_compare('9', '>=') + update: False + when: ansible_distribution_major_version is version('9', '>=') - name: Change ownership on git repository file: dest: "{{ evoadmin_document_root }}" owner: "{{ evoadmin_username }}" group: "{{ evoadmin_username }}" - recurse: yes + recurse: True - name: Create evoadmin log directory file: @@ -77,13 +79,13 @@ state: directory - include_role: - name: remount-usr - when: evoadmin_scripts_dir | search ("/usr") + name: evolix/remount-usr + when: evoadmin_scripts_dir is search ("/usr") - name: "Create {{ evoadmin_scripts_dir }}" file: dest: "{{ evoadmin_scripts_dir }}" - # recurse: yes + # recurse: True mode: "0700" state: directory @@ -99,17 +101,18 @@ changed_when: "'changed' in command_result.stdout" # failed_when: False args: - warn: no + warn: False - name: Add evoadmin sudoers file template: - src: sudoers.j2 + src: "{{ item }}" dest: /etc/sudoers.d/evoadmin mode: "0600" + force: "{{ evoadmin_sudoers_conf_force }}" validate: "visudo -cf %s" - -- name: Modify bashrc skel file - lineinfile: - dest: /etc/skel/.bashrc - line: "alias php='sudo /usr/local/bin/phpContainer'" - when: evoadmin_multi_php == True + with_first_found: + - "templates/evoadmin-web/sudoers.{{ inventory_hostname }}.j2" + - "templates/evoadmin-web/sudoers.{{ host_group }}.j2" + - "templates/evoadmin-web/sudoers.j2" + - "sudoers.j2" + register: evoadmin_sudoers_conf diff --git a/webapps/evoadmin-web/tasks/web.yml b/webapps/evoadmin-web/tasks/web.yml index 7cd79b96..cafccc72 100644 --- a/webapps/evoadmin-web/tasks/web.yml +++ b/webapps/evoadmin-web/tasks/web.yml @@ -9,19 +9,35 @@ notify: reload apache2 when: ansible_distribution_release == "jessie" -- name: "Set custom values for PHP config (Debian 9 or later)" +- name: "Set custom values for PHP config (Debian 9)" ini_file: dest: /etc/php/7.0/apache2/conf.d/zzz-evolinux-custom.ini section: PHP option: "disable_functions" value: "shell-exec,system,passthru,putenv,popen" notify: reload apache2 - when: ansible_distribution_major_version | version_compare('9', '>=') + when: ansible_distribution_major_version is version('9', '=') + +- name: "Set custom values for PHP config (Debian 10)" + ini_file: + dest: /etc/php/7.3/apache2/conf.d/zzz-evolinux-custom.ini + section: PHP + option: "disable_functions" + value: "shell-exec,system,passthru,putenv,popen" + notify: reload apache2 + when: ansible_distribution_major_version is version('10', '=') - name: Install evoadmin VHost template: - src: evoadmin.conf.j2 + src: "{{ item }}" dest: /etc/apache2/sites-available/evoadmin.conf + force: "{{ evoadmin_force_vhost }}" + with_first_found: + - "templates/evoadmin-web/evoadmin.{{ inventory_hostname }}.conf.j2" + - "templates/evoadmin-web/evoadmin.{{ host_group }}.conf.j2" + - "templates/evoadmin-web/evoadmin.conf.j2" + - "evoadmin.conf.j2" + register: evoadmin_vhost_template notify: reload apache2 - name: Enable evoadmin vhost @@ -38,18 +54,33 @@ notify: reload apache2 when: not evoadmin_enable_vhost +- name: Copy htpasswd for evoadmin + template: + src: "{{ item }}" + dest: "/var/www/.ansible_evoadmin_htpasswd" + mode: "0644" + owner: root + group: www-data + force: "{{ evoadmin_htpasswd_force }}" + with_first_found: + - "templates/evoadmin-web/htpasswd.{{ inventory_hostname }}.j2" + - "templates/evoadmin-web/htpasswd.{{ host_group }}.j2" + - "templates/evoadmin-web/htpasswd.j2" + - "htpasswd.j2" + register: evoadmin_htpasswd_template + when: evoadmin_htpasswd + - name: Copy config file for evoadmin template: - src: config.local.php.j2 - dest: "{{ evoadmin_document_root}}/conf/config.local.php" + src: "{{ item }}" + dest: "{{ evoadmin_document_root }}/conf/config.local.php" mode: "0640" owner: evoadmin group: evoadmin - force: no - -- name: Enable proxy_fcgi - apache2_module: - state: present - name: proxy_fcgi - notify: restart apache2 - when: evoadmin_multi_php == True + force: "{{ evoadmin_config_local_php_force }}" + with_first_found: + - "templates/evoadmin-web/config.local.{{ inventory_hostname }}.php.j2" + - "templates/evoadmin-web/config.local.{{ host_group }}.php.j2" + - "templates/evoadmin-web/config.local.php.j2" + - "config.local.php.j2" + register: evoadmin_config_local_php_template diff --git a/webapps/evoadmin-web/templates/config.local.php.j2 b/webapps/evoadmin-web/templates/config.local.php.j2 index 0584675c..d4cd4903 100644 --- a/webapps/evoadmin-web/templates/config.local.php.j2 +++ b/webapps/evoadmin-web/templates/config.local.php.j2 @@ -7,9 +7,11 @@ $localconf['superadmin'] = array(); $localconf['script_path'] = '{{ evoadmin_scripts_dir }}'; $localconf['cluster'] = FALSE; // auth (sha256 hashs) / echo -n YourPass | sha256sum + $oriconf['logins'] = array(); //$oriconf['logins']['foo'] = 'd5d3c723fb82cb0078f399888af78204234535ec2ef3da56710fdd51f90d2477'; //$oriconf['logins']['bar'] = '7938c84d6e43d1659612a7ea7c1101ed02e52751bb64597a8c20ebaba8ba4303'; -{% if evoadmin_multi_php == True %} -$localconf['php_versions'] = array(56, 70, 73); + +{% if evoadmin_multiphp_versions != [] %} +$localconf['php_versions'] = array( {{ evoadmin_multiphp_versions | join(', ') | replace('php', '') }} ); {% endif %} diff --git a/webapps/evoadmin-web/templates/evoadmin.conf.j2 b/webapps/evoadmin-web/templates/evoadmin.conf.j2 index 182cddd7..cbe08bec 100644 --- a/webapps/evoadmin-web/templates/evoadmin.conf.j2 +++ b/webapps/evoadmin-web/templates/evoadmin.conf.j2 @@ -23,7 +23,20 @@ #Options Indexes SymLinksIfOwnerMatch Options SymLinksIfOwnerMatch AllowOverride AuthConfig Limit FileInfo +{% if evoadmin_htpasswd %} + Require all denied + Include /etc/apache2/ipaddr_whitelist.conf + AuthType Basic + AuthName "Restricted" + AuthUserFile "/var/www/.ansible_evoadmin_htpasswd" + Require valid-user + + SecRuleEngine Off + +{% else %} Require all granted +{% endif %} + # user - group (thanks to sesse@debian.org) diff --git a/webapps/evoadmin-web/templates/htpasswd.j2 b/webapps/evoadmin-web/templates/htpasswd.j2 new file mode 100644 index 00000000..5c92307e --- /dev/null +++ b/webapps/evoadmin-web/templates/htpasswd.j2 @@ -0,0 +1 @@ +# use `htpasswd -Bn $user` to create passwords. diff --git a/webapps/evoadmin-web/templates/sudoers.j2 b/webapps/evoadmin-web/templates/sudoers.j2 index 0634f7fc..7926b5f3 100644 --- a/webapps/evoadmin-web/templates/sudoers.j2 +++ b/webapps/evoadmin-web/templates/sudoers.j2 @@ -1,7 +1,3 @@ User_Alias EVOADMIN = www-evoadmin Cmnd_Alias EVOADMIN_WEB = {{ evoadmin_scripts_dir | mandatory }}/web-*.sh, {{ evoadmin_scripts_dir | mandatory }}/ftpadmin.sh, {{ evoadmin_scripts_dir | mandatory }}/dbadmin.sh EVOADMIN ALL=NOPASSWD: EVOADMIN_WEB -{% if evoadmin_multi_php == True %} -Defaults env_keep += "LOGNAME PWD" -ALL ALL = NOPASSWD: /usr/local/bin/phpContainer -{% endif %} diff --git a/webapps/evoadmin-web/templates/web-add.conf.j2 b/webapps/evoadmin-web/templates/web-add.conf.j2 index 4bc41ad8..30b6fb9d 100644 --- a/webapps/evoadmin-web/templates/web-add.conf.j2 +++ b/webapps/evoadmin-web/templates/web-add.conf.j2 @@ -1,5 +1,6 @@ CONTACT_MAIL="{{ evoadmin_contact_email or general_alert_email | mandatory }}" WWWBOUNCE_MAIL="{{ evoadmin_bounce_email or general_alert_email | mandatory }}" -{% if evoadmin_multi_php == True %} -PHP_VERSIONS=(56 70 73) + +{% if evoadmin_multiphp_versions != [] %} +PHP_VERSIONS=({{ evoadmin_multiphp_versions | join(' ') | replace('php', '') }}); {% endif %} diff --git a/webapps/roundcube/tasks/main.yml b/webapps/roundcube/tasks/main.yml index ba65b1f8..2efd1823 100644 --- a/webapps/roundcube/tasks/main.yml +++ b/webapps/roundcube/tasks/main.yml @@ -13,15 +13,14 @@ - name: install Roundcube apt: - name: "{{ item }}" + name: + - imapproxy + - roundcube + - roundcube-sqlite3 + - roundcube-plugins + - php-net-sieve + - php-zip state: present - with_items: - - imapproxy - - roundcube - - roundcube-sqlite3 - - roundcube-plugins - - php-net-sieve - - php-zip tags: - roundcube @@ -88,7 +87,7 @@ - name: deploy apache roundcube vhost template: src: apache2.conf.j2 - dest: /etc/apache2/sites-available/rouncube.conf + dest: /etc/apache2/sites-available/roundcube.conf mode: "0640" notify: reload apache2 when: roundcube_webserver == "apache" @@ -97,8 +96,8 @@ - name: enable apache roundcube vhost file: - src: /etc/apache2/sites-available/rouncube.conf - dest: /etc/apache2/sites-enabled/rouncube.conf + src: /etc/apache2/sites-available/roundcube.conf + dest: /etc/apache2/sites-enabled/roundcube.conf state: link notify: reload apache2 when: roundcube_webserver == "apache" @@ -108,14 +107,14 @@ - name: deploy Nginx roundcube vhost template: src: nginx.conf.j2 - dest: /etc/nginx/sites-available/rouncube.conf + dest: /etc/nginx/sites-available/roundcube.conf when: roundcube_webserver == "nginx" notify: reload nginx - name: enable Nginx roundcube vhost file: - src: "/etc/nginx/sites-available/rouncube.conf" - dest: "/etc/nginx/sites-enabled/rouncube.conf" + src: "/etc/nginx/sites-available/roundcube.conf" + dest: "/etc/nginx/sites-enabled/roundcube.conf" state: link when: roundcube_webserver == "nginx" notify: reload nginx