diff options
Diffstat (limited to 'nixos/modules/services/monitoring')
16 files changed, 1807 insertions, 0 deletions
diff --git a/nixos/modules/services/monitoring/apcupsd.nix b/nixos/modules/services/monitoring/apcupsd.nix new file mode 100644 index 0000000000000..114bad5c947e8 --- /dev/null +++ b/nixos/modules/services/monitoring/apcupsd.nix @@ -0,0 +1,190 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + cfg = config.services.apcupsd; + + configFile = pkgs.writeText "apcupsd.conf" '' + ## apcupsd.conf v1.1 ## + # apcupsd complains if the first line is not like above. + ${cfg.configText} + SCRIPTDIR ${toString scriptDir} + ''; + + # List of events from "man apccontrol" + eventList = [ + "annoyme" + "battattach" + "battdetach" + "changeme" + "commfailure" + "commok" + "doreboot" + "doshutdown" + "emergency" + "failing" + "killpower" + "loadlimit" + "mainsback" + "onbattery" + "offbattery" + "powerout" + "remotedown" + "runlimit" + "timeout" + "startselftest" + "endselftest" + ]; + + shellCmdsForEventScript = eventname: commands: '' + echo "#!${pkgs.stdenv.shell}" > "$out/${eventname}" + echo "${commands}" >> "$out/${eventname}" + chmod a+x "$out/${eventname}" + ''; + + eventToShellCmds = event: if builtins.hasAttr event cfg.hooks then (shellCmdsForEventScript event (builtins.getAttr event cfg.hooks)) else ""; + + scriptDir = pkgs.runCommand "apcupsd-scriptdir" {} ('' + mkdir "$out" + # Copy SCRIPTDIR from apcupsd package + cp -r ${pkgs.apcupsd}/etc/apcupsd/* "$out"/ + # Make the files writeable (nix will unset the write bits afterwards) + chmod u+w "$out"/* + # Remove the sample event notification scripts, because they don't work + # anyways (they try to send mail to "root" with the "mail" command) + (cd "$out" && rm changeme commok commfailure onbattery offbattery) + # Remove the sample apcupsd.conf file (we're generating our own) + rm "$out/apcupsd.conf" + # Set the SCRIPTDIR= line in apccontrol to the dir we're creating now + sed -i -e "s|^SCRIPTDIR=.*|SCRIPTDIR=$out|" "$out/apccontrol" + '' + concatStringsSep "\n" (map eventToShellCmds eventList) + + ); + +in + +{ + + ###### interface + + options = { + + services.apcupsd = { + + enable = mkOption { + default = false; + type = types.uniq types.bool; + description = '' + Whether to enable the APC UPS daemon. apcupsd monitors your UPS and + permits orderly shutdown of your computer in the event of a power + failure. User manual: http://www.apcupsd.com/manual/manual.html. + Note that apcupsd runs as root (to allow shutdown of computer). + You can check the status of your UPS with the "apcaccess" command. + ''; + }; + + configText = mkOption { + default = '' + UPSTYPE usb + NISIP 127.0.0.1 + BATTERYLEVEL 50 + MINUTES 5 + ''; + type = types.string; + description = '' + Contents of the runtime configuration file, apcupsd.conf. The default + settings makes apcupsd autodetect USB UPSes, limit network access to + localhost and shutdown the system when the battery level is below 50 + percent, or when the UPS has calculated that it has 5 minutes or less + of remaining power-on time. See man apcupsd.conf for details. + ''; + }; + + hooks = mkOption { + default = {}; + example = { + doshutdown = ''# shell commands to notify that the computer is shutting down''; + }; + type = types.attrsOf types.string; + description = '' + Each attribute in this option names an apcupsd event and the string + value it contains will be executed in a shell, in response to that + event (prior to the default action). See "man apccontrol" for the + list of events and what they represent. + + A hook script can stop apccontrol from doing its default action by + exiting with value 99. Do not do this unless you know what you're + doing. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + assertions = [ { + assertion = let hooknames = builtins.attrNames cfg.hooks; in all (x: elem x eventList) hooknames; + message = '' + One (or more) attribute names in services.apcupsd.hooks are invalid. + Current attribute names: ${toString (builtins.attrNames cfg.hooks)} + Valid attribute names : ${toString eventList} + ''; + } ]; + + # Give users access to the "apcaccess" tool + environment.systemPackages = [ pkgs.apcupsd ]; + + # NOTE 1: apcupsd runs as root because it needs permission to run + # "shutdown" + # + # NOTE 2: When apcupsd calls "wall", it prints an error because stdout is + # not connected to a tty (it is connected to the journal): + # wall: cannot get tty name: Inappropriate ioctl for device + # The message still gets through. + systemd.services.apcupsd = { + description = "APC UPS daemon"; + wantedBy = [ "multi-user.target" ]; + preStart = "mkdir -p /run/apcupsd/"; + serviceConfig = { + ExecStart = "${pkgs.apcupsd}/bin/apcupsd -b -f ${configFile} -d1"; + # TODO: When apcupsd has initiated a shutdown, systemd always ends up + # waiting for it to stop ("A stop job is running for UPS daemon"). This + # is weird, because in the journal one can clearly see that apcupsd has + # received the SIGTERM signal and has already quit (or so it seems). + # This reduces the wait time from 90 seconds (default) to just 5. Then + # systemd kills it with SIGKILL. + TimeoutStopSec = 5; + }; + }; + + # A special service to tell the UPS to power down/hibernate just before the + # computer shuts down. (The UPS has a built in delay before it actually + # shuts off power.) Copied from here: + # http://forums.opensuse.org/english/get-technical-help-here/applications/479499-apcupsd-systemd-killpower-issues.html + systemd.services.apcupsd-killpower = { + after = [ "shutdown.target" ]; # append umount.target? + before = [ "final.target" ]; + wantedBy = [ "shutdown.target" ]; + unitConfig = { + Description = "APC UPS killpower"; + ConditionPathExists = "/run/apcupsd/powerfail"; + DefaultDependencies = "no"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}"; + TimeoutSec = 0; + StandardOutput = "tty"; + RemainAfterExit = "yes"; + }; + }; + + }; + +} diff --git a/nixos/modules/services/monitoring/dd-agent.nix b/nixos/modules/services/monitoring/dd-agent.nix new file mode 100644 index 0000000000000..ef658523c1f34 --- /dev/null +++ b/nixos/modules/services/monitoring/dd-agent.nix @@ -0,0 +1,83 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + cfg = config.services.dd-agent; + + datadog_conf = pkgs.runCommand "datadog.conf" {} '' + sed -e 's|^api_key:|api_key: ${cfg.api_key}|' ${optionalString (cfg.hostname != null) + "-e 's|^#hostname: mymachine.mydomain|hostname: ${cfg.hostname}|'" + } ${pkgs.dd-agent}/etc/dd-agent/datadog.conf.example > $out + ''; +in { + options.services.dd-agent = { + enable = mkOption { + description = "Whether to enable the dd-agent montioring service"; + + default = false; + + type = types.bool; + }; + + # !!! This gets stored in the store (world-readable), wish we had https://github.com/NixOS/nix/issues/8 + api_key = mkOption { + description = "The Datadog API key to associate the agent with your account"; + + example = "ae0aa6a8f08efa988ba0a17578f009ab"; + + type = types.uniq types.string; + }; + + hostname = mkOption { + description = "The hostname to show in the Datadog dashboard (optional)"; + + default = null; + + example = "mymachine.mydomain"; + + type = types.uniq (types.nullOr types.string); + }; + }; + + config = mkIf cfg.enable { + environment.etc = [ { source = datadog_conf; target = "dd-agent/datadog.conf"; } ]; + environment.systemPackages = [ pkgs."dd-agent" pkgs.sysstat pkgs.procps ]; + + users.extraUsers."dd-agent" = { + description = "Datadog Agent User"; + uid = config.ids.uids.dd-agent; + group = "dd-agent"; + home = "/var/log/datadog/"; + createHome = true; + }; + + users.extraGroups.dd-agent.gid = config.ids.gids.dd-agent; + + systemd.services.dd-agent = { + description = "Datadog agent monitor"; + path = [ pkgs."dd-agent" pkgs.python pkgs.sysstat pkgs.procps]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.dd-agent}/bin/dd-agent foreground"; + User = "dd-agent"; + Group = "dd-agent"; + }; + restartTriggers = [ pkgs.dd-agent datadog_conf ]; + }; + + systemd.services.dogstatsd = { + description = "Datadog statsd"; + path = [ pkgs."dd-agent" pkgs.python ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.dd-agent}/bin/dogstatsd start"; + User = "dd-agent"; + Group = "dd-agent"; + Type = "forking"; + PIDFile = "/tmp/dogstatsd.pid"; + }; + restartTriggers = [ pkgs.dd-agent datadog_conf ]; + }; + }; +} diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix new file mode 100644 index 0000000000000..ec36db7b21c32 --- /dev/null +++ b/nixos/modules/services/monitoring/graphite.nix @@ -0,0 +1,265 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + cfg = config.services.graphite; + writeTextOrNull = f: t: if t == null then null else pkgs.writeText f t; + dataDir = "/var/db/graphite"; +in { + + ###### interface + + options.services.graphite = { + web = { + enable = mkOption { + description = "Whether to enable graphite web frontend"; + default = false; + type = types.uniq types.bool; + }; + + host = mkOption { + description = "Graphite web frontend listen address"; + default = "127.0.0.1"; + types = type.uniq types.string; + }; + + port = mkOption { + description = "Graphite web frontend port"; + default = "8080"; + types = type.uniq types.string; + }; + }; + + carbon = { + config = mkOption { + description = "Content of carbon configuration file"; + default = ""; + type = types.uniq types.string; + }; + + enableCache = mkOption { + description = "Whether to enable carbon cache, the graphite storage daemon"; + default = false; + type = types.uniq types.bool; + }; + + storageAggregation = mkOption { + description = "Defines how to aggregate data to lower-precision retentions"; + default = null; + type = types.uniq (types.nullOr types.string); + example = '' + [all_min] + pattern = \.min$ + xFilesFactor = 0.1 + aggregationMethod = min + ''; + }; + + storageSchemas = mkOption { + description = "Defines retention rates for storing metrics"; + default = ""; + type = types.uniq (types.nullOr types.string); + example = '' + [apache_busyWorkers] + pattern = ^servers\.www.*\.workers\.busyWorkers$ + retentions = 15s:7d,1m:21d,15m:5y + ''; + }; + + blacklist = mkOption { + description = "Any metrics received which match one of the experssions will be dropped"; + default = null; + type = types.uniq (types.nullOr types.string); + example = "^some\.noisy\.metric\.prefix\..*"; + }; + + whitelist = mkOption { + description = "Only metrics received which match one of the experssions will be persisted"; + default = null; + type = types.uniq (types.nullOr types.string); + example = ".*"; + }; + + rewriteRules = mkOption { + description = "Regular expression patterns that can be used to rewrite metric names in a search and replace fashion"; + default = null; + type = types.uniq (types.nullOr types.string); + example = '' + [post] + _sum$ = + _avg$ = + ''; + }; + + enableRelay = mkOption { + description = "Whether to enable carbon relay, the carbon replication and sharding service"; + default = false; + type = types.uniq types.bool; + }; + + relayRules = mkOption { + description = "Relay rules are used to send certain metrics to a certain backend."; + default = null; + type = types.uniq (types.nullOr types.string); + example = '' + [example] + pattern = ^mydata\.foo\..+ + servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com + ''; + }; + + enableAggregator = mkOption { + description = "Whether to enable carbon agregator, the carbon buffering service"; + default = false; + type = types.uniq types.bool; + }; + + aggregationRules = mkOption { + description = "Defines if and how received metrics will be agregated"; + default = null; + type = types.uniq (types.nullOr types.string); + example = '' + <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests + <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency + ''; + }; + }; + }; + + ###### implementation + + config = mkIf (cfg.carbon.enableAggregator || cfg.carbon.enableCache || cfg.carbon.enableRelay || cfg.web.enable) { + environment.etc = lists.filter (el: el.source != null) [ + { source = writeTextOrNull "carbon.conf" cfg.carbon.config; + target = "graphite/carbon.conf"; } + { source = writeTextOrNull "storage-agregation.conf" cfg.carbon.storageAggregation; + target = "graphite/storage-agregation.conf"; } + { source = writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas; + target = "graphite/storage-schemas.conf"; } + { source = writeTextOrNull "blacklist.conf" cfg.carbon.blacklist; + target = "graphite/blacklist.conf"; } + { source = writeTextOrNull "whitelist.conf" cfg.carbon.whitelist; + target = "graphite/whitelist.conf"; } + { source = writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules; + target = "graphite/rewrite-rules.conf"; } + { source = writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules; + target = "graphite/relay-rules.conf"; } + { source = writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules; + target = "graphite/aggregation-rules.conf"; } + ]; + + systemd.services.carbonCache = mkIf cfg.carbon.enableCache { + description = "Graphite data storage backend"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-interfaces.target" ]; + environment = { + GRAPHITE_CONF_DIR = "/etc/graphite/"; + GRAPHITE_STORAGE_DIR = "/var/db/graphite/"; + }; + serviceConfig = { + ExecStart = "${pkgs.pythonPackages.carbon}/bin/carbon-cache.py --pidfile /tmp/carbonCache.pid start"; + User = "graphite"; + Group = "graphite"; + }; + restartTriggers = [ + pkgs.pythonPackages.carbon + cfg.carbon.config + cfg.carbon.storageAggregation + cfg.carbon.storageSchemas + cfg.carbon.rewriteRules + ]; + preStart = '' + mkdir -p ${dataDir}/whisper + ''; + }; + + systemd.services.carbonAggregator = mkIf cfg.carbon.enableAggregator { + description = "Carbon data aggregator"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-interfaces.target" ]; + environment = { + GRAPHITE_CONF_DIR = "/etc/graphite/"; + GRAPHITE_STORAGE_DIR = "${dataDir}"; + }; + serviceConfig = { + ExecStart = "${pkgs.pythonPackages.carbon}/bin/carbon-aggregator.py --pidfile /tmp/carbonAggregator.pid start"; + User = "graphite"; + Group = "graphite"; + }; + restartTriggers = [ + pkgs.pythonPackages.carbon cfg.carbon.config cfg.carbon.aggregationRules + ]; + }; + + systemd.services.carbonRelay = mkIf cfg.carbon.enableRelay { + description = "Carbon data relay"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-interfaces.target" ]; + environment = { + GRAPHITE_CONF_DIR = "/etc/graphite/"; + GRAPHITE_STORAGE_DIR = "${dataDir}"; + }; + serviceConfig = { + ExecStart = "${pkgs.pythonPackages.carbon}/bin/carbon-relay.py --pidfile /tmp/carbonRelay.pid start"; + User = "graphite"; + Group = "graphite"; + }; + restartTriggers = [ + pkgs.pythonPackages.carbon cfg.carbon.config cfg.carbon.relayRules + ]; + }; + + systemd.services.graphiteWeb = mkIf cfg.web.enable { + description = "Graphite web interface"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-interfaces.target" ]; + environment = { + PYTHONPATH = "${pkgs.python27Packages.graphite_web}/lib/python2.7/site-packages"; + DJANGO_SETTINGS_MODULE = "graphite.settings"; + GRAPHITE_CONF_DIR = "/etc/graphite/"; + GRAPHITE_STORAGE_DIR = "${dataDir}"; + }; + serviceConfig = { + ExecStart = '' + ${pkgs.python27Packages.waitress}/bin/waitress-serve \ + --host=${cfg.web.host} --port=${cfg.web.port} \ + --call django.core.handlers.wsgi:WSGIHandler''; + User = "graphite"; + Group = "graphite"; + }; + preStart = '' + if ! test -e ${dataDir}/db-created; then + mkdir -p ${dataDir}/{whisper/,log/webapp/} + + # populate database + ${pkgs.python27Packages.graphite_web}/bin/manage-graphite.py syncdb --noinput + + # create index + ${pkgs.python27Packages.graphite_web}/bin/build-index.sh + + touch ${dataDir}/db-created + fi + ''; + restartTriggers = [ + pkgs.python27Packages.graphite_web + pkgs.python27Packages.waitress + ]; + }; + + environment.systemPackages = [ + pkgs.pythonPackages.carbon + pkgs.python27Packages.graphite_web + pkgs.python27Packages.waitress + ]; + + users.extraUsers = singleton { + name = "graphite"; + uid = config.ids.uids.graphite; + description = "Graphite daemon user"; + home = "${dataDir}"; + createHome = true; + }; + users.extraGroups.graphite.gid = config.ids.gids.graphite; + }; +} diff --git a/nixos/modules/services/monitoring/monit.nix b/nixos/modules/services/monitoring/monit.nix new file mode 100644 index 0000000000000..2acc51c64a6aa --- /dev/null +++ b/nixos/modules/services/monitoring/monit.nix @@ -0,0 +1,52 @@ +# Monit system watcher +# http://mmonit.org/monit/ + +{config, pkgs, ...}: + +let inherit (pkgs.lib) mkOption mkIf; +in + +{ + options = { + services.monit = { + enable = mkOption { + default = false; + description = '' + Whether to run Monit system watcher. + ''; + }; + config = mkOption { + default = ""; + description = "monit.conf content"; + }; + startOn = mkOption { + default = "started network-interfaces"; + description = "What Monit supposes to be already present"; + }; + }; + }; + + config = mkIf config.services.monit.enable { + + environment.etc = [ + { + source = pkgs.writeTextFile { + name = "monit.conf"; + text = config.services.monit.config; + }; + target = "monit.conf"; + mode = "0400"; + } + ]; + + jobs.monit = { + description = "Monit system watcher"; + + startOn = config.services.monit.startOn; + + exec = "${pkgs.monit}/bin/monit -I -c /etc/monit.conf"; + + respawn = true; + }; + }; +} diff --git a/nixos/modules/services/monitoring/nagios/commands.cfg b/nixos/modules/services/monitoring/nagios/commands.cfg new file mode 100644 index 0000000000000..6efdefcd37dcd --- /dev/null +++ b/nixos/modules/services/monitoring/nagios/commands.cfg @@ -0,0 +1,34 @@ +define command { + command_name host-notify-by-email + command_line printf "%b" "To: $CONTACTEMAIL$\nSubject: [Nagios] Host $HOSTSTATE$ alert for $HOSTNAME$\n\n***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | sendmail $CONTACTEMAIL$ +} + + +define command { + command_name notify-by-email + command_line printf "%b" "To: $CONTACTEMAIL$\nSubject: [Nagios] $NOTIFICATIONTYPE$ alert - $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$\n\n***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nService: $SERVICEDESC$\nHost: $HOSTALIAS$\nAddress: $HOSTADDRESS$\nState: $SERVICESTATE$\n\nDate/Time: $LONGDATETIME$\n\nAdditional Info:\n\n$SERVICEOUTPUT$" | sendmail $CONTACTEMAIL$ +} + + +define command { + command_name dummy-ok + command_line true +} + + +define command { + command_name check-host-alive + command_line check_ping -H $HOSTADDRESS$ -w 3000.0,80% -c 5000.0,100% -p 1 +} + + +define command { + command_name check_local_disk + command_line check_disk -w $ARG1$ -c $ARG2$ -p $ARG3$ +} + + +define command { + command_name check_ssh + command_line check_ssh $HOSTADDRESS$ +} diff --git a/nixos/modules/services/monitoring/nagios/default.nix b/nixos/modules/services/monitoring/nagios/default.nix new file mode 100644 index 0000000000000..c809a3b84573f --- /dev/null +++ b/nixos/modules/services/monitoring/nagios/default.nix @@ -0,0 +1,186 @@ +# Nagios system/network monitoring daemon. +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.nagios; + + nagiosUser = "nagios"; + nagiosGroup = "nogroup"; + + nagiosState = "/var/lib/nagios"; + nagiosLogDir = "/var/log/nagios"; + + nagiosObjectDefs = + [ ./timeperiods.cfg + ./host-templates.cfg + ./service-templates.cfg + ./commands.cfg + ] ++ cfg.objectDefs; + + nagiosObjectDefsDir = pkgs.runCommand "nagios-objects" {inherit nagiosObjectDefs;} + "ensureDir $out; ln -s $nagiosObjectDefs $out/"; + + nagiosCfgFile = pkgs.writeText "nagios.cfg" + '' + # Paths for state and logs. + log_file=${nagiosLogDir}/current + log_archive_path=${nagiosLogDir}/archive + status_file=${nagiosState}/status.dat + object_cache_file=${nagiosState}/objects.cache + comment_file=${nagiosState}/comment.dat + downtime_file=${nagiosState}/downtime.dat + temp_file=${nagiosState}/nagios.tmp + lock_file=/var/run/nagios.lock # Not used I think. + state_retention_file=${nagiosState}/retention.dat + + # Configuration files. + #resource_file=resource.cfg + cfg_dir=${nagiosObjectDefsDir} + + # Uid/gid that the daemon runs under. + nagios_user=${nagiosUser} + nagios_group=${nagiosGroup} + + # Misc. options. + illegal_macro_output_chars=`~$&|'"<> + retain_state_information=1 + ''; # " + + # Plain configuration for the Nagios web-interface with no + # authentication. + nagiosCGICfgFile = pkgs.writeText "nagios.cgi.conf" + '' + main_config_file=${nagiosCfgFile} + use_authentication=0 + url_html_path=/nagios + ''; + + urlPath = cfg.urlPath; + + extraHttpdConfig = + '' + ScriptAlias ${urlPath}/cgi-bin ${pkgs.nagios}/sbin + + <Directory "${pkgs.nagios}/sbin"> + Options ExecCGI + AllowOverride None + Order allow,deny + Allow from all + SetEnv NAGIOS_CGI_CONFIG ${nagiosCGICfgFile} + </Directory> + + Alias ${urlPath} ${pkgs.nagios}/share + + <Directory "${pkgs.nagios}/share"> + Options None + AllowOverride None + Order allow,deny + Allow from all + </Directory> + ''; + +in + +{ + ###### interface + + options = { + + services.nagios = { + + enable = mkOption { + default = false; + description = " + Whether to use <link + xlink:href='http://www.nagios.org/'>Nagios</link> to monitor + your system or network. + "; + }; + + objectDefs = mkOption { + description = " + A list of Nagios object configuration files that must define + the hosts, host groups, services and contacts for the + network that you want Nagios to monitor. + "; + }; + + plugins = mkOption { + default = [pkgs.nagiosPluginsOfficial pkgs.ssmtp]; + description = " + Packages to be added to the Nagios <envar>PATH</envar>. + Typically used to add plugins, but can be anything. + "; + }; + + enableWebInterface = mkOption { + default = false; + description = " + Whether to enable the Nagios web interface. You should also + enable Apache (<option>services.httpd.enable</option>). + "; + }; + + urlPath = mkOption { + default = "/nagios"; + description = " + The URL path under which the Nagios web interface appears. + That is, you can access the Nagios web interface through + <literal>http://<replaceable>server</replaceable>/<replaceable>urlPath</replaceable></literal>. + "; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.extraUsers = singleton + { name = nagiosUser; + uid = config.ids.uids.nagios; + description = "Nagios monitoring daemon"; + home = nagiosState; + }; + + # This isn't needed, it's just so that the user can type "nagiostats + # -c /etc/nagios.cfg". + environment.etc = singleton + { source = nagiosCfgFile; + target = "nagios.cfg"; + }; + + environment.systemPackages = [ pkgs.nagios ]; + + jobs.nagios = + { description = "Nagios monitoring daemon"; + + startOn = "started network-interfaces"; + stopOn = "stopping network-interfaces"; + + preStart = + '' + mkdir -m 0755 -p ${nagiosState} ${nagiosLogDir} + chown ${nagiosUser} ${nagiosState} ${nagiosLogDir} + ''; + + script = + '' + for i in ${toString config.services.nagios.plugins}; do + export PATH=$i/bin:$i/sbin:$i/libexec:$PATH + done + exec ${pkgs.nagios}/bin/nagios ${nagiosCfgFile} + ''; + }; + + services.httpd.extraConfig = optionalString cfg.enableWebInterface extraHttpdConfig; + + }; + +} diff --git a/nixos/modules/services/monitoring/nagios/host-templates.cfg b/nixos/modules/services/monitoring/nagios/host-templates.cfg new file mode 100644 index 0000000000000..3a4c269e25721 --- /dev/null +++ b/nixos/modules/services/monitoring/nagios/host-templates.cfg @@ -0,0 +1,27 @@ +define host { + name generic-host + notifications_enabled 1 + event_handler_enabled 1 + flap_detection_enabled 1 + failure_prediction_enabled 1 + process_perf_data 1 + retain_status_information 1 + retain_nonstatus_information 1 + notification_period 24x7 + register 0 +} + + +define host { + name generic-server + use generic-host + check_period 24x7 + max_check_attempts 10 + check_command check-host-alive + notification_period 24x7 + notification_interval 120 + notification_options d,u,r + contact_groups admins + register 0 + #check_interval 1 +} diff --git a/nixos/modules/services/monitoring/nagios/service-templates.cfg b/nixos/modules/services/monitoring/nagios/service-templates.cfg new file mode 100644 index 0000000000000..e729ea77675d3 --- /dev/null +++ b/nixos/modules/services/monitoring/nagios/service-templates.cfg @@ -0,0 +1,32 @@ +define service { + name generic-service + active_checks_enabled 1 + passive_checks_enabled 1 + parallelize_check 1 + obsess_over_service 1 + check_freshness 0 + notifications_enabled 1 + event_handler_enabled 1 + flap_detection_enabled 1 + failure_prediction_enabled 1 + process_perf_data 1 + retain_status_information 1 + retain_nonstatus_information 1 + is_volatile 0 + register 0 +} + + +define service { + name local-service + use generic-service + check_period 24x7 + max_check_attempts 4 + normal_check_interval 5 + retry_check_interval 1 + contact_groups admins + notification_options w,u,c,r + notification_interval 0 # notify only once + notification_period 24x7 + register 0 +} diff --git a/nixos/modules/services/monitoring/nagios/timeperiods.cfg b/nixos/modules/services/monitoring/nagios/timeperiods.cfg new file mode 100644 index 0000000000000..2669be54d3db1 --- /dev/null +++ b/nixos/modules/services/monitoring/nagios/timeperiods.cfg @@ -0,0 +1,11 @@ +define timeperiod { + timeperiod_name 24x7 + alias 24 Hours A Day, 7 Days A Week + sunday 00:00-24:00 + monday 00:00-24:00 + tuesday 00:00-24:00 + wednesday 00:00-24:00 + thursday 00:00-24:00 + friday 00:00-24:00 + saturday 00:00-24:00 +} diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix new file mode 100644 index 0000000000000..de07dc0dbaa62 --- /dev/null +++ b/nixos/modules/services/monitoring/smartd.nix @@ -0,0 +1,117 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.smartd; + + smartdOpts = { name, ... }: { + + options = { + + device = mkOption { + example = "/dev/sda"; + type = types.string; + description = "Location of the device."; + }; + + options = mkOption { + default = ""; + example = "-d sat"; + type = types.string; + merge = pkgs.lib.concatStringsSep " "; + description = "Options that determine how smartd monitors the device"; + }; + }; + + }; + + smartdMail = pkgs.writeScript "smartdmail.sh" '' + #! ${pkgs.stdenv.shell} + TMPNAM=/tmp/smartd-message.$$.tmp + if test -n "$SMARTD_ADDRESS"; then + echo >"$TMPNAM" "From: smartd <root>" + echo >>"$TMPNAM" 'To: undisclosed-recipients:;' + echo >>"$TMPNAM" "Subject: $SMARTD_SUBJECT" + echo >>"$TMPNAM" + echo >>"$TMPNAM" "Failure on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE" + echo >>"$TMPNAM" + cat >>"$TMPNAM" + ${pkgs.smartmontools}/sbin/smartctl >>"$TMPNAM" -a -d "$SMARTD_DEVICETYPE" "$SMARTD_DEVICE" + /var/setuid-wrappers/sendmail <"$TMPNAM" -f "$SENDER" -i "$SMARTD_ADDRESS" + fi + ''; + + smartdConf = pkgs.writeText "smartd.conf" (concatMapStrings (device: + '' + ${device.device} -a -m root -M exec ${smartdMail} ${device.options} ${cfg.deviceOpts} + '' + ) cfg.devices); + + smartdFlags = if (cfg.devices == []) then "" else "--configfile=${smartdConf}"; + +in + +{ + ###### interface + + options = { + + services.smartd = { + + enable = mkOption { + default = false; + type = types.bool; + example = "true"; + description = '' + Run smartd from the smartmontools package. Note that e-mail + notifications will not be enabled unless you configure the list of + devices with <varname>services.smartd.devices</varname> as well. + ''; + }; + + deviceOpts = mkOption { + default = ""; + type = types.string; + example = "-o on -s (S/../.././02|L/../../7/04)"; + description = '' + Additional options for each device that is monitored. The example + turns on SMART Automatic Offline Testing on startup, and schedules short + self-tests daily, and long self-tests weekly. + ''; + }; + + devices = mkOption { + default = []; + example = [ { device = "/dev/sda"; } { device = "/dev/sdb"; options = "-d sat"; } ]; + type = types.listOf types.optionSet; + options = [ smartdOpts ]; + description = '' + List of devices to monitor. By default -- if this list is empty --, + smartd will monitor all devices connected to the machine at the time + it's being run. Configuring this option has the added benefit of + enabling e-mail notifications to "root" every time smartd detects an + error. + ''; + }; + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + systemd.services.smartd = { + description = "S.M.A.R.T. Daemon"; + + wantedBy = [ "multi-user.target" ]; + + serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd --no-fork ${smartdFlags}"; + }; + + }; + +} diff --git a/nixos/modules/services/monitoring/statsd.nix b/nixos/modules/services/monitoring/statsd.nix new file mode 100644 index 0000000000000..a326660567140 --- /dev/null +++ b/nixos/modules/services/monitoring/statsd.nix @@ -0,0 +1,94 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.statsd; + + configFile = pkgs.writeText "statsd.conf" '' + { + host: "${cfg.host}", + port: "${toString cfg.port}", + backends: [${concatMapStrings (el: ''"./backends/${el}",'') cfg.backends}], + graphiteHost: "${cfg.graphiteHost}", + graphitePort: "${toString cfg.graphitePort}", + ${cfg.extraConfig} + } + ''; + +in + +{ + + ###### interface + + options.services.statsd = { + + enable = mkOption { + description = "Whether to enable statsd stats aggregation service"; + default = false; + type = types.uniq types.bool; + }; + + host = mkOption { + description = "Address that statsd listens on over UDP"; + default = "127.0.0.1"; + type = types.uniq types.string; + }; + + port = mkOption { + description = "Port that stats listens for messages on over UDP"; + default = 8125; + type = types.uniq types.int; + }; + + backends = mkOption { + description = "List of backends statsd will use for data persistance"; + default = ["graphite"]; + }; + + graphiteHost = mkOption { + description = "Hostname or IP of Graphite server"; + default = "127.0.0.1"; + type = types.uniq types.string; + }; + + graphitePort = mkOption { + description = "Port of Graphite server"; + default = 2003; + type = types.uniq types.int; + }; + + extraConfig = mkOption { + default = ""; + description = "Extra configuration options for statsd"; + type = types.uniq types.string; + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + + users.extraUsers = singleton { + name = "statsd"; + uid = config.ids.uids.statsd; + description = "Statsd daemon user"; + }; + + systemd.services.statsd = { + description = "Statsd Server"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.nodePackages.statsd}/bin/statsd ${configFile}"; + User = "statsd"; + }; + }; + + environment.systemPackages = [pkgs.nodePackages.statsd]; + + }; + +} diff --git a/nixos/modules/services/monitoring/systemhealth.nix b/nixos/modules/services/monitoring/systemhealth.nix new file mode 100644 index 0000000000000..0a3e666ad4e7a --- /dev/null +++ b/nixos/modules/services/monitoring/systemhealth.nix @@ -0,0 +1,133 @@ +{config, pkgs, ...}: + +with pkgs.lib; + +let + cfg = config.services.systemhealth; + + systemhealth = with pkgs; stdenv.mkDerivation { + name = "systemhealth-1.0"; + src = fetchurl { + url = "http://www.brianlane.com/static/downloads/systemhealth/systemhealth-1.0.tar.bz2"; + sha256 = "1q69lz7hmpbdpbz36zb06nzfkj651413n9icx0njmyr3xzq1j9qy"; + }; + buildInputs = [ python ]; + installPhase = '' + ensureDir $out/bin + # Make it work for kernels 3.x, not so different than 2.6 + sed -i 's/2\.6/4.0/' system_health.py + cp system_health.py $out/bin + ''; + }; + + rrdDir = "/var/lib/health/rrd"; + htmlDir = "/var/lib/health/html"; + + configFile = rrdDir + "/.syshealthrc"; + # The program will try to read $HOME/.syshealthrc, so we set the proper home. + command = "HOME=${rrdDir} ${systemhealth}/bin/system_health.py"; + + cronJob = '' + */5 * * * * wwwrun ${command} --log + 5 * * * * wwwrun ${command} --graph + ''; + + nameEqualName = s: "${s} = ${s}"; + interfacesSection = concatStringsSep "\n" (map nameEqualName cfg.interfaces); + + driveLine = d: "${d.path} = ${d.name}"; + drivesSection = concatStringsSep "\n" (map driveLine cfg.drives); + +in +{ + options = { + services.systemhealth = { + enable = mkOption { + default = false; + description = '' + Enable the system health monitor and its generation of graphs. + ''; + }; + + urlPrefix = mkOption { + default = "/health"; + description = '' + The URL prefix under which the System Health web pages appear in httpd. + ''; + }; + + interfaces = mkOption { + default = [ "lo" ]; + example = [ "lo" "eth0" "eth1" ]; + description = '' + Interfaces to monitor (minimum one). + ''; + }; + + drives = mkOption { + default = [ ]; + example = [ { name = "root"; path = "/"; } ]; + description = '' + Drives to monitor. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + services.cron.systemCronJobs = [ cronJob ]; + + system.activationScripts.systemhealth = stringAfter [ "var" ] + '' + mkdir -p ${rrdDir} ${htmlDir} + chown wwwrun:wwwrun ${rrdDir} ${htmlDir} + + cat >${configFile} << EOF + [paths] + rrdtool = ${pkgs.rrdtool}/bin/rrdtool + loadavg_rrd = loadavg + ps = /run/current-system/sw/bin/ps + df = /run/current-system/sw/bin/df + meminfo_rrd = meminfo + uptime_rrd = uptime + rrd_path = ${rrdDir} + png_path = ${htmlDir} + + [processes] + + [interfaces] + ${interfacesSection} + + [drives] + ${drivesSection} + + [graphs] + width = 400 + time = ['-3hours', '-32hours', '-8days', '-5weeks', '-13months'] + height = 100 + + [external] + + EOF + + chown wwwrun:wwwrun ${configFile} + + ${pkgs.su}/bin/su -s "/bin/sh" -c "${command} --check" wwwrun + ${pkgs.su}/bin/su -s "/bin/sh" -c "${command} --html" wwwrun + ''; + + services.httpd.extraSubservices = [ + { function = f: { + extraConfig = '' + Alias ${cfg.urlPrefix} ${htmlDir} + + <Directory ${htmlDir}> + Order allow,deny + Allow from all + </Directory> + ''; + }; + } + ]; + }; +} diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix new file mode 100644 index 0000000000000..a7b72e53f0a47 --- /dev/null +++ b/nixos/modules/services/monitoring/ups.nix @@ -0,0 +1,275 @@ +{config, pkgs, ...}: + +# TODO: This is not secure, have a look at the file docs/security.txt inside +# the project sources. +with pkgs.lib; + +let + cfg = config.power.ups; +in + +let + upsOptions = {name, config, ...}: + { + options = { + # This can be infered from the UPS model by looking at + # /nix/store/nut/share/driver.list + driver = mkOption { + type = types.uniq types.string; + description = '' + Specify the program to run to talk to this UPS. apcsmart, + bestups, and sec are some examples. + ''; + }; + + port = mkOption { + type = types.uniq types.string; + description = '' + The serial port to which your UPS is connected. /dev/ttyS0 is + usually the first port on Linux boxes, for example. + ''; + }; + + shutdownOrder = mkOption { + default = 0; + type = types.uniq types.int; + description = '' + When you have multiple UPSes on your system, you usually need to + turn them off in a certain order. upsdrvctl shuts down all the + 0s, then the 1s, 2s, and so on. To exclude a UPS from the + shutdown sequence, set this to -1. + ''; + }; + + maxStartDelay = mkOption { + default = null; + type = types.uniq (types.nullOr types.int); + description = '' + This can be set as a global variable above your first UPS + definition and it can also be set in a UPS section. This value + controls how long upsdrvctl will wait for the driver to finish + starting. This keeps your system from getting stuck due to a + broken driver or UPS. + ''; + }; + + description = mkOption { + default = ""; + type = types.string; + description = '' + Description of the UPS. + ''; + }; + + directives = mkOption { + default = []; + type = types.listOf types.string; + description = '' + List of configuration directives for this UPS. + ''; + }; + + summary = mkOption { + default = ""; + type = types.string; + description = '' + Lines which would be added inside ups.conf for handling this UPS. + ''; + }; + + }; + + config = { + directives = mkHeader ([ + "driver = ${config.driver}" + "port = ${config.port}" + ''desc = "${config.description}"'' + "sdorder = ${toString config.shutdownOrder}" + ] ++ (optional (config.maxStartDelay != null) + "maxstartdelay = ${toString config.maxStartDelay}") + ); + + summary = + concatStringsSep "\n " + (["[${name}]"] ++ config.directives); + }; + }; + +in + + +{ + options = { + # powerManagement.powerDownCommands + + power.ups = { + enable = mkOption { + default = false; + type = with types; bool; + description = '' + Enables support for Power Devices, such as Uninterruptible Power + Supplies, Power Distribution Units and Solar Controllers. + ''; + }; + + # This option is not used yet. + mode = mkOption { + default = "standalone"; + type = types.uniq types.string; + description = '' + The MODE determines which part of the NUT is to be started, and + which configuration files must be modified. + + The values of MODE can be: + + - none: NUT is not configured, or use the Integrated Power + Management, or use some external system to startup NUT + components. So nothing is to be started. + + - standalone: This mode address a local only configuration, with 1 + UPS protecting the local system. This implies to start the 3 NUT + layers (driver, upsd and upsmon) and the matching configuration + files. This mode can also address UPS redundancy. + + - netserver: same as for the standalone configuration, but also + need some more ACLs and possibly a specific LISTEN directive in + upsd.conf. Since this MODE is opened to the network, a special + care should be applied to security concerns. + + - netclient: this mode only requires upsmon. + ''; + }; + + schedulerRules = mkOption { + example = "/etc/nixos/upssched.conf"; + type = types.uniq types.string; + description = '' + File which contains the rules to handle UPS events. + ''; + }; + + + maxStartDelay = mkOption { + default = 45; + type = types.uniq types.int; + description = '' + This can be set as a global variable above your first UPS + definition and it can also be set in a UPS section. This value + controls how long upsdrvctl will wait for the driver to finish + starting. This keeps your system from getting stuck due to a + broken driver or UPS. + ''; + }; + + ups = mkOption { + default = {}; + # see nut/etc/ups.conf.sample + description = '' + This is where you configure all the UPSes that this system will be + monitoring directly. These are usually attached to serial ports, + but USB devices are also supported. + ''; + type = types.attrsOf types.optionSet; + options = [ upsOptions ]; + }; + + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.nut ]; + + jobs.upsmon = { + description = "Uninterruptible Power Supplies (Monitor)"; + startOn = "ip-up"; + daemonType = "fork"; + exec = ''${pkgs.nut}/sbin/upsmon''; + environment.NUT_CONFPATH = "/etc/nut/"; + environment.NUT_STATEPATH = "/var/lib/nut/"; + }; + + jobs.upsd = { + description = "Uninterruptible Power Supplies (Daemon)"; + startOn = "started network-interfaces and started upsmon"; + daemonType = "fork"; + # TODO: replace 'root' by another username. + exec = ''${pkgs.nut}/sbin/upsd -u root''; + environment.NUT_CONFPATH = "/etc/nut/"; + environment.NUT_STATEPATH = "/var/lib/nut/"; + }; + + jobs.upsdrv = { + description = "Uninterruptible Power Supplies (Register all UPS)"; + startOn = "started upsd"; + # TODO: replace 'root' by another username. + exec = ''${pkgs.nut}/bin/upsdrvctl -u root start''; + task = true; + environment.NUT_CONFPATH = "/etc/nut/"; + environment.NUT_STATEPATH = "/var/lib/nut/"; + }; + + environment.etc = [ + { source = pkgs.writeText "nut.conf" + '' + MODE = ${cfg.mode} + ''; + target = "nut/nut.conf"; + } + { source = pkgs.writeText "ups.conf" + '' + maxstartdelay = ${toString cfg.maxStartDelay} + + ${flip concatStringsSep (flip map (attrValues cfg.ups) (ups: ups.summary)) " + + "} + ''; + target = "nut/ups.conf"; + } + { source = cfg.schedulerRules; + target = "nut/upssched.conf"; + } + # These file are containing private informations and thus should not + # be stored inside the Nix store. + /* + { source = ; + target = "nut/upsd.conf"; + } + { source = ; + target = "nut/upsd.users"; + } + { source = ; + target = "nut/upsmon.conf; + } + */ + ]; + + power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample"; + + system.activationScripts.upsSetup = stringAfter [ "users" "groups" ] + '' + # Used to store pid files of drivers. + mkdir -p /var/state/ups + ''; + + +/* + users.extraUsers = [ + { name = "nut"; + uid = 84; + home = "/var/lib/nut"; + createHome = true; + group = "nut"; + description = "UPnP A/V Media Server user"; + } + ]; + + users.extraGroups = [ + { name = "nut"; + gid = 84; + } + ]; +*/ + + }; +} diff --git a/nixos/modules/services/monitoring/uptime.nix b/nixos/modules/services/monitoring/uptime.nix new file mode 100644 index 0000000000000..fa3de7d90bc60 --- /dev/null +++ b/nixos/modules/services/monitoring/uptime.nix @@ -0,0 +1,95 @@ +{ config, pkgs, ... }: +let + inherit (pkgs.lib) mkOption mkEnableOption mkIf mkMerge types optionalAttrs optional; + + cfg = config.services.uptime; + + configDir = pkgs.runCommand "config" {} (if cfg.configFile != null then '' + mkdir $out + ext=`echo ${cfg.configFile} | grep -o \\..*` + ln -sv ${cfg.configFile} $out/default$ext + ln -sv /var/lib/uptime/runtime.json $out/runtime.json + '' else '' + mkdir $out + cat ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/config/default.yaml > $out/default.yaml + cat >> $out/default.yaml <<EOF + + autoStartMonitor: false + + mongodb: + connectionString: 'mongodb://localhost/uptime' + EOF + ln -sv /var/lib/uptime/runtime.json $out/runtime.json + ''); +in { + options.services.uptime = { + configFile = mkOption { + description = '' + The uptime configuration file + + If mongodb: server != localhost, please set usesRemoteMongo = true + + If you only want to run the monitor, please set enableWebService = false + and enableSeparateMonitoringService = true + + If autoStartMonitor: false (recommended) and you want to run both + services, please set enableSeparateMonitoringService = true + ''; + + type = types.nullOr types.path; + + default = null; + }; + + usesRemoteMongo = mkOption { + description = "Whether the configuration file specifies a remote mongo instance"; + + default = false; + + type = types.bool; + }; + + enableWebService = mkEnableOption "the uptime monitoring program web service"; + + enableSeparateMonitoringService = mkEnableOption "the uptime monitoring service (default: enableWebService == true)" // { default = cfg.enableWebService; }; + + nodeEnv = mkOption { + description = "The node environment to run in (development, production, etc.)"; + + type = types.string; + + default = "production"; + }; + }; + + config = mkMerge [ (mkIf cfg.enableWebService { + systemd.services.uptime = { + description = "uptime web service"; + wantedBy = [ "multi-user.target" ]; + environment = { + NODE_CONFIG_DIR = configDir; + NODE_ENV = cfg.nodeEnv; + NODE_PATH = "${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/node_modules"; + }; + preStart = "mkdir -p /var/lib/uptime"; + serviceConfig.ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/app.js"; + }; + + services.mongodb.enable = mkIf (!cfg.usesRemoteMongo) true; + }) (mkIf cfg.enableSeparateMonitoringService { + systemd.services.uptime-monitor = { + description = "uptime monitoring service"; + wantedBy = [ "multi-user.target" ]; + requires = optional cfg.enableWebService "uptime.service"; + after = optional cfg.enableWebService "uptime.service"; + environment = { + NODE_CONFIG_DIR = configDir; + NODE_ENV = cfg.nodeEnv; + NODE_PATH = "${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/node_modules"; + }; + # Ugh, need to wait for web service to be up + preStart = if cfg.enableWebService then "sleep 1s" else "mkdir -p /var/lib/uptime"; + serviceConfig.ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/monitor.js"; + }; + }) ]; +} diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix new file mode 100644 index 0000000000000..229236c1bbd44 --- /dev/null +++ b/nixos/modules/services/monitoring/zabbix-agent.nix @@ -0,0 +1,100 @@ +# Zabbix agent daemon. +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.zabbixAgent; + + stateDir = "/var/run/zabbix"; + + logDir = "/var/log/zabbix"; + + pidFile = "${stateDir}/zabbix_agentd.pid"; + + configFile = pkgs.writeText "zabbix_agentd.conf" + '' + Server = ${cfg.server} + + LogFile = ${logDir}/zabbix_agentd + + PidFile = ${pidFile} + + StartAgents = 1 + + ${config.services.zabbixAgent.extraConfig} + ''; + +in + +{ + + ###### interface + + options = { + + services.zabbixAgent = { + + enable = mkOption { + default = false; + description = '' + Whether to run the Zabbix monitoring agent on this machine. + It will send monitoring data to a Zabbix server. + ''; + }; + + server = mkOption { + default = "127.0.0.1"; + description = '' + The IP address or hostname of the Zabbix server to connect to. + ''; + }; + + extraConfig = mkOption { + default = ""; + description = '' + Configuration that is injected verbatim into the configuration file. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.extraUsers = singleton + { name = "zabbix"; + uid = config.ids.uids.zabbix; + description = "Zabbix daemon user"; + }; + + systemd.services."zabbix-agent" = + { description = "Zabbix Agent"; + + wantedBy = [ "multi-user.target" ]; + + path = [ pkgs.nettools ]; + + preStart = + '' + mkdir -m 0755 -p ${stateDir} ${logDir} + chown zabbix ${stateDir} ${logDir} + ''; + + serviceConfig.ExecStart = "@${pkgs.zabbix.agent}/sbin/zabbix_agentd zabbix_agentd --config ${configFile}"; + serviceConfig.Type = "forking"; + serviceConfig.RemainAfterExit = true; + serviceConfig.Restart = "always"; + serviceConfig.RestartSec = 2; + }; + + environment.systemPackages = [ pkgs.zabbix.agent ]; + + }; + +} diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix new file mode 100644 index 0000000000000..6735b4ca32794 --- /dev/null +++ b/nixos/modules/services/monitoring/zabbix-server.nix @@ -0,0 +1,113 @@ +# Zabbix server daemon. +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.zabbixServer; + + stateDir = "/var/run/zabbix"; + + logDir = "/var/log/zabbix"; + + libDir = "/var/lib/zabbix"; + + pidFile = "${stateDir}/zabbix_server.pid"; + + configFile = pkgs.writeText "zabbix_server.conf" + '' + LogFile = ${logDir}/zabbix_server + + PidFile = ${pidFile} + + ${optionalString (cfg.dbServer != "localhost") '' + DBHost = ${cfg.dbServer} + ''} + + DBName = zabbix + + DBUser = zabbix + + ${optionalString (cfg.dbPassword != "") '' + DBPassword = ${cfg.dbPassword} + ''} + ''; + + useLocalPostgres = cfg.dbServer == "localhost" || cfg.dbServer == ""; + +in + +{ + + ###### interface + + options = { + + services.zabbixServer.enable = mkOption { + default = false; + description = '' + Whether to run the Zabbix server on this machine. + ''; + }; + + services.zabbixServer.dbServer = mkOption { + default = "localhost"; + description = '' + Hostname or IP address of the database server. + Use an empty string ("") to use peer authentication. + ''; + }; + + services.zabbixServer.dbPassword = mkOption { + default = ""; + description = "Password used to connect to the database server."; + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + + services.postgresql.enable = useLocalPostgres; + + users.extraUsers = singleton + { name = "zabbix"; + uid = config.ids.uids.zabbix; + description = "Zabbix daemon user"; + }; + + systemd.services."zabbix-server" = + { description = "Zabbix Server"; + + wantedBy = [ "multi-user.target" ]; + after = optional useLocalPostgres "postgresql.service"; + + preStart = + '' + mkdir -m 0755 -p ${stateDir} ${logDir} ${libDir} + chown zabbix ${stateDir} ${logDir} ${libDir} + + if ! test -e "${libDir}/db-created"; then + ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole zabbix || true + ${pkgs.postgresql}/bin/createdb --owner zabbix zabbix || true + cat ${pkgs.zabbix.server}/share/zabbix/db/schema/postgresql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix' + cat ${pkgs.zabbix.server}/share/zabbix/db/data/images_pgsql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix' + cat ${pkgs.zabbix.server}/share/zabbix/db/data/data.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix' + touch "${libDir}/db-created" + fi + ''; + + path = [ pkgs.nettools ]; + + serviceConfig.ExecStart = "@${pkgs.zabbix.server}/sbin/zabbix_server zabbix_server --config ${configFile}"; + serviceConfig.Type = "forking"; + serviceConfig.Restart = "always"; + serviceConfig.RestartSec = 2; + serviceConfig.PIDFile = pidFile; + }; + + }; + +} |