1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.netdata;
7
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 '';
16
17 plugins = [
18 "${cfg.package}/libexec/netdata/plugins.d"
19 "${wrappedPlugins}/libexec/netdata/plugins.d"
20 ] ++ cfg.extraPluginPaths;
21
22 configDirectory = pkgs.runCommand "netdata-config-d" { } ''
23 mkdir $out
24 ${concatStringsSep "\n" (mapAttrsToList (path: file: ''
25 mkdir -p "$out/$(dirname ${path})"
26 ln -s "${file}" "$out/${path}"
27 '') cfg.configDir)}
28 '';
29
30 localConfig = {
31 global = {
32 "config directory" = "/etc/netdata/conf.d";
33 "plugins directory" = concatStringsSep " " plugins;
34 };
35 web = {
36 "web files owner" = "root";
37 "web files group" = "root";
38 };
39 "plugin:cgroups" = {
40 "script to get cgroup network interfaces" = "${wrappedPlugins}/libexec/netdata/plugins.d/cgroup-network";
41 "use unified cgroups" = "yes";
42 };
43 };
44 mkConfig = generators.toINI {} (recursiveUpdate localConfig cfg.config);
45 configFile = pkgs.writeText "netdata.conf" (if cfg.configText != null then cfg.configText else mkConfig);
46
47 defaultUser = "netdata";
48
49in {
50 options = {
51 services.netdata = {
52 enable = mkEnableOption (lib.mdDoc "netdata");
53
54 package = mkOption {
55 type = types.package;
56 default = pkgs.netdata;
57 defaultText = literalExpression "pkgs.netdata";
58 description = lib.mdDoc "Netdata package to use.";
59 };
60
61 user = mkOption {
62 type = types.str;
63 default = "netdata";
64 description = lib.mdDoc "User account under which netdata runs.";
65 };
66
67 group = mkOption {
68 type = types.str;
69 default = "netdata";
70 description = lib.mdDoc "Group under which netdata runs.";
71 };
72
73 configText = mkOption {
74 type = types.nullOr types.lines;
75 description = lib.mdDoc "Verbatim netdata.conf, cannot be combined with config.";
76 default = null;
77 example = ''
78 [global]
79 debug log = syslog
80 access log = syslog
81 error log = syslog
82 '';
83 };
84
85 python = {
86 enable = mkOption {
87 type = types.bool;
88 default = true;
89 description = lib.mdDoc ''
90 Whether to enable python-based plugins
91 '';
92 };
93 extraPackages = mkOption {
94 type = types.functionTo (types.listOf types.package);
95 default = ps: [];
96 defaultText = literalExpression "ps: []";
97 example = literalExpression ''
98 ps: [
99 ps.psycopg2
100 ps.docker
101 ps.dnspython
102 ]
103 '';
104 description = lib.mdDoc ''
105 Extra python packages available at runtime
106 to enable additional python plugins.
107 '';
108 };
109 };
110
111 extraPluginPaths = mkOption {
112 type = types.listOf types.path;
113 default = [ ];
114 example = literalExpression ''
115 [ "/path/to/plugins.d" ]
116 '';
117 description = lib.mdDoc ''
118 Extra paths to add to the netdata global "plugins directory"
119 option. Useful for when you want to include your own
120 collection scripts.
121
122 Details about writing a custom netdata plugin are available at:
123 <https://docs.netdata.cloud/collectors/plugins.d/>
124
125 Cannot be combined with configText.
126 '';
127 };
128
129 config = mkOption {
130 type = types.attrsOf types.attrs;
131 default = {};
132 description = lib.mdDoc "netdata.conf configuration as nix attributes. cannot be combined with configText.";
133 example = literalExpression ''
134 global = {
135 "debug log" = "syslog";
136 "access log" = "syslog";
137 "error log" = "syslog";
138 };
139 '';
140 };
141
142 configDir = mkOption {
143 type = types.attrsOf types.path;
144 default = {};
145 description = lib.mdDoc ''
146 Complete netdata config directory except netdata.conf.
147 The default configuration is merged with changes
148 defined in this option.
149 Each top-level attribute denotes a path in the configuration
150 directory as in environment.etc.
151 Its value is the absolute path and must be readable by netdata.
152 Cannot be combined with configText.
153 '';
154 example = literalExpression ''
155 "health_alarm_notify.conf" = pkgs.writeText "health_alarm_notify.conf" '''
156 sendmail="/path/to/sendmail"
157 ''';
158 "health.d" = "/run/secrets/netdata/health.d";
159 '';
160 };
161
162 enableAnalyticsReporting = mkOption {
163 type = types.bool;
164 default = false;
165 description = lib.mdDoc ''
166 Enable reporting of anonymous usage statistics to Netdata Inc. via either
167 Google Analytics (in versions prior to 1.29.4), or Netdata Inc.'s
168 self-hosted PostHog (in versions 1.29.4 and later).
169 See: <https://learn.netdata.cloud/docs/agent/anonymous-statistics>
170 '';
171 };
172 };
173 };
174
175 config = mkIf cfg.enable {
176 assertions =
177 [ { assertion = cfg.config != {} -> cfg.configText == null ;
178 message = "Cannot specify both config and configText";
179 }
180 ];
181
182 environment.etc."netdata/netdata.conf".source = configFile;
183 environment.etc."netdata/conf.d".source = configDirectory;
184
185 systemd.services.netdata = {
186 description = "Real time performance monitoring";
187 after = [ "network.target" ];
188 wantedBy = [ "multi-user.target" ];
189 path = (with pkgs; [ curl gawk iproute2 which procps bash ])
190 ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
191 ++ lib.optional config.virtualisation.libvirtd.enable (config.virtualisation.libvirtd.package);
192 environment = {
193 PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules";
194 } // lib.optionalAttrs (!cfg.enableAnalyticsReporting) {
195 DO_NOT_TRACK = "1";
196 };
197 restartTriggers = [
198 config.environment.etc."netdata/netdata.conf".source
199 config.environment.etc."netdata/conf.d".source
200 ];
201 serviceConfig = {
202 ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c /etc/netdata/netdata.conf";
203 ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
204 ExecStartPost = pkgs.writeShellScript "wait-for-netdata-up" ''
205 while [ "$(${pkgs.netdata}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done
206 '';
207
208 TimeoutStopSec = 60;
209 Restart = "on-failure";
210 # User and group
211 User = cfg.user;
212 Group = cfg.group;
213 # Performance
214 LimitNOFILE = "30000";
215 # Runtime directory and mode
216 RuntimeDirectory = "netdata";
217 RuntimeDirectoryMode = "0750";
218 # State directory and mode
219 StateDirectory = "netdata";
220 StateDirectoryMode = "0750";
221 # Cache directory and mode
222 CacheDirectory = "netdata";
223 CacheDirectoryMode = "0750";
224 # Logs directory and mode
225 LogsDirectory = "netdata";
226 LogsDirectoryMode = "0750";
227 # Configuration directory and mode
228 ConfigurationDirectory = "netdata";
229 ConfigurationDirectoryMode = "0755";
230 # Capabilities
231 CapabilityBoundingSet = [
232 "CAP_DAC_OVERRIDE" # is required for freeipmi and slabinfo plugins
233 "CAP_DAC_READ_SEARCH" # is required for apps plugin
234 "CAP_FOWNER" # is required for freeipmi plugin
235 "CAP_SETPCAP" # is required for apps, perf and slabinfo plugins
236 "CAP_SYS_ADMIN" # is required for perf plugin
237 "CAP_SYS_PTRACE" # is required for apps plugin
238 "CAP_SYS_RESOURCE" # is required for ebpf plugin
239 "CAP_NET_RAW" # is required for fping app
240 "CAP_SYS_CHROOT" # is required for cgroups plugin
241 "CAP_SETUID" # is required for cgroups and cgroups-network plugins
242 ];
243 # Sandboxing
244 ProtectSystem = "full";
245 ProtectHome = "read-only";
246 PrivateTmp = true;
247 ProtectControlGroups = true;
248 PrivateMounts = true;
249 };
250 };
251
252 systemd.enableCgroupAccounting = true;
253
254 security.wrappers = {
255 "apps.plugin" = {
256 source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org";
257 capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
258 owner = cfg.user;
259 group = cfg.group;
260 permissions = "u+rx,g+x,o-rwx";
261 };
262
263 "cgroup-network" = {
264 source = "${cfg.package}/libexec/netdata/plugins.d/cgroup-network.org";
265 capabilities = "cap_setuid+ep";
266 owner = cfg.user;
267 group = cfg.group;
268 permissions = "u+rx,g+x,o-rwx";
269 };
270
271 "perf.plugin" = {
272 source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org";
273 capabilities = "cap_sys_admin+ep";
274 owner = cfg.user;
275 group = cfg.group;
276 permissions = "u+rx,g+x,o-rwx";
277 };
278
279 "slabinfo.plugin" = {
280 source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
281 capabilities = "cap_dac_override+ep";
282 owner = cfg.user;
283 group = cfg.group;
284 permissions = "u+rx,g+x,o-rwx";
285 };
286
287 } // optionalAttrs (cfg.package.withIpmi) {
288 "freeipmi.plugin" = {
289 source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org";
290 capabilities = "cap_dac_override,cap_fowner+ep";
291 owner = cfg.user;
292 group = cfg.group;
293 permissions = "u+rx,g+x,o-rwx";
294 };
295 };
296
297 security.pam.loginLimits = [
298 { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; }
299 { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; }
300 ];
301
302 users.users = optionalAttrs (cfg.user == defaultUser) {
303 ${defaultUser} = {
304 group = defaultUser;
305 isSystemUser = true;
306 };
307 };
308
309 users.groups = optionalAttrs (cfg.group == defaultUser) {
310 ${defaultUser} = { };
311 };
312
313 };
314}