1{ config, pkgs, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.services.prometheus.alertmanager;
7 mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
8
9 checkedConfig = file:
10 if cfg.checkConfig then
11 pkgs.runCommand "checked-config" { nativeBuildInputs = [ cfg.package ]; } ''
12 ln -s ${file} $out
13 amtool check-config $out
14 '' else file;
15
16 alertmanagerYml = let
17 yml = if cfg.configText != null then
18 pkgs.writeText "alertmanager.yml" cfg.configText
19 else mkConfigFile;
20 in checkedConfig yml;
21
22 cmdlineArgs = cfg.extraFlags ++ [
23 "--config.file /tmp/alert-manager-substituted.yaml"
24 "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
25 "--log.level ${cfg.logLevel}"
26 "--storage.path /var/lib/alertmanager"
27 (toString (map (peer: "--cluster.peer ${peer}:9094") cfg.clusterPeers))
28 ] ++ (optional (cfg.webExternalUrl != null)
29 "--web.external-url ${cfg.webExternalUrl}"
30 ) ++ (optional (cfg.logFormat != null)
31 "--log.format ${cfg.logFormat}"
32 );
33in {
34 imports = [
35 (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.")
36 (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.")
37 (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] ''
38 Due to incompatibility, the alertmanagerURL option has been removed,
39 please use 'services.prometheus.alertmanagers' instead.
40 '')
41 ];
42
43 options = {
44 services.prometheus.alertmanager = {
45 enable = mkEnableOption "Prometheus Alertmanager";
46
47 package = mkPackageOption pkgs "prometheus-alertmanager" { };
48
49 configuration = mkOption {
50 type = types.nullOr types.attrs;
51 default = null;
52 description = ''
53 Alertmanager configuration as nix attribute set.
54 '';
55 };
56
57 configText = mkOption {
58 type = types.nullOr types.lines;
59 default = null;
60 description = ''
61 Alertmanager configuration as YAML text. If non-null, this option
62 defines the text that is written to alertmanager.yml. If null, the
63 contents of alertmanager.yml is generated from the structured config
64 options.
65 '';
66 };
67
68 checkConfig = mkOption {
69 type = types.bool;
70 default = true;
71 description = ''
72 Check configuration with `amtool check-config`. The call to `amtool` is
73 subject to sandboxing by Nix.
74
75 If you use credentials stored in external files
76 (`environmentFile`, etc),
77 they will not be visible to `amtool`
78 and it will report errors, despite a correct configuration.
79 '';
80 };
81
82 logFormat = mkOption {
83 type = types.nullOr types.str;
84 default = null;
85 description = ''
86 If set use a syslog logger or JSON logging.
87 '';
88 };
89
90 logLevel = mkOption {
91 type = types.enum ["debug" "info" "warn" "error" "fatal"];
92 default = "warn";
93 description = ''
94 Only log messages with the given severity or above.
95 '';
96 };
97
98 webExternalUrl = mkOption {
99 type = types.nullOr types.str;
100 default = null;
101 description = ''
102 The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy).
103 Used for generating relative and absolute links back to Alertmanager itself.
104 If the URL has a path portion, it will be used to prefix all HTTP endoints served by Alertmanager.
105 If omitted, relevant URL components will be derived automatically.
106 '';
107 };
108
109 listenAddress = mkOption {
110 type = types.str;
111 default = "";
112 description = ''
113 Address to listen on for the web interface and API. Empty string will listen on all interfaces.
114 "localhost" will listen on 127.0.0.1 (but not ::1).
115 '';
116 };
117
118 port = mkOption {
119 type = types.port;
120 default = 9093;
121 description = ''
122 Port to listen on for the web interface and API.
123 '';
124 };
125
126 openFirewall = mkOption {
127 type = types.bool;
128 default = false;
129 description = ''
130 Open port in firewall for incoming connections.
131 '';
132 };
133
134 clusterPeers = mkOption {
135 type = types.listOf types.str;
136 default = [];
137 description = ''
138 Initial peers for HA cluster.
139 '';
140 };
141
142 extraFlags = mkOption {
143 type = types.listOf types.str;
144 default = [];
145 description = ''
146 Extra commandline options when launching the Alertmanager.
147 '';
148 };
149
150 environmentFile = mkOption {
151 type = types.nullOr types.path;
152 default = null;
153 example = "/root/alertmanager.env";
154 description = ''
155 File to load as environment file. Environment variables
156 from this file will be interpolated into the config file
157 using envsubst with this syntax:
158 `$ENVIRONMENT ''${VARIABLE}`
159 '';
160 };
161 };
162 };
163
164 config = mkMerge [
165 (mkIf cfg.enable {
166 assertions = singleton {
167 assertion = cfg.configuration != null || cfg.configText != null;
168 message = "Can not enable alertmanager without a configuration. "
169 + "Set either the `configuration` or `configText` attribute.";
170 };
171 })
172 (mkIf cfg.enable {
173 networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
174
175 systemd.services.alertmanager = {
176 wantedBy = [ "multi-user.target" ];
177 wants = [ "network-online.target" ];
178 after = [ "network-online.target" ];
179 preStart = ''
180 ${lib.getBin pkgs.envsubst}/bin/envsubst -o "/tmp/alert-manager-substituted.yaml" \
181 -i "${alertmanagerYml}"
182 '';
183 serviceConfig = {
184 Restart = "always";
185 StateDirectory = "alertmanager";
186 DynamicUser = true; # implies PrivateTmp
187 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
188 WorkingDirectory = "/tmp";
189 ExecStart = "${cfg.package}/bin/alertmanager" +
190 optionalString (length cmdlineArgs != 0) (" \\\n " +
191 concatStringsSep " \\\n " cmdlineArgs);
192 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
193 };
194 };
195 })
196 ];
197}