diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2016-09-29 13:51:44 +0300 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2016-09-29 13:51:44 +0300 |
commit | 62f28d30a069135f9c48678507203958adfc334f (patch) | |
tree | 7f38af0c8d3f445ee8cc50906a639baec7011127 /modules/apps/icinga2.nix | |
parent | 1af9e6589bdd18e6ba7eeabf073aa7d710020cdd (diff) | |
download | nixsap-62f28d30a069135f9c48678507203958adfc334f.tar.gz |
Moved everything into ./modules
Diffstat (limited to 'modules/apps/icinga2.nix')
-rw-r--r-- | modules/apps/icinga2.nix | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/modules/apps/icinga2.nix b/modules/apps/icinga2.nix new file mode 100644 index 0000000..6df18fc --- /dev/null +++ b/modules/apps/icinga2.nix @@ -0,0 +1,375 @@ +{ config, pkgs, lib, ... }: + +let + inherit (builtins) + attrNames dirOf toString ; + inherit (lib) + concatMapStringsSep mapAttrsToList concatStringsSep filter hasPrefix + isString mkEnableOption mkIf mkOption optionalString types ; + inherit (types) + attrsOf bool either enum int listOf path str ; + + environment = { + SSL_CERT_FILE = "/etc/ssl/certs/ca-bundle.crt"; + }; + + cfg = config.nixsap.apps.icinga2; + rundir = "/run/icinga2"; + pidFile = "${rundir}/icinga2.pid"; + + mutableDir = "mutable.d"; + mutableTmpDir = "mutable.tmp.d"; + mutablePath = "${cfg.stateDir}/etc/icinga2/${mutableDir}"; + mutableTmpPath = "${cfg.stateDir}/etc/icinga2/${mutableTmpDir}"; + mutableRestart = "${mutablePath}/restart"; + + icingaMutableUpdate = + let + job = n: j: pkgs.writeBashScript "icinga-mutable-${n}" '' + set -euo pipefail + f='${mutableTmpPath}/${n}.conf' + ${j} > "$f.tmp" + mv -f "$f.tmp" "$f" + ''; + in pkgs.writeBashScript "icinga-mutable-update" '' + set -euo pipefail + + rm -rf ${mutableTmpPath} + mkdir -p ${mutableTmpPath} + + HOME=${rundir} + PARALLEL_SHELL=${pkgs.bash}/bin/bash + export PARALLEL_SHELL + + # shellcheck disable=SC2016 + ${pkgs.parallel}/bin/parallel \ + --delay 2 \ + --halt-on-error 0 \ + --line-buffer \ + --no-notice \ + --no-run-if-empty \ + --rpl '{name} s:^.*-icinga-mutable-(.+)$:$1:' \ + --timeout 120 \ + --tagstr '* {name}:' \ + ::: \ + ${concatStringsSep " " ( + mapAttrsToList job cfg.mutable.conf + )} \ + || exit 1 # WARNING + + old=$(${pkgs.nix}/bin/nix-hash --type sha1 '${mutablePath}') + new=$(${pkgs.nix}/bin/nix-hash --type sha1 '${mutableTmpPath}') + if [ "$old" != "$new" ]; then + ${pkgs.gnused}/bin/sed 's,${mutablePath},${mutableTmpPath},' \ + ${icingaConf} > \ + ${cfg.stateDir}/etc/icinga2/icinga2.tmp.conf + if ! ${pkgs.icinga2}/bin/icinga2 daemon -C -x critical -c ${cfg.stateDir}/etc/icinga2/icinga2.tmp.conf; then + exit 2 # CRITICAL + fi + rm -f ${cfg.stateDir}/etc/icinga2/icinga2.tmp.conf + rm -rf ${mutablePath}.bak + mv -f ${mutablePath} ${mutablePath}.bak + mv -f ${mutableTmpPath} ${mutablePath} + rm -rf ${mutablePath}.bak + if [ -f ${pidFile} ]; then + pid=$(cat ${pidFile}) + if ${pkgs.coreutils}/bin/kill -0 "$pid"; then + touch ${mutableRestart} + ${pkgs.coreutils}/bin/kill -HUP "$pid" + echo "Restart: $old -> $new" + fi + fi + else + echo "No changes: $old" + fi + ''; + + icingaMutableCheckCommand = pkgs.writeText "icinga-${cfg.mutable.checkCommand}.conf" '' + object CheckCommand "${cfg.mutable.checkCommand}" { + import "plugin-check-command" + command = [ "${icingaMutableUpdate}" ] + } + ''; + + icingaConf = pkgs.writeText "icinga2.conf" + '' + const PluginDir = "${pkgs.monitoringPlugins}/libexec" + const RunAsGroup = "${cfg.user}" + const RunAsUser = "${cfg.user}" + + include <itl> + include <plugins> + + object Endpoint NodeName { + host = NodeName + } + object Zone NodeName { + endpoints = [ NodeName ] + } + + include "${cfg.stateDir}/etc/icinga2/features-enabled/*.conf" + include "${cfg.stateDir}/etc/icinga2/conf.d/*.conf" + include_recursive "${cfg.stateDir}/etc/icinga2/repository.d" + include "${mutablePath}/*.conf" + + ${concatMapStringsSep "\n" (f: + if hasPrefix "/" f + then ''include "${f}"'' + else ''include "${pkgs.writeText "icinga2.inc.conf" f}"'' + ) cfg.configFiles} + ''; + + console = pkgs.writeBashScriptBin "icinga2console" '' + if [ -z "$ICINGA2_API_USERNAME" ] && [ -r ${cfg.stateDir}/etc/icinga2/conf.d/api-users.conf ]; then + pwd=$(${pkgs.gnused}/bin/sed -rn 's,.*password\s*=\s*"(.+)".*,\1,p' ${cfg.stateDir}/etc/icinga2/conf.d/api-users.conf) + export ICINGA2_API_USERNAME=root + export ICINGA2_API_PASSWORD="$pwd" + fi + exec ${pkgs.icinga2}/bin/icinga2 console --connect 'https://localhost/' "$@" + ''; + + configureMySQL = pkgs.writeBashScript "icinga2-mysql" '' + set -euo pipefail + nconn=$(icinga2console --eval 'len(get_objects(IdoMysqlConnection))') + nconn=''${nconn%.*} # float to int + if [ "$nconn" -eq 0 ]; then + exit + fi + for i in $( seq 0 $(( nconn - 1 )) ); do + db=$(icinga2console --eval "get_objects(IdoMysqlConnection)[$i].database") + host=$(icinga2console --eval "get_objects(IdoMysqlConnection)[$i].host") + port=$(icinga2console --eval "get_objects(IdoMysqlConnection)[$i].port") + pwd=$(icinga2console --eval "get_objects(IdoMysqlConnection)[$i].password") + user=$(icinga2console --eval "get_objects(IdoMysqlConnection)[$i].user") + + # XXX Removing quotes: + db=''${db%\"} ; db=''${db#\"} + host=''${host%\"} ; host=''${host#\"} + pwd=''${pwd%\"} ; pwd=''${pwd#\"} + user=''${user%\"} ; user=''${user#\"} + port=''${port%.*} + mysql=(${pkgs.mysql}/bin/mysql --no-defaults "-h$host" "-P$port" "-u$user" "--password=$pwd") + while ! "''${mysql[@]}" -e ';'; do + sleep 20s + done + tt=$("''${mysql[@]}" -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '$db';") + # TODO: Migrations: + if [ "$tt" -eq 0 ]; then + "''${mysql[@]}" -v "$db" < ${pkgs.icinga2}/share/icinga2-ido-mysql/schema/mysql.sql + fi + done + ''; + + configureDBs = pkgs.writeBashScriptBin "icinga2db" '' + set -eu + while ! icinga2console -e '"connected to icinga"'; do + sleep 30s + done + ${configureMySQL} + ''; + + preStart = '' + umask 0077 + mkdir -p \ + ${cfg.stateDir}/cache/icinga2 \ + ${cfg.stateDir}/lib/icinga2/api/log \ + ${cfg.stateDir}/lib/icinga2/api/repository \ + ${cfg.stateDir}/lib/icinga2/api/zones \ + ${cfg.stateDir}/log/icinga2/compat/archives \ + ${cfg.stateDir}/log/icinga2/crash \ + ${cfg.stateDir}/spool/icinga2/perfdata \ + ${cfg.stateDir}/spool/icinga2/tmp + + ${pkgs.findutils}/bin/find \ + ${cfg.stateDir}/etc/icinga2 \ + -mindepth 1 -maxdepth 1 \ + -not -name ${mutableDir} \ + -not -name pki \ + -not -name repository.d \ + -exec rm -rf '{}' \; || true + + mkdir -p \ + ${cfg.stateDir}/etc/icinga2/conf.d \ + ${mutablePath} \ + ${cfg.stateDir}/etc/icinga2/repository.d \ + ${cfg.stateDir}/etc/icinga2/features-enabled + ln -sf ${pkgs.icinga2}${cfg.stateDir}/etc/icinga2/features-available \ + ${cfg.stateDir}/etc/icinga2/features-available + ln -sf ${pkgs.icinga2}${cfg.stateDir}/etc/icinga2/scripts \ + ${cfg.stateDir}/etc/icinga2/scripts + + # XXX Can't include in the main file due to infinite recursion + ln -sf ${icingaMutableCheckCommand} \ + ${cfg.stateDir}/etc/icinga2/conf.d/${cfg.mutable.checkCommand}.conf + + # XXX: requires root (!?) + ${pkgs.icinga2}/bin/icinga2 api setup + ${pkgs.icinga2}/bin/icinga2 feature enable checker + ${pkgs.icinga2}/bin/icinga2 feature enable command + ${pkgs.icinga2}/bin/icinga2 feature enable livestatus + + ${optionalString cfg.notifications '' + ${pkgs.icinga2}/bin/icinga2 feature enable notification + ''} + + rm -rf ${rundir} + mkdir --mode=0755 -p ${rundir} + mkdir --mode=2710 -p ${dirOf cfg.commandPipe} + mkdir --mode=2710 -p ${dirOf cfg.livestatusSocket} + chown -R ${cfg.user}:${cfg.user} ${rundir} + chown -Rc ${cfg.user}:${cfg.user} ${cfg.stateDir} + chmod -R u=rwX,g=rX,o= ${cfg.stateDir} + chown ${cfg.user}:${cfg.commandGroup} ${dirOf cfg.commandPipe} + chown ${cfg.user}:${cfg.commandGroup} ${dirOf cfg.livestatusSocket} + ''; + + ExecStart = pkgs.writeBashScript "icinga2-start" '' + set -euo pipefail + + umask 0077 + + printf 'const TicketSalt = "%s"\n' "$(${pkgs.pwgen}/bin/pwgen -1 -s 23)" \ + > ${cfg.stateDir}/etc/icinga2/conf.d/ticketsalt.conf + + if [ -e ${mutableRestart} ]; then + rm ${mutableRestart} + else + ${icingaMutableUpdate} || true + if ! ${pkgs.icinga2}/bin/icinga2 daemon -C -x critical -c ${icingaConf}; then + rm -rf ${mutablePath} + mkdir -p ${mutablePath} + fi + fi + + exec ${pkgs.icinga2}/bin/icinga2 daemon -x ${cfg.logLevel} -c ${icingaConf} + ''; + +in { + + options.nixsap = { + apps.icinga2 = { + enable = mkEnableOption "icinga2"; + + logLevel = mkOption { + description = "Icinga2 daemon log level"; + type = enum [ "debug" "notice" "information" "warning" "critical" ]; + default = "information"; + }; + + notifications = mkOption { + description = "Enable notifications"; + type = bool; + default = false; + }; + + configFiles = mkOption { + description = '' + Configuration files or inline text + to be included in the main file''; + type = listOf (either str path); + }; + + mutable.conf = mkOption { + description = '' + A set of executables to write mutable config files. + ''; + type = attrsOf path; + default = {}; + }; + mutable.checkCommand = mkOption { + description = '' + Name of the mutable check command. You may need to alter this + only in an unlikely case of conflict with your custom commands. + Mutable files are updated every time icinga2 restart. If you want + better control and observability on this, create a service with + this check command. If exists, this service will make icinga2 + restart when mutable files change (and pass syntax check) via + sending the HUP signal to the main icinga2 process. + ''; + type = str; + default = "mutable-conf-refresh"; + }; + + # these are hard-coded into icinga2 package: + user = mkOption { + type = types.str; + description = "User to run as"; + default = "icinga"; + readOnly = true; + }; + + commandGroup = mkOption { + type = types.str; + description = "Dedicated command group for command pipe and livestatus"; + default = "icingacmd"; + readOnly = true; + }; + + stateDir = mkOption { + type = types.path; + description = "Icinga2 logs, state, config files"; + default = "/icinga2"; + readOnly = true; + }; + + commandPipe = mkOption { + type = types.path; + description = "Icinga2 command pipe"; + default = "${rundir}/cmd/icinga2.cmd"; + readOnly = true; + }; + + livestatusSocket = mkOption { + type = types.path; + description = "Icinga2 Livestatus socket"; + default = "${rundir}/cmd/livestatus"; + readOnly = true; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ console ]; + nixsap.apps.icinga2.configFiles = [ + "${pkgs.icinga2}/icinga2/etc/icinga2/conf.d/app.conf" + "${pkgs.icinga2}/icinga2/etc/icinga2/conf.d/commands.conf" + "${pkgs.icinga2}/icinga2/etc/icinga2/conf.d/notifications.conf" + "${pkgs.icinga2}/icinga2/etc/icinga2/conf.d/templates.conf" + "${pkgs.icinga2}/icinga2/etc/icinga2/conf.d/timeperiods.conf" + ]; + nixsap.system.users.daemons = [ cfg.user ]; + nixsap.system.groups = [ cfg.commandGroup ]; + nixsap.deployment.keyrings.${cfg.user} = filter (hasPrefix "/run/keys/") cfg.configFiles; + users.users.${cfg.user}.extraGroups = [ "proc" ]; + systemd.services.icinga2 = { + description = "Icinga2 daemon"; + after = [ "local-fs.target" "keys.target" "network.target" ]; + wants = [ "keys.target" ]; + wantedBy = [ "multi-user.target" ]; + inherit environment preStart; + serviceConfig = { + inherit ExecStart; + KillMode = "mixed"; + PermissionsStartOnly = true; + Restart = "always"; + TimeoutSec = 600; + User = cfg.user; + }; + }; + + systemd.services.icinga2db = { + description = "Icinga2 databases configurator"; + after = [ "icinga2.service" ]; + wantedBy = [ "multi-user.target" ]; + path = [ console ]; + inherit environment; + serviceConfig = { + ExecStart = "${configureDBs}/bin/icinga2db"; + User = cfg.user; + RemainAfterExit = true; + Restart = "on-failure"; + }; + }; + }; +} + |