push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.alertmanagerGotify;
pkg = cfg.package;
inherit (lib)
mkEnableOption
mkOption
types
mkIf
mkPackageOption
optionalString
;
in
{
meta.maintainers = with lib.maintainers; [ juli0604 ];
options.services.prometheus.alertmanagerGotify = {
enable = mkEnableOption "alertmagager-gotify";
package = mkPackageOption pkgs "alertmanager-gotify-bridge" { };
bindAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = "The address the server will listen on (bind address).";
};
defaultPriority = mkOption {
type = types.int;
default = 5;
description = "The default priority for messages sent to gotify.";
};
debug = mkOption {
type = types.bool;
default = false;
description = "Enables extended logs for debugging purposes. Should be disabled in productive mode.";
};
dispatchErrors = mkOption {
type = types.bool;
default = false;
description = "When enabled, alerts will be tried to dispatch with an error message regarding faulty templating or missing fields to help debugging.";
};
extendedDetails = mkOption {
type = types.bool;
default = false;
description = "When enabled, alerts are presented in HTML format and include colorized status (FIR|RES), alert start time, and a link to the generator of the alert.";
};
messageAnnotation = mkOption {
type = types.str;
description = "Annotation holding the alert message.";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Opens the bridge port in the firewall.";
};
port = mkOption {
type = types.port;
default = 8080;
description = "The local port the bridge is listening on.";
};
priorityAnnotation = mkOption {
type = types.str;
default = "priority";
description = "Annotation holding the priority of the alert.";
};
timeout = mkOption {
type = types.ints.positive;
default = 5;
description = "The time between sending a message and the timeout.";
};
titleAnnotation = mkOption {
type = types.str;
default = "summary";
description = "Annotation holding the title of the alert";
};
webhookPath = mkOption {
type = types.str;
default = "/gotify_webhook";
description = "The URL path to handle requests on.";
};
environmentFile = mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
File containing additional config environment variables for alertmanager-gotify-bridge.
This is especially for secrets like GOTIFY_TOKEN and AUTH_PASSWORD.
'';
};
gotifyEndpoint = {
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The hostname or ip your gotify endpoint is running.";
};
port = mkOption {
type = types.port;
default = 443;
description = "The port your gotify endpoint is running.";
};
tls = mkOption {
type = types.bool;
default = true;
description = "If your gotify endpoint uses https, leave this option set to default";
};
};
metrics = {
username = mkOption {
type = types.str;
description = "The username used to access your metrics.";
};
namespace = mkOption {
type = types.str;
default = "alertmanager-gotify-bridge";
description = "The namescape of the metrics.";
};
path = mkOption {
type = types.str;
default = "/metrics";
description = "The path under which the metrics will be exposed.";
};
};
};
config = mkIf cfg.enable {
users = {
groups.alertmanager-gotify = { };
users.alertmanager-gotify = {
group = "alertmanager-gotify";
isSystemUser = true;
};
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
systemd.services.alertmanager-gotify-bridge = {
description = "A bridge between Prometheus AlertManager and a Gotify server";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${lib.getExe pkg} ${optionalString cfg.debug "--debug"}";
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
User = "alertmanager-gotify";
Group = "alertmanager-gotify";
#hardening
NoNewPrivileges = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateIPC = true;
DevicePolicy = "closed";
ProtectSystem = "strict";
ProtectHome = "read-only";
ProtectControlGroups = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectKernelTunables = true;
ProtectHostname = true;
ProtectProc = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
ProcSubset = "pid";
SystemCallArchitectures = "native";
RemoveIPC = true;
};
environment = {
BIND_ADDRESS = cfg.bindAddress;
DEFAULT_PRIORITY = toString cfg.defaultPriority;
DISPATCH_ERRORS = toString cfg.dispatchErrors;
EXTENDED_DETAILS = toString cfg.extendedDetails;
MESSAGE_ANNOTATION = cfg.messageAnnotation;
PORT = toString cfg.port;
PRIORITY_ANNOTATION = cfg.priorityAnnotation;
TIMEOUT = "${toString cfg.timeout}s";
TITLE_ANNOTATION = cfg.titleAnnotation;
WEBHOOK_PATH = cfg.webhookPath;
GOTIFY_ENDPOINT = "${
if cfg.gotifyEndpoint.tls then "https://" else "http://"
}${toString cfg.gotifyEndpoint.host}:${toString cfg.gotifyEndpoint.port}/message";
AUTH_USERNAME = cfg.metrics.username;
};
};
};
}

View File

@@ -0,0 +1,108 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.alertmanagerIrcRelay;
configFormat = pkgs.formats.yaml { };
configFile = configFormat.generate "alertmanager-irc-relay.yml" cfg.settings;
in
{
options.services.prometheus.alertmanagerIrcRelay = {
enable = lib.mkEnableOption "Alertmanager IRC Relay";
package = lib.mkPackageOption pkgs "alertmanager-irc-relay" { };
extraFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command line options to pass to alertmanager-irc-relay.";
};
settings = lib.mkOption {
type = configFormat.type;
example = lib.literalExpression ''
{
http_host = "localhost";
http_port = 8000;
irc_host = "irc.example.com";
irc_port = 7000;
irc_nickname = "myalertbot";
irc_channels = [
{ name = "#mychannel"; }
];
}
'';
description = ''
Configuration for Alertmanager IRC Relay as a Nix attribute set.
For a reference, check out the
[example configuration](https://github.com/google/alertmanager-irc-relay#configuring-and-running-the-bot)
and the
[source code](https://github.com/google/alertmanager-irc-relay/blob/master/config.go).
Note: The webhook's URL MUST point to the IRC channel where the message
should be posted. For `#mychannel` from the example, this would be
`http://localhost:8080/mychannel`.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.alertmanager-irc-relay = {
description = "Alertmanager IRC Relay";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/alertmanager-irc-relay \
-config ${configFile} \
${lib.escapeShellArgs cfg.extraFlags}
'';
DynamicUser = true;
NoNewPrivileges = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ProtectHome = "tmpfs";
PrivateTmp = true;
PrivateDevices = true;
PrivateIPC = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallFilter = [
"@system-service"
"~@cpu-emulation"
"~@privileged"
"~@reboot"
"~@setuid"
"~@swap"
];
};
};
};
meta.maintainers = [ lib.maintainers.oxzi ];
}

View File

@@ -0,0 +1,206 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.alertmanager-ntfy;
settingsFormat = pkgs.formats.yaml { };
settingsFile = settingsFormat.generate "settings.yml" cfg.settings;
configsArg = lib.concatStringsSep "," (
[ settingsFile ] ++ lib.imap0 (i: _: "%d/config-${toString i}.yml") cfg.extraConfigFiles
);
in
{
meta.maintainers = with lib.maintainers; [ defelo ];
options.services.prometheus.alertmanager-ntfy = {
enable = lib.mkEnableOption "alertmanager-ntfy";
package = lib.mkPackageOption pkgs "alertmanager-ntfy" { };
settings = lib.mkOption {
description = ''
Configuration of alertmanager-ntfy.
See <https://github.com/alexbakker/alertmanager-ntfy> for more information.
'';
default = { };
type = lib.types.submodule {
freeformType = settingsFormat.type;
options = {
http.addr = lib.mkOption {
type = lib.types.str;
description = "The address to listen on.";
default = "127.0.0.1:8000";
example = ":8000";
};
ntfy = {
baseurl = lib.mkOption {
type = lib.types.str;
description = "The base URL of the ntfy.sh instance.";
example = "https://ntfy.sh";
};
notification = {
topic = lib.mkOption {
type = lib.types.str;
description = ''
__Note:__ when using ntfy.sh and other public instances
it is recommended to set this option to an empty string and set the actual topic via
[](#opt-services.prometheus.alertmanager-ntfy.extraConfigFiles) since
the `topic` in `ntfy.sh` is essentially a password.
The topic to which alerts should be published.
Can either be a hardcoded string or a gval expression that evaluates to a string.
'';
example = "alertmanager";
};
priority = lib.mkOption {
type = lib.types.str;
description = ''
The ntfy.sh message priority (see <https://docs.ntfy.sh/publish/#message-priority> for more information).
Can either be a hardcoded string or a gval expression that evaluates to a string.
'';
default = ''status == "firing" ? "high" : "default"'';
};
tags = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
tag = lib.mkOption {
type = lib.types.str;
description = ''
The tag to add.
See <https://docs.ntfy.sh/emojis> for a list of all supported emojis.
'';
example = "rotating_light";
};
condition = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = ''
The condition under which this tag should be added.
Tags with no condition are always included.
'';
default = null;
example = ''status == "firing"'';
};
};
}
);
description = ''
Tags to add to ntfy.sh messages.
See <https://docs.ntfy.sh/publish/#tags-emojis> for more information.
'';
default = [
{
tag = "green_circle";
condition = ''status == "resolved"'';
}
{
tag = "red_circle";
condition = ''status == "firing"'';
}
];
};
templates = {
title = lib.mkOption {
type = lib.types.str;
description = "The ntfy.sh message title template.";
default = ''
{{ if eq .Status "resolved" }}Resolved: {{ end }}{{ index .Annotations "summary" }}
'';
};
description = lib.mkOption {
type = lib.types.str;
description = "The ntfy.sh message description template.";
default = ''
{{ index .Annotations "description" }}
'';
};
};
};
};
};
};
};
extraConfigFiles = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
example = [ "/run/secrets/alertmanager-ntfy.yml" ];
description = ''
Config files to merge into the settings defined in [](#opt-services.prometheus.alertmanager-ntfy.settings).
This is useful to avoid putting secrets into the Nix store.
See <https://github.com/alexbakker/alertmanager-ntfy> for more information.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.alertmanager-ntfy = {
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
User = "alertmanager-ntfy";
Group = "alertmanager-ntfy";
DynamicUser = true;
LoadCredential = lib.imap0 (i: path: "config-${toString i}.yml:${path}") cfg.extraConfigFiles;
ExecStart = "${lib.getExe cfg.package} --configs ${configsArg}";
Restart = "always";
RestartSec = 5;
# Hardening
AmbientCapabilities = "";
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
"~@resources"
];
UMask = "0077";
};
};
};
}

View File

@@ -0,0 +1,86 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.alertmanagerWebhookLogger;
in
{
options.services.prometheus.alertmanagerWebhookLogger = {
enable = lib.mkEnableOption "Alertmanager Webhook Logger";
package = lib.mkPackageOption pkgs "alertmanager-webhook-logger" { };
extraFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command line options to pass to alertmanager-webhook-logger.";
};
};
config = lib.mkIf cfg.enable {
systemd.services.alertmanager-webhook-logger = {
description = "Alertmanager Webhook Logger";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/alertmanager-webhook-logger \
${lib.escapeShellArgs cfg.extraFlags}
'';
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
DynamicUser = true;
NoNewPrivileges = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ProtectHome = "tmpfs";
PrivateTmp = true;
PrivateDevices = true;
PrivateIPC = true;
ProcSubset = "pid";
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
Restart = "on-failure";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallFilter = [
"@system-service"
"~@cpu-emulation"
"~@privileged"
"~@reboot"
"~@setuid"
"~@swap"
];
};
};
};
meta.maintainers = [ lib.maintainers.jpds ];
}

View File

@@ -0,0 +1,269 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.prometheus.alertmanager;
mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
checkedConfig =
file:
if cfg.checkConfig then
pkgs.runCommand "checked-config" { nativeBuildInputs = [ cfg.package ]; } ''
ln -s ${file} $out
amtool check-config $out
''
else
file;
alertmanagerYml =
let
yml =
if cfg.configText != null then pkgs.writeText "alertmanager.yml" cfg.configText else mkConfigFile;
in
checkedConfig yml;
cmdlineArgs =
cfg.extraFlags
++ [
"--config.file /tmp/alert-manager-substituted.yaml"
"--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
"--log.level ${cfg.logLevel}"
"--storage.path /var/lib/alertmanager"
(toString (map (peer: "--cluster.peer ${peer}:9094") cfg.clusterPeers))
]
++ (lib.optional (cfg.webExternalUrl != null) "--web.external-url ${cfg.webExternalUrl}")
++ (lib.optional (cfg.logFormat != null) "--log.format ${cfg.logFormat}");
in
{
imports = [
(lib.mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ]
"The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting."
)
(lib.mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ]
"The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting."
)
(lib.mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] ''
Due to incompatibility, the alertmanagerURL option has been removed,
please use 'services.prometheus.alertmanagers' instead.
'')
];
options = {
services.prometheus.alertmanager = {
enable = lib.mkEnableOption "Prometheus Alertmanager";
package = lib.mkPackageOption pkgs "prometheus-alertmanager" { };
configuration = lib.mkOption {
type = lib.types.nullOr lib.types.attrs;
default = null;
description = ''
Alertmanager configuration as nix attribute set.
The contents of the resulting config file are processed using envsubst.
`$` needs to be escaped as `$$` to be preserved.
'';
};
configText = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = ''
Alertmanager configuration as YAML text. If non-null, this option
defines the text that is written to alertmanager.yml. If null, the
contents of alertmanager.yml is generated from the structured config
options.
The contents of the resulting config file are processed using envsubst.
`$` needs to be escaped as `$$` to be preserved.
'';
};
checkConfig = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Check configuration with `amtool check-config`. The call to `amtool` is
subject to sandboxing by Nix.
If you use credentials stored in external files
(`environmentFile`, etc),
they will not be visible to `amtool`
and it will report errors, despite a correct configuration.
'';
};
logFormat = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
If set use a syslog logger or JSON logging.
'';
};
logLevel = lib.mkOption {
type = lib.types.enum [
"debug"
"info"
"warn"
"error"
"fatal"
];
default = "warn";
description = ''
Only log messages with the given severity or above.
'';
};
webExternalUrl = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy).
Used for generating relative and absolute links back to Alertmanager itself.
If the URL has a path portion, it will be used to prefix all HTTP endoints served by Alertmanager.
If omitted, relevant URL components will be derived automatically.
'';
};
listenAddress = lib.mkOption {
type = lib.types.str;
default = "";
description = ''
Address to listen on for the web interface and API. Empty string will listen on all interfaces.
"localhost" will listen on 127.0.0.1 (but not ::1).
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 9093;
description = ''
Port to listen on for the web interface and API.
'';
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Open port in firewall for incoming connections.
'';
};
clusterPeers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Initial peers for HA cluster.
'';
};
extraFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra commandline options when launching the Alertmanager.
'';
};
environmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/root/alertmanager.env";
description = ''
File to load as environment file. Environment variables
from this file will be interpolated into the config file
using envsubst with this syntax:
`$ENVIRONMENT ''${VARIABLE}`
'';
};
};
};
config = lib.mkMerge [
(lib.mkIf cfg.enable {
assertions = lib.singleton {
assertion = cfg.configuration != null || cfg.configText != null;
message =
"Can not enable alertmanager without a configuration. "
+ "Set either the `configuration` or `configText` attribute.";
};
})
(lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port;
systemd.services.alertmanager = {
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
preStart = ''
${lib.getBin pkgs.envsubst}/bin/envsubst -o "/tmp/alert-manager-substituted.yaml" \
-i "${alertmanagerYml}"
'';
serviceConfig = {
ExecStart =
"${cfg.package}/bin/alertmanager"
+ lib.optionalString (lib.length cmdlineArgs != 0) (
" \\\n " + lib.concatStringsSep " \\\n " cmdlineArgs
);
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
DynamicUser = true;
NoNewPrivileges = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ProtectHome = "tmpfs";
PrivateTmp = true;
PrivateDevices = true;
PrivateIPC = true;
ProcSubset = "pid";
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
Restart = "always";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
StateDirectory = "alertmanager";
SystemCallFilter = [
"@system-service"
"~@cpu-emulation"
"~@privileged"
"~@reboot"
"~@setuid"
"~@swap"
];
WorkingDirectory = "/tmp";
};
};
})
];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
# Prometheus exporters {#module-services-prometheus-exporters}
Prometheus exporters provide metrics for the
[prometheus monitoring system](https://prometheus.io).
## Configuration {#module-services-prometheus-exporters-configuration}
One of the most common exporters is the
[node exporter](https://github.com/prometheus/node_exporter),
it provides hardware and OS metrics from the host it's
running on. The exporter could be configured as follows:
```nix
{
services.prometheus.exporters.node = {
enable = true;
port = 9100;
enabledCollectors = [
"logind"
"systemd"
];
disabledCollectors = [ "textfile" ];
openFirewall = true;
firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
};
}
```
It should now serve all metrics from the collectors that are explicitly
enabled and the ones that are
[enabled by default](https://github.com/prometheus/node_exporter#enabled-by-default),
via http under `/metrics`. In this
example the firewall should just allow incoming connections to the
exporter's port on the bridge interface `br0` (this would
have to be configured separately of course). For more information about
configuration see `man configuration.nix` or search through
the [available options](https://nixos.org/nixos/options.html#prometheus.exporters).
Prometheus can now be configured to consume the metrics produced by the exporter:
```nix
{
services.prometheus = {
# ...
scrapeConfigs = [
{
job_name = "node";
static_configs = [
{
targets = [
"localhost:${toString config.services.prometheus.exporters.node.port}"
];
}
];
}
];
# ...
};
}
```
## Adding a new exporter {#module-services-prometheus-exporters-new-exporter}
To add a new exporter, it has to be packaged first (see
`nixpkgs/pkgs/servers/monitoring/prometheus/` for
examples), then a module can be added. The postfix exporter is used in this
example:
- Some default options for all exporters are provided by
`nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix`:
- `enable`
- `port`
- `listenAddress`
- `extraFlags`
- `openFirewall`
- `firewallFilter`
- `firewallRules`
- `user`
- `group`
- As there is already a package available, the module can now be added. This
is accomplished by adding a new file to the
`nixos/modules/services/monitoring/prometheus/exporters/`
directory, which will be called postfix.nix and contains all exporter
specific options and configuration:
```nix
# nixpkgs/nixos/modules/services/prometheus/exporters/postfix.nix
{
config,
lib,
pkgs,
options,
}:
let
# for convenience we define cfg here
cfg = config.services.prometheus.exporters.postfix;
in
{
port = 9154; # The postfix exporter listens on this port by default
# `extraOpts` is an attribute set which contains additional options
# (and optional overrides for default options).
# Note that this attribute is optional.
extraOpts = {
telemetryPath = lib.mkOption {
type = lib.types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
logfilePath = lib.mkOption {
type = lib.types.path;
default = /var/log/postfix_exporter_input.log;
example = /var/log/mail.log;
description = ''
Path where Postfix writes log entries.
This file will be truncated by this exporter!
'';
};
showqPath = lib.mkOption {
type = lib.types.path;
default = /var/spool/postfix/public/showq;
example = /var/lib/postfix/queue/public/showq;
description = ''
Path at which Postfix places its showq socket.
'';
};
};
# `serviceOpts` is an attribute set which contains configuration
# for the exporter's systemd service. One of
# `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
# has to be specified here. This will be merged with the default
# service configuration.
# Note that by default 'DynamicUser' is 'true'.
serviceOpts = {
serviceConfig = {
DynamicUser = false;
ExecStart = ''
${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
${lib.concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}
```
- This should already be enough for the postfix exporter. Additionally one
could now add assertions and conditional default values. This can be done
in the 'meta-module' that combines all exporter definitions and generates
the submodules:
`nixpkgs/nixos/modules/services/prometheus/exporters.nix`
## Updating an exporter module {#module-services-prometheus-exporters-update-exporter-module}
Should an exporter option change at some point, it is possible to add
information about the change to the exporter definition similar to
`nixpkgs/nixos/modules/rename.nix`:
```nix
{
config,
lib,
pkgs,
options,
}:
let
cfg = config.services.prometheus.exporters.nginx;
in
{
port = 9113;
extraOpts = {
# additional module options
# ...
};
serviceOpts = {
# service configuration
# ...
};
imports = [
# 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath'
(lib.mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
# removed option 'services.prometheus.exporters.nginx.insecure'
(lib.mkRemovedOptionModule [ "insecure" ] ''
This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
'')
({ options.warnings = options.warnings; })
];
}
```

View File

@@ -0,0 +1,597 @@
{
config,
pkgs,
lib,
options,
utils,
...
}:
let
inherit (lib)
concatStrings
foldl
foldl'
genAttrs
literalExpression
maintainers
mapAttrs
mapAttrsToList
mkDefault
mkEnableOption
mkIf
mkMerge
mkOption
optional
types
mkOptionDefault
flip
attrNames
xor
;
cfg = config.services.prometheus.exporters;
# each attribute in `exporterOpts` is expected to have specified:
# - port (types.int): port on which the exporter listens
# - serviceOpts (types.attrs): config that is merged with the
# default definition of the exporter's
# systemd service
# - extraOpts (types.attrs): extra configuration options to
# configure the exporter with, which
# are appended to the default options
#
# Note that `extraOpts` is optional, but a script for the exporter's
# systemd service must be provided by specifying either
# `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
exporterOpts =
(genAttrs
[
"apcupsd"
"artifactory"
"bind"
"bird"
"bitcoin"
"blackbox"
"borgmatic"
"buildkite-agent"
"ecoflow"
"chrony"
"collectd"
"deluge"
"dmarc"
"dnsmasq"
"dnssec"
"domain"
"dovecot"
"ebpf"
"fastly"
"flow"
"fritz"
"fritzbox"
"frr"
"graphite"
"idrac"
"imap-mailstat"
"influxdb"
"ipmi"
"jitsi"
"json"
"junos-czerwonk"
"kafka"
"kea"
"keylight"
"klipper"
"knot"
"libvirt"
"lnd"
"mail"
"mailman3"
"mikrotik"
"modemmanager"
"mongodb"
"mqtt"
"mysqld"
"nats"
"nextcloud"
"nginx"
"nginxlog"
"node"
"node-cert"
"nut"
"nvidia-gpu"
"pgbouncer"
"php-fpm"
"pihole"
"ping"
"postfix"
"postgres"
"process"
"pve"
"py-air-control"
"rasdaemon"
"redis"
"restic"
"rspamd"
"rtl_433"
"sabnzbd"
"scaphandre"
"script"
"shelly"
"smartctl"
"smokeping"
"snmp"
"sql"
"statsd"
"storagebox"
"surfboard"
"systemd"
"tibber"
"unbound"
"unpoller"
"v2ray"
"varnish"
"wireguard"
"zfs"
]
(
name:
import (./. + "/exporters/${name}.nix") {
inherit
config
lib
pkgs
options
utils
;
}
)
)
// (mapAttrs
(
name: params:
import (./. + "/exporters/${params.name}.nix") {
inherit
config
lib
pkgs
options
utils
;
type = params.type;
}
)
{
exportarr-bazarr = {
name = "exportarr";
type = "bazarr";
};
exportarr-lidarr = {
name = "exportarr";
type = "lidarr";
};
exportarr-prowlarr = {
name = "exportarr";
type = "prowlarr";
};
exportarr-radarr = {
name = "exportarr";
type = "radarr";
};
exportarr-readarr = {
name = "exportarr";
type = "readarr";
};
exportarr-sonarr = {
name = "exportarr";
type = "sonarr";
};
}
);
mkExporterOpts = (
{ name, port }:
{
enable = mkEnableOption "the prometheus ${name} exporter";
port = mkOption {
type = types.port;
default = port;
description = ''
Port to listen on.
'';
};
listenAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = ''
Address to listen on.
'';
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Extra commandline options to pass to the ${name} exporter.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Open port in firewall for incoming connections.
'';
};
firewallFilter = mkOption {
type = types.nullOr types.str;
default = null;
example = literalExpression ''
"-i eth0 -p tcp -m tcp --dport ${toString port}"
'';
description = ''
Specify a filter for iptables to use when
{option}`services.prometheus.exporters.${name}.openFirewall`
is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`.
'';
};
firewallRules = mkOption {
type = types.nullOr types.lines;
default = null;
example = literalExpression ''
iifname "eth0" tcp dport ${toString port} counter accept
'';
description = ''
Specify rules for nftables to add to the input chain
when {option}`services.prometheus.exporters.${name}.openFirewall` is true.
'';
};
user = mkOption {
type = types.str;
default = "${name}-exporter";
description = ''
User name under which the ${name} exporter shall be run.
'';
};
group = mkOption {
type = types.str;
default = "${name}-exporter";
description = ''
Group under which the ${name} exporter shall be run.
'';
};
}
);
mkSubModule =
{
name,
port,
extraOpts,
imports,
}:
{
${name} = mkOption {
type = types.submodule [
{
inherit imports;
options = (
mkExporterOpts {
inherit name port;
}
// extraOpts
);
}
(
{ config, ... }:
mkIf config.openFirewall {
firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}";
firewallRules = mkDefault ''tcp dport ${toString config.port} accept comment "${name}-exporter"'';
}
)
];
internal = true;
default = { };
};
};
mkSubModules = (
foldl' (a: b: a // b) { } (
mapAttrsToList (
name: opts:
mkSubModule {
inherit name;
inherit (opts) port;
extraOpts = opts.extraOpts or { };
imports = opts.imports or [ ];
}
) exporterOpts
)
);
mkExporterConf =
{
name,
conf,
serviceOpts,
}:
let
enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
nftables = config.networking.nftables.enable;
in
mkIf conf.enable {
warnings = conf.warnings or [ ];
assertions = conf.assertions or [ ];
users.users."${name}-exporter" = (
mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
description = "Prometheus ${name} exporter service user";
isSystemUser = true;
inherit (conf) group;
}
);
users.groups = mkMerge [
(mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
"${name}-exporter" = { };
})
(mkIf (name == "smartctl") {
"smartctl-exporter-access" = { };
})
];
services.udev.extraRules = mkIf (name == "smartctl") ''
ACTION=="add", SUBSYSTEM=="nvme", KERNEL=="nvme[0-9]*", RUN+="${pkgs.acl}/bin/setfacl -m g:smartctl-exporter-access:rw /dev/$kernel"
'';
networking.firewall.extraCommands = mkIf (conf.openFirewall && !nftables) (concatStrings [
"ip46tables -A nixos-fw ${conf.firewallFilter} "
"-m comment --comment ${name}-exporter -j nixos-fw-accept"
]);
networking.firewall.extraInputRules = mkIf (conf.openFirewall && nftables) conf.firewallRules;
systemd.services."prometheus-${name}-exporter" = mkMerge [
{
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig.Restart = mkDefault "always";
serviceConfig.PrivateTmp = mkDefault true;
serviceConfig.WorkingDirectory = mkDefault /tmp;
serviceConfig.DynamicUser = mkDefault enableDynamicUser;
serviceConfig.User = mkDefault conf.user;
serviceConfig.Group = conf.group;
# Hardening
serviceConfig.CapabilityBoundingSet = mkDefault [ "" ];
serviceConfig.DeviceAllow = [ "" ];
serviceConfig.LockPersonality = true;
serviceConfig.MemoryDenyWriteExecute = true;
serviceConfig.NoNewPrivileges = true;
serviceConfig.PrivateDevices = mkDefault true;
serviceConfig.ProtectClock = mkDefault true;
serviceConfig.ProtectControlGroups = true;
serviceConfig.ProtectHome = true;
serviceConfig.ProtectHostname = true;
serviceConfig.ProtectKernelLogs = true;
serviceConfig.ProtectKernelModules = true;
serviceConfig.ProtectKernelTunables = true;
serviceConfig.ProtectSystem = mkDefault "strict";
serviceConfig.RemoveIPC = true;
serviceConfig.RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
serviceConfig.RestrictNamespaces = true;
serviceConfig.RestrictRealtime = true;
serviceConfig.RestrictSUIDSGID = true;
serviceConfig.SystemCallArchitectures = "native";
serviceConfig.UMask = "0077";
}
serviceOpts
];
};
in
{
options.services.prometheus.exporters = mkOption {
type = types.submodule {
options = mkSubModules;
imports = [
../../../misc/assertions.nix
(lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ])
(lib.mkRemovedOptionModule [ "minio" ] ''
The Minio exporter has been removed, as it was broken and unmaintained.
See the 24.11 release notes for more information.
'')
(lib.mkRemovedOptionModule [ "tor" ] ''
The Tor exporter has been removed, as it was broken and unmaintained.
'')
];
};
description = "Prometheus exporter configuration";
default = { };
example = literalExpression ''
{
node = {
enable = true;
enabledCollectors = [ "systemd" ];
};
varnish.enable = true;
}
'';
};
config = mkMerge (
[
{
assertions = [
{
assertion =
cfg.ipmi.enable -> (cfg.ipmi.configFile != null) -> (!(lib.hasPrefix "/tmp/" cfg.ipmi.configFile));
message = ''
Config file specified in `services.prometheus.exporters.ipmi.configFile' must
not reside within /tmp - it won't be visible to the systemd service.
'';
}
{
assertion =
cfg.ipmi.enable
-> (cfg.ipmi.webConfigFile != null)
-> (!(lib.hasPrefix "/tmp/" cfg.ipmi.webConfigFile));
message = ''
Config file specified in `services.prometheus.exporters.ipmi.webConfigFile' must
not reside within /tmp - it won't be visible to the systemd service.
'';
}
{
assertion =
cfg.restic.enable -> ((cfg.restic.repository == null) != (cfg.restic.repositoryFile == null));
message = ''
Please specify either 'services.prometheus.exporters.restic.repository'
or 'services.prometheus.exporters.restic.repositoryFile'.
'';
}
{
assertion =
cfg.snmp.enable -> ((cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null));
message = ''
Please ensure you have either `services.prometheus.exporters.snmp.configuration'
or `services.prometheus.exporters.snmp.configurationPath' set!
'';
}
{
assertion =
cfg.mikrotik.enable -> ((cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null));
message = ''
Please specify either `services.prometheus.exporters.mikrotik.configuration'
or `services.prometheus.exporters.mikrotik.configFile'.
'';
}
{
assertion = cfg.mail.enable -> ((cfg.mail.configFile == null) != (cfg.mail.configuration == null));
message = ''
Please specify either 'services.prometheus.exporters.mail.configuration'
or 'services.prometheus.exporters.mail.configFile'.
'';
}
{
assertion = cfg.mysqld.runAsLocalSuperUser -> config.services.mysql.enable;
message = ''
The exporter is configured to run as 'services.mysql.user', but
'services.mysql.enable' is set to false.
'';
}
{
assertion =
cfg.nextcloud.enable -> ((cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null));
message = ''
Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or
'services.prometheus.exporters.nextcloud.tokenFile'
'';
}
{
assertion = cfg.sql.enable -> ((cfg.sql.configFile == null) != (cfg.sql.configuration == null));
message = ''
Please specify either 'services.prometheus.exporters.sql.configuration' or
'services.prometheus.exporters.sql.configFile'
'';
}
{
assertion = cfg.scaphandre.enable -> (pkgs.stdenv.targetPlatform.isx86_64 == true);
message = ''
Scaphandre only support x86_64 architectures.
'';
}
{
assertion =
cfg.scaphandre.enable
-> ((lib.kernel.whenHelpers pkgs.linux.version).whenOlder "5.11" true).condition == false;
message = ''
Scaphandre requires a kernel version newer than '5.11', '${pkgs.linux.version}' given.
'';
}
{
assertion = cfg.scaphandre.enable -> (builtins.elem "intel_rapl_common" config.boot.kernelModules);
message = ''
Scaphandre needs 'intel_rapl_common' kernel module to be enabled. Please add it in 'boot.kernelModules'.
'';
}
{
assertion =
cfg.idrac.enable -> ((cfg.idrac.configurationPath == null) != (cfg.idrac.configuration == null));
message = ''
Please ensure you have either `services.prometheus.exporters.idrac.configuration'
or `services.prometheus.exporters.idrac.configurationPath' set!
'';
}
{
assertion =
cfg.deluge.enable
-> ((cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null));
message = ''
Please ensure you have either `services.prometheus.exporters.deluge.delugePassword'
or `services.prometheus.exporters.deluge.delugePasswordFile' set!
'';
}
{
assertion =
cfg.pgbouncer.enable
-> (xor (cfg.pgbouncer.connectionEnvFile == null) (cfg.pgbouncer.connectionString == null));
message = ''
Options `services.prometheus.exporters.pgbouncer.connectionEnvFile` and
`services.prometheus.exporters.pgbouncer.connectionString` are mutually exclusive!
'';
}
]
++ (flip map (attrNames exporterOpts) (exporter: {
assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
message = ''
The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
`openFirewall' is set to `true'!
'';
}))
++ config.services.prometheus.exporters.assertions;
warnings = [
(mkIf
(
config.services.prometheus.exporters.idrac.enable
&& config.services.prometheus.exporters.idrac.configurationPath != null
)
''
Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
`services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
Consider using `services.prometheus.exporters.idrac.configuration` instead.
''
)
]
++ config.services.prometheus.exporters.warnings;
}
]
++ [
(mkIf config.services.prometheus.exporters.rtl_433.enable {
hardware.rtl-sdr.enable = mkDefault true;
})
]
++ [
(mkIf config.services.postfix.enable {
services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
})
]
++ [
(mkIf config.services.prometheus.exporters.deluge.enable {
system.activationScripts = {
deluge-exported.text = ''
mkdir -p /etc/deluge-exporter
echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password
'';
};
})
]
++ (mapAttrsToList (
name: conf:
mkExporterConf {
inherit name;
inherit (conf) serviceOpts;
conf = cfg.${name};
}
) exporterOpts)
);
meta = {
doc = ./exporters.md;
maintainers = [ ];
};
}

View File

@@ -0,0 +1,47 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.apcupsd;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9162;
extraOpts = {
apcupsdAddress = mkOption {
type = types.str;
default = ":3551";
description = ''
Address of the apcupsd Network Information Server (NIS).
'';
};
apcupsdNetwork = mkOption {
type = types.enum [
"tcp"
"tcp4"
"tcp6"
];
default = "tcp";
description = ''
Network of the apcupsd Network Information Server (NIS): one of "tcp", "tcp4", or "tcp6".
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-apcupsd-exporter}/bin/apcupsd_exporter \
-telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
-apcupsd.addr ${cfg.apcupsdAddress} \
-apcupsd.network ${cfg.apcupsdNetwork} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,64 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.artifactory;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9531;
extraOpts = {
scrapeUri = mkOption {
type = types.str;
default = "http://localhost:8081/artifactory";
description = ''
URI on which to scrape JFrog Artifactory.
'';
};
artiUsername = mkOption {
type = types.str;
description = ''
Username for authentication against JFrog Artifactory API.
'';
};
artiPassword = mkOption {
type = types.str;
default = "";
description = ''
Password for authentication against JFrog Artifactory API.
One of the password or access token needs to be set.
'';
};
artiAccessToken = mkOption {
type = types.str;
default = "";
description = ''
Access token for authentication against JFrog Artifactory API.
One of the password or access token needs to be set.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-artifactory-exporter}/bin/artifactory_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--artifactory.scrape-uri ${cfg.scrapeUri} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
Environment = [
"ARTI_USERNAME=${cfg.artiUsername}"
"ARTI_PASSWORD=${cfg.artiPassword}"
"ARTI_ACCESS_TOKEN=${cfg.artiAccessToken}"
];
};
};
}

View File

@@ -0,0 +1,72 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.bind;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9119;
extraOpts = {
bindURI = mkOption {
type = types.str;
default = "http://localhost:8053/";
description = ''
HTTP XML API address of an Bind server.
'';
};
bindTimeout = mkOption {
type = types.str;
default = "10s";
description = ''
Timeout for trying to get stats from Bind.
'';
};
bindVersion = mkOption {
type = types.enum [
"xml.v2"
"xml.v3"
"auto"
];
default = "auto";
description = ''
BIND statistics version. Can be detected automatically.
'';
};
bindGroups = mkOption {
type = types.listOf (
types.enum [
"server"
"view"
"tasks"
]
);
default = [
"server"
"view"
];
description = ''
List of statistics to collect. Available: [server, view, tasks]
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-bind-exporter}/bin/bind_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--bind.pid-file /var/run/named/named.pid \
--bind.timeout ${toString cfg.bindTimeout} \
--bind.stats-url ${cfg.bindURI} \
--bind.stats-version ${cfg.bindVersion} \
--bind.stats-groups ${concatStringsSep "," cfg.bindGroups} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,63 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.bird;
inherit (lib)
mkOption
types
concatStringsSep
singleton
;
in
{
port = 9324;
extraOpts = {
birdVersion = mkOption {
type = types.enum [
1
2
];
default = 2;
description = ''
Specifies whether BIRD1 or BIRD2 is in use.
'';
};
birdSocket = mkOption {
type = types.path;
default = "/run/bird/bird.ctl";
description = ''
Path to BIRD2 (or BIRD1 v4) socket.
'';
};
newMetricFormat = mkOption {
type = types.bool;
default = true;
description = ''
Enable the new more-generic metric format.
'';
};
};
serviceOpts = {
serviceConfig = {
SupplementaryGroups = "bird";
ExecStart = ''
${pkgs.prometheus-bird-exporter}/bin/bird_exporter \
-web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-bird.socket ${cfg.birdSocket} \
-bird.v2=${if cfg.birdVersion == 2 then "true" else "false"} \
-format.new=${if cfg.newMetricFormat then "true" else "false"} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,93 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.bitcoin;
inherit (lib) mkOption types;
in
{
port = 9332;
extraOpts = {
package = lib.mkPackageOption pkgs "prometheus-bitcoin-exporter" { };
rpcUser = mkOption {
type = types.str;
default = "bitcoinrpc";
description = ''
RPC user name.
'';
};
rpcPasswordFile = mkOption {
type = types.path;
description = ''
File containing RPC password.
'';
};
rpcScheme = mkOption {
type = types.enum [
"http"
"https"
];
default = "http";
description = ''
Whether to connect to bitcoind over http or https.
'';
};
rpcHost = mkOption {
type = types.str;
default = "localhost";
description = ''
RPC host.
'';
};
rpcPort = mkOption {
type = types.port;
default = 8332;
description = ''
RPC port number.
'';
};
refreshSeconds = mkOption {
type = types.ints.unsigned;
default = 300;
description = ''
How often to ask bitcoind for metrics.
'';
};
extraEnv = mkOption {
type = types.attrsOf types.str;
default = { };
description = ''
Extra environment variables for the exporter.
'';
};
};
serviceOpts = {
script = ''
BITCOIN_RPC_PASSWORD=$(cat ${cfg.rpcPasswordFile})
export BITCOIN_RPC_PASSWORD
exec ${cfg.package}/bin/bitcoind-monitor.py
'';
environment = {
BITCOIN_RPC_USER = cfg.rpcUser;
BITCOIN_RPC_SCHEME = cfg.rpcScheme;
BITCOIN_RPC_HOST = cfg.rpcHost;
BITCOIN_RPC_PORT = toString cfg.rpcPort;
METRICS_ADDR = cfg.listenAddress;
METRICS_PORT = toString cfg.port;
REFRESH_SECONDS = toString cfg.refreshSeconds;
}
// cfg.extraEnv;
};
}

View File

@@ -0,0 +1,88 @@
{
config,
lib,
pkgs,
options,
...
}:
let
logPrefix = "services.prometheus.exporter.blackbox";
cfg = config.services.prometheus.exporters.blackbox;
inherit (lib)
mkOption
types
concatStringsSep
escapeShellArg
;
# This ensures that we can deal with string paths, path types and
# store-path strings with context.
coerceConfigFile =
file:
if (builtins.isPath file) || (lib.isStorePath file) then
file
else
(
lib.warn ''
${logPrefix}: configuration file "${file}" is being copied to the nix-store.
If you would like to avoid that, please set enableConfigCheck to false.
'' /.
+ file
);
checkConfigLocation =
file:
if lib.hasPrefix "/tmp/" file then
throw "${logPrefix}: configuration file must not reside within /tmp - it won't be visible to the systemd service."
else
file;
checkConfig =
file:
pkgs.runCommand "checked-blackbox-exporter.conf"
{
preferLocalBuild = true;
nativeBuildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
}
''
ln -s ${coerceConfigFile file} $out
blackbox_exporter --config.check --config.file $out
'';
in
{
port = 9115;
extraOpts = {
configFile = mkOption {
type = types.path;
description = ''
Path to configuration file.
'';
};
enableConfigCheck = mkOption {
type = types.bool;
default = true;
description = ''
Whether to run a correctness check for the configuration file. This depends
on the configuration file residing in the nix-store. Paths passed as string will
be copied to the store.
'';
};
};
serviceOpts =
let
adjustedConfigFile =
if cfg.enableConfigCheck then checkConfig cfg.configFile else checkConfigLocation cfg.configFile;
in
{
serviceConfig = {
AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes
ExecStart = ''
${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--config.file ${escapeShellArg adjustedConfigFile} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
};
};
}

View File

@@ -0,0 +1,34 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.borgmatic;
in
{
port = 9996;
extraOpts.configFile = lib.mkOption {
type = lib.types.path;
default = "/etc/borgmatic/config.yaml";
description = ''
The path to the borgmatic config file
'';
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
ProtectSystem = false;
ProtectHome = lib.mkForce false;
ExecStart = ''
${pkgs.prometheus-borgmatic-exporter}/bin/borgmatic-exporter run \
--port ${toString cfg.port} \
--config ${toString cfg.configFile} \
${lib.concatMapStringsSep " " (f: lib.escapeShellArg f) cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,75 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.buildkite-agent;
inherit (lib)
mkOption
types
concatStringsSep
optionalString
literalExpression
;
in
{
port = 9876;
extraOpts = {
tokenPath = mkOption {
type = types.nullOr types.path;
apply = final: if final == null then null else toString final;
description = ''
The token from your Buildkite "Agents" page.
A run-time path to the token file, which is supposed to be provisioned
outside of Nix store.
'';
};
interval = mkOption {
type = types.str;
default = "30s";
example = "1min";
description = ''
How often to update metrics.
'';
};
endpoint = mkOption {
type = types.str;
default = "https://agent.buildkite.com/v3";
description = ''
The Buildkite Agent API endpoint.
'';
};
queues = mkOption {
type = with types; nullOr (listOf str);
default = null;
example = literalExpression ''[ "my-queue1" "my-queue2" ]'';
description = ''
Which specific queues to process.
'';
};
};
serviceOpts = {
script =
let
queues = concatStringsSep " " (map (q: "-queue ${q}") cfg.queues);
in
''
export BUILDKITE_AGENT_TOKEN="$(cat ${toString cfg.tokenPath})"
exec ${pkgs.buildkite-agent-metrics}/bin/buildkite-agent-metrics \
-backend prometheus \
-interval ${cfg.interval} \
-endpoint ${cfg.endpoint} \
${optionalString (cfg.queues != null) queues} \
-prometheus-addr "${cfg.listenAddress}:${toString cfg.port}" ${concatStringsSep " " cfg.extraFlags}
'';
serviceConfig = {
DynamicUser = false;
RuntimeDirectory = "buildkite-agent-metrics";
};
};
}

View File

@@ -0,0 +1,97 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.chrony;
inherit (lib)
mkOption
types
concatStringsSep
concatMapStringsSep
;
in
{
port = 9123;
extraOpts = {
chronyServerAddress = mkOption {
type = types.str;
default = "unix:///run/chrony/chronyd.sock";
example = [ "192.82.0.1:323" ];
description = ''
ChronyServerAddress of the chrony server side command port. (Not enabled by default.)
Defaults to the local unix socket.
'';
};
user = mkOption {
type = types.str;
default = "chrony";
description = ''
User name under which the chrony exporter shall be run.
This allows the exporter to talk to chrony using a unix socket, which is owned by chrony.
The exporter startup with the default user chrony will fail without local chrony instance.
'';
};
group = mkOption {
type = types.str;
default = "chrony";
description = ''
Group under which the chrony exporter shall be run.
This allows the exporter to talk to chrony using a unix socket, which is owned by chrony group.
The service startup with the default group chrony will fail without local chrony instance.
'';
};
enabledCollectors = mkOption {
type = types.listOf types.str;
default = [
"tracking"
"sources"
"sources.with-ntpdata"
"serverstats"
"dns-lookups"
];
example = [ "dns-lookups" ];
description = ''
Collectors to enable.
Currently all collectors are enabled by default.
'';
};
disabledCollectors = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "sources.with-ntpdata" ];
description = ''
Collectors to disable which are enabled by default.
Disable sources.with-ntpdata for network scraper. Option requires unix socket.
'';
};
};
serviceOpts = {
serviceConfig = {
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
ProtectClock = true;
ProtectSystem = "strict";
Restart = "on-failure";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
ExecStart = ''
${lib.getExe pkgs.prometheus-chrony-exporter} \
${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
--chrony.address ${cfg.chronyServerAddress} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,104 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.collectd;
inherit (lib)
mkOption
mkEnableOption
types
optionalString
concatStringsSep
escapeShellArg
;
in
{
port = 9103;
extraOpts = {
collectdBinary = {
enable = mkEnableOption "collectd binary protocol receiver";
authFile = mkOption {
default = null;
type = types.nullOr types.path;
description = "File mapping user names to pre-shared keys (passwords).";
};
port = mkOption {
type = types.port;
default = 25826;
description = "Network address on which to accept collectd binary network packets.";
};
listenAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = ''
Address to listen on for binary network packets.
'';
};
securityLevel = mkOption {
type = types.enum [
"None"
"Sign"
"Encrypt"
];
default = "None";
description = ''
Minimum required security level for accepted packets.
'';
};
};
logFormat = mkOption {
type = types.enum [
"logfmt"
"json"
];
default = "logfmt";
example = "json";
description = ''
Set the log format.
'';
};
logLevel = mkOption {
type = types.enum [
"debug"
"info"
"warn"
"error"
"fatal"
];
default = "info";
description = ''
Only log messages with the given severity or above.
'';
};
};
serviceOpts =
let
collectSettingsArgs = optionalString (cfg.collectdBinary.enable) ''
--collectd.listen-address ${cfg.collectdBinary.listenAddress}:${toString cfg.collectdBinary.port} \
--collectd.security-level ${cfg.collectdBinary.securityLevel} \
'';
in
{
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \
--log.format ${escapeShellArg cfg.logFormat} \
--log.level ${cfg.logLevel} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
${collectSettingsArgs} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,94 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.deluge;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9354;
extraOpts = {
delugeHost = mkOption {
type = types.str;
default = "localhost";
description = ''
Hostname where deluge server is running.
'';
};
delugePort = mkOption {
type = types.port;
default = 58846;
description = ''
Port where deluge server is listening.
'';
};
delugeUser = mkOption {
type = types.str;
default = "localclient";
description = ''
User to connect to deluge server.
'';
};
delugePassword = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Password to connect to deluge server.
This stores the password unencrypted in the nix store and is thus considered unsafe. Prefer
using the delugePasswordFile option.
'';
};
delugePasswordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
File containing the password to connect to deluge server.
'';
};
exportPerTorrentMetrics = mkOption {
type = types.bool;
default = false;
description = ''
Enable per-torrent metrics.
This may significantly increase the number of time series depending on the number of
torrents in your Deluge instance.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-deluge-exporter}/bin/deluge-exporter
'';
Environment = [
"LISTEN_PORT=${toString cfg.port}"
"LISTEN_ADDRESS=${toString cfg.listenAddress}"
"DELUGE_HOST=${cfg.delugeHost}"
"DELUGE_USER=${cfg.delugeUser}"
"DELUGE_PORT=${toString cfg.delugePort}"
]
++ lib.optionals (cfg.delugePassword != null) [
"DELUGE_PASSWORD=${cfg.delugePassword}"
]
++ lib.optionals cfg.exportPerTorrentMetrics [
"PER_TORRENT_METRICS=1"
];
EnvironmentFile = lib.optionalString (
cfg.delugePasswordFile != null
) "/etc/deluge-exporter/password";
};
};
}

View File

@@ -0,0 +1,129 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.dmarc;
inherit (lib) mkOption types optionalString;
json = builtins.toJSON {
inherit (cfg) folders port;
listen_addr = cfg.listenAddress;
storage_path = "$STATE_DIRECTORY";
imap = (builtins.removeAttrs cfg.imap [ "passwordFile" ]) // {
password = "$IMAP_PASSWORD";
use_ssl = true;
};
poll_interval_seconds = cfg.pollIntervalSeconds;
deduplication_max_seconds = cfg.deduplicationMaxSeconds;
logging = {
version = 1;
disable_existing_loggers = false;
};
};
in
{
port = 9797;
extraOpts = {
imap = {
host = mkOption {
type = types.str;
default = "localhost";
description = ''
Hostname of IMAP server to connect to.
'';
};
port = mkOption {
type = types.port;
default = 993;
description = ''
Port of the IMAP server to connect to.
'';
};
username = mkOption {
type = types.str;
example = "postmaster@example.org";
description = ''
Login username for the IMAP connection.
'';
};
passwordFile = mkOption {
type = types.str;
example = "/run/secrets/dovecot_pw";
description = ''
File containing the login password for the IMAP connection.
'';
};
};
folders = {
inbox = mkOption {
type = types.str;
default = "INBOX";
description = ''
IMAP mailbox that is checked for incoming DMARC aggregate reports
'';
};
done = mkOption {
type = types.str;
default = "Archive";
description = ''
IMAP mailbox that successfully processed reports are moved to.
'';
};
error = mkOption {
type = types.str;
default = "Invalid";
description = ''
IMAP mailbox that emails are moved to that could not be processed.
'';
};
};
pollIntervalSeconds = mkOption {
type = types.ints.unsigned;
default = 60;
description = ''
How often to poll the IMAP server in seconds.
'';
};
deduplicationMaxSeconds = mkOption {
type = types.ints.unsigned;
default = 604800;
defaultText = "7 days (in seconds)";
description = ''
How long individual report IDs will be remembered to avoid
counting double delivered reports twice.
'';
};
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to declare enable `--debug`.
'';
};
};
serviceOpts = {
path = with pkgs; [
envsubst
coreutils
];
serviceConfig = {
StateDirectory = "prometheus-dmarc-exporter";
WorkingDirectory = "/var/lib/prometheus-dmarc-exporter";
ExecStart = "${pkgs.writeShellScript "setup-cfg" ''
export IMAP_PASSWORD="$(<${cfg.imap.passwordFile})"
envsubst \
-i ${pkgs.writeText "dmarc-exporter.json.template" json} \
-o ''${STATE_DIRECTORY}/dmarc-exporter.json
exec ${pkgs.dmarc-metrics-exporter}/bin/dmarc-metrics-exporter \
--configuration /var/lib/prometheus-dmarc-exporter/dmarc-exporter.json \
${optionalString cfg.debug "--debug"}
''}";
};
};
}

View File

@@ -0,0 +1,48 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.dnsmasq;
inherit (lib)
mkOption
types
concatStringsSep
escapeShellArg
;
in
{
port = 9153;
extraOpts = {
dnsmasqListenAddress = mkOption {
type = types.str;
default = "localhost:53";
description = ''
Address on which dnsmasq listens.
'';
};
leasesPath = mkOption {
type = types.path;
default = "/var/lib/dnsmasq/dnsmasq.leases";
example = "/var/lib/misc/dnsmasq.leases";
description = ''
Path to the `dnsmasq.leases` file.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \
--listen ${cfg.listenAddress}:${toString cfg.port} \
--dnsmasq ${cfg.dnsmasqListenAddress} \
--leases_path ${escapeShellArg cfg.leasesPath} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,101 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.dnssec;
configFormat = pkgs.formats.toml { };
configFile = configFormat.generate "dnssec-checks.toml" cfg.configuration;
in
{
port = 9204;
extraOpts = {
configuration = lib.mkOption {
type = lib.types.nullOr lib.types.attrs;
default = null;
description = ''
dnssec exporter configuration as nix attribute set.
See <https://github.com/chrj/prometheus-dnssec-exporter/blob/master/README.md>
for the description of the configuration file format.
'';
example = lib.literalExpression ''
{
records = [
{
zone = "ietf.org";
record = "@";
type = "SOA";
}
{
zone = "verisigninc.com";
record = "@";
type = "SOA";
}
];
}
'';
};
listenAddress = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Listen address as host IP and port definition.
'';
example = ":9204";
};
resolvers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
DNSSEC capable resolver to be used for the check.
'';
example = [ "0.0.0.0:53" ];
};
timeout = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
DNS request timeout duration.
'';
example = "10s";
};
extraFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra commandline options when launching Prometheus.
'';
};
};
serviceOpts = {
serviceConfig =
let
startScript = pkgs.writeShellScriptBin "prometheus-dnssec-exporter-start" "${lib.concatStringsSep
" "
(
[ "${pkgs.prometheus-dnssec-exporter}/bin/prometheus-dnssec-exporter" ]
++ lib.optionals (cfg.configuration != null) [ "-config ${configFile}" ]
++ lib.optionals (cfg.listenAddress != null) [
"-listen-address ${lib.escapeShellArg cfg.listenAddress}"
]
++ lib.optionals (cfg.resolvers != [ ]) [
"-resolvers ${lib.escapeShellArg (lib.concatStringsSep "," cfg.resolvers)}"
]
++ lib.optionals (cfg.timeout != null) [ "-timeout ${lib.escapeShellArg cfg.timeout}" ]
++ cfg.extraFlags
)
}";
in
{
ExecStart = lib.getExe startScript;
};
};
}

View File

@@ -0,0 +1,24 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.domain;
inherit (lib) concatStringsSep;
in
{
port = 9222;
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-domain-exporter}/bin/domain_exporter \
--bind ${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,105 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.dovecot;
inherit (lib)
mkOption
types
escapeShellArg
concatStringsSep
;
in
{
port = 9166;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
socketPath = mkOption {
type = types.path;
default = "/var/run/dovecot/stats";
example = "/var/run/dovecot2/old-stats";
description = ''
Path under which the stats socket is placed.
The user/group under which the exporter runs,
should be able to access the socket in order
to scrape the metrics successfully.
Please keep in mind that the stats module has changed in
[Dovecot 2.3+](https://wiki2.dovecot.org/Upgrading/2.3) which
is not [compatible with this exporter](https://github.com/kumina/dovecot_exporter/issues/8).
The following extra config has to be passed to Dovecot to ensure that recent versions
work with this exporter:
```
{
services.prometheus.exporters.dovecot.enable = true;
services.prometheus.exporters.dovecot.socketPath = "/var/run/dovecot2/old-stats";
services.dovecot2.mailPlugins.globally.enable = [ "old_stats" ];
services.dovecot2.extraConfig = '''
service old-stats {
unix_listener old-stats {
user = dovecot-exporter
group = dovecot-exporter
mode = 0660
}
fifo_listener old-stats-mail {
mode = 0660
user = dovecot
group = dovecot
}
fifo_listener old-stats-user {
mode = 0660
user = dovecot
group = dovecot
}
}
plugin {
old_stats_refresh = 30 secs
old_stats_track_cmds = yes
}
''';
}
```
'';
};
scopes = mkOption {
type = types.listOf types.str;
default = [ "user" ];
example = [
"user"
"global"
];
description = ''
Stats scopes to query.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
ExecStart = ''
${lib.getExe pkgs.dovecot_exporter} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
--dovecot.socket-path ${escapeShellArg cfg.socketPath} \
--dovecot.scopes ${concatStringsSep "," cfg.scopes} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,49 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.ebpf;
inherit (lib)
mkOption
types
concatStringsSep
;
in
{
port = 9435;
extraOpts = {
names = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "timers" ];
description = ''
List of eBPF programs to load
'';
};
};
serviceOpts = {
serviceConfig = {
AmbientCapabilities = [
"CAP_BPF"
"CAP_DAC_READ_SEARCH"
"CAP_PERFMON"
];
CapabilityBoundingSet = [
"CAP_BPF"
"CAP_DAC_READ_SEARCH"
"CAP_PERFMON"
];
ExecStart = ''
${pkgs.prometheus-ebpf-exporter}/bin/ebpf_exporter \
--config.dir=${pkgs.prometheus-ebpf-exporter}/examples \
--config.names=${concatStringsSep "," cfg.names} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port}
'';
};
};
}

View File

@@ -0,0 +1,151 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.ecoflow;
inherit (lib) mkOption types;
in
{
port = 2112;
extraOpts = {
exporterType = mkOption {
type = types.enum [
"rest"
"mqtt"
];
default = "rest";
example = "mqtt";
description = ''
The type of exporter you'd like to use.
Possible values: "rest" and "mqtt". Default value is "rest".
Choose "rest" for the ecoflow online cloud api use "rest" and define: accessKey, secretKey.
Choose "mqtt" for the lan realtime integration use "mqtt" and define: email, password, devices.
'';
};
ecoflowAccessKeyFile = mkOption {
type = types.path;
default = /etc/ecoflow-access-key;
description = ''
Path to the file with your personal api access string from the Ecoflow development website <https://developer-eu.ecoflow.com>.
Do to share or commit your plaintext scecrets to a public repo use: agenix or soaps.
'';
};
ecoflowSecretKeyFile = mkOption {
type = types.path;
default = /etc/ecoflow-secret-key;
description = ''
Path to the file with your personal api secret string from the Ecoflow development website <https://developer-eu.ecoflow.com>.
Do to share or commit your plaintext scecrets to a public repo use: agenix or soaps.
'';
};
ecoflowEmailFile = mkOption {
type = types.path;
default = /etc/ecoflow-email;
description = ''
Path to the file with your personal ecoflow app login email address.
Do to share or commit your plaintext scecrets to a public repo use: agenix or soaps.
'';
};
ecoflowPasswordFile = mkOption {
type = types.path;
default = /etc/ecoflow-password;
description = ''
Path to the file with your personal ecoflow app login email password.
Do to share or commit your plaintext passwords to a public repo use: agenix or soaps here!
'';
};
ecoflowDevicesFile = mkOption {
type = types.path;
default = /etc/ecoflow-devices;
description = ''
File must contain one line, example: R3300000,R3400000,NC430000,....
The list of devices serial numbers separated by comma. For instance: SN1,SN2,SN3.
Instead of "devicesFile" you can specify "devicesPrettynamesFile" which will also work. You can specify both.
Do to share or commit your plaintext serial numbers to a public repo use: agenix or soaps.
'';
};
ecoflowDevicesPrettyNamesFile = mkOption {
type = types.path;
default = /etc/ecoflow-devices-pretty-names;
description = ''
File must contain one line, example: {"R3300000":"Delta 2","R3400000":"Delta Pro",...}
The key/value map of custom names for your devices. Key is a serial number, value is a device name you want
to see in Grafana Dashboard. It's helpful to see a meaningful name in Grafana dashboard instead of a serialnumber.
Do to share or commit your plaintext serial numbers to a public repo use: agenix or soaps.
'';
};
debug = mkOption {
type = types.str;
default = "0";
example = "1";
description = ''
Enable debug log messages. Disabled by default. Set to "1" to enable.
'';
};
prefix = mkOption {
type = types.str;
default = "ecoflow";
example = "ecoflow_privateSite";
description = ''
The prefix that will be added to all metrics. Default value is ecoflow.
For instance metric bms_bmsStatus.minCellTemp will be exported to prometheus as ecoflow.bms_bmsStatus.minCellTemp.
With default value "ecoflow" you can use Grafana Dashboard with ID 17812 without any changes.
'';
};
scrapingInterval = mkOption {
type = types.ints.positive;
default = 30;
example = 120;
description = ''
Scrapping interval in seconds. How often should the exporter execute requests to Ecoflow Rest API in order to get the data.
Default value is 30 seconds. Align this value with your prometheus scraper interval settings.
'';
};
mqttDeviceOfflineThreshold = mkOption {
type = types.ints.positive;
default = 60;
example = 120;
description = ''
The threshold in seconds which indicates how long we should wait for a metric message from MQTT broker.
Default value: 60 seconds. If we don't receive message within 60 seconds we consider that device is offline.
If we don't receive messages within the threshold for all devices, we'll try to reconnect to the MQTT broker.
There is a strange behavior that MQTT stop sends messages if you open Ecoflow mobile app and then close it).
'';
};
};
serviceOpts = {
environment = {
PROMETHEUS_ENABLED = "1";
EXPORTER_TYPE = cfg.exporterType;
DEBUG_ENABLED = cfg.debug;
METRIC_PREFIX = cfg.prefix;
SCRAPING_INTERVAL = toString cfg.scrapingInterval;
MQTT_DEVICE_OFFLINE_THRESHOLD_SECONDS = toString cfg.mqttDeviceOfflineThreshold;
};
script = ''
export ECOFLOW_ACCESS_KEY="$(cat ${toString cfg.ecoflowAccessKeyFile})"
export ECOFLOW_SECRET_KEY="$(cat ${toString cfg.ecoflowSecretKeyFile})"
export ECOFLOW_EMAIL="$(cat ${toString cfg.ecoflowEmailFile})"
export ECOFLOW_PASSWORD="$(cat ${toString cfg.ecoflowPasswordFile})"
export ECOFLOW_DEVICES="$(cat ${toString cfg.ecoflowDevicesFile})"
export ECOFLOW_DEVICES_PRETTY_NAMES="$(cat ${toString cfg.ecoflowDevicesPrettyNamesFile})"
exec ${lib.getExe pkgs.go-ecoflow-exporter}'';
serviceConfig = {
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
ProtectSystem = "strict";
Restart = "on-failure";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
User = "prometheus"; # context needed to runtime access encrypted token and secrets
};
};
}

View File

@@ -0,0 +1,63 @@
{
config,
lib,
pkgs,
options,
type,
...
}:
let
cfg = config.services.prometheus.exporters."exportarr-${type}";
exportarrEnvironment = (lib.mapAttrs (_: toString) cfg.environment) // {
PORT = toString cfg.port;
URL = cfg.url;
API_KEY_FILE = lib.mkIf (cfg.apiKeyFile != null) "%d/api-key";
};
in
{
port = 9708;
extraOpts = {
url = lib.mkOption {
type = lib.types.str;
default = "http://127.0.0.1";
description = ''
The full URL to Sonarr, Radarr, or Lidarr.
'';
};
apiKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
File containing the api-key.
'';
};
package = lib.mkPackageOption pkgs "exportarr" { };
environment = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = ''
See [the configuration guide](https://github.com/onedr0p/exportarr#configuration) for available options.
'';
example = {
PROWLARR__BACKFILL = true;
};
};
};
serviceOpts = {
serviceConfig = {
LoadCredential = lib.optionalString (cfg.apiKeyFile != null) "api-key:${cfg.apiKeyFile}";
ExecStart = ''${cfg.package}/bin/exportarr ${type} "$@"'';
ProcSubset = "pid";
ProtectProc = "invisible";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
};
environment = exportarrEnvironment;
};
}

View File

@@ -0,0 +1,59 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib)
getExe
mkOption
optionals
types
;
inherit (utils) escapeSystemdExecArgs;
cfg = config.services.prometheus.exporters.fastly;
in
{
port = 9118;
extraOpts = with types; {
configFile = mkOption {
type = nullOr path;
default = null;
example = "./fastly-exporter-config.txt";
description = ''
Path to a fastly-exporter configuration file.
Example one can be generated with `fastly-exporter --config-file-example`.
'';
};
environmentFile = mkOption {
type = path;
description = ''
An environment file containg at least the FASTLY_API_TOKEN= environment
variable.
'';
};
};
serviceOpts = {
serviceConfig = {
EnvironmentFile = cfg.environmentFile;
ExecStart = escapeSystemdExecArgs (
[
(getExe pkgs.prometheus-fastly-exporter)
"-listen"
"${cfg.listenAddress}:${toString cfg.port}"
]
++ optionals (cfg.configFile != null) [
"--config-file"
cfg.configFile
]
++ cfg.extraFlags
);
};
};
}

View File

@@ -0,0 +1,62 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.flow;
inherit (lib)
mkOption
types
literalExpression
concatStringsSep
optionalString
;
in
{
port = 9590;
extraOpts = {
brokers = mkOption {
type = types.listOf types.str;
example = literalExpression ''[ "kafka.example.org:19092" ]'';
description = "List of Kafka brokers to connect to.";
};
asn = mkOption {
type = types.ints.positive;
example = 65542;
description = "The ASN being monitored.";
};
partitions = mkOption {
type = types.listOf types.int;
default = [ ];
description = ''
The number of the partitions to consume, none means all.
'';
};
topic = mkOption {
type = types.str;
example = "pmacct.acct";
description = "The Kafka topic to consume from.";
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = true;
ExecStart = ''
${pkgs.prometheus-flow-exporter}/bin/flow-exporter \
-asn ${toString cfg.asn} \
-topic ${cfg.topic} \
-brokers ${concatStringsSep "," cfg.brokers} \
${optionalString (cfg.partitions != [ ]) "-partitions ${concatStringsSep "," cfg.partitions}"} \
-addr ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,115 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib) mkOption types;
cfg = config.services.prometheus.exporters.fritz;
yaml = pkgs.formats.yaml { };
configFile = yaml.generate "fritz-exporter.yaml" cfg.settings;
in
{
port = 9787;
extraOpts = {
settings = mkOption {
description = "Configuration settings for fritz-exporter.";
type = types.submodule {
freeformType = yaml.type;
options = {
# Pull existing port option into config file.
port = mkOption {
type = types.port;
default = cfg.port;
internal = true;
visible = false;
};
# Pull existing listen address option into config file.
listen_address = mkOption {
type = types.str;
default = cfg.listenAddress;
internal = true;
visible = false;
};
log_level = mkOption {
type = types.enum [
"DEBUG"
"INFO"
"WARNING"
"ERROR"
"CRITICAL"
];
default = "INFO";
description = ''
Log level to use for the exporter.
'';
};
devices = mkOption {
default = [ ];
description = "Fritz!-devices to monitor using the exporter.";
type =
with types;
listOf (submodule {
freeformType = yaml.type;
options = {
name = mkOption {
type = types.str;
default = "";
description = ''
Name to use for the device.
'';
};
hostname = mkOption {
type = types.str;
default = "fritz.box";
description = ''
Hostname under which the target device is reachable.
'';
};
username = mkOption {
type = types.str;
description = ''
Username to authenticate with the target device.
'';
};
password_file = mkOption {
type = types.path;
description = ''
Path to a file which contains the password to authenticate with the target device.
Needs to be readable by the user the exporter runs under.
'';
};
host_info = mkOption {
type = types.bool;
description = ''
Enable extended host info for this device. *Warning*: This will heavily increase scrape time.
'';
default = false;
};
};
});
};
};
};
};
};
serviceOpts = {
serviceConfig = {
ExecStart = utils.escapeSystemdExecArgs (
[
(lib.getExe pkgs.fritz-exporter)
"--config"
configFile
]
++ cfg.extraFlags
);
DynamicUser = false;
};
};
}

View File

@@ -0,0 +1,43 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.fritzbox;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9133;
extraOpts = {
gatewayAddress = mkOption {
type = types.str;
default = "fritz.box";
description = ''
The hostname or IP of the FRITZ!Box.
'';
};
gatewayPort = mkOption {
type = types.port;
default = 49000;
description = ''
The port of the FRITZ!Box UPnP service.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
-listen-address ${cfg.listenAddress}:${toString cfg.port} \
-gateway-address ${cfg.gatewayAddress} \
-gateway-port ${toString cfg.gatewayPort} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,66 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.frr;
inherit (lib)
mkOption
types
concatStringsSep
concatMapStringsSep
;
in
{
port = 9342;
extraOpts = {
user = mkOption {
type = types.str;
default = "frr";
description = ''
User name under which the frr exporter shall be run.
The exporter talks to frr using a unix socket, which is owned by frr.
'';
};
group = mkOption {
type = types.str;
default = "frrtty";
description = ''
Group under which the frr exporter shall be run.
The exporter talks to frr using a unix socket, which is owned by frrtty group.
'';
};
enabledCollectors = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "vrrp" ];
description = ''
Collectors to enable. The collectors listed here are enabled in addition to the default ones.
'';
};
disabledCollectors = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "bfd" ];
description = ''
Collectors to disable which are enabled by default.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
RuntimeDirectory = "prometheus-frr-exporter";
RestrictAddressFamilies = [ "AF_UNIX" ];
ExecStart = ''
${lib.getExe pkgs.prometheus-frr-exporter} \
${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,47 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.graphite;
format = pkgs.formats.yaml { };
in
{
port = 9108;
extraOpts = {
graphitePort = lib.mkOption {
type = lib.types.port;
default = 9109;
description = ''
Port to use for the graphite server.
'';
};
mappingSettings = lib.mkOption {
type = lib.types.submodule {
freeformType = format.type;
options = { };
};
default = { };
description = ''
Mapping configuration for the exporter, see
<https://github.com/prometheus/graphite_exporter#yaml-config> for
available options.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-graphite-exporter}/bin/graphite_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--graphite.listen-address ${cfg.listenAddress}:${toString cfg.graphitePort} \
--graphite.mapping-config ${format.generate "mapping.yml" cfg.mappingSettings} \
${lib.concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,77 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.idrac;
inherit (lib) mkOption types;
configFile =
if cfg.configurationPath != null then
cfg.configurationPath
else
pkgs.writeText "idrac.yml" (builtins.toJSON cfg.configuration);
in
{
port = 9348;
extraOpts = {
configurationPath = mkOption {
type = with types; nullOr path;
default = null;
example = "/etc/prometheus-idrac-exporter/idrac.yml";
description = ''
Path to the service's config file. This path can either be a computed path in /nix/store or a path in the local filesystem.
The config file should NOT be stored in /nix/store as it will contain passwords and/or keys in plain text.
Mutually exclusive with `configuration` option.
Configuration reference: <https://github.com/mrlhansen/idrac_exporter/#configuration>
'';
};
configuration = mkOption {
type = types.nullOr types.attrs;
description = ''
Configuration for iDRAC exporter, as a nix attribute set.
Configuration reference: <https://github.com/mrlhansen/idrac_exporter/#configuration>
Mutually exclusive with `configurationPath` option.
'';
default = null;
example = {
timeout = 10;
retries = 1;
hosts = {
default = {
username = "username";
password = "password";
};
};
metrics = {
system = true;
sensors = true;
power = true;
sel = true;
storage = true;
memory = true;
};
};
};
};
serviceOpts = {
serviceConfig = {
LoadCredential = "configFile:${configFile}";
ExecStart = "${pkgs.prometheus-idrac-exporter}/bin/idrac_exporter -config %d/configFile";
Environment = [
"IDRAC_EXPORTER_LISTEN_ADDRESS=${cfg.listenAddress}"
"IDRAC_EXPORTER_LISTEN_PORT=${toString cfg.port}"
];
};
};
}

View File

@@ -0,0 +1,103 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.imap-mailstat;
valueToString =
value:
if (builtins.typeOf value == "string") then
"\"${value}\""
else
(
if (builtins.typeOf value == "int") then
"${toString value}"
else
(
if (builtins.typeOf value == "bool") then
(if value then "true" else "false")
else
"XXX ${toString value}"
)
);
inherit (lib)
mkOption
types
concatStrings
concatStringsSep
attrValues
mapAttrs
optionalString
;
createConfigFile =
accounts:
# unfortunately on toTOML yet
# https://github.com/NixOS/nix/issues/3929
pkgs.writeText "imap-mailstat-exporter.conf" ''
${concatStrings (
attrValues (
mapAttrs (
name: config:
"[[Accounts]]\nname = \"${name}\"\n${
concatStrings (attrValues (mapAttrs (k: v: "${k} = ${valueToString v}\n") config))
}"
) accounts
)
)}
'';
mkOpt =
type: description:
mkOption {
type = types.nullOr type;
default = null;
description = description;
};
accountOptions.options = {
mailaddress = mkOpt types.str "Your email address (at the moment used as login name)";
username = mkOpt types.str "If empty string mailaddress value is used";
password = mkOpt types.str "";
serveraddress = mkOpt types.str "mailserver name or address";
serverport = mkOpt types.int "imap port number (at the moment only tls connection is supported)";
starttls = mkOpt types.bool "set to true for using STARTTLS to start a TLS connection";
};
in
{
port = 8081;
extraOpts = {
oldestUnseenDate = mkOption {
type = types.bool;
default = false;
description = ''
Enable metric with timestamp of oldest unseen mail
'';
};
accounts = mkOption {
type = types.attrsOf (types.submodule accountOptions);
default = { };
description = ''
Accounts to monitor
'';
};
configurationFile = mkOption {
type = types.path;
example = "/path/to/config-file";
description = ''
File containing the configuration
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-imap-mailstat-exporter}/bin/imap-mailstat-exporter \
-config ${createConfigFile cfg.accounts} \
${optionalString cfg.oldestUnseenDate "-oldestunseendate"} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,39 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.influxdb;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9122;
extraOpts = {
sampleExpiry = mkOption {
type = types.str;
default = "5m";
example = "10m";
description = "How long a sample is valid for";
};
udpBindAddress = mkOption {
type = types.str;
default = ":9122";
example = "192.0.2.1:9122";
description = "Address on which to listen for udp packets";
};
};
serviceOpts = {
serviceConfig = {
RuntimeDirectory = "prometheus-influxdb-exporter";
ExecStart = ''
${pkgs.prometheus-influxdb-exporter}/bin/influxdb_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--influxdb.sample-expiry ${cfg.sampleExpiry} ${concatStringsSep " " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,65 @@
{
config,
lib,
pkgs,
options,
...
}:
let
logPrefix = "services.prometheus.exporter.ipmi";
cfg = config.services.prometheus.exporters.ipmi;
inherit (lib)
mkOption
types
concatStringsSep
optionals
escapeShellArg
;
in
{
port = 9290;
extraOpts = {
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to configuration file.
'';
};
webConfigFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to configuration file that can enable TLS or authentication.
'';
};
};
serviceOpts.serviceConfig = {
ExecStart =
with cfg;
concatStringsSep " " (
[
"${pkgs.prometheus-ipmi-exporter}/bin/ipmi_exporter"
"--web.listen-address ${listenAddress}:${toString port}"
]
++ optionals (cfg.webConfigFile != null) [
"--web.config.file ${escapeShellArg cfg.webConfigFile}"
]
++ optionals (cfg.configFile != null) [
"--config.file ${escapeShellArg cfg.configFile}"
]
++ extraFlags
);
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
};
}

View File

@@ -0,0 +1,50 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.jitsi;
inherit (lib)
mkOption
types
escapeShellArg
concatStringsSep
;
in
{
port = 9700;
extraOpts = {
url = mkOption {
type = types.str;
default = "http://localhost:8080/colibri/stats";
description = ''
Jitsi Videobridge metrics URL to monitor.
This is usually /colibri/stats on port 8080 of the jitsi videobridge host.
'';
};
interval = mkOption {
type = types.str;
default = "30s";
example = "1min";
description = ''
How often to scrape new data
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-jitsi-exporter}/bin/jitsiexporter \
-url ${escapeShellArg cfg.url} \
-host ${cfg.listenAddress} \
-port ${toString cfg.port} \
-interval ${toString cfg.interval} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,57 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.json;
inherit (lib)
mkOption
types
escapeShellArg
concatStringsSep
mkRemovedOptionModule
;
in
{
port = 7979;
extraOpts = {
configFile = mkOption {
type = types.path;
description = ''
Path to configuration file.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-json-exporter}/bin/json_exporter \
--config.file ${escapeShellArg cfg.configFile} \
--web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
imports = [
(mkRemovedOptionModule [ "url" ] ''
This option was removed. The URL of the endpoint serving JSON
must now be provided to the exporter by prometheus via the url
parameter `target'.
In prometheus a scrape URL would look like this:
http://some.json-exporter.host:7979/probe?target=https://example.com/some/json/endpoint
For more information, take a look at the official documentation
(https://github.com/prometheus-community/json_exporter) of the json_exporter.
'')
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
}

View File

@@ -0,0 +1,86 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.junos-czerwonk;
inherit (lib)
mkOption
types
escapeShellArg
mkIf
concatStringsSep
;
configFile =
if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configurationFile);
configurationFile = pkgs.writeText "prometheus-junos-czerwonk-exporter.conf" (
builtins.toJSON (cfg.configuration)
);
in
{
port = 9326;
extraOpts = {
environmentFile = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
File containing env-vars to be substituted into the exporter's config.
'';
};
configurationFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Specify the JunOS exporter configuration file to use.
'';
};
configuration = mkOption {
type = types.nullOr types.attrs;
default = null;
description = ''
JunOS exporter configuration as nix attribute set. Mutually exclusive with the `configurationFile` option.
'';
example = {
devices = [
{
host = "router1";
key_file = "/path/to/key";
}
];
};
};
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
RuntimeDirectory = "prometheus-junos-czerwonk-exporter";
ExecStartPre = [
"${pkgs.writeShellScript "subst-secrets-junos-czerwonk-exporter" ''
umask 0077
${pkgs.envsubst}/bin/envsubst -i ${configFile} -o ''${RUNTIME_DIRECTORY}/junos-exporter.json
''}"
];
ExecStart = ''
${pkgs.prometheus-junos-czerwonk-exporter}/bin/junos_exporter \
-web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-web.telemetry-path ${cfg.telemetryPath} \
-config.file ''${RUNTIME_DIRECTORY}/junos-exporter.json \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,56 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.kafka;
inherit (lib)
mkIf
mkOption
mkMerge
types
concatStringsSep
;
in
{
port = 8080;
extraOpts = {
package = lib.mkPackageOption pkgs "kminion" { };
environmentFile = mkOption {
type = with types; nullOr path;
default = null;
description = ''
File containing the credentials to access the repository, in the
format of an EnvironmentFile as described by systemd.exec(5)
'';
};
};
serviceOpts = mkMerge (
[
{
serviceConfig = {
ExecStart = ''
${lib.getExe cfg.package}
'';
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
RestartSec = "5s";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
};
}
]
++ [
(mkIf config.services.apache-kafka.enable {
after = [ "apache-kafka.service" ];
requires = [ "apache-kafka.service" ];
})
]
);
}

View File

@@ -0,0 +1,65 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.prometheus.exporters.kea;
inherit (lib)
mkOption
types
mkRenamedOptionModule
literalExpression
;
in
{
imports = [
(mkRenamedOptionModule [ "controlSocketPaths" ] [ "targets" ])
];
port = 9547;
extraOpts = {
targets = mkOption {
type = types.listOf types.str;
example = literalExpression ''
[
"/run/kea/kea-dhcp4.socket"
"/run/kea/kea-dhcp6.socket"
"http://127.0.0.1:8547"
]
'';
description = ''
Paths or URLs to the Kea control socket.
'';
};
};
serviceOpts = {
after = [
"kea-dhcp4-server.service"
"kea-dhcp6-server.service"
];
serviceConfig = {
User = "kea";
DynamicUser = true;
ExecStart = utils.escapeSystemdExecArgs (
[
(lib.getExe pkgs.prometheus-kea-exporter)
"--address"
cfg.listenAddress
"--port"
cfg.port
]
++ cfg.extraFlags
++ cfg.targets
);
RuntimeDirectory = "kea";
RuntimeDirectoryPreserve = true;
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,24 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.keylight;
inherit (lib) concatStringsSep;
in
{
port = 9288;
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-keylight-exporter}/bin/keylight_exporter \
-metrics.addr ${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,55 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.klipper;
inherit (lib)
mkOption
mkMerge
mkIf
types
concatStringsSep
any
optionalString
;
moonraker = config.services.moonraker;
in
{
port = 9101;
extraOpts = {
package = lib.mkPackageOption pkgs "prometheus-klipper-exporter" { };
moonrakerApiKey = mkOption {
type = types.str;
default = "";
description = ''
API Key to authenticate with the Moonraker APIs.
Only needed if the host running the exporter is not a trusted client to Moonraker.
'';
};
};
serviceOpts = mkMerge (
[
{
serviceConfig = {
ExecStart = concatStringsSep " " [
"${cfg.package}/bin/prometheus-klipper-exporter"
(optionalString (cfg.moonrakerApiKey != "") "--moonraker.apikey ${cfg.moonrakerApiKey}")
"--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
"${concatStringsSep " " cfg.extraFlags}"
];
};
}
]
++ [
(mkIf config.services.moonraker.enable {
after = [ "moonraker.service" ];
requires = [ "moonraker.service" ];
})
]
);
}

View File

@@ -0,0 +1,69 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.knot;
inherit (lib)
mkOption
types
literalExpression
concatStringsSep
;
in
{
port = 9433;
extraOpts = {
knotLibraryPath = mkOption {
type = types.nullOr types.str;
default = null;
example = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
description = ''
Path to the library of `knot-dns`.
'';
};
knotSocketPath = mkOption {
type = types.str;
default = "/run/knot/knot.sock";
description = ''
Socket path of {manpage}`knotd(8)`.
'';
};
knotSocketTimeout = mkOption {
type = types.ints.positive;
default = 2000;
description = ''
Timeout in seconds.
'';
};
};
serviceOpts = {
path = with pkgs; [
procps
];
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-knot-exporter}/bin/knot-exporter \
--web-listen-addr ${cfg.listenAddress} \
--web-listen-port ${toString cfg.port} \
--knot-socket-path ${cfg.knotSocketPath} \
--knot-socket-timeout ${toString cfg.knotSocketTimeout} \
${lib.optionalString (cfg.knotLibraryPath != null) "--knot-library-path ${cfg.knotLibraryPath}"} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
SupplementaryGroups = [
"knot"
];
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,29 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.libvirt;
in
{
port = 9177;
extraOpts = {
libvirtUri = lib.mkOption {
type = lib.types.str;
default = "qemu:///system";
description = "Libvirt URI from which to extract metrics";
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${lib.getExe pkgs.prometheus-libvirt-exporter} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--libvirt.uri ${cfg.libvirtUri} ${lib.concatStringsSep " " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,54 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.lnd;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9092;
extraOpts = {
lndHost = mkOption {
type = types.str;
default = "localhost:10009";
description = ''
lnd instance gRPC address:port.
'';
};
lndTlsPath = mkOption {
type = types.path;
description = ''
Path to lnd TLS certificate.
'';
};
lndMacaroonDir = mkOption {
type = types.path;
description = ''
Path to lnd macaroons.
'';
};
};
serviceOpts.serviceConfig = {
ExecStart = ''
${pkgs.prometheus-lnd-exporter}/bin/lndmon \
--prometheus.listenaddr=${cfg.listenAddress}:${toString cfg.port} \
--prometheus.logdir=/var/log/prometheus-lnd-exporter \
--lnd.host=${cfg.lndHost} \
--lnd.tlspath=${cfg.lndTlsPath} \
--lnd.macaroondir=${cfg.lndMacaroonDir} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
LogsDirectory = "prometheus-lnd-exporter";
ReadOnlyPaths = [
cfg.lndTlsPath
cfg.lndMacaroonDir
];
};
}

View File

@@ -0,0 +1,216 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.mail;
inherit (lib)
mkOption
types
mapAttrs'
nameValuePair
toLower
filterAttrs
escapeShellArg
literalExpression
mkIf
concatStringsSep
;
configFile =
if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configFile);
configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (
builtins.toJSON (
# removes the _module attribute, null values and converts attrNames to lowercase
mapAttrs' (
name: value:
if name == "servers" then
nameValuePair (toLower name) (
(map (
srv:
(mapAttrs' (n: v: nameValuePair (toLower n) v) (
filterAttrs (n: v: !(n == "_module" || v == null)) srv
))
))
value
)
else
nameValuePair (toLower name) value
) (filterAttrs (n: _: !(n == "_module")) cfg.configuration)
)
);
serverOptions.options = {
name = mkOption {
type = types.str;
description = ''
Value for label 'configname' which will be added to all metrics.
'';
};
server = mkOption {
type = types.str;
description = ''
Hostname of the server that should be probed.
'';
};
port = mkOption {
type = types.port;
example = 587;
description = ''
Port to use for SMTP.
'';
};
from = mkOption {
type = types.str;
example = "exporteruser@domain.tld";
description = ''
Content of 'From' Header for probing mails.
'';
};
to = mkOption {
type = types.str;
example = "exporteruser@domain.tld";
description = ''
Content of 'To' Header for probing mails.
'';
};
detectionDir = mkOption {
type = types.path;
example = "/var/spool/mail/exporteruser/new";
description = ''
Directory in which new mails for the exporter user are placed.
Note that this needs to exist when the exporter starts.
'';
};
login = mkOption {
type = types.nullOr types.str;
default = null;
example = "exporteruser@domain.tld";
description = ''
Username to use for SMTP authentication.
'';
};
passphrase = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Password to use for SMTP authentication.
'';
};
};
exporterOptions.options = {
monitoringInterval = mkOption {
type = types.str;
example = "10s";
description = ''
Time interval between two probe attempts.
'';
};
mailCheckTimeout = mkOption {
type = types.str;
description = ''
Timeout until mails are considered "didn't make it".
'';
};
disableFileDeletion = mkOption {
type = types.bool;
default = false;
description = ''
Disables the exporter's function to delete probing mails.
'';
};
servers = mkOption {
type = types.listOf (types.submodule serverOptions);
default = [ ];
example = literalExpression ''
[ {
name = "testserver";
server = "smtp.domain.tld";
port = 587;
from = "exporteruser@domain.tld";
to = "exporteruser@domain.tld";
detectionDir = "/path/to/Maildir/new";
} ]
'';
description = ''
List of servers that should be probed.
*Note:* if your mailserver has {manpage}`rspamd(8)` configured,
it can happen that emails from this exporter are marked as spam.
It's possible to work around the issue with a config like this:
```
{
services.rspamd.locals."multimap.conf".text = '''
ALLOWLIST_PROMETHEUS {
filter = "email:domain:tld";
type = "from";
map = "''${pkgs.writeText "allowmap" "domain.tld"}";
score = -100.0;
}
''';
}
```
'';
};
};
in
{
port = 9225;
extraOpts = {
environmentFile = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
File containing env-vars to be substituted into the exporter's config.
'';
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Specify the mailexporter configuration file to use.
'';
};
configuration = mkOption {
type = types.nullOr (types.submodule exporterOptions);
default = null;
description = ''
Specify the mailexporter configuration file to use.
'';
};
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
RuntimeDirectory = "prometheus-mail-exporter";
ExecStartPre = [
"${pkgs.writeShellScript "subst-secrets-mail-exporter" ''
umask 0077
${pkgs.envsubst}/bin/envsubst -i ${configFile} -o ''${RUNTIME_DIRECTORY}/mail-exporter.json
''}"
];
ExecStart = ''
${pkgs.prometheus-mail-exporter}/bin/mailexporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
--config.file ''${RUNTIME_DIRECTORY}/mail-exporter.json \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,77 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.mailman3;
in
{
port = 9934;
extraOpts = {
logLevel = lib.mkOption {
type = lib.types.enum [
"debug"
"info"
"warning"
"error"
"critical"
];
default = "info";
description = ''
Detail level to log.
'';
};
mailman = {
addr = lib.mkOption {
type = lib.types.str;
default = "http://127.0.0.1:8001";
description = ''
Mailman3 Core REST API address.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "restadmin";
description = ''
Mailman3 Core REST API username.
'';
};
passFile = lib.mkOption {
type = lib.types.str;
default = config.services.mailman.restApiPassFile;
defaultText = lib.literalExpression "config.services.mailman.restApiPassFile";
description = ''
Mailman3 Core REST API password.
'';
};
};
};
serviceOpts = {
serviceConfig = {
LoadCredential = [
"password:${cfg.mailman.passFile}"
];
ExecStart =
let
addr = "${
if (lib.hasInfix ":" cfg.listenAddress) then "[${cfg.listenAddress}]" else cfg.listenAddress
}:${toString cfg.port}";
in
''
${lib.getExe pkgs.prometheus-mailman3-exporter} \
--log-level ${cfg.logLevel} \
--web.listen ${addr} \
--mailman.address ${cfg.mailman.addr} \
--mailman.user ${cfg.mailman.user} \
--mailman.password-file %d/password \
${lib.concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,81 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.mikrotik;
inherit (lib)
mkOption
types
literalExpression
concatStringsSep
escapeShellArg
;
in
{
port = 9436;
extraOpts = {
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a mikrotik exporter configuration file. Mutually exclusive with
{option}`configuration` option.
'';
example = literalExpression "./mikrotik.yml";
};
configuration = mkOption {
type = types.nullOr types.attrs;
default = null;
description = ''
Mikrotik exporter configuration as nix attribute set. Mutually exclusive with
{option}`configFile` option.
See <https://github.com/nshttpd/mikrotik-exporter/blob/master/README.md>
for the description of the configuration file format.
'';
example = literalExpression ''
{
devices = [
{
name = "my_router";
address = "10.10.0.1";
user = "prometheus";
password = "changeme";
}
];
features = {
bgp = true;
dhcp = true;
routes = true;
optics = true;
};
}
'';
};
};
serviceOpts =
let
configFile =
if cfg.configFile != null then
cfg.configFile
else
"${pkgs.writeText "mikrotik-exporter.yml" (builtins.toJSON cfg.configuration)}";
in
{
serviceConfig = {
# -port is misleading name, it actually accepts address too
ExecStart = ''
${pkgs.prometheus-mikrotik-exporter}/bin/mikrotik-exporter \
-config-file=${escapeShellArg configFile} \
-port=${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,42 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.modemmanager;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9539;
extraOpts = {
refreshRate = mkOption {
type = types.str;
default = "5s";
description = ''
How frequently ModemManager will refresh the extended signal quality
information for each modem. The duration should be specified in seconds
("5s"), minutes ("1m"), or hours ("1h").
'';
};
};
serviceOpts = {
serviceConfig = {
# Required in order to authenticate with ModemManager via D-Bus.
SupplementaryGroups = "networkmanager";
ExecStart = ''
${pkgs.prometheus-modemmanager-exporter}/bin/modemmanager_exporter \
-addr ${cfg.listenAddress}:${toString cfg.port} \
-rate ${cfg.refreshRate} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,110 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.mongodb;
inherit (lib)
mkOption
types
optionalString
getExe
length
concatStringsSep
concatMapStringsSep
escapeShellArgs
;
in
{
port = 9216;
extraOpts = {
uri = mkOption {
type = types.str;
default = "mongodb://localhost:27017/test";
example = "mongodb://localhost:27017/test";
description = "MongoDB URI to connect to.";
};
collStats = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"db1.coll1"
"db2"
];
description = ''
List of comma separared databases.collections to get $collStats
'';
};
indexStats = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"db1.coll1"
"db2"
];
description = ''
List of comma separared databases.collections to get $indexStats
'';
};
collector = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"diagnosticdata"
"replicasetstatus"
"dbstats"
"topmetrics"
"currentopmetrics"
"indexstats"
"dbstats"
"profile"
];
description = "Enabled collectors";
};
collectAll = mkOption {
type = types.bool;
default = false;
description = ''
Enable all collectors. Same as specifying all --collector.<name>
'';
};
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
example = "/metrics";
description = "Metrics expose path";
};
};
serviceOpts = {
serviceConfig = {
RuntimeDirectory = "prometheus-mongodb-exporter";
ExecStart = ''
${getExe pkgs.prometheus-mongodb-exporter} \
--mongodb.uri="${cfg.uri}" \
${
if cfg.collectAll then
"--collect-all"
else
concatMapStringsSep " " (x: "--collect.${x}") cfg.collector
} \
${
optionalString (
length cfg.collStats > 0
) "--mongodb.collstats-colls=${concatStringsSep "," cfg.collStats}"
} \
${
optionalString (
length cfg.indexStats > 0
) "--mongodb.indexstats-colls=${concatStringsSep "," cfg.indexStats}"
} \
--web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
--web.telemetry-path="${cfg.telemetryPath}" \
${escapeShellArgs cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,140 @@
{
config,
lib,
pkgs,
options,
utils,
}:
let
inherit (lib)
mkIf
mkEnableOption
mkOption
types
;
cfg = config.services.prometheus.exporters.mqtt;
toConfigBoolean = x: if x then "True" else "False";
toConfigList = builtins.concatStringsSep ",";
in
{
# https://github.com/kpetremann/mqtt-exporter/tree/master?tab=readme-ov-file#configuration
port = 9000;
extraOpts = {
keepFullTopic = mkEnableOption "Keep entire topic instead of the first two elements only. Usecase: Shelly 3EM";
logLevel = mkOption {
type = types.enum [
"CRITICAL"
"ERROR"
"WARNING"
"INFO"
"DEBUG"
];
default = "INFO";
example = "DEBUG";
description = "Logging level";
};
logMqttMessage = mkEnableOption "Log MQTT original message, only if `LOG_LEVEL` is set to DEBUG.";
mqttIgnoredTopics = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Lists of topics to ignore. Accepts wildcards.";
};
mqttAddress = mkOption {
type = types.str;
default = "127.0.0.1";
description = "IP or hostname of MQTT broker.";
};
mqttPort = mkOption {
type = types.port;
default = 1883;
description = "TCP port of MQTT broker.";
};
mqttTopic = mkOption {
type = types.str;
default = "#";
description = "Topic path to subscribe to.";
};
mqttKeepAlive = mkOption {
type = types.int;
default = 60;
example = 30;
description = "Keep alive interval to maintain connection with MQTT broker.";
};
mqttUsername = mkOption {
type = types.nullOr types.str;
default = null;
example = "mqttexporter";
description = "Username which should be used to authenticate against the MQTT broker.";
};
mqttV5Protocol = mkEnableOption "Force to use MQTT protocol v5 instead of 3.1.1.";
mqttClientId = mkOption {
type = types.nullOr types.str;
default = null;
description = "Set client ID manually for MQTT connection";
};
mqttExposeClientId = mkEnableOption "Expose the client ID as a label in Prometheus metrics.";
prometheusPrefix = mkOption {
type = types.str;
default = "mqtt_";
description = "Prefix added to the metric name.";
};
topicLabel = mkOption {
type = types.str;
default = "topic";
description = "Define the Prometheus label for the topic.";
};
zigbee2MqttAvailability = mkEnableOption "Normalize sensor name for device availability metric added by Zigbee2MQTT.";
zwaveTopicPrefix = mkOption {
type = types.str;
default = "zwave/";
description = "MQTT topic used for Zwavejs2Mqtt messages.";
};
esphomeTopicPrefixes = mkOption {
type = types.listOf types.str;
default = [ ];
description = "MQTT topic used for ESPHome messages.";
};
hubitatTopicPrefixes = mkOption {
type = types.listOf types.str;
default = [ "hubitat/" ];
description = "MQTT topic used for Hubitat messages.";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
example = [ "/run/secrets/mqtt-exporter" ];
description = ''
File to load as environment file. Useful for e.g. setting `MQTT_PASSWORD`
without putting any secrets into the Nix store.
'';
};
};
serviceOpts = {
environment = {
KEEP_FULL_TOPIC = toConfigBoolean cfg.keepFullTopic;
LOG_LEVEL = cfg.logLevel;
LOG_MQTT_MESSAGE = toConfigBoolean cfg.logMqttMessage;
MQTT_IGNORED_TOPIC = toConfigList cfg.mqttIgnoredTopics;
MQTT_ADDRESS = cfg.mqttAddress;
MQTT_PORT = toString cfg.mqttPort;
MQTT_TOPIC = cfg.mqttTopic;
MQTT_KEEPALIVE = toString cfg.mqttKeepAlive;
MQTT_USERNAME = cfg.mqttUsername;
MQTT_V5_PROTOCOL = toConfigBoolean cfg.mqttV5Protocol;
MQTT_CLIENT_ID = mkIf (cfg.mqttClientId != null) cfg.mqttClientId;
PROMETHEUS_ADDRESS = cfg.listenAddress;
PROMETHEUS_PORT = toString cfg.port;
PROMETHEUS_PREFIX = cfg.prometheusPrefix;
TOPIC_LABEL = cfg.topicLabel;
ZIGBEE2MQTT_AVAILABILITY = toConfigBoolean cfg.zigbee2MqttAvailability;
ZWAVE_TOPIC_PREFIX = cfg.zwaveTopicPrefix;
ESPHOME_TOPIC_PREFIXES = toConfigList cfg.esphomeTopicPrefixes;
HUBITAT_TOPIC_PREFIXES = toConfigList cfg.hubitatTopicPrefixes;
};
serviceConfig = {
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
ExecStart = lib.getExe pkgs.mqtt-exporter;
};
};
}

View File

@@ -0,0 +1,75 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.mysqld;
inherit (lib)
types
mkOption
mkIf
mkForce
cli
concatStringsSep
optionalString
escapeShellArgs
;
in
{
port = 9104;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
runAsLocalSuperUser = mkOption {
type = types.bool;
default = false;
description = ''
Whether to run the exporter as {option}`services.mysql.user`.
'';
};
configFile = mkOption {
type = types.path;
example = "/var/lib/prometheus-mysqld-exporter.cnf";
description = ''
Path to the services config file.
See <https://github.com/prometheus/mysqld_exporter#running> for more information about
the available options.
::: {.warn}
Please do not store this file in the nix store if you choose to include any credentials here,
as it would be world-readable.
:::
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = !cfg.runAsLocalSuperUser;
User = mkIf cfg.runAsLocalSuperUser (mkForce config.services.mysql.user);
LoadCredential = mkIf (cfg.configFile != null) (mkForce ("config:" + cfg.configFile));
ExecStart = concatStringsSep " " [
"${pkgs.prometheus-mysqld-exporter}/bin/mysqld_exporter"
"--web.listen-address=${cfg.listenAddress}:${toString cfg.port}"
"--web.telemetry-path=${cfg.telemetryPath}"
(optionalString (cfg.configFile != null) ''--config.my-cnf=''${CREDENTIALS_DIRECTORY}/config'')
(escapeShellArgs cfg.extraFlags)
];
RestrictAddressFamilies = [
# The exporter can be configured to talk to a local mysql server via a unix socket.
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,37 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.nats;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 7777;
extraOpts = {
url = mkOption {
type = types.str;
default = "http://127.0.0.1:8222";
description = ''
NATS monitor endpoint to query.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-nats-exporter}/bin/prometheus-nats-exporter \
-addr ${cfg.listenAddress} \
-port ${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags} \
${cfg.url}
'';
};
};
}

View File

@@ -0,0 +1,87 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.nextcloud;
inherit (lib)
mkOption
types
escapeShellArg
concatStringsSep
;
in
{
port = 9205;
extraOpts = {
url = mkOption {
type = types.str;
example = "https://domain.tld";
description = ''
URL to the Nextcloud serverinfo page.
Adding the path to the serverinfo API is optional, it defaults
to `/ocs/v2.php/apps/serverinfo/api/v1/info`.
'';
};
username = mkOption {
type = types.str;
default = "nextcloud-exporter";
description = ''
Username for connecting to Nextcloud.
Note that this account needs to have admin privileges in Nextcloud.
Unused when using token authentication.
'';
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/path/to/password-file";
description = ''
File containing the password for connecting to Nextcloud.
Make sure that this file is readable by the exporter user.
'';
};
tokenFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/path/to/token-file";
description = ''
File containing the token for connecting to Nextcloud.
Make sure that this file is readable by the exporter user.
'';
};
timeout = mkOption {
type = types.str;
default = "5s";
description = ''
Timeout for getting server info document.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
ExecStart = ''
${pkgs.prometheus-nextcloud-exporter}/bin/nextcloud-exporter \
--addr ${cfg.listenAddress}:${toString cfg.port} \
--timeout ${cfg.timeout} \
--server ${cfg.url} \
${
if cfg.passwordFile != null then
''
--username ${cfg.username} \
--password ${escapeShellArg "@${cfg.passwordFile}"} \
''
else
''
--auth-token ${escapeShellArg "@${cfg.tokenFile}"} \
''
} \
${concatStringsSep " \\\n " cfg.extraFlags}'';
};
};
}

View File

@@ -0,0 +1,91 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.nginx;
inherit (lib)
mkOption
types
mkMerge
mkRemovedOptionModule
mkRenamedOptionModule
mkIf
concatStringsSep
;
in
{
port = 9113;
extraOpts = {
scrapeUri = mkOption {
type = types.str;
default = "http://localhost/nginx_status";
description = ''
Address to access the nginx status page.
Can be enabled with services.nginx.statusPage = true.
'';
};
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
sslVerify = mkOption {
type = types.bool;
default = true;
description = ''
Whether to perform certificate verification for https.
'';
};
constLabels = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"label1=value1"
"label2=value2"
];
description = ''
A list of constant labels that will be used in every metric.
'';
};
};
serviceOpts = mkMerge (
[
{
environment.CONST_LABELS = concatStringsSep "," cfg.constLabels;
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
--nginx.scrape-uri='${cfg.scrapeUri}' \
--${lib.optionalString (!cfg.sslVerify) "no-"}nginx.ssl-verify \
--web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path=${cfg.telemetryPath} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
}
]
++ [
(mkIf config.services.nginx.enable {
after = [ "nginx.service" ];
requires = [ "nginx.service" ];
})
]
);
imports = [
(mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
(mkRemovedOptionModule [ "insecure" ] ''
This option was replaced by 'prometheus.exporters.nginx.sslVerify'.
'')
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
}

View File

@@ -0,0 +1,81 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.nginxlog;
inherit (lib) mkOption types;
in
{
port = 9117;
extraOpts = {
settings = mkOption {
type = types.submodule {
options = {
consul = mkOption {
default = null;
type = types.nullOr (types.attrsOf types.anything);
description = ''
Consul integration options. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
This is disabled by default.
'';
};
namespaces = mkOption {
default = [ ];
type = types.listOf (types.attrsOf types.anything);
description = ''
Namespaces to collect the metrics for. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
'';
};
};
};
default = { };
description = ''
All settings of nginxlog expressed as an Nix attrset.
Check the official documentation for the corresponding YAML
settings that can all be used here: <https://github.com/martin-helmich/prometheus-nginxlog-exporter>
The `listen` object is already generated by `port`, `listenAddress` and `metricsEndpoint` and
will be merged with the value of `settings` before writing it as JSON.
'';
};
metricsEndpoint = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
};
serviceOpts =
let
listenConfig = {
listen = {
port = cfg.port;
address = cfg.listenAddress;
metrics_endpoint = cfg.metricsEndpoint;
};
};
completeConfig = pkgs.writeText "nginxlog-exporter.yaml" (
builtins.toJSON (lib.recursiveUpdate listenConfig cfg.settings)
);
in
{
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-nginxlog-exporter}/bin/prometheus-nginxlog-exporter -config-file ${completeConfig}
'';
Restart = "always";
ProtectSystem = "full";
};
};
}

View File

@@ -0,0 +1,70 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.node-cert;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9141;
extraOpts = {
paths = mkOption {
type = types.listOf types.str;
description = ''
List of paths to search for SSL certificates.
'';
};
excludePaths = mkOption {
type = types.listOf types.str;
description = ''
List of paths to exclute from searching for SSL certificates.
'';
default = [ ];
};
includeGlobs = mkOption {
type = types.listOf types.str;
description = ''
List files matching a pattern to include. Uses Go blob pattern.
'';
default = [ ];
};
excludeGlobs = mkOption {
type = types.listOf types.str;
description = ''
List files matching a pattern to include. Uses Go blob pattern.
'';
default = [ ];
};
user = mkOption {
type = types.str;
description = ''
User owning the certs.
'';
default = "acme";
};
};
serviceOpts = {
serviceConfig = {
User = cfg.user;
ExecStart = ''
${lib.getExe pkgs.prometheus-node-cert-exporter} \
--listen ${toString cfg.listenAddress}:${toString cfg.port} \
--path ${concatStringsSep "," cfg.paths} \
--exclude-path "${concatStringsSep "," cfg.excludePaths}" \
--include-glob "${concatStringsSep "," cfg.includeGlobs}" \
--exclude-glob "${concatStringsSep "," cfg.excludeGlobs}" \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,70 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.node;
inherit (lib)
mkOption
types
concatStringsSep
concatMapStringsSep
any
optionals
;
collectorIsEnabled = final: any (collector: (final == collector)) cfg.enabledCollectors;
collectorIsDisabled = final: any (collector: (final == collector)) cfg.disabledCollectors;
in
{
port = 9100;
extraOpts = {
enabledCollectors = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "systemd" ];
description = ''
Collectors to enable. The collectors listed here are enabled in addition to the default ones.
'';
};
disabledCollectors = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "timex" ];
description = ''
Collectors to disable which are enabled by default.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
RuntimeDirectory = "prometheus-node-exporter";
ExecStart = ''
${pkgs.prometheus-node-exporter}/bin/node_exporter \
${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
'';
RestrictAddressFamilies =
optionals (collectorIsEnabled "logind" || collectorIsEnabled "systemd") [
# needs access to dbus via unix sockets (logind/systemd)
"AF_UNIX"
]
++
optionals
(collectorIsEnabled "network_route" || collectorIsEnabled "wifi" || !collectorIsDisabled "netdev")
[
# needs netlink sockets for wireless collector
"AF_NETLINK"
];
# The timex collector needs to access clock APIs
ProtectClock = collectorIsDisabled "timex";
# Allow space monitoring under /home
ProtectHome = true;
};
};
}

View File

@@ -0,0 +1,78 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.nut;
inherit (lib)
mkOption
types
optionalString
concatStringsSep
;
in
{
port = 9199;
extraOpts = {
nutServer = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
Hostname or address of the NUT server
'';
};
nutUser = mkOption {
type = types.str;
default = "";
example = "nut";
description = ''
The user to log in into NUT server. If set, passwordPath should
also be set.
Default NUT configs usually permit reading variables without
authentication.
'';
};
passwordPath = mkOption {
type = types.nullOr types.path;
default = null;
apply = final: if final == null then null else toString final;
description = ''
A run-time path to the nutUser password file, which should be
provisioned outside of Nix store.
'';
};
nutVariables = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
List of NUT variable names to monitor.
If no variables are set, all numeric variables will be exported automatically.
See the [upstream docs](https://github.com/DRuggeri/nut_exporter?tab=readme-ov-file#variables-and-information)
for more information.
'';
};
};
serviceOpts = {
script = ''
${optionalString (
cfg.passwordPath != null
) "export NUT_EXPORTER_PASSWORD=$(cat ${toString cfg.passwordPath})"}
${pkgs.prometheus-nut-exporter}/bin/nut_exporter \
--nut.server=${cfg.nutServer} \
--web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
${optionalString (cfg.nutUser != "") "--nut.username=${cfg.nutUser}"} \
${
optionalString (
cfg.nutVariables != [ ]
) "--nut.vars_enable=${concatStringsSep "," cfg.nutVariables}"
} \
${concatStringsSep " " cfg.extraFlags}
'';
};
}

View File

@@ -0,0 +1,29 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.nvidia-gpu;
inherit (lib)
types
concatStringsSep
;
in
{
port = 9835;
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-nvidia-gpu-exporter}/bin/nvidia_gpu_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--nvidia-smi-command ${config.hardware.nvidia.package.bin}/bin/nvidia-smi \
${concatStringsSep " " cfg.extraFlags}
'';
PrivateDevices = false;
};
wantedBy = [ "multi-user.target" ];
};
}

View File

@@ -0,0 +1,196 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.pgbouncer;
inherit (lib)
mkOption
mkPackageOption
types
optionals
getExe
escapeShellArg
concatStringsSep
;
in
{
port = 9127;
extraOpts = {
package = mkPackageOption pkgs "prometheus-pgbouncer-exporter" { };
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
connectionString = mkOption {
type = types.nullOr types.str;
default = null;
example = "postgres://admin:@localhost:6432/pgbouncer?sslmode=require";
description = ''
Connection string for accessing pgBouncer.
NOTE: You MUST keep pgbouncer as database name (special internal db)!!!
NOTE: ignore_startup_parameters MUST contain "extra_float_digits".
NOTE: Admin user (with password or passwordless) MUST exist in the
auth_file if auth_type other than "any" is used.
WARNING: this secret is stored in the world-readable Nix store!
Use [](#opt-services.prometheus.exporters.pgbouncer.connectionEnvFile) if the
URL contains a secret.
'';
};
connectionEnvFile = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
File that must contain the environment variable
`PGBOUNCER_EXPORTER_CONNECTION_STRING` which is set to the connection
string used by pgbouncer. I.e. the format is supposed to look like this:
```
PGBOUNCER_EXPORTER_CONNECTION_STRING="postgres://admin@localhost:6432/pgbouncer?sslmode=require"
```
NOTE: You MUST keep pgbouncer as database name (special internal db)!
NOTE: `services.pgbouncer.settings.pgbouncer.ignore_startup_parameters`
MUST contain "extra_float_digits".
Mutually exclusive with [](#opt-services.prometheus.exporters.pgbouncer.connectionString).
'';
};
pidFile = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Path to PgBouncer pid file.
If provided, the standard process metrics get exported for the PgBouncer
process, prefixed with 'pgbouncer_process_...'. The pgbouncer_process exporter
needs to have read access to files owned by the PgBouncer process. Depends on
the availability of /proc.
<https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics>.
'';
};
webSystemdSocket = mkOption {
type = types.bool;
default = false;
description = ''
Use systemd socket activation listeners instead of port listeners (Linux only).
'';
};
logLevel = mkOption {
type = types.enum [
"debug"
"info"
"warn"
"error"
];
default = "info";
description = ''
Only log messages with the given severity or above.
'';
};
logFormat = mkOption {
type = types.enum [
"logfmt"
"json"
];
default = "logfmt";
description = ''
Output format of log messages. One of: [logfmt, json]
'';
};
webConfigFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to configuration file that can enable TLS or authentication.
'';
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Extra commandline options when launching Prometheus.
'';
};
};
serviceOpts = {
after = [ "pgbouncer.service" ];
script = concatStringsSep " " (
[
"exec -- ${escapeShellArg (getExe cfg.package)}"
"--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
]
++ optionals (cfg.connectionString != null) [
"--pgBouncer.connectionString ${escapeShellArg cfg.connectionString}"
]
++ optionals (cfg.telemetryPath != null) [
"--web.telemetry-path ${escapeShellArg cfg.telemetryPath}"
]
++ optionals (cfg.pidFile != null) [
"--pgBouncer.pid-file ${escapeShellArg cfg.pidFile}"
]
++ optionals (cfg.logLevel != null) [
"--log.level ${escapeShellArg cfg.logLevel}"
]
++ optionals (cfg.logFormat != null) [
"--log.format ${escapeShellArg cfg.logFormat}"
]
++ optionals (cfg.webSystemdSocket != false) [
"--web.systemd-socket ${escapeShellArg cfg.webSystemdSocket}"
]
++ optionals (cfg.webConfigFile != null) [
"--web.config.file ${escapeShellArg cfg.webConfigFile}"
]
++ cfg.extraFlags
);
serviceConfig.RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
serviceConfig.EnvironmentFile = lib.mkIf (cfg.connectionEnvFile != null) [
cfg.connectionEnvFile
];
};
imports = [
(lib.mkRemovedOptionModule [ "connectionStringFile" ] ''
As replacement, the option `services.prometheus.exporters.pgbouncer.connectionEnvFile`
has been added. In contrast to `connectionStringFile` it must be an environment file
with the connection string being set to `PGBOUNCER_EXPORTER_CONNECTION_STRING`.
The change was necessary since the former option wrote the contents of the file
into the cmdline of the exporter making the connection string effectively
world-readable.
'')
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
}

View File

@@ -0,0 +1,68 @@
{
config,
lib,
pkgs,
options,
...
}:
let
logPrefix = "services.prometheus.exporter.php-fpm";
cfg = config.services.prometheus.exporters.php-fpm;
in
{
port = 9253;
extraOpts = {
package = lib.mkPackageOption pkgs "prometheus-php-fpm-exporter" { };
telemetryPath = lib.mkOption {
type = lib.types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
environmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/root/prometheus-php-fpm-exporter.env";
description = ''
Environment file as defined in {manpage}`systemd.exec(5)`.
Secrets may be passed to the service without adding them to the
world-readable Nix store, by specifying placeholder variables as
the option value in Nix and setting these variables accordingly in the
environment file.
Environment variables from this file will be interpolated into the
config file using envsubst with this syntax:
`$ENVIRONMENT ''${VARIABLE}`
For variables to use see [options and defaults](https://github.com/hipages/php-fpm_exporter#options-and-defaults).
The main use is to set the PHP_FPM_SCRAPE_URI that indicate how to connect to PHP-FPM process.
```
# Content of the environment file
PHP_FPM_SCRAPE_URI="unix:///tmp/php.sock;/status"
```
Note that this file needs to be available on the host on which
this exporter is running.
'';
};
};
serviceOpts = {
serviceConfig = {
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
ExecStart = ''
${lib.getExe cfg.package} server \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
${lib.concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,94 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.pihole;
inherit (lib)
mkOption
types
mkRemovedOptionModule
optionalString
;
in
{
imports = [
(mkRemovedOptionModule [ "interval" ] "This option has been removed.")
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
port = 9617;
extraOpts = {
apiToken = mkOption {
type = types.str;
default = "";
example = "580a770cb40511eb85290242ac130003580a770cb40511eb85290242ac130003";
description = ''
Pi-Hole API token which can be used instead of a password
'';
};
password = mkOption {
type = types.str;
default = "";
example = "password";
description = ''
The password to login into Pi-Hole. An api token can be used instead.
'';
};
piholeHostname = mkOption {
type = types.str;
default = "pihole";
example = "127.0.0.1";
description = ''
Hostname or address where to find the Pi-Hole webinterface
'';
};
piholePort = mkOption {
type = types.port;
default = 80;
example = 443;
description = ''
The port Pi-Hole webinterface is reachable on
'';
};
protocol = mkOption {
type = types.enum [
"http"
"https"
];
default = "http";
example = "https";
description = ''
The protocol which is used to connect to Pi-Hole
'';
};
timeout = mkOption {
type = types.str;
default = "5s";
description = ''
Controls the timeout to connect to a Pi-Hole instance
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-pihole-exporter}/bin/pihole-exporter \
${optionalString (cfg.apiToken != "") "-pihole_api_token ${cfg.apiToken}"} \
-pihole_hostname ${cfg.piholeHostname} \
${optionalString (cfg.password != "") "-pihole_password ${cfg.password}"} \
-pihole_port ${toString cfg.piholePort} \
-pihole_protocol ${cfg.protocol} \
-port ${toString cfg.port} \
-timeout ${cfg.timeout}
'';
};
};
}

View File

@@ -0,0 +1,53 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.ping;
inherit (lib) mkOption types concatStringsSep;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "config.yml" cfg.settings;
in
{
port = 9427;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
settings = mkOption {
type = settingsFormat.type;
default = { };
description = ''
Configuration for ping_exporter, see
<https://github.com/czerwonk/ping_exporter>
for supported values.
'';
};
};
serviceOpts = {
serviceConfig = {
# ping-exporter needs `CAP_NET_RAW` to run as non root https://github.com/czerwonk/ping_exporter#running-as-non-root-user
CapabilityBoundingSet = [ "CAP_NET_RAW" ];
AmbientCapabilities = [ "CAP_NET_RAW" ];
ExecStart = ''
${pkgs.prometheus-ping-exporter}/bin/ping_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
--config.path="${configFile}" \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,118 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.postfix;
inherit (lib)
mkOption
types
mkIf
escapeShellArg
concatStringsSep
optional
;
in
{
port = 9154;
extraOpts = {
package = lib.mkPackageOption pkgs "prometheus-postfix-exporter" { };
group = mkOption {
type = types.str;
description = ''
Group under which the postfix exporter shall be run.
It should match the group that is allowed to access the
`showq` socket in the `queue/public/` directory.
Defaults to `services.postfix.setgidGroup` when postfix is enabled.
'';
};
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
logfilePath = mkOption {
type = types.path;
default = "/var/log/postfix_exporter_input.log";
example = "/var/log/mail.log";
description = ''
Path where Postfix writes log entries.
This file will be truncated by this exporter!
'';
};
showqPath = mkOption {
type = types.path;
default = "/var/lib/postfix/queue/public/showq";
example = "/var/spool/postfix/public/showq";
description = ''
Path where Postfix places its showq socket.
'';
};
systemd = {
enable = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable reading metrics from the systemd journal instead of from a logfile
'';
};
unit = mkOption {
type = types.str;
default = "postfix.service";
description = ''
Name of the postfix systemd unit.
'';
};
slice = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Name of the postfix systemd slice.
This overrides the {option}`systemd.unit`.
'';
};
journalPath = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to the systemd journal.
'';
};
};
};
serviceOpts = {
after = mkIf cfg.systemd.enable [ cfg.systemd.unit ];
serviceConfig = {
DynamicUser = false;
# By default, each prometheus exporter only gets AF_INET & AF_INET6,
# but AF_UNIX is needed to read from the `showq`-socket.
RestrictAddressFamilies = [ "AF_UNIX" ];
SupplementaryGroups = mkIf cfg.systemd.enable [ "systemd-journal" ];
ExecStart = ''
${lib.getExe cfg.package} \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
--postfix.showq_path ${escapeShellArg cfg.showqPath} \
${concatStringsSep " \\\n " (
cfg.extraFlags
++ optional cfg.systemd.enable "--systemd.enable"
++ optional cfg.systemd.enable (
if cfg.systemd.slice != null then
"--systemd.slice ${cfg.systemd.slice}"
else
"--systemd.unit ${cfg.systemd.unit}"
)
++ optional (
cfg.systemd.enable && (cfg.systemd.journalPath != null)
) "--systemd.journal_path ${escapeShellArg cfg.systemd.journalPath}"
++ optional (!cfg.systemd.enable) "--postfix.logfile_path ${escapeShellArg cfg.logfilePath}"
)}
'';
};
};
}

View File

@@ -0,0 +1,97 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.postgres;
inherit (lib)
mkOption
types
mkIf
mkForce
concatStringsSep
;
in
{
port = 9187;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
dataSourceName = mkOption {
type = types.str;
default = "user=postgres database=postgres host=/run/postgresql sslmode=disable";
example = "postgresql://username:password@localhost:5432/postgres?sslmode=disable";
description = ''
Accepts PostgreSQL URI form and key=value form arguments.
'';
};
runAsLocalSuperUser = mkOption {
type = types.bool;
default = false;
description = ''
Whether to run the exporter as the local 'postgres' super user.
'';
};
# TODO perhaps LoadCredential would be more appropriate
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/root/prometheus-postgres-exporter.env";
description = ''
Environment file as defined in {manpage}`systemd.exec(5)`.
Secrets may be passed to the service without adding them to the
world-readable Nix store, by specifying placeholder variables as
the option value in Nix and setting these variables accordingly in the
environment file.
Environment variables from this file will be interpolated into the
config file using envsubst with this syntax:
`$ENVIRONMENT ''${VARIABLE}`
The main use is to set the DATA_SOURCE_NAME that contains the
postgres password
note that contents from this file will override dataSourceName
if you have set it from nix.
```
# Content of the environment file
DATA_SOURCE_NAME=postgresql://username:password@localhost:5432/postgres?sslmode=disable
```
Note that this file needs to be available on the host on which
this exporter is running.
'';
};
};
serviceOpts = {
environment.DATA_SOURCE_NAME = cfg.dataSourceName;
serviceConfig = {
DynamicUser = false;
User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres");
EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
ExecStart = ''
${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,56 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.process;
inherit (lib)
mkOption
types
literalExpression
concatStringsSep
;
configFile = pkgs.writeText "process-exporter.yaml" (builtins.toJSON cfg.settings);
in
{
port = 9256;
extraOpts = {
settings.process_names = mkOption {
type = types.listOf types.anything;
default = [ ];
example = literalExpression ''
[
# Remove nix store path from process name
{ name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
]
'';
description = ''
All settings expressed as an Nix attrset.
Check the official documentation for the corresponding YAML
settings that can all be used here: <https://github.com/ncabatoff/process-exporter>
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
ExecStart = ''
${pkgs.prometheus-process-exporter}/bin/process-exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--config.path ${configFile} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
NoNewPrivileges = true;
ProtectHome = true;
ProtectSystem = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
};
};
}

View File

@@ -0,0 +1,155 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.pve;
inherit (lib)
mkOption
types
mkPackageOption
optionalString
optionalAttrs
;
# pve exporter requires a config file so create an empty one if configFile is not provided
emptyConfigFile = pkgs.writeTextFile {
name = "pve.yml";
text = "default:";
};
computedConfigFile = if cfg.configFile == null then emptyConfigFile else cfg.configFile;
in
{
port = 9221;
extraOpts = {
package = mkPackageOption pkgs "prometheus-pve-exporter" { };
environmentFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/etc/prometheus-pve-exporter/pve.env";
description = ''
Path to the service's environment file. This path can either be a computed path in /nix/store or a path in the local filesystem.
The environment file should NOT be stored in /nix/store as it contains passwords and/or keys in plain text.
Environment reference: <https://github.com/prometheus-pve/prometheus-pve-exporter#authentication>
'';
};
configFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/etc/prometheus-pve-exporter/pve.yml";
description = ''
Path to the service's config file. This path can either be a computed path in /nix/store or a path in the local filesystem.
The config file should NOT be stored in /nix/store as it will contain passwords and/or keys in plain text.
If both configFile and environmentFile are provided, the configFile option will be ignored.
Configuration reference: <https://github.com/prometheus-pve/prometheus-pve-exporter/#authentication>
'';
};
server = {
keyFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/var/lib/prometheus-pve-exporter/privkey.key";
description = ''
Path to a SSL private key file for the server
'';
};
certFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/var/lib/prometheus-pve-exporter/full-chain.pem";
description = ''
Path to a SSL certificate file for the server
'';
};
};
collectors = {
status = mkOption {
type = types.bool;
default = true;
description = ''
Collect Node/VM/CT status
'';
};
version = mkOption {
type = types.bool;
default = true;
description = ''
Collect PVE version info
'';
};
node = mkOption {
type = types.bool;
default = true;
description = ''
Collect PVE node info
'';
};
cluster = mkOption {
type = types.bool;
default = true;
description = ''
Collect PVE cluster info
'';
};
resources = mkOption {
type = types.bool;
default = true;
description = ''
Collect PVE resources info
'';
};
config = mkOption {
type = types.bool;
default = true;
description = ''
Collect PVE onboot status
'';
};
replication = mkOption {
type = types.bool;
default = true;
description = ''
Collect PVE replication info
'';
};
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = cfg.environmentFile == null;
LoadCredential = "configFile:${computedConfigFile}";
ExecStart = ''
${cfg.package}/bin/pve_exporter \
--${optionalString (!cfg.collectors.status) "no-"}collector.status \
--${optionalString (!cfg.collectors.version) "no-"}collector.version \
--${optionalString (!cfg.collectors.node) "no-"}collector.node \
--${optionalString (!cfg.collectors.cluster) "no-"}collector.cluster \
--${optionalString (!cfg.collectors.resources) "no-"}collector.resources \
--${optionalString (!cfg.collectors.config) "no-"}collector.config \
--${optionalString (!cfg.collectors.replication) "no-"}collector.replication \
${optionalString (cfg.server.keyFile != null) "--server.keyfile ${cfg.server.keyFile}"} \
${optionalString (cfg.server.certFile != null) "--server.certfile ${cfg.server.certFile}"} \
--config.file %d/configFile \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port}
'';
}
// optionalAttrs (cfg.environmentFile != null) {
EnvironmentFile = cfg.environmentFile;
};
};
}

View File

@@ -0,0 +1,58 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.py-air-control;
inherit (lib) mkOption types;
workingDir = "/var/lib/${cfg.stateDir}";
in
{
port = 9896;
extraOpts = {
deviceHostname = mkOption {
type = types.str;
example = "192.168.1.123";
description = ''
The hostname of the air purification device from which to scrape the metrics.
'';
};
protocol = mkOption {
type = types.str;
default = "http";
description = ''
The protocol to use when communicating with the air purification device.
Available: [http, coap, plain_coap]
'';
};
stateDir = mkOption {
type = types.str;
default = "prometheus-py-air-control-exporter";
description = ''
Directory below `/var/lib` to store runtime data.
This directory will be created automatically using systemd's StateDirectory mechanism.
'';
};
};
serviceOpts = {
serviceConfig = {
DynamicUser = false;
StateDirectory = cfg.stateDir;
WorkingDirectory = workingDir;
ExecStart = ''
${pkgs.python3Packages.py-air-control-exporter}/bin/py-air-control-exporter \
--host ${cfg.deviceHostname} \
--protocol ${cfg.protocol} \
--listen-port ${toString cfg.port} \
--listen-address ${cfg.listenAddress}
'';
Environment = [ "HOME=${workingDir}" ];
};
};
}

View File

@@ -0,0 +1,67 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib)
getExe
mkOption
types
;
inherit (utils) escapeSystemdExecArgs;
cfg = config.services.prometheus.exporters.rasdaemon;
in
{
port = 10029;
extraOpts = with types; {
databasePath = mkOption {
type = path;
default = "/var/lib/rasdaemon/ras-mc_event.db";
description = ''
Path to the RAS daemon machine check event database.
'';
};
enabledCollectors = mkOption {
type = listOf (enum [
"aer"
"mce"
"mc"
"extlog"
"devlink"
"disk"
]);
default = [
"aer"
"mce"
"mc"
];
description = ''
List of error types to collect from the event database.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = escapeSystemdExecArgs (
[
(getExe pkgs.prometheus-rasdaemon-exporter)
"--address"
cfg.listenAddress
"--port"
(toString cfg.port)
"--db"
cfg.databasePath
]
++ map (collector: "--collector-${collector}") cfg.enabledCollectors
++ cfg.extraFlags
);
};
};
}

View File

@@ -0,0 +1,25 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.redis;
inherit (lib) concatStringsSep;
in
{
port = 9121;
serviceOpts = {
serviceConfig = {
RestrictAddressFamilies = [ "AF_UNIX" ];
ExecStart = ''
${pkgs.prometheus-redis-exporter}/bin/redis_exporter \
-web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,173 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.restic;
inherit (lib)
mkOption
types
concatStringsSep
mkIf
mapAttrs'
splitString
toUpper
optional
optionalAttrs
nameValuePair
;
in
{
port = 9753;
extraOpts = {
repository = mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
URI pointing to the repository to monitor.
'';
example = "sftp:backup@192.168.1.100:/backups/example";
};
repositoryFile = mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
Path to the file containing the URI for the repository to monitor.
'';
};
passwordFile = mkOption {
type = types.path;
description = ''
File containing the password to the repository.
'';
example = "/etc/nixos/restic-password";
};
environmentFile = mkOption {
type = with types; nullOr path;
default = null;
description = ''
File containing the credentials to access the repository, in the
format of an EnvironmentFile as described by {manpage}`systemd.exec(5)`
'';
};
refreshInterval = mkOption {
type = types.ints.unsigned;
default = 60;
description = ''
Refresh interval for the metrics in seconds.
Computing the metrics is an expensive task, keep this value as high as possible.
'';
};
rcloneOptions = mkOption {
type =
with types;
attrsOf (oneOf [
str
bool
]);
default = { };
description = ''
Options to pass to rclone to control its behavior.
See <https://rclone.org/docs/#options> for
available options. When specifying option names, strip the
leading `--`. To set a flag such as
`--drive-use-trash`, which does not take a value,
set the value to the Boolean `true`.
'';
};
rcloneConfig = mkOption {
type =
with types;
attrsOf (oneOf [
str
bool
]);
default = { };
description = ''
Configuration for the rclone remote being used for backup.
See the remote's specific options under rclone's docs at
<https://rclone.org/docs/>. When specifying
option names, use the "config" name specified in the docs.
For example, to set `--b2-hard-delete` for a B2
remote, use `hard_delete = true` in the
attribute set.
::: {.warning}
Secrets set in here will be world-readable in the Nix
store! Consider using the {option}`rcloneConfigFile`
option instead to specify secret values separately. Note that
options set here will override those set in the config file.
:::
'';
};
rcloneConfigFile = mkOption {
type = with types; nullOr path;
default = null;
description = ''
Path to the file containing rclone configuration. This file
must contain configuration for the remote specified in this backup
set and also must be readable by root.
::: {.caution}
Options set in `rcloneConfig` will override those set in this
file.
:::
'';
};
};
serviceOpts = {
script = ''
export RESTIC_REPOSITORY=${
if cfg.repositoryFile != null then
"$(cat $CREDENTIALS_DIRECTORY/RESTIC_REPOSITORY)"
else
"${cfg.repository}"
}
export RESTIC_PASSWORD_FILE=$CREDENTIALS_DIRECTORY/RESTIC_PASSWORD_FILE
${pkgs.prometheus-restic-exporter}/bin/restic-exporter.py \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
serviceConfig = {
CacheDirectory = "restic-exporter";
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
LoadCredential = [
"RESTIC_PASSWORD_FILE:${cfg.passwordFile}"
]
++ optional (cfg.repositoryFile != null) [ "RESTIC_REPOSITORY:${cfg.repositoryFile}" ];
};
environment =
let
rcloneRemoteName = builtins.elemAt (splitString ":" cfg.repository) 1;
rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
in
{
LISTEN_ADDRESS = cfg.listenAddress;
LISTEN_PORT = toString cfg.port;
REFRESH_INTERVAL = toString cfg.refreshInterval;
RESTIC_CACHE_DIR = "$CACHE_DIRECTORY";
}
// (mapAttrs' (
name: value: nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
) cfg.rcloneOptions)
// optionalAttrs (cfg.rcloneConfigFile != null) {
RCLONE_CONFIG = cfg.rcloneConfigFile;
}
// (mapAttrs' (
name: value: nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
) cfg.rcloneConfig);
};
}

View File

@@ -0,0 +1,119 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.rspamd;
inherit (lib)
mkOption
types
replaceStrings
mkRemovedOptionModule
recursiveUpdate
concatStringsSep
literalExpression
;
mkFile = conf: pkgs.writeText "rspamd-exporter-config.yml" (builtins.toJSON conf);
generateConfig = extraLabels: {
modules.default.metrics =
(map
(path: {
name = "rspamd_${replaceStrings [ "[" "." " " "]" "\\" "'" ] [ "_" "_" "_" "" "" "" ] path}";
path = "{ .${path} }";
labels = extraLabels;
})
[
"actions['add\\ header']"
"actions['no\\ action']"
"actions['rewrite\\ subject']"
"actions['soft\\ reject']"
"actions.greylist"
"actions.reject"
"bytes_allocated"
"chunks_allocated"
"chunks_freed"
"chunks_oversized"
"connections"
"control_connections"
"ham_count"
"learned"
"pools_allocated"
"pools_freed"
"read_only"
"scanned"
"shared_chunks_allocated"
"spam_count"
"total_learns"
]
)
++ [
{
name = "rspamd_statfiles";
type = "object";
path = "{.statfiles[*]}";
labels = recursiveUpdate {
symbol = "{.symbol}";
type = "{.type}";
} extraLabels;
values = {
revision = "{.revision}";
size = "{.size}";
total = "{.total}";
used = "{.used}";
languages = "{.languages}";
users = "{.users}";
};
}
];
};
in
{
port = 7980;
extraOpts = {
extraLabels = mkOption {
type = types.attrsOf types.str;
default = {
host = config.networking.hostName;
};
defaultText = literalExpression "{ host = config.networking.hostName; }";
example = literalExpression ''
{
host = config.networking.hostName;
custom_label = "some_value";
}
'';
description = "Set of labels added to each metric.";
};
};
serviceOpts.serviceConfig.ExecStart = ''
${pkgs.prometheus-json-exporter}/bin/json_exporter \
--config.file ${mkFile (generateConfig cfg.extraLabels)} \
--web.listen-address "${cfg.listenAddress}:${toString cfg.port}" \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
imports = [
(mkRemovedOptionModule [ "url" ] ''
This option was removed. The URL of the rspamd metrics endpoint
must now be provided to the exporter by prometheus via the url
parameter `target'.
In prometheus a scrape URL would look like this:
http://some.rspamd-exporter.host:7980/probe?target=http://some.rspamd.host:11334/stat
For more information, take a look at the official documentation
(https://github.com/prometheus-community/json_exporter) of the json_exporter.
'')
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
}

View File

@@ -0,0 +1,101 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.rtl_433;
in
{
port = 9550;
extraOpts =
let
mkMatcherOptionType =
field: description:
with lib.types;
listOf (submodule {
options = {
name = lib.mkOption {
type = str;
description = "Name to match.";
};
"${field}" = lib.mkOption {
type = int;
description = description;
};
location = lib.mkOption {
type = str;
description = "Location to match.";
};
};
});
in
{
rtl433Flags = lib.mkOption {
type = lib.types.str;
default = "-C si";
example = "-C si -R 19";
description = ''
Flags passed verbatim to rtl_433 binary.
Having `-C si` (the default) is recommended since only Celsius temperatures are parsed.
'';
};
channels = lib.mkOption {
type = mkMatcherOptionType "channel" "Channel to match.";
default = [ ];
example = [
{
name = "Acurite";
channel = 6543;
location = "Kitchen";
}
];
description = ''
List of channel matchers to export.
'';
};
ids = lib.mkOption {
type = mkMatcherOptionType "id" "ID to match.";
default = [ ];
example = [
{
name = "Nexus";
id = 1;
location = "Bedroom";
}
];
description = ''
List of ID matchers to export.
'';
};
};
serviceOpts = {
serviceConfig = {
# rtl-sdr udev rules make supported USB devices +rw by plugdev.
SupplementaryGroups = "plugdev";
# rtl_433 needs rw access to the USB radio.
PrivateDevices = lib.mkForce false;
DeviceAllow = lib.mkForce "char-usb_device rw";
RestrictAddressFamilies = [ "AF_NETLINK" ];
ExecStart =
let
matchers =
(map (m: "--channel_matcher '${m.name},${toString m.channel},${m.location}'") cfg.channels)
++ (map (m: "--id_matcher '${m.name},${toString m.id},${m.location}'") cfg.ids);
in
''
${pkgs.prometheus-rtl_433-exporter}/bin/rtl_433_prometheus \
-listen ${cfg.listenAddress}:${toString cfg.port} \
-subprocess "${pkgs.rtl_433}/bin/rtl_433 -F json ${cfg.rtl433Flags}" \
${lib.concatStringsSep " \\\n " matchers} \
${lib.concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,70 @@
{
config,
lib,
pkgs,
options,
...
}:
let
inherit (lib) mkOption types;
cfg = config.services.prometheus.exporters.sabnzbd;
in
{
port = 9387;
extraOpts = {
servers = mkOption {
description = "List of sabnzbd servers to connect to.";
type = types.listOf (
types.submodule {
options = {
baseUrl = mkOption {
type = types.str;
description = "Base URL of the sabnzbd server.";
example = "http://localhost:8080/sabnzbd";
};
apiKeyFile = mkOption {
type = types.str;
description = ''
The path to a file containing the API key.
The file is securely passed to the service by leveraging systemd credentials.
No special permissions need to be set on this file.
'';
example = "/run/secrets/sabnzbd_apikey";
};
};
}
);
};
};
serviceOpts =
let
servers = lib.zipAttrs cfg.servers;
credentials = lib.imap0 (i: v: {
name = "apikey-${toString i}";
path = v;
}) servers.apiKeyFile;
in
{
serviceConfig.LoadCredential = builtins.map ({ name, path }: "${name}:${path}") credentials;
environment = {
METRICS_PORT = toString cfg.port;
METRICS_ADDR = cfg.listenAddress;
SABNZBD_BASEURLS = lib.concatStringsSep "," servers.baseUrl;
};
script =
let
apiKeys = lib.concatStringsSep "," (
builtins.map (cred: "$(< $CREDENTIALS_DIRECTORY/${cred.name})") credentials
);
in
''
export SABNZBD_APIKEYS="${apiKeys}"
exec ${lib.getExe pkgs.prometheus-sabnzbd-exporter}
'';
};
}

View File

@@ -0,0 +1,36 @@
{
config,
lib,
pkgs,
options,
...
}:
let
logPrefix = "services.prometheus.exporter.scaphandre";
cfg = config.services.prometheus.exporters.scaphandre;
in
{
port = 8080;
extraOpts = {
telemetryPath = lib.mkOption {
type = lib.types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.scaphandre}/bin/scaphandre prometheus \
--address ${cfg.listenAddress} \
--port ${toString cfg.port} \
--suffix ${cfg.telemetryPath} \
${lib.concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,59 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.script;
inherit (lib)
mkOption
types
literalExpression
concatStringsSep
;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "script-exporter.yaml" cfg.settings;
in
{
port = 9172;
extraOpts = {
settings = mkOption {
type = (pkgs.formats.yaml { }).type;
default = { };
example = literalExpression ''
{
scripts = [
{ name = "sleep"; command = [ "sleep" ]; args = [ "5" ]; }
];
}
'';
description = ''
Free-form configuration for script_exporter, expressed as a Nix attrset and rendered to YAML.
**Migration note:**
The previous format using `script = "sleep 5"` is no longer supported. You must use `command` (list) and `args` (list), e.g. `{ command = [ "sleep" ]; args = [ "5" ]; }`.
See the official documentation for all available options: <https://github.com/ricoberger/script_exporter#configuration-file>
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-script-exporter}/bin/script_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--config.files ${configFile} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
NoNewPrivileges = true;
ProtectHome = true;
ProtectSystem = "strict";
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
};
};
}

View File

@@ -0,0 +1,32 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.shelly;
inherit (lib) mkOption types;
in
{
port = 9784;
extraOpts = {
metrics-file = mkOption {
type = types.path;
description = ''
Path to the JSON file with the metric definitions
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-shelly-exporter}/bin/shelly_exporter \
-metrics-file ${cfg.metrics-file} \
-listen-address ${cfg.listenAddress}:${toString cfg.port}
'';
};
};
}

View File

@@ -0,0 +1,80 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.smartctl;
inherit (lib) mkOption types literalExpression;
args = lib.escapeShellArgs (
[
"--web.listen-address=${cfg.listenAddress}:${toString cfg.port}"
"--smartctl.interval=${cfg.maxInterval}"
]
++ map (device: "--smartctl.device=${device}") cfg.devices
++ cfg.extraFlags
);
in
{
port = 9633;
extraOpts = {
devices = mkOption {
type = types.listOf types.str;
default = [ ];
example = literalExpression ''
[ "/dev/sda", "/dev/nvme0n1" ];
'';
description = ''
Paths to the disks that will be monitored. Will autodiscover
all disks if none given.
'';
};
maxInterval = mkOption {
type = types.str;
default = "60s";
example = "2m";
description = ''
Interval that limits how often a disk can be queried.
'';
};
};
serviceOpts = {
serviceConfig = {
AmbientCapabilities = [
"CAP_SYS_RAWIO"
"CAP_SYS_ADMIN"
];
CapabilityBoundingSet = [
"CAP_SYS_RAWIO"
"CAP_SYS_ADMIN"
];
DevicePolicy = "closed";
DeviceAllow = lib.mkOverride 50 [
"block-blkext rw"
"block-sd rw"
"char-nvme rw"
];
ExecStart = "${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter ${args}";
PrivateDevices = lib.mkForce false;
ProtectProc = "invisible";
ProcSubset = "pid";
SupplementaryGroups = [
"disk"
"smartctl-exporter-access"
];
SystemCallFilter = [
"@system-service"
"~@privileged"
];
};
};
}

View File

@@ -0,0 +1,67 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.smokeping;
inherit (lib) mkOption types concatStringsSep;
goDuration = types.mkOptionType {
name = "goDuration";
description = "Go duration (https://golang.org/pkg/time/#ParseDuration)";
check =
x: types.str.check x && builtins.match "(-?[0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+" x != null;
inherit (types.str) merge;
};
in
{
port = 9374;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
pingInterval = mkOption {
type = goDuration;
default = "1s";
description = ''
Interval between pings.
'';
};
buckets = mkOption {
type = types.commas;
default = "5e-05,0.0001,0.0002,0.0004,0.0008,0.0016,0.0032,0.0064,0.0128,0.0256,0.0512,0.1024,0.2048,0.4096,0.8192,1.6384,3.2768,6.5536,13.1072,26.2144";
description = ''
List of buckets to use for the response duration histogram.
'';
};
hosts = mkOption {
type = with types; listOf str;
description = ''
List of endpoints to probe.
'';
};
};
serviceOpts = {
serviceConfig = {
AmbientCapabilities = [ "CAP_NET_RAW" ];
CapabilityBoundingSet = [ "CAP_NET_RAW" ];
ExecStart = ''
${pkgs.prometheus-smokeping-prober}/bin/smokeping_prober \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
--buckets ${cfg.buckets} \
--ping.interval ${cfg.pingInterval} \
--privileged \
${concatStringsSep " \\\n " cfg.extraFlags} \
${concatStringsSep " " cfg.hosts}
'';
};
};
}

View File

@@ -0,0 +1,157 @@
{
config,
lib,
pkgs,
options,
...
}:
let
logPrefix = "services.prometheus.exporters.snmp";
cfg = config.services.prometheus.exporters.snmp;
inherit (lib)
mkOption
types
literalExpression
escapeShellArg
concatStringsSep
;
# This ensures that we can deal with string paths, path types and
# store-path strings with context.
coerceConfigFile =
file:
if (builtins.isPath file) || (lib.isStorePath file) then
file
else
(
lib.warn ''
${logPrefix}: configuration file "${file}" is being copied to the nix-store.
If you would like to avoid that, please set enableConfigCheck to false.
'' /.
+ file
);
checkConfig =
file:
pkgs.runCommand "checked-snmp-exporter-config.yml"
{
preferLocalBuild = true;
nativeBuildInputs = [ pkgs.buildPackages.prometheus-snmp-exporter ];
}
''
ln -s ${coerceConfigFile file} $out
snmp_exporter --dry-run --config.file $out
'';
in
{
port = 9116;
extraOpts = {
configurationPath = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a snmp exporter configuration file. Mutually exclusive with 'configuration' option.
'';
example = literalExpression "./snmp.yml";
};
configuration = mkOption {
type = types.nullOr types.attrs;
default = null;
description = ''
Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option.
'';
example = {
auths.public_v2 = {
community = "public";
version = 2;
};
};
};
enableConfigCheck = mkOption {
type = types.bool;
default = true;
description = ''
Whether to run a correctness check for the configuration file. This depends
on the configuration file residing in the nix-store. Paths passed as string will
be copied to the store.
'';
};
logFormat = mkOption {
type = types.enum [
"logfmt"
"json"
];
default = "logfmt";
description = ''
Output format of log messages.
'';
};
logLevel = mkOption {
type = types.enum [
"debug"
"info"
"warn"
"error"
];
default = "info";
description = ''
Only log messages with the given severity or above.
'';
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/root/prometheus-snmp-exporter.env";
description = ''
EnvironmentFile as defined in {manpage}`systemd.exec(5)`.
Secrets may be passed to the service without adding them to the
world-readable Nix store, by specifying placeholder variables as
the option value in Nix and setting these variables accordingly in the
environment file.
Environment variables from this file will be interpolated into the
config file using envsubst with this syntax:
`$ENVIRONMENT ''${VARIABLE}`
For variables to use see [Prometheus Configuration](https://github.com/prometheus/snmp_exporter#prometheus-configuration).
If the file path is set to this option, the parameter
`--config.expand-environment-variables` is implicitly added to
`ExecStart`.
Note that this file needs to be available on the host on which
this exporter is running.
'';
};
};
serviceOpts =
let
uncheckedConfigFile =
if cfg.configurationPath != null then
cfg.configurationPath
else
"${pkgs.writeText "snmp-exporter-conf.yml" (builtins.toJSON cfg.configuration)}";
configFile = if cfg.enableConfigCheck then checkConfig uncheckedConfigFile else uncheckedConfigFile;
in
{
serviceConfig = {
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
ExecStart = ''
${pkgs.prometheus-snmp-exporter}/bin/snmp_exporter \
--config.file=${escapeShellArg configFile} \
${lib.optionalString (cfg.environmentFile != null) "--config.expand-environment-variables"} \
--log.format=${escapeShellArg cfg.logFormat} \
--log.level=${cfg.logLevel} \
--web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,121 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.sql;
inherit (lib)
mkOption
types
mapAttrs
mapAttrsToList
concatStringsSep
;
cfgOptions = {
options = with types; {
jobs = mkOption {
type = attrsOf (submodule jobOptions);
default = { };
description = "An attrset of metrics scraping jobs to run.";
};
};
};
jobOptions = {
options = with types; {
interval = mkOption {
type = str;
description = ''
How often to run this job, specified in
[Go duration](https://golang.org/pkg/time/#ParseDuration) format.
'';
};
connections = mkOption {
type = listOf str;
description = "A list of connection strings of the SQL servers to scrape metrics from";
};
startupSql = mkOption {
type = listOf str;
default = [ ];
description = "A list of SQL statements to execute once after making a connection.";
};
queries = mkOption {
type = attrsOf (submodule queryOptions);
description = "SQL queries to run.";
};
};
};
queryOptions = {
options = with types; {
help = mkOption {
type = nullOr str;
default = null;
description = "A human-readable description of this metric.";
};
labels = mkOption {
type = listOf str;
default = [ ];
description = "A set of columns that will be used as Prometheus labels.";
};
query = mkOption {
type = str;
description = "The SQL query to run.";
};
values = mkOption {
type = listOf str;
description = "A set of columns that will be used as values of this metric.";
};
};
};
configFile =
if cfg.configFile != null then
cfg.configFile
else
let
nameInline = mapAttrsToList (k: v: v // { name = k; });
renameStartupSql = j: removeAttrs (j // { startup_sql = j.startupSql; }) [ "startupSql" ];
configuration = {
jobs = map renameStartupSql (
nameInline (mapAttrs (k: v: (v // { queries = nameInline v.queries; })) cfg.configuration.jobs)
);
};
in
builtins.toFile "config.yaml" (builtins.toJSON configuration);
in
{
extraOpts = {
configFile = mkOption {
type = with types; nullOr path;
default = null;
description = ''
Path to configuration file.
'';
};
configuration = mkOption {
type = with types; nullOr (submodule cfgOptions);
default = null;
description = ''
Exporter configuration as nix attribute set. Mutually exclusive with 'configFile' option.
'';
};
};
port = 9237;
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-sql-exporter}/bin/sql_exporter \
-web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-config.file ${configFile} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,24 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.statsd;
inherit (lib) concatStringsSep;
in
{
port = 9102;
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-statsd-exporter}/bin/statsd_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,47 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.storagebox;
inherit (lib) mkPackageOption;
in
{
port = 9509;
extraOpts = {
package = mkPackageOption pkgs "prometheus-storagebox-exporter" { };
tokenFile = lib.mkOption {
type = lib.types.pathWith {
inStore = false;
absolute = true;
};
description = "File that contains the Hetzner API token to use.";
};
};
serviceOpts = {
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
script = ''
export HETZNER_TOKEN=$(< "''${CREDENTIALS_DIRECTORY}/token")
exec ${lib.getExe cfg.package}
'';
environment = {
LISTEN_ADDR = "${toString cfg.listenAddress}:${toString cfg.port}";
};
serviceConfig = {
DynamicUser = true;
Restart = "always";
RestartSec = "10s";
LoadCredential = [
"token:${cfg.tokenFile}"
];
};
};
}

View File

@@ -0,0 +1,36 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.surfboard;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9239;
extraOpts = {
modemAddress = mkOption {
type = types.str;
default = "192.168.100.1";
description = ''
The hostname or IP of the cable modem.
'';
};
};
serviceOpts = {
description = "Prometheus exporter for surfboard cable modem";
unitConfig.Documentation = "https://github.com/ipstatic/surfboard_exporter";
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-surfboard-exporter}/bin/surfboard_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--modem-address ${cfg.modemAddress} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,27 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.prometheus.exporters.systemd;
inherit (lib) concatStringsSep;
in
{
port = 9558;
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-systemd-exporter}/bin/systemd_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_UNIX to collect data
"AF_UNIX"
];
};
};
}

View File

@@ -0,0 +1,44 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.exporters.tibber;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9489;
extraOpts = {
apiTokenPath = mkOption {
type = types.path;
default = null;
description = ''
Add here the path to your personal Tibber API Token ('Bearer Token') File.
Get your personal Tibber API Token here: <https://developer.tibber.com>
Do not share your personal plaintext Tibber API Token via github. (see: ryantm/agenix, mic92/sops)
'';
};
};
serviceOpts = {
script = ''
export TIBBER_TOKEN="$(cat ${toString cfg.apiTokenPath})"
exec ${pkgs.prometheus-tibber-exporter}/bin/tibber-exporter --listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " \\\n " cfg.extraFlags}
'';
serviceConfig = {
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
ProtectSystem = "strict";
Restart = "on-failure";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
User = "prometheus"; # context needed to runtime access encrypted token and secrets
};
};
}

View File

@@ -0,0 +1,117 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.unbound;
inherit (lib)
mkOption
types
mkRemovedOptionModule
optionalAttrs
optionalString
mkMerge
mkIf
;
in
{
imports = [
(mkRemovedOptionModule [
"controlInterface"
] "This option was removed, use the `unbound.host` option instead.")
(mkRemovedOptionModule [
"fetchType"
] "This option was removed, use the `unbound.host` option instead.")
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
port = 9167;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
unbound = {
ca = mkOption {
type = types.nullOr types.path;
default = "/var/lib/unbound/unbound_server.pem";
example = null;
description = ''
Path to the Unbound server certificate authority
'';
};
certificate = mkOption {
type = types.nullOr types.path;
default = "/var/lib/unbound/unbound_control.pem";
example = null;
description = ''
Path to the Unbound control socket certificate
'';
};
key = mkOption {
type = types.nullOr types.path;
default = "/var/lib/unbound/unbound_control.key";
example = null;
description = ''
Path to the Unbound control socket key.
'';
};
host = mkOption {
type = types.str;
default = "tcp://127.0.0.1:8953";
example = "unix:///run/unbound/unbound.socket";
description = ''
Path to the unbound control socket. Supports unix domain sockets, as well as the TCP interface.
'';
};
};
};
serviceOpts = mkMerge (
[
{
serviceConfig = {
User = "unbound"; # to access the unbound_control.key
ExecStart = ''
${pkgs.prometheus-unbound-exporter}/bin/unbound_exporter \
--unbound.host "${cfg.unbound.host}" \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
${optionalString (cfg.unbound.ca != null) "--unbound.ca ${cfg.unbound.ca}"} \
${optionalString (cfg.unbound.certificate != null) "--unbound.cert ${cfg.unbound.certificate}"} \
${optionalString (cfg.unbound.key != null) "--unbound.key ${cfg.unbound.key}"} \
${toString cfg.extraFlags}
'';
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
}
// optionalAttrs (!config.services.unbound.enable) {
DynamicUser = true;
};
}
]
++ [
(mkIf config.services.unbound.enable {
after = [ "unbound.service" ];
requires = [ "unbound.service" ];
})
]
);
}

View File

@@ -0,0 +1,45 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.unpoller;
inherit (lib) mkEnableOption generators;
configFile = pkgs.writeText "prometheus-unpoller-exporter.json" (
generators.toJSON { } {
poller = { inherit (cfg.log) debug quiet; };
unifi = { inherit (cfg) controllers; };
influxdb.disable = true;
datadog.disable = true; # workaround for https://github.com/unpoller/unpoller/issues/442
prometheus = {
http_listen = "${cfg.listenAddress}:${toString cfg.port}";
report_errors = cfg.log.prometheusErrors;
};
inherit (cfg) loki;
}
);
in
{
port = 9130;
extraOpts = {
inherit (options.services.unpoller.unifi) controllers;
inherit (options.services.unpoller) loki;
log = {
debug = mkEnableOption "debug logging including line numbers, high resolution timestamps, per-device logs";
quiet = mkEnableOption "startup and error logs only";
prometheusErrors = mkEnableOption "emitting errors to prometheus";
};
};
serviceOpts.serviceConfig = {
ExecStart = "${pkgs.unpoller}/bin/unpoller --config ${configFile}";
DynamicUser = false;
};
}

View File

@@ -0,0 +1,34 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.v2ray;
inherit (lib) mkOption types concatStringsSep;
in
{
port = 9299;
extraOpts = {
v2rayEndpoint = mkOption {
type = types.str;
default = "127.0.0.1:54321";
description = ''
v2ray grpc api endpoint
'';
};
};
serviceOpts = {
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-v2ray-exporter}/bin/v2ray-exporter \
--v2ray-endpoint ${cfg.v2rayEndpoint} \
--listen ${cfg.listenAddress}:${toString cfg.port} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
};
};
}

View File

@@ -0,0 +1,103 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.varnish;
inherit (lib)
mkOption
types
mkDefault
optional
escapeShellArg
concatStringsSep
;
in
{
port = 9131;
extraOpts = {
noExit = mkOption {
type = types.bool;
default = false;
description = ''
Do not exit server on Varnish scrape errors.
'';
};
withGoMetrics = mkOption {
type = types.bool;
default = false;
description = ''
Export go runtime and http handler metrics.
'';
};
verbose = mkOption {
type = types.bool;
default = false;
description = ''
Enable verbose logging.
'';
};
raw = mkOption {
type = types.bool;
default = false;
description = ''
Enable raw stdout logging without timestamps.
'';
};
varnishStatPath = mkOption {
type = types.str;
default = "varnishstat";
description = ''
Path to varnishstat.
'';
};
instance = mkOption {
type = types.nullOr types.str;
default = config.services.varnish.stateDir;
defaultText = lib.literalExpression "config.services.varnish.stateDir";
description = ''
varnishstat -n value.
'';
};
healthPath = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Path under which to expose healthcheck. Disabled unless configured.
'';
};
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
};
serviceOpts = {
path = [ config.services.varnish.package ];
serviceConfig = {
RestartSec = mkDefault 1;
DynamicUser = false;
ExecStart = ''
${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
--varnishstat-path ${escapeShellArg cfg.varnishStatPath} \
${concatStringsSep " \\\n " (
cfg.extraFlags
++ optional (cfg.healthPath != null) "--web.health-path ${cfg.healthPath}"
++ optional (cfg.instance != null) "-n ${escapeShellArg cfg.instance}"
++ optional cfg.noExit "--no-exit"
++ optional cfg.withGoMetrics "--with-go-metrics"
++ optional cfg.verbose "--verbose"
++ optional cfg.raw "--raw"
)}
'';
};
};
}

View File

@@ -0,0 +1,118 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.wireguard;
inherit (lib)
mkOption
types
mkRenamedOptionModule
mkEnableOption
optionalString
escapeShellArg
concatStringsSep
concatMapStringsSep
;
in
{
port = 9586;
imports = [
(mkRenamedOptionModule [ "addr" ] [ "listenAddress" ])
{
options.warnings = options.warnings;
options.assertions = options.assertions;
}
];
extraOpts = {
verbose = mkEnableOption "verbose logging mode for prometheus-wireguard-exporter";
wireguardConfig = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
Path to the Wireguard Config to
[add the peer's name to the stats of a peer](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/2.0.0#usage).
Please note that `networking.wg-quick` is required for this feature
as `networking.wireguard` uses
{manpage}`wg(8)`
to set the peers up.
'';
};
interfaces = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Specifies the interface(s) passed to the wg show <interface> dump parameter.
By default all interfaces are used.
'';
};
singleSubnetPerField = mkOption {
type = types.bool;
default = false;
description = ''
By default, all allowed IPs and subnets are comma-separated in the
`allowed_ips` field. With this option enabled,
a single IP and subnet will be listed in fields like `allowed_ip_0`,
`allowed_ip_1` and so on.
'';
};
withRemoteIp = mkOption {
type = types.bool;
default = false;
description = ''
Whether or not the remote IP of a WireGuard peer should be exposed via prometheus.
'';
};
latestHandshakeDelay = mkOption {
type = types.bool;
default = false;
description = ''
Adds the `wireguard_latest_handshake_delay_seconds` metric that automatically calculates the seconds passed since the last handshake.
'';
};
prependSudo = mkOption {
type = types.bool;
default = false;
description = ''
Whether or no to prepend sudo to wg commands.
'';
};
};
serviceOpts = {
path = [ pkgs.wireguard-tools ];
serviceConfig = {
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
ExecStart = ''
${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
-p ${toString cfg.port} \
-l ${cfg.listenAddress} \
${optionalString cfg.verbose "-v true"} \
${optionalString cfg.singleSubnetPerField "-s true"} \
${optionalString cfg.withRemoteIp "-r true"} \
${optionalString cfg.latestHandshakeDelay "-d true"} \
${optionalString cfg.prependSudo "-a true"} \
${optionalString (cfg.wireguardConfig != null) "-n ${escapeShellArg cfg.wireguardConfig}"} \
${concatMapStringsSep " " (i: "-i ${i}") cfg.interfaces} \
${concatStringsSep " " cfg.extraFlags}
'';
RestrictAddressFamilies = [
# Need AF_NETLINK to collect data
"AF_NETLINK"
];
};
};
}

View File

@@ -0,0 +1,54 @@
{
config,
lib,
pkgs,
options,
...
}:
let
cfg = config.services.prometheus.exporters.zfs;
inherit (lib)
mkOption
types
concatStringsSep
concatMapStringsSep
;
in
{
port = 9134;
extraOpts = {
telemetryPath = mkOption {
type = types.str;
default = "/metrics";
description = ''
Path under which to expose metrics.
'';
};
pools = mkOption {
type = with types; nullOr (listOf str);
default = [ ];
description = ''
Name of the pool(s) to collect, repeat for multiple pools (default: all pools).
'';
};
};
serviceOpts = {
# needs zpool
path = [ config.boot.zfs.package ];
serviceConfig = {
ExecStart = ''
${pkgs.prometheus-zfs-exporter}/bin/zfs_exporter \
--web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
--web.telemetry-path ${cfg.telemetryPath} \
${concatMapStringsSep " " (x: "--pool=${x}") cfg.pools} \
${concatStringsSep " \\\n " cfg.extraFlags}
'';
ProtectClock = false;
PrivateDevices = false;
};
};
}

View File

@@ -0,0 +1,215 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.prometheus.pushgateway;
cmdlineArgs =
opt "web.listen-address" cfg.web.listen-address
++ opt "web.telemetry-path" cfg.web.telemetry-path
++ opt "web.external-url" cfg.web.external-url
++ opt "web.route-prefix" cfg.web.route-prefix
++ lib.optional cfg.persistMetrics ''--persistence.file="/var/lib/${cfg.stateDir}/metrics"''
++ opt "persistence.interval" cfg.persistence.interval
++ opt "log.level" cfg.log.level
++ opt "log.format" cfg.log.format
++ cfg.extraFlags;
opt = k: v: lib.optional (v != null) ''--${k}="${v}"'';
in
{
options = {
services.prometheus.pushgateway = {
enable = lib.mkEnableOption "Prometheus Pushgateway";
package = lib.mkPackageOption pkgs "prometheus-pushgateway" { };
web.listen-address = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Address to listen on for the web interface, API and telemetry.
`null` will default to `:9091`.
'';
};
web.telemetry-path = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Path under which to expose metrics.
`null` will default to `/metrics`.
'';
};
web.external-url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
The URL under which Pushgateway is externally reachable.
'';
};
web.route-prefix = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Prefix for the internal routes of web endpoints.
Defaults to the path of
{option}`services.prometheus.pushgateway.web.external-url`.
'';
};
persistence.interval = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "10m";
description = ''
The minimum interval at which to write out the persistence file.
`null` will default to `5m`.
'';
};
log.level = lib.mkOption {
type = lib.types.nullOr (
lib.types.enum [
"debug"
"info"
"warn"
"error"
"fatal"
]
);
default = null;
description = ''
Only log messages with the given severity or above.
`null` will default to `info`.
'';
};
log.format = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "logger:syslog?appname=bob&local=7";
description = ''
Set the log target and format.
`null` will default to `logger:stderr`.
'';
};
extraFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra commandline options when launching the Pushgateway.
'';
};
persistMetrics = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to persist metrics to a file.
When enabled metrics will be saved to a file called
`metrics` in the directory
`/var/lib/pushgateway`. The directory below
`/var/lib` can be set using
{option}`services.prometheus.pushgateway.stateDir`.
'';
};
stateDir = lib.mkOption {
type = lib.types.str;
default = "pushgateway";
description = ''
Directory below `/var/lib` to store metrics.
This directory will be created automatically using systemd's
StateDirectory mechanism when
{option}`services.prometheus.pushgateway.persistMetrics`
is enabled.
'';
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !lib.hasPrefix "/" cfg.stateDir;
message =
"The option services.prometheus.pushgateway.stateDir"
+ " shouldn't be an absolute directory."
+ " It should be a directory relative to /var/lib.";
}
];
systemd.services.pushgateway = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart =
"${cfg.package}/bin/pushgateway"
+ lib.optionalString (lib.length cmdlineArgs != 0) (
" \\\n " + lib.concatStringsSep " \\\n " cmdlineArgs
);
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
DynamicUser = true;
NoNewPrivileges = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ProtectHome = "tmpfs";
PrivateTmp = true;
PrivateDevices = true;
PrivateIPC = true;
ProcSubset = "pid";
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
Restart = "always";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
StateDirectory = if cfg.persistMetrics then cfg.stateDir else null;
SystemCallFilter = [
"@system-service"
"~@cpu-emulation"
"~@privileged"
"~@reboot"
"~@setuid"
"~@swap"
];
};
};
};
}

View File

@@ -0,0 +1,93 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.prometheus.sachet;
configFile = pkgs.writeText "sachet.yml" (builtins.toJSON cfg.configuration);
in
{
options = {
services.prometheus.sachet = {
enable = lib.mkEnableOption "Sachet, an SMS alerting tool for the Prometheus Alertmanager";
configuration = lib.mkOption {
type = lib.types.nullOr lib.types.attrs;
default = null;
example = lib.literalExpression ''
{
providers = {
twilio = {
# environment variables gets expanded at runtime
account_sid = "$TWILIO_ACCOUNT";
auth_token = "$TWILIO_TOKEN";
};
};
templates = [ ./some-template.tmpl ];
receivers = [{
name = "pager";
provider = "twilio";
to = [ "+33123456789" ];
text = "{{ template \"message\" . }}";
}];
}
'';
description = ''
Sachet's configuration as a nix attribute set.
'';
};
address = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = ''
The address Sachet will listen to.
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 9876;
description = ''
The port Sachet will listen to.
'';
};
};
};
config = lib.mkIf cfg.enable {
assertions = lib.singleton {
assertion = cfg.configuration != null;
message = "Cannot enable Sachet without a configuration.";
};
systemd.services.sachet = {
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"network-online.target"
];
script = ''
${pkgs.envsubst}/bin/envsubst -i "${configFile}" > /tmp/sachet.yaml
exec ${pkgs.prometheus-sachet}/bin/sachet -config /tmp/sachet.yaml -listen-address ${cfg.address}:${builtins.toString cfg.port}
'';
serviceConfig = {
Restart = "always";
ProtectSystem = "strict";
ProtectHome = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
DynamicUser = true;
PrivateTmp = true;
WorkingDirectory = "/tmp/";
};
};
};
}

View File

@@ -0,0 +1,61 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.prometheus.xmpp-alerts;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "prometheus-xmpp-alerts.yml" cfg.settings;
in
{
imports = [
(lib.mkRenamedOptionModule
[ "services" "prometheus" "xmpp-alerts" "configuration" ]
[ "services" "prometheus" "xmpp-alerts" "settings" ]
)
];
options.services.prometheus.xmpp-alerts = {
enable = lib.mkEnableOption "XMPP Web hook service for Alertmanager";
settings = lib.mkOption {
type = settingsFormat.type;
default = { };
description = ''
Configuration for prometheus xmpp-alerts, see
<https://github.com/jelmer/prometheus-xmpp-alerts/blob/master/xmpp-alerts.yml.example>
for supported values.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.prometheus-xmpp-alerts = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
ExecStart = "${pkgs.prometheus-xmpp-alerts}/bin/prometheus-xmpp-alerts --config ${configFile}";
Restart = "on-failure";
DynamicUser = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectHome = true;
ProtectSystem = "strict";
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
NoNewPrivileges = true;
SystemCallArchitectures = "native";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
SystemCallFilter = [ "@system-service" ];
};
};
};
}