summary refs log tree commit diff
path: root/nixos/modules/services/monitoring
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
commit5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010 (patch)
treea6c0f605be6de3f372ae69905b331f9f75452da7 /nixos/modules/services/monitoring
parent6070bc016bd2fd945b04347e25cfd3738622d2ac (diff)
downloadnixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.gz
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.bz2
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.lz
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.xz
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.zst
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.zip
Move all of NixOS to nixos/ in preparation of the repository merge
Diffstat (limited to 'nixos/modules/services/monitoring')
-rw-r--r--nixos/modules/services/monitoring/apcupsd.nix190
-rw-r--r--nixos/modules/services/monitoring/dd-agent.nix83
-rw-r--r--nixos/modules/services/monitoring/graphite.nix265
-rw-r--r--nixos/modules/services/monitoring/monit.nix52
-rw-r--r--nixos/modules/services/monitoring/nagios/commands.cfg34
-rw-r--r--nixos/modules/services/monitoring/nagios/default.nix186
-rw-r--r--nixos/modules/services/monitoring/nagios/host-templates.cfg27
-rw-r--r--nixos/modules/services/monitoring/nagios/service-templates.cfg32
-rw-r--r--nixos/modules/services/monitoring/nagios/timeperiods.cfg11
-rw-r--r--nixos/modules/services/monitoring/smartd.nix117
-rw-r--r--nixos/modules/services/monitoring/statsd.nix94
-rw-r--r--nixos/modules/services/monitoring/systemhealth.nix133
-rw-r--r--nixos/modules/services/monitoring/ups.nix275
-rw-r--r--nixos/modules/services/monitoring/uptime.nix95
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix100
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix113
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 00000000000..114bad5c947
--- /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 00000000000..ef658523c1f
--- /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 00000000000..ec36db7b21c
--- /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 00000000000..2acc51c64a6
--- /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 00000000000..6efdefcd37d
--- /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 00000000000..c809a3b8457
--- /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 00000000000..3a4c269e257
--- /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 00000000000..e729ea77675
--- /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 00000000000..2669be54d3d
--- /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 00000000000..de07dc0dbaa
--- /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 00000000000..a3266605671
--- /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 00000000000..0a3e666ad4e
--- /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 00000000000..a7b72e53f0a
--- /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 00000000000..fa3de7d90bc
--- /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 00000000000..229236c1bbd
--- /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 00000000000..6735b4ca327
--- /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;
+      };
+
+  };
+
+}