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