1 { config, pkgs, lib, ... }:
6 cfg = config.services.netdata;
8 wrappedPlugins = pkgs.runCommand "wrapped-plugins" { preferLocalBuild = true; } ''
9 mkdir -p $out/libexec/netdata/plugins.d
10 ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin
11 ln -s /run/wrappers/bin/cgroup-network $out/libexec/netdata/plugins.d/cgroup-network
12 ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin
13 ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin
14 ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
15 ln -s /run/wrappers/bin/systemd-journal.plugin $out/libexec/netdata/plugins.d/systemd-journal.plugin
16 ln -s /run/wrappers/bin/logs-management.plugin $out/libexec/netdata/plugins.d/logs-management.plugin
17 ln -s /run/wrappers/bin/network-viewer.plugin $out/libexec/netdata/plugins.d/network-viewer.plugin
18 ln -s /run/wrappers/bin/debugfs.plugin $out/libexec/netdata/plugins.d/debugfs.plugin
22 "${cfg.package}/libexec/netdata/plugins.d"
23 "${wrappedPlugins}/libexec/netdata/plugins.d"
24 ] ++ cfg.extraPluginPaths;
26 configDirectory = pkgs.runCommand "netdata-config-d" { } ''
28 ${concatStringsSep "\n" (mapAttrsToList (path: file: ''
29 mkdir -p "$out/$(dirname ${path})"
30 ln -s "${file}" "$out/${path}"
36 "config directory" = "/etc/netdata/conf.d";
37 "plugins directory" = concatStringsSep " " plugins;
40 "web files owner" = "root";
41 "web files group" = "root";
44 "script to get cgroup network interfaces" = "${wrappedPlugins}/libexec/netdata/plugins.d/cgroup-network";
45 "use unified cgroups" = "yes";
48 mkConfig = generators.toINI {} (recursiveUpdate localConfig cfg.config);
49 configFile = pkgs.writeText "netdata.conf" (if cfg.configText != null then cfg.configText else mkConfig);
51 defaultUser = "netdata";
53 isThereAnyWireGuardTunnels = config.networking.wireguard.enable || lib.any (c: lib.hasAttrByPath [ "netdevConfig" "Kind" ] c && c.netdevConfig.Kind == "wireguard") (builtins.attrValues config.systemd.network.netdevs);
57 enable = mkEnableOption "netdata";
59 package = mkPackageOption pkgs "netdata" { };
64 description = "User account under which netdata runs.";
70 description = "Group under which netdata runs.";
73 configText = mkOption {
74 type = types.nullOr types.lines;
75 description = "Verbatim netdata.conf, cannot be combined with config.";
90 Whether to enable python-based plugins
93 recommendedPythonPackages = mkOption {
97 Whether to enable a set of recommended Python plugins
98 by installing extra Python packages.
101 extraPackages = mkOption {
102 type = types.functionTo (types.listOf types.package);
104 defaultText = literalExpression "ps: []";
105 example = literalExpression ''
113 Extra python packages available at runtime
114 to enable additional python plugins.
119 extraPluginPaths = mkOption {
120 type = types.listOf types.path;
122 example = literalExpression ''
123 [ "/path/to/plugins.d" ]
126 Extra paths to add to the netdata global "plugins directory"
127 option. Useful for when you want to include your own
130 Details about writing a custom netdata plugin are available at:
131 <https://docs.netdata.cloud/collectors/plugins.d/>
133 Cannot be combined with configText.
138 type = types.attrsOf types.attrs;
140 description = "netdata.conf configuration as nix attributes. cannot be combined with configText.";
141 example = literalExpression ''
143 "debug log" = "syslog";
144 "access log" = "syslog";
145 "error log" = "syslog";
150 configDir = mkOption {
151 type = types.attrsOf types.path;
154 Complete netdata config directory except netdata.conf.
155 The default configuration is merged with changes
156 defined in this option.
157 Each top-level attribute denotes a path in the configuration
158 directory as in environment.etc.
159 Its value is the absolute path and must be readable by netdata.
160 Cannot be combined with configText.
162 example = literalExpression ''
163 "health_alarm_notify.conf" = pkgs.writeText "health_alarm_notify.conf" '''
164 sendmail="/path/to/sendmail"
166 "health.d" = "/run/secrets/netdata/health.d";
170 claimTokenFile = mkOption {
171 type = types.nullOr types.path;
174 If set, automatically registers the agent using the given claim token
179 enableAnalyticsReporting = mkOption {
183 Enable reporting of anonymous usage statistics to Netdata Inc. via either
184 Google Analytics (in versions prior to 1.29.4), or Netdata Inc.'s
185 self-hosted PostHog (in versions 1.29.4 and later).
186 See: <https://learn.netdata.cloud/docs/agent/anonymous-statistics>
190 deadlineBeforeStopSec = mkOption {
194 In order to detect when netdata is misbehaving, we run a concurrent task pinging netdata (wait-for-netdata-up)
197 If after a while, this task does not succeed, we stop the unit and mark it as failed.
199 You can control this deadline in seconds with this option, it's useful to bump it
200 if you have (1) a lot of data (2) doing upgrades (3) have low IOPS/throughput.
206 config = mkIf cfg.enable {
208 [ { assertion = cfg.config != {} -> cfg.configText == null ;
209 message = "Cannot specify both config and configText";
213 # Includes a set of recommended Python plugins in exchange of imperfect disk consumption.
214 services.netdata.python.extraPackages = lib.mkIf cfg.python.recommendedPythonPackages (ps: [
224 services.netdata.configDir.".opt-out-from-anonymous-statistics" = mkIf (!cfg.enableAnalyticsReporting) (pkgs.writeText ".opt-out-from-anonymous-statistics" "");
225 environment.etc."netdata/netdata.conf".source = configFile;
226 environment.etc."netdata/conf.d".source = configDirectory;
228 systemd.services.netdata = {
229 description = "Real time performance monitoring";
230 after = [ "network.target" "suid-sgid-wrappers.service" ];
231 # No wrapper means no "useful" netdata.
232 requires = [ "suid-sgid-wrappers.service" ];
233 wantedBy = [ "multi-user.target" ];
243 apcupsd # for charts.d
244 # TODO: firehol # for FireQoS -- this requires more NixOS module support.
245 util-linux # provides logger command; required for syslog health alarms
247 ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
248 ++ lib.optional config.virtualisation.libvirtd.enable config.virtualisation.libvirtd.package
249 ++ lib.optional config.virtualisation.docker.enable config.virtualisation.docker.package
250 ++ lib.optionals config.virtualisation.podman.enable [ pkgs.jq config.virtualisation.podman.package ]
251 ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package;
253 PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules";
254 NETDATA_PIPENAME = "/run/netdata/ipc";
255 } // lib.optionalAttrs (!cfg.enableAnalyticsReporting) {
259 config.environment.etc."netdata/netdata.conf".source
260 config.environment.etc."netdata/conf.d".source
263 ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c /etc/netdata/netdata.conf";
264 ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
265 ExecStartPost = pkgs.writeShellScript "wait-for-netdata-up" ''
266 while [ "$(${cfg.package}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done
269 TimeoutStopSec = cfg.deadlineBeforeStopSec;
270 Restart = "on-failure";
275 LimitNOFILE = "30000";
276 # Runtime directory and mode
277 RuntimeDirectory = "netdata";
278 RuntimeDirectoryMode = "0750";
279 # State directory and mode
280 StateDirectory = "netdata";
281 StateDirectoryMode = "0750";
282 # Cache directory and mode
283 CacheDirectory = "netdata";
284 CacheDirectoryMode = "0750";
285 # Logs directory and mode
286 LogsDirectory = "netdata";
287 LogsDirectoryMode = "0750";
288 # Configuration directory and mode
289 ConfigurationDirectory = "netdata";
290 ConfigurationDirectoryMode = "0755";
291 # AmbientCapabilities
292 AmbientCapabilities = lib.optional isThereAnyWireGuardTunnels "CAP_NET_ADMIN";
294 CapabilityBoundingSet = [
295 "CAP_DAC_OVERRIDE" # is required for freeipmi and slabinfo plugins
296 "CAP_DAC_READ_SEARCH" # is required for apps and systemd-journal plugin
297 "CAP_FOWNER" # is required for freeipmi plugin
298 "CAP_SETPCAP" # is required for apps, perf and slabinfo plugins
299 "CAP_SYS_ADMIN" # is required for perf plugin
300 "CAP_SYS_PTRACE" # is required for apps plugin
301 "CAP_SYS_RESOURCE" # is required for ebpf plugin
302 "CAP_NET_RAW" # is required for fping app
303 "CAP_SYS_CHROOT" # is required for cgroups plugin
304 "CAP_SETUID" # is required for cgroups and cgroups-network plugins
305 "CAP_SYSLOG" # is required for systemd-journal plugin
306 ] ++ lib.optional isThereAnyWireGuardTunnels "CAP_NET_ADMIN";
308 ProtectSystem = "full";
309 ProtectHome = "read-only";
311 ProtectControlGroups = true;
312 PrivateMounts = true;
313 } // (lib.optionalAttrs (cfg.claimTokenFile != null) {
315 "netdata_claim_token:${cfg.claimTokenFile}"
318 ExecStartPre = pkgs.writeShellScript "netdata-claim" ''
321 if [[ -f /var/lib/netdata/cloud.d/claimed_id ]]; then
326 exec ${cfg.package}/bin/netdata-claim.sh \
327 -token="$(< "$CREDENTIALS_DIRECTORY/netdata_claim_token")" \
328 -url=https://app.netdata.cloud \
334 systemd.enableCgroupAccounting = true;
336 security.wrappers = {
338 source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org";
339 capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
342 permissions = "u+rx,g+x,o-rwx";
346 source = "${cfg.package}/libexec/netdata/plugins.d/debugfs.plugin.org";
347 capabilities = "cap_dac_read_search+ep";
350 permissions = "u+rx,g+x,o-rwx";
354 source = "${cfg.package}/libexec/netdata/plugins.d/cgroup-network.org";
355 capabilities = "cap_setuid+ep";
358 permissions = "u+rx,g+x,o-rwx";
362 source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org";
363 capabilities = "cap_sys_admin+ep";
366 permissions = "u+rx,g+x,o-rwx";
369 "systemd-journal.plugin" = {
370 source = "${cfg.package}/libexec/netdata/plugins.d/systemd-journal.plugin.org";
371 capabilities = "cap_dac_read_search,cap_syslog+ep";
374 permissions = "u+rx,g+x,o-rwx";
377 "slabinfo.plugin" = {
378 source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
379 capabilities = "cap_dac_override+ep";
382 permissions = "u+rx,g+x,o-rwx";
385 } // optionalAttrs (cfg.package.withIpmi) {
386 "freeipmi.plugin" = {
387 source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org";
388 capabilities = "cap_dac_override,cap_fowner+ep";
391 permissions = "u+rx,g+x,o-rwx";
393 } // optionalAttrs (cfg.package.withNetworkViewer) {
394 "network-viewer.plugin" = {
395 source = "${cfg.package}/libexec/netdata/plugins.d/network-viewer.plugin.org";
396 capabilities = "cap_sys_admin,cap_dac_read_search,cap_sys_ptrace+ep";
399 permissions = "u+rx,g+x,o-rwx";
403 security.pam.loginLimits = [
404 { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; }
405 { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; }
408 users.users = optionalAttrs (cfg.user == defaultUser) {
412 extraGroups = lib.optional config.virtualisation.docker.enable "docker"
413 ++ lib.optional config.virtualisation.podman.enable "podman";
417 users.groups = optionalAttrs (cfg.group == defaultUser) {
418 ${defaultUser} = { };