From 70092c9acbfb1a7f15814d817686f19e363821f3 Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Thu, 11 Jul 2019 18:54:15 -0400 Subject: nixos/zabbixAgent & nixos/zabbixServer: various module updates --- nixos/modules/rename.nix | 4 + nixos/modules/services/monitoring/zabbix-agent.nix | 163 ++++++---- .../modules/services/monitoring/zabbix-server.nix | 352 +++++++++++++++------ 3 files changed, 357 insertions(+), 162 deletions(-) (limited to 'nixos/modules') diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 1b77a895d71..e127782e85f 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -171,6 +171,9 @@ with lib; The starting time can be configured via services.postgresqlBackup.startAt. '') + # zabbixServer + (mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ]) + # Profile splitting (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ]) @@ -214,6 +217,7 @@ with lib; (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd") (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.") + (mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.") # ZSH (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ]) diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix index 0519e7c2ad6..b1645f86110 100644 --- a/nixos/modules/services/monitoring/zabbix-agent.nix +++ b/nixos/modules/services/monitoring/zabbix-agent.nix @@ -1,73 +1,118 @@ -# Zabbix agent daemon. { config, lib, pkgs, ... }: -with lib; - let - cfg = config.services.zabbixAgent; - zabbix = cfg.package; - - stateDir = "/run/zabbix"; - - logDir = "/var/log/zabbix"; - - pidFile = "${stateDir}/zabbix_agentd.pid"; + inherit (lib) mkDefault mkEnableOption mkIf mkOption; + inherit (lib) attrValues concatMapStringsSep literalExample optionalString types; - configFile = pkgs.writeText "zabbix_agentd.conf" - '' - Server = ${cfg.server} + user = "zabbix-agent"; + group = "zabbix-agent"; - LogFile = ${logDir}/zabbix_agentd - - PidFile = ${pidFile} - - StartAgents = 1 + moduleEnv = pkgs.symlinkJoin { + name = "zabbix-agent-module-env"; + paths = attrValues cfg.modules; + }; - ${config.services.zabbixAgent.extraConfig} - ''; + configFile = pkgs.writeText "zabbix_agent.conf" '' + LogType = console + Server = ${cfg.server} + ListenIP = ${cfg.listen.ip} + ListenPort = ${toString cfg.listen.port} + ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"} + ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)} + ${cfg.extraConfig} + ''; in { - - ###### interface + # interface options = { services.zabbixAgent = { + enable = mkEnableOption "the Zabbix Agent"; - enable = mkOption { - default = false; + package = mkOption { + type = types.package; + default = pkgs.zabbix.agent; + defaultText = "pkgs.zabbix.agent"; + description = "The Zabbix package to use."; + }; + + extraPackages = mkOption { + type = types.listOf types.package; + default = with pkgs; [ nettools ]; + defaultText = "[ nettools ]"; + example = "[ nettools mysql ]"; description = '' - Whether to run the Zabbix monitoring agent on this machine. - It will send monitoring data to a Zabbix server. + Packages to be added to the Zabbix PATH. + Typically used to add executables for scripts, but can be anything. ''; }; - package = mkOption { - type = types.attrs; # Note: pkgs.zabbixXY isn't a derivation, but an attrset of { server = ...; agent = ...; }. - default = pkgs.zabbix; - defaultText = "pkgs.zabbix"; - example = literalExample "pkgs.zabbix34"; - description = '' - The Zabbix package to use. + modules = mkOption { + type = types.attrsOf types.package; + description = "A set of modules to load."; + default = {}; + example = literalExample '' + { + "dummy.so" = pkgs.stdenv.mkDerivation { + name = "zabbix-dummy-module-''${cfg.package.version}"; + src = cfg.package.src; + buildInputs = [ cfg.package ]; + sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy"; + installPhase = ''' + mkdir -p $out/lib + cp dummy.so $out/lib/ + '''; + }; + } ''; }; server = mkOption { - default = "127.0.0.1"; + type = types.str; description = '' The IP address or hostname of the Zabbix server to connect to. ''; }; + listen = { + ip = mkOption { + type = types.str; + default = "0.0.0.0"; + description = '' + List of comma delimited IP addresses that the agent should listen on. + ''; + }; + + port = mkOption { + type = types.port; + default = 10050; + description = '' + Agent will listen on this port for connections from the server. + ''; + }; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the Zabbix Agent. + ''; + }; + + # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42 extraConfig = mkOption { default = ""; type = types.lines; description = '' - Configuration that is injected verbatim into the configuration file. + Configuration that is injected verbatim into the configuration file. Refer to + + for details on supported values. ''; }; @@ -75,38 +120,38 @@ in }; - - ###### implementation + # implementation config = mkIf cfg.enable { - users.users = mkIf (!config.services.zabbixServer.enable) (singleton - { name = "zabbix"; - uid = config.ids.uids.zabbix; - description = "Zabbix daemon user"; - }); + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.listen.port ]; + }; - systemd.services."zabbix-agent" = - { description = "Zabbix Agent"; + users.users.${user} = { + description = "Zabbix Agent daemon user"; + inherit group; + }; - wantedBy = [ "multi-user.target" ]; + users.groups.${group} = { }; - path = [ pkgs.nettools ]; + systemd.services."zabbix-agent" = { + description = "Zabbix Agent"; - preStart = - '' - mkdir -m 0755 -p ${stateDir} ${logDir} - chown zabbix ${stateDir} ${logDir} - ''; + wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecStart = "@${zabbix.agent}/sbin/zabbix_agentd zabbix_agentd --config ${configFile}"; - serviceConfig.Type = "forking"; - serviceConfig.RemainAfterExit = true; - serviceConfig.Restart = "always"; - serviceConfig.RestartSec = 2; - }; + path = [ "/run/wrappers" ] ++ cfg.extraPackages; + + serviceConfig = { + ExecStart = "@${cfg.package}/sbin/zabbix_agentd zabbix_agentd -f --config ${configFile}"; + Restart = "always"; + RestartSec = 2; - environment.systemPackages = [ zabbix.agent ]; + User = user; + Group = group; + PrivateTmp = true; + }; + }; }; diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix index 823145d8b90..11311b466c3 100644 --- a/nixos/modules/services/monitoring/zabbix-server.nix +++ b/nixos/modules/services/monitoring/zabbix-server.nix @@ -1,147 +1,293 @@ -# Zabbix server daemon. { config, lib, pkgs, ... }: -with lib; - let - cfg = config.services.zabbixServer; + pgsql = config.services.postgresql; + mysql = config.services.mysql; - stateDir = "/run/zabbix"; + inherit (lib) mkDefault mkEnableOption mkIf mkOption; + inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types; - logDir = "/var/log/zabbix"; + user = "zabbix"; + group = "zabbix"; + runtimeDir = "/run/zabbix"; + stateDir = "/var/lib/zabbix"; + passwordFile = "${runtimeDir}/zabbix-dbpassword.conf"; - libDir = "/var/lib/zabbix"; + moduleEnv = pkgs.symlinkJoin { + name = "zabbix-server-module-env"; + paths = attrValues cfg.modules; + }; - pidFile = "${stateDir}/zabbix_server.pid"; + configFile = pkgs.writeText "zabbix_server.conf" '' + LogType = console + ListenIP = ${cfg.listen.ip} + ListenPort = ${toString cfg.listen.port} + # TODO: set to cfg.database.socket if database type is pgsql? + DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host} + ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"} + DBName = ${cfg.database.name} + DBUser = ${cfg.database.user} + ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"} + ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"} + SocketDir = ${runtimeDir} + FpingLocation = /run/wrappers/bin/fping + ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"} + ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)} + ${cfg.extraConfig} + ''; + + mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; + pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; - configFile = pkgs.writeText "zabbix_server.conf" - '' - ListenPort = ${cfg.listenPort} +in - LogFile = ${logDir}/zabbix_server +{ + # interface - PidFile = ${pidFile} + options = { - ${optionalString (cfg.dbServer != "localhost") '' - DBHost = ${cfg.dbServer} - ''} + services.zabbixServer = { + enable = mkEnableOption "the Zabbix Server"; - DBName = ${cfg.dbName} + package = mkOption { + type = types.package; + default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql; + defaultText = "pkgs.zabbix.server-pgsql"; + description = "The Zabbix package to use."; + }; - DBUser = ${cfg.dbUser} + extraPackages = mkOption { + type = types.listOf types.package; + default = with pkgs; [ nettools nmap traceroute ]; + defaultText = "[ nettools nmap traceroute ]"; + description = '' + Packages to be added to the Zabbix PATH. + Typically used to add executables for scripts, but can be anything. + ''; + }; - DBPort = ${cfg.dbPort} + modules = mkOption { + type = types.attrsOf types.package; + description = "A set of modules to load."; + default = {}; + example = literalExample '' + { + "dummy.so" = pkgs.stdenv.mkDerivation { + name = "zabbix-dummy-module-''${cfg.package.version}"; + src = cfg.package.src; + buildInputs = [ cfg.package ]; + sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy"; + installPhase = ''' + mkdir -p $out/lib + cp dummy.so $out/lib/ + '''; + }; + } + ''; + }; - DBPassword = ${cfg.dbPassword} + database = { + type = mkOption { + type = types.enum [ "mysql" "pgsql" ]; + example = "mysql"; + default = "pgsql"; + description = "Database engine to use."; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address."; + }; + + port = mkOption { + type = types.int; + default = if cfg.database.type == "mysql" then mysql.port else pgsql.port; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "zabbix"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "zabbix"; + description = "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/zabbix-dbpassword"; + description = '' + A file containing the password corresponding to + . + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/postgresql"; + description = "Path to the unix socket file to use for authentication."; + }; + + createLocally = mkOption { + type = types.bool; + default = true; + description = "Whether to create a local database automatically."; + }; + }; - ${config.services.zabbixServer.extraConfig} - ''; + listen = { + ip = mkOption { + type = types.str; + default = "0.0.0.0"; + description = '' + List of comma delimited IP addresses that the trapper should listen on. + Trapper will listen on all network interfaces if this parameter is missing. + ''; + }; - useLocalMysql = cfg.dbServer == "localhost" || cfg.dbServer == ""; + port = mkOption { + type = types.port; + default = 10051; + description = '' + Listen port for trapper. + ''; + }; + }; -in + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the Zabbix Server. + ''; + }; -{ + # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42 + extraConfig = mkOption { + default = ""; + type = types.lines; + description = '' + Configuration that is injected verbatim into the configuration file. Refer to + + for details on supported values. + ''; + }; - ###### interface + }; - options = { + }; - services.zabbixServer.enable = mkOption { - default = false; - type = types.bool; - description = '' - Whether to run the Zabbix server on this machine. - ''; - }; + # implementation - services.zabbixServer.dbServer = mkOption { - default = "localhost"; - type = types.str; - description = '' - Hostname or IP address of the database server. - Use an empty string ("") to use peer authentication. - ''; - }; + config = mkIf cfg.enable { - services.zabbixServer.dbPassword = mkOption { - type = types.str; - description = "Password used to connect to the database server."; + assertions = [ + { assertion = cfg.database.createLocally -> cfg.database.user == user; + message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true"; + } + { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.zabbixServer.database.createLocally is set to true"; + } + ]; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.listen.port ]; }; - services.zabbixServer.dbUser = mkOption { - default = "zabbix"; - type = types.str; - description = "User used to connect to the database server."; + services.mysql = optionalAttrs mysqlLocal { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ]; }; - services.zabbixServer.dbPort = mkOption { - default = "3306"; - type = types.str; - description = "Port used to connect to the database server."; + services.postgresql = optionalAttrs pgsqlLocal { + enable = true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.database.user; + ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; + } + ]; }; - services.zabbixServer.dbName = mkOption { - default = "zabbix"; - type = types.str; - description = "Port used to connect to the database server."; + users.users.${user} = { + description = "Zabbix daemon user"; + uid = config.ids.uids.zabbix; + inherit group; }; - services.zabbixServer.listenPort = mkOption { - default = "10051"; - type = types.str; - description = "Port used to listen to the agent."; + users.groups.${group} = { + gid = config.ids.gids.zabbix; }; - services.zabbixServer.extraConfig = mkOption { - default = ""; - type = types.lines; - description = '' - Configuration that is injected verbatim into the configuration file. - ''; + security.wrappers = { + fping.source = "${pkgs.fping}/bin/fping"; }; - }; - - ###### implementation - config = mkIf cfg.enable { + systemd.services."zabbix-server" = { + description = "Zabbix Server"; + + wantedBy = [ "multi-user.target" ]; + after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; + + path = [ "/run/wrappers" ] ++ cfg.extraPackages; + preStart = '' + # pre 19.09 compatibility + if test -e "${runtimeDir}/db-created"; then + mv "${runtimeDir}/db-created" "${stateDir}/" + fi + '' + optionalString pgsqlLocal '' + if ! test -e "${stateDir}/db-created"; then + cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name} + cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name} + cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name} + touch "${stateDir}/db-created" + fi + '' + optionalString mysqlLocal '' + if ! test -e "${stateDir}/db-created"; then + cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name} + cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name} + cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name} + touch "${stateDir}/db-created" + fi + '' + optionalString (cfg.database.passwordFile != null) '' + # create a copy of the supplied password file in a format zabbix can consume + touch ${passwordFile} + chmod 0600 ${passwordFile} + echo -n "DBPassword = " > ${passwordFile} + cat ${cfg.database.passwordFile} >> ${passwordFile} + ''; - services.mysql.enable = useLocalMysql; - services.mysql.package = pkgs.mysql; + serviceConfig = { + ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}"; + Restart = "always"; + RestartSec = 2; - users.users = singleton - { name = "zabbix"; - uid = config.ids.uids.zabbix; - description = "Zabbix daemon user"; + User = user; + Group = group; + RuntimeDirectory = "zabbix"; + StateDirectory = "zabbix"; + PrivateTmp = true; }; + }; - systemd.services."zabbix-server" = - { description = "Zabbix Server"; - - wantedBy = [ "multi-user.target" ]; - after = optional useLocalMysql "mysql.service"; - - preStart = - '' - mkdir -m 0755 -p ${stateDir} ${logDir} ${libDir} - chown zabbix ${stateDir} ${logDir} ${libDir} - ${lib.optionalString (useLocalMysql) '' - if ! test -e "${libDir}/db-created"; then - ${pkgs.sudo}/bin/sudo -u ${config.services.mysql.user} ${pkgs.mysql}/bin/mysql -uroot -e 'CREATE DATABASE ${cfg.dbName}' - ${pkgs.sudo}/bin/sudo -u ${config.services.mysql.user} ${pkgs.mysql}/bin/mysql -uroot -e "GRANT ALL ON ${cfg.dbName}.* TO ${cfg.dbUser}@localhost IDENTIFIED BY \"${cfg.dbPassword}\";" - cat ${pkgs.zabbix.server}/share/zabbix/db/schema/mysql.sql | ${pkgs.sudo}/bin/sudo -u zabbix ${pkgs.mysql}/bin/mysql -u${cfg.dbUser} -p${cfg.dbPassword} ${cfg.dbName} - cat ${pkgs.zabbix.server}/share/zabbix/db/data/images.sql | ${pkgs.sudo}/bin/sudo -u zabbix ${pkgs.mysql}/bin/mysql -u${cfg.dbUser} -p${cfg.dbPassword} ${cfg.dbName} - cat ${pkgs.zabbix.server}/share/zabbix/db/data/data.sql | ${pkgs.sudo}/bin/sudo -u zabbix ${pkgs.mysql}/bin/mysql -u${cfg.dbUser} -p${cfg.dbPassword} ${cfg.dbName} - touch "${libDir}/db-created" - fi''} - ''; - - path = [ pkgs.nettools ]; + systemd.services.httpd.after = + optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++ + optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service"; - serviceConfig.ExecStart = "${pkgs.zabbix.server}/sbin/zabbix_server --config ${configFile}"; - serviceConfig.Type = "forking"; - serviceConfig.PIDFile = pidFile; - }; }; + } -- cgit 1.4.1