at 23.11-pre 11 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 ''; 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 deadlineBeforeStopSec = mkOption { 174 type = types.int; 175 default = 120; 176 description = lib.mdDoc '' 177 In order to detect when netdata is misbehaving, we run a concurrent task pinging netdata (wait-for-netdata-up) 178 in the systemd unit. 179 180 If after a while, this task does not succeed, we stop the unit and mark it as failed. 181 182 You can control this deadline in seconds with this option, it's useful to bump it 183 if you have (1) a lot of data (2) doing upgrades (3) have low IOPS/throughput. 184 ''; 185 }; 186 }; 187 }; 188 189 config = mkIf cfg.enable { 190 assertions = 191 [ { assertion = cfg.config != {} -> cfg.configText == null ; 192 message = "Cannot specify both config and configText"; 193 } 194 ]; 195 196 environment.etc."netdata/netdata.conf".source = configFile; 197 environment.etc."netdata/conf.d".source = configDirectory; 198 199 systemd.services.netdata = { 200 description = "Real time performance monitoring"; 201 after = [ "network.target" ]; 202 wantedBy = [ "multi-user.target" ]; 203 path = (with pkgs; [ curl gawk iproute2 which procps bash ]) 204 ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages) 205 ++ lib.optional config.virtualisation.libvirtd.enable (config.virtualisation.libvirtd.package); 206 environment = { 207 PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules"; 208 } // lib.optionalAttrs (!cfg.enableAnalyticsReporting) { 209 DO_NOT_TRACK = "1"; 210 }; 211 restartTriggers = [ 212 config.environment.etc."netdata/netdata.conf".source 213 config.environment.etc."netdata/conf.d".source 214 ]; 215 serviceConfig = { 216 ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c /etc/netdata/netdata.conf"; 217 ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID"; 218 ExecStartPost = pkgs.writeShellScript "wait-for-netdata-up" '' 219 while [ "$(${pkgs.netdata}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done 220 ''; 221 222 TimeoutStopSec = cfg.deadlineBeforeStopSec; 223 Restart = "on-failure"; 224 # User and group 225 User = cfg.user; 226 Group = cfg.group; 227 # Performance 228 LimitNOFILE = "30000"; 229 # Runtime directory and mode 230 RuntimeDirectory = "netdata"; 231 RuntimeDirectoryMode = "0750"; 232 # State directory and mode 233 StateDirectory = "netdata"; 234 StateDirectoryMode = "0750"; 235 # Cache directory and mode 236 CacheDirectory = "netdata"; 237 CacheDirectoryMode = "0750"; 238 # Logs directory and mode 239 LogsDirectory = "netdata"; 240 LogsDirectoryMode = "0750"; 241 # Configuration directory and mode 242 ConfigurationDirectory = "netdata"; 243 ConfigurationDirectoryMode = "0755"; 244 # Capabilities 245 CapabilityBoundingSet = [ 246 "CAP_DAC_OVERRIDE" # is required for freeipmi and slabinfo plugins 247 "CAP_DAC_READ_SEARCH" # is required for apps plugin 248 "CAP_FOWNER" # is required for freeipmi plugin 249 "CAP_SETPCAP" # is required for apps, perf and slabinfo plugins 250 "CAP_SYS_ADMIN" # is required for perf plugin 251 "CAP_SYS_PTRACE" # is required for apps plugin 252 "CAP_SYS_RESOURCE" # is required for ebpf plugin 253 "CAP_NET_RAW" # is required for fping app 254 "CAP_SYS_CHROOT" # is required for cgroups plugin 255 "CAP_SETUID" # is required for cgroups and cgroups-network plugins 256 ]; 257 # Sandboxing 258 ProtectSystem = "full"; 259 ProtectHome = "read-only"; 260 PrivateTmp = true; 261 ProtectControlGroups = true; 262 PrivateMounts = true; 263 }; 264 }; 265 266 systemd.enableCgroupAccounting = true; 267 268 security.wrappers = { 269 "apps.plugin" = { 270 source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org"; 271 capabilities = "cap_dac_read_search,cap_sys_ptrace+ep"; 272 owner = cfg.user; 273 group = cfg.group; 274 permissions = "u+rx,g+x,o-rwx"; 275 }; 276 277 "cgroup-network" = { 278 source = "${cfg.package}/libexec/netdata/plugins.d/cgroup-network.org"; 279 capabilities = "cap_setuid+ep"; 280 owner = cfg.user; 281 group = cfg.group; 282 permissions = "u+rx,g+x,o-rwx"; 283 }; 284 285 "perf.plugin" = { 286 source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org"; 287 capabilities = "cap_sys_admin+ep"; 288 owner = cfg.user; 289 group = cfg.group; 290 permissions = "u+rx,g+x,o-rwx"; 291 }; 292 293 "slabinfo.plugin" = { 294 source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org"; 295 capabilities = "cap_dac_override+ep"; 296 owner = cfg.user; 297 group = cfg.group; 298 permissions = "u+rx,g+x,o-rwx"; 299 }; 300 301 } // optionalAttrs (cfg.package.withIpmi) { 302 "freeipmi.plugin" = { 303 source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org"; 304 capabilities = "cap_dac_override,cap_fowner+ep"; 305 owner = cfg.user; 306 group = cfg.group; 307 permissions = "u+rx,g+x,o-rwx"; 308 }; 309 }; 310 311 security.pam.loginLimits = [ 312 { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; } 313 { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; } 314 ]; 315 316 users.users = optionalAttrs (cfg.user == defaultUser) { 317 ${defaultUser} = { 318 group = defaultUser; 319 isSystemUser = true; 320 }; 321 }; 322 323 users.groups = optionalAttrs (cfg.group == defaultUser) { 324 ${defaultUser} = { }; 325 }; 326 327 }; 328}