at master 18 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 cfg = config.services.netdata; 9 10 wrappedPlugins = pkgs.runCommand "wrapped-plugins" { preferLocalBuild = true; } '' 11 mkdir -p $out/libexec/netdata/plugins.d 12 ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin 13 ln -s /run/wrappers/bin/cgroup-network $out/libexec/netdata/plugins.d/cgroup-network 14 ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin 15 ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin 16 ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin 17 ln -s /run/wrappers/bin/systemd-journal.plugin $out/libexec/netdata/plugins.d/systemd-journal.plugin 18 ln -s /run/wrappers/bin/logs-management.plugin $out/libexec/netdata/plugins.d/logs-management.plugin 19 ln -s /run/wrappers/bin/network-viewer.plugin $out/libexec/netdata/plugins.d/network-viewer.plugin 20 ln -s /run/wrappers/bin/debugfs.plugin $out/libexec/netdata/plugins.d/debugfs.plugin 21 ''; 22 23 plugins = [ 24 "${cfg.package}/libexec/netdata/plugins.d" 25 "${wrappedPlugins}/libexec/netdata/plugins.d" 26 ] 27 ++ cfg.extraPluginPaths; 28 29 configDirectory = pkgs.runCommand "netdata-config-d" { } '' 30 mkdir $out 31 ${lib.concatStringsSep "\n" ( 32 lib.mapAttrsToList (path: file: '' 33 mkdir -p "$out/$(dirname ${path})" 34 ${if path == "apps_groups.conf" then "cp" else "ln -s"} "${file}" "$out/${path}" 35 '') cfg.configDir 36 )} 37 ''; 38 39 localConfig = { 40 global = { 41 "config directory" = "/etc/netdata/conf.d"; 42 "plugins directory" = lib.concatStringsSep " " plugins; 43 }; 44 web = { 45 "web files owner" = "root"; 46 "web files group" = "root"; 47 }; 48 "plugin:cgroups" = { 49 "script to get cgroup network interfaces" = 50 "${wrappedPlugins}/libexec/netdata/plugins.d/cgroup-network"; 51 "use unified cgroups" = "yes"; 52 }; 53 }; 54 mkConfig = lib.generators.toINI { } (lib.recursiveUpdate localConfig cfg.config); 55 configFile = pkgs.writeText "netdata.conf" ( 56 if cfg.configText != null then cfg.configText else mkConfig 57 ); 58 59 defaultUser = "netdata"; 60 61 isThereAnyWireGuardTunnels = 62 config.networking.wireguard.enable 63 || lib.any ( 64 c: lib.hasAttrByPath [ "netdevConfig" "Kind" ] c && c.netdevConfig.Kind == "wireguard" 65 ) (builtins.attrValues config.systemd.network.netdevs); 66 67 extraNdsudoPathsEnv = pkgs.buildEnv { 68 name = "netdata-ndsudo-env"; 69 paths = cfg.extraNdsudoPackages; 70 pathsToLink = [ "/bin" ]; 71 }; 72 73in 74{ 75 options = { 76 services.netdata = { 77 enable = lib.mkEnableOption "netdata"; 78 79 package = lib.mkPackageOption pkgs "netdata" { }; 80 81 user = lib.mkOption { 82 type = lib.types.str; 83 default = "netdata"; 84 description = "User account under which netdata runs."; 85 }; 86 87 group = lib.mkOption { 88 type = lib.types.str; 89 default = "netdata"; 90 description = "Group under which netdata runs."; 91 }; 92 93 configText = lib.mkOption { 94 type = lib.types.nullOr lib.types.lines; 95 description = "Verbatim netdata.conf, cannot be combined with config."; 96 default = null; 97 example = '' 98 [global] 99 debug log = syslog 100 access log = syslog 101 error log = syslog 102 ''; 103 }; 104 105 python = { 106 enable = lib.mkOption { 107 type = lib.types.bool; 108 default = true; 109 description = '' 110 Whether to enable python-based plugins 111 ''; 112 }; 113 recommendedPythonPackages = lib.mkOption { 114 type = lib.types.bool; 115 default = false; 116 description = '' 117 Whether to enable a set of recommended Python plugins 118 by installing extra Python packages. 119 ''; 120 }; 121 extraPackages = lib.mkOption { 122 type = lib.types.functionTo (lib.types.listOf lib.types.package); 123 default = ps: [ ]; 124 defaultText = lib.literalExpression "ps: []"; 125 example = lib.literalExpression '' 126 ps: [ 127 ps.psycopg2 128 ps.docker 129 ps.dnspython 130 ] 131 ''; 132 description = '' 133 Extra python packages available at runtime 134 to enable additional python plugins. 135 ''; 136 }; 137 }; 138 139 extraPluginPaths = lib.mkOption { 140 type = lib.types.listOf lib.types.path; 141 default = [ ]; 142 example = lib.literalExpression '' 143 [ "/path/to/plugins.d" ] 144 ''; 145 description = '' 146 Extra paths to add to the netdata global "plugins directory" 147 option. Useful for when you want to include your own 148 collection scripts. 149 150 Details about writing a custom netdata plugin are available at: 151 <https://docs.netdata.cloud/collectors/plugins.d/> 152 153 Cannot be combined with configText. 154 ''; 155 }; 156 157 extraNdsudoPackages = lib.mkOption { 158 type = lib.types.listOf lib.types.package; 159 default = [ ]; 160 description = '' 161 Extra packages to add to `PATH` to make available to `ndsudo`. 162 ::: {.warning} 163 `ndsudo` has SUID privileges, be careful what packages you list here. 164 ::: 165 166 ::: {.note} 167 `cfg.package` must be built with `withNdsudo = true` 168 ::: 169 ''; 170 example = '' 171 [ 172 pkgs.smartmontools 173 pkgs.nvme-cli 174 ] 175 ''; 176 }; 177 178 config = lib.mkOption { 179 type = lib.types.attrsOf lib.types.attrs; 180 default = { }; 181 description = "netdata.conf configuration as nix attributes. cannot be combined with configText."; 182 example = lib.literalExpression '' 183 global = { 184 "debug log" = "syslog"; 185 "access log" = "syslog"; 186 "error log" = "syslog"; 187 }; 188 ''; 189 }; 190 191 configDir = lib.mkOption { 192 type = lib.types.attrsOf lib.types.path; 193 default = { }; 194 description = '' 195 Complete netdata config directory except netdata.conf. 196 The default configuration is merged with changes 197 defined in this option. 198 Each top-level attribute denotes a path in the configuration 199 directory as in environment.etc. 200 Its value is the absolute path and must be readable by netdata. 201 Cannot be combined with configText. 202 ''; 203 example = lib.literalExpression '' 204 "health_alarm_notify.conf" = pkgs.writeText "health_alarm_notify.conf" ''' 205 sendmail="/path/to/sendmail" 206 '''; 207 "health.d" = "/run/secrets/netdata/health.d"; 208 ''; 209 }; 210 211 claimTokenFile = lib.mkOption { 212 type = lib.types.nullOr lib.types.path; 213 default = null; 214 description = '' 215 If set, automatically registers the agent using the given claim token 216 file. 217 ''; 218 }; 219 220 enableAnalyticsReporting = lib.mkOption { 221 type = lib.types.bool; 222 default = false; 223 description = '' 224 Enable reporting of anonymous usage statistics to Netdata Inc. via either 225 Google Analytics (in versions prior to 1.29.4), or Netdata Inc.'s 226 self-hosted PostHog (in versions 1.29.4 and later). 227 See: <https://learn.netdata.cloud/docs/agent/anonymous-statistics> 228 ''; 229 }; 230 231 deadlineBeforeStopSec = lib.mkOption { 232 type = lib.types.int; 233 default = 120; 234 description = '' 235 In order to detect when netdata is misbehaving, we run a concurrent task pinging netdata (wait-for-netdata-up) 236 in the systemd unit. 237 238 If after a while, this task does not succeed, we stop the unit and mark it as failed. 239 240 You can control this deadline in seconds with this option, it's useful to bump it 241 if you have (1) a lot of data (2) doing upgrades (3) have low IOPS/throughput. 242 ''; 243 }; 244 }; 245 }; 246 247 config = lib.mkIf cfg.enable { 248 assertions = [ 249 { 250 assertion = cfg.config != { } -> cfg.configText == null; 251 message = "Cannot specify both config and configText"; 252 } 253 ]; 254 255 # Includes a set of recommended Python plugins in exchange of imperfect disk consumption. 256 services.netdata.python.extraPackages = lib.mkIf cfg.python.recommendedPythonPackages (ps: [ 257 ps.requests 258 ps.pandas 259 ps.numpy 260 ps.psycopg2 261 ps.python-ldap 262 ps.netdata-pandas 263 ]); 264 265 services.netdata.configDir.".opt-out-from-anonymous-statistics" = lib.mkIf ( 266 !cfg.enableAnalyticsReporting 267 ) (pkgs.writeText ".opt-out-from-anonymous-statistics" ""); 268 environment.etc."netdata/netdata.conf".source = configFile; 269 environment.etc."netdata/conf.d".source = configDirectory; 270 271 systemd.tmpfiles.settings = lib.mkIf cfg.package.withNdsudo { 272 "95-netdata-ndsudo" = { 273 "/var/lib/netdata/ndsudo" = { 274 "d" = { 275 mode = "0550"; 276 user = cfg.user; 277 group = cfg.group; 278 }; 279 }; 280 281 "/var/lib/netdata/ndsudo/ndsudo" = { 282 "L+" = { 283 argument = "/run/wrappers/bin/ndsudo"; 284 }; 285 }; 286 287 "/var/lib/netdata/ndsudo/runtime-dependencies" = { 288 "L+" = { 289 argument = "${extraNdsudoPathsEnv}/bin"; 290 }; 291 }; 292 }; 293 }; 294 295 systemd.services.netdata = { 296 description = "Real time performance monitoring"; 297 after = [ 298 "network.target" 299 "suid-sgid-wrappers.service" 300 ]; 301 # No wrapper means no "useful" netdata. 302 requires = [ "suid-sgid-wrappers.service" ]; 303 wantedBy = [ "multi-user.target" ]; 304 path = 305 (with pkgs; [ 306 curl 307 gawk 308 iproute2 309 which 310 procps 311 bash 312 nvme-cli # for go.d 313 iw # for charts.d 314 apcupsd # for charts.d 315 # TODO: firehol # for FireQoS -- this requires more NixOS module support. 316 util-linux # provides logger command; required for syslog health alarms 317 ]) 318 ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages) 319 ++ lib.optional config.virtualisation.libvirtd.enable config.virtualisation.libvirtd.package 320 ++ lib.optional config.virtualisation.docker.enable config.virtualisation.docker.package 321 ++ lib.optionals config.virtualisation.podman.enable [ 322 pkgs.jq 323 config.virtualisation.podman.package 324 ] 325 ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package; 326 environment = { 327 PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules"; 328 NETDATA_PIPENAME = "/run/netdata/ipc"; 329 } 330 // lib.optionalAttrs (!cfg.enableAnalyticsReporting) { 331 DO_NOT_TRACK = "1"; 332 }; 333 restartTriggers = [ 334 config.environment.etc."netdata/netdata.conf".source 335 config.environment.etc."netdata/conf.d".source 336 ]; 337 serviceConfig = { 338 ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c /etc/netdata/netdata.conf"; 339 ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID"; 340 ExecStartPost = pkgs.writeShellScript "wait-for-netdata-up" '' 341 while [ "$(${cfg.package}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done 342 ''; 343 344 TimeoutStopSec = cfg.deadlineBeforeStopSec; 345 Restart = "on-failure"; 346 # User and group 347 User = cfg.user; 348 Group = cfg.group; 349 # Performance 350 LimitNOFILE = "30000"; 351 # Runtime directory and mode 352 RuntimeDirectory = "netdata"; 353 RuntimeDirectoryMode = "0750"; 354 # State directory and mode 355 StateDirectory = "netdata"; 356 StateDirectoryMode = "0750"; 357 # Cache directory and mode 358 CacheDirectory = "netdata"; 359 CacheDirectoryMode = "0750"; 360 # Logs directory and mode 361 LogsDirectory = "netdata"; 362 LogsDirectoryMode = "0750"; 363 # Configuration directory and mode 364 ConfigurationDirectory = "netdata"; 365 ConfigurationDirectoryMode = "0755"; 366 # AmbientCapabilities 367 AmbientCapabilities = lib.optional isThereAnyWireGuardTunnels "CAP_NET_ADMIN"; 368 # Capabilities 369 CapabilityBoundingSet = [ 370 "CAP_DAC_OVERRIDE" # is required for freeipmi and slabinfo plugins 371 "CAP_DAC_READ_SEARCH" # is required for apps and systemd-journal plugin 372 "CAP_NET_RAW" # is required for fping app 373 "CAP_PERFMON" # is required for perf plugin 374 "CAP_SETPCAP" # is required for apps, perf and slabinfo plugins 375 "CAP_SETUID" # is required for cgroups and cgroups-network plugins 376 "CAP_SYSLOG" # is required for systemd-journal plugin 377 "CAP_SYS_ADMIN" # is required for perf plugin 378 "CAP_SYS_CHROOT" # is required for cgroups plugin 379 "CAP_SYS_PTRACE" # is required for apps plugin 380 "CAP_SYS_RESOURCE" # is required for ebpf plugin 381 ] 382 ++ lib.optionals cfg.package.withIpmi [ 383 "CAP_FOWNER" 384 "CAP_SYS_RAWIO" 385 ] 386 ++ lib.optional isThereAnyWireGuardTunnels "CAP_NET_ADMIN"; 387 # Sandboxing 388 ProtectSystem = "full"; 389 ProtectHome = "read-only"; 390 PrivateTmp = true; 391 ProtectControlGroups = true; 392 PrivateMounts = true; 393 } 394 // (lib.optionalAttrs (cfg.claimTokenFile != null) { 395 LoadCredential = [ 396 "netdata_claim_token:${cfg.claimTokenFile}" 397 ]; 398 399 ExecStartPre = pkgs.writeShellScript "netdata-claim" '' 400 set -euo pipefail 401 402 if [[ -f /var/lib/netdata/cloud.d/claimed_id ]]; then 403 # Already registered 404 exit 405 fi 406 407 exec ${cfg.package}/bin/netdata-claim.sh \ 408 -token="$(< "$CREDENTIALS_DIRECTORY/netdata_claim_token")" \ 409 -url=https://app.netdata.cloud \ 410 -daemon-not-running 411 ''; 412 }); 413 }; 414 415 security.wrappers = { 416 "apps.plugin" = { 417 source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org"; 418 capabilities = "cap_dac_read_search,cap_sys_ptrace+ep"; 419 owner = cfg.user; 420 group = cfg.group; 421 permissions = "u+rx,g+x,o-rwx"; 422 }; 423 424 "debugfs.plugin" = { 425 source = "${cfg.package}/libexec/netdata/plugins.d/debugfs.plugin.org"; 426 capabilities = "cap_dac_read_search+ep"; 427 owner = cfg.user; 428 group = cfg.group; 429 permissions = "u+rx,g+x,o-rwx"; 430 }; 431 432 "cgroup-network" = { 433 source = "${cfg.package}/libexec/netdata/plugins.d/cgroup-network.org"; 434 capabilities = "cap_setuid+ep"; 435 owner = cfg.user; 436 group = cfg.group; 437 permissions = "u+rx,g+x,o-rwx"; 438 }; 439 440 "perf.plugin" = { 441 source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org"; 442 capabilities = "cap_sys_admin+ep"; 443 owner = cfg.user; 444 group = cfg.group; 445 permissions = "u+rx,g+x,o-rwx"; 446 }; 447 448 "slabinfo.plugin" = { 449 source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org"; 450 capabilities = "cap_dac_override+ep"; 451 owner = cfg.user; 452 group = cfg.group; 453 permissions = "u+rx,g+x,o-rwx"; 454 }; 455 456 } 457 // lib.optionalAttrs (cfg.package.withIpmi) { 458 "freeipmi.plugin" = { 459 source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org"; 460 capabilities = "cap_dac_override,cap_fowner,cap_sys_rawio+ep"; 461 owner = cfg.user; 462 group = cfg.group; 463 permissions = "u+rx,g+x,o-rwx"; 464 }; 465 } 466 // lib.optionalAttrs (cfg.package.withNetworkViewer) { 467 "network-viewer.plugin" = { 468 source = "${cfg.package}/libexec/netdata/plugins.d/network-viewer.plugin.org"; 469 capabilities = "cap_sys_admin,cap_dac_read_search,cap_sys_ptrace+ep"; 470 owner = cfg.user; 471 group = cfg.group; 472 permissions = "u+rx,g+x,o-rwx"; 473 }; 474 } 475 // lib.optionalAttrs (cfg.package.withNdsudo) { 476 "ndsudo" = { 477 source = "${cfg.package}/libexec/netdata/plugins.d/ndsudo.org"; 478 setuid = true; 479 owner = "root"; 480 group = cfg.group; 481 permissions = "u+rx,g+x,o-rwx"; 482 }; 483 } 484 // lib.optionalAttrs (cfg.package.withSystemdJournal) { 485 "systemd-journal.plugin" = { 486 source = "${cfg.package}/libexec/netdata/plugins.d/systemd-journal.plugin.org"; 487 capabilities = "cap_dac_read_search,cap_syslog+ep"; 488 owner = cfg.user; 489 group = cfg.group; 490 permissions = "u+rx,g+x,o-rwx"; 491 }; 492 }; 493 494 security.pam.loginLimits = [ 495 { 496 domain = "netdata"; 497 type = "soft"; 498 item = "nofile"; 499 value = "10000"; 500 } 501 { 502 domain = "netdata"; 503 type = "hard"; 504 item = "nofile"; 505 value = "30000"; 506 } 507 ]; 508 509 users.users = lib.optionalAttrs (cfg.user == defaultUser) { 510 ${defaultUser} = { 511 group = defaultUser; 512 isSystemUser = true; 513 extraGroups = 514 lib.optional config.virtualisation.docker.enable "docker" 515 ++ lib.optional config.virtualisation.podman.enable "podman"; 516 }; 517 }; 518 519 users.groups = lib.optionalAttrs (cfg.group == defaultUser) { 520 ${defaultUser} = { }; 521 }; 522 523 }; 524}