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