main
  1{
  2  den,
  3  lib,
  4  ...
  5}: {
  6  den.aspects.services.node-exporter = {
  7    settings.host = {host, ...}: {
  8      address = lib.mkOption {
  9        type = lib.types.str;
 10        default = let
 11          tsIp = host.address.ipv4.tailscale;
 12        in
 13          if tsIp != null
 14          then tsIp
 15          else "127.0.0.1";
 16      };
 17      port = lib.mkOption {
 18        type = lib.types.port;
 19        default = 9100;
 20      };
 21      enabledCollectors = lib.mkOption {
 22        type = lib.types.listOf lib.types.str;
 23        default = [
 24        ];
 25      };
 26      disabledCollectors = lib.mkOption {
 27        type = lib.types.listOf lib.types.str;
 28        default = [
 29        ];
 30      };
 31    };
 32
 33    tailscaleGrant = {host, ...}: [
 34      {
 35        src = let
 36          vmIPsList = lib.pipe den.hosts [
 37            lib.attrValues
 38            lib.mergeAttrsList
 39            (lib.filterAttrs (_: {hasAspect, ...}: hasAspect den.aspects.services.victoriametrics))
 40            (lib.mapAttrsToList (
 41              _: {address, ...}: let
 42                v4 = address.ipv4.tailscale;
 43                v6 = address.ipv6.tailscale;
 44              in
 45                lib.optionals (v4 != null) [v4] ++ lib.optionals (v6 != null) [v6]
 46            ))
 47            lib.concatLists
 48          ];
 49        in
 50          if vmIPsList != []
 51          then vmIPsList
 52          else ["autogroup:member"];
 53        ip = ["tcp:${toString host.settings.services.node-exporter.port}"];
 54      }
 55    ];
 56
 57    nixos = {
 58      config,
 59      host,
 60      ...
 61    }: let
 62      cfg = host.settings.services.node-exporter;
 63
 64      defaultMountPointsExclude = "^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+|var/lib/containers/storage/.+)($|/)";
 65
 66      preservationMountPoints =
 67        if !(host.hasAspect den.aspects.disk.preservation)
 68        then []
 69        else let
 70          bindMounts = entries:
 71            map (e: e.directory or e.file)
 72            (lib.filter (e: (e.how or "bindmount") == "bindmount") entries);
 73          stateMountPoints = state:
 74            bindMounts (state.directories ++ state.files)
 75            ++ lib.concatMap (user: bindMounts (user.directories ++ user.files)) (lib.attrValues state.users);
 76        in
 77          lib.filter
 78          (path: builtins.match "${defaultMountPointsExclude}.*" path == null)
 79          (lib.unique (
 80            lib.concatMap
 81            (prefix: [prefix] ++ stateMountPoints config.preservation.preserveAt.${prefix})
 82            (lib.attrNames config.preservation.preserveAt)
 83          ));
 84
 85      mountPointsExclude = lib.concatStringsSep "|" (
 86        [defaultMountPointsExclude]
 87        ++ map (path: "^${lib.escapeRegex path}($|/)") preservationMountPoints
 88      );
 89    in {
 90      services.prometheus.exporters.node = {
 91        enable = true;
 92        listenAddress = cfg.address;
 93        inherit (cfg) port enabledCollectors disabledCollectors;
 94        extraFlags = ["--collector.filesystem.mount-points-exclude=${mountPointsExclude}"];
 95      };
 96
 97      systemd.services.prometheus-node-exporter = {
 98        wants = lib.optional (cfg.address == host.address.ipv4.tailscale) "tailscale-wait-online.service";
 99        after = lib.optional (cfg.address == host.address.ipv4.tailscale) "tailscale-wait-online.service";
100      };
101    };
102  };
103}