1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.prometheus.alertmanagerGotify;
9 pkg = cfg.package;
10 inherit (lib)
11 mkEnableOption
12 mkOption
13 types
14 mkIf
15 mkPackageOption
16 optionalString
17 ;
18in
19{
20 meta.maintainers = with lib.maintainers; [ juli0604 ];
21 options.services.prometheus.alertmanagerGotify = {
22 enable = mkEnableOption "alertmagager-gotify";
23 package = mkPackageOption pkgs "alertmanager-gotify-bridge" { };
24 bindAddress = mkOption {
25 type = types.str;
26 default = "0.0.0.0";
27 description = "The address the server will listen on (bind address).";
28 };
29 defaultPriority = mkOption {
30 type = types.int;
31 default = 5;
32 description = "The default priority for messages sent to gotify.";
33 };
34 debug = mkOption {
35 type = types.bool;
36 default = false;
37 description = "Enables extended logs for debugging purposes. Should be disabled in productive mode.";
38 };
39 dispatchErrors = mkOption {
40 type = types.bool;
41 default = false;
42 description = "When enabled, alerts will be tried to dispatch with an error message regarding faulty templating or missing fields to help debugging.";
43 };
44 extendedDetails = mkOption {
45 type = types.bool;
46 default = false;
47 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.";
48 };
49 messageAnnotation = mkOption {
50 type = types.str;
51 description = "Annotation holding the alert message.";
52 };
53 openFirewall = mkOption {
54 type = types.bool;
55 default = false;
56 description = "Opens the bridge port in the firewall.";
57 };
58 port = mkOption {
59 type = types.port;
60 default = 8080;
61 description = "The local port the bridge is listening on.";
62 };
63 priorityAnnotation = mkOption {
64 type = types.str;
65 default = "priority";
66 description = "Annotation holding the priority of the alert.";
67 };
68 timeout = mkOption {
69 type = types.ints.positive;
70 default = 5;
71 description = "The time between sending a message and the timeout.";
72 };
73 titleAnnotation = mkOption {
74 type = types.str;
75 default = "summary";
76 description = "Annotation holding the title of the alert";
77 };
78 webhookPath = mkOption {
79 type = types.str;
80 default = "/gotify_webhook";
81 description = "The URL path to handle requests on.";
82 };
83 environmentFile = mkOption {
84 type = lib.types.nullOr lib.types.path;
85 default = null;
86 description = ''
87 File containing additional config environment variables for alertmanager-gotify-bridge.
88 This is especially for secrets like GOTIFY_TOKEN and AUTH_PASSWORD.
89 '';
90 };
91 gotifyEndpoint = {
92 host = mkOption {
93 type = types.str;
94 default = "127.0.0.1";
95 description = "The hostname or ip your gotify endpoint is running.";
96 };
97 port = mkOption {
98 type = types.port;
99 default = 443;
100 description = "The port your gotify endpoint is running.";
101 };
102 tls = mkOption {
103 type = types.bool;
104 default = true;
105 description = "If your gotify endpoint uses https, leave this option set to default";
106 };
107 };
108 metrics = {
109 username = mkOption {
110 type = types.str;
111 description = "The username used to access your metrics.";
112 };
113 namespace = mkOption {
114 type = types.str;
115 default = "alertmanager-gotify-bridge";
116 description = "The namescape of the metrics.";
117 };
118 path = mkOption {
119 type = types.str;
120 default = "/metrics";
121 description = "The path under which the metrics will be exposed.";
122 };
123 };
124 };
125
126 config = mkIf cfg.enable {
127 users = {
128 groups.alertmanager-gotify = { };
129 users.alertmanager-gotify = {
130 group = "alertmanager-gotify";
131 isSystemUser = true;
132 };
133 };
134
135 networking.firewall = mkIf cfg.openFirewall {
136 allowedTCPPorts = [ cfg.port ];
137 };
138
139 systemd.services.alertmanager-gotify-bridge = {
140 description = "A bridge between Prometheus AlertManager and a Gotify server";
141 wantedBy = [ "multi-user.target" ];
142 serviceConfig = {
143 ExecStart = "${lib.getExe pkg} ${optionalString cfg.debug "--debug"}";
144 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
145 User = "alertmanager-gotify";
146 Group = "alertmanager-gotify";
147
148 #hardening
149 NoNewPrivileges = true;
150 PrivateTmp = true;
151 PrivateDevices = true;
152 PrivateIPC = true;
153 DevicePolicy = "closed";
154 ProtectSystem = "strict";
155 ProtectHome = "read-only";
156 ProtectControlGroups = true;
157 ProtectKernelModules = true;
158 ProtectKernelLogs = true;
159 ProtectKernelTunables = true;
160 ProtectHostname = true;
161 ProtectProc = true;
162 RestrictAddressFamilies = [
163 "AF_INET"
164 "AF_INET6"
165 ];
166 RestrictNamespaces = true;
167 RestrictRealtime = true;
168 RestrictSUIDSGID = true;
169 MemoryDenyWriteExecute = true;
170 LockPersonality = true;
171 ProcSubset = "pid";
172 SystemCallArchitectures = "native";
173 RemoveIPC = true;
174
175 };
176 environment = {
177 BIND_ADDRESS = cfg.bindAddress;
178 DEFAULT_PRIORITY = toString cfg.defaultPriority;
179 DISPATCH_ERRORS = toString cfg.dispatchErrors;
180 EXTENDED_DETAILS = toString cfg.extendedDetails;
181 MESSAGE_ANNOTATION = cfg.messageAnnotation;
182 PORT = toString cfg.port;
183 PRIORITY_ANNOTATION = cfg.priorityAnnotation;
184 TIMEOUT = "${toString cfg.timeout}s";
185 TITLE_ANNOTATION = cfg.titleAnnotation;
186 WEBHOOK_PATH = cfg.webhookPath;
187 GOTIFY_ENDPOINT = "${
188 if cfg.gotifyEndpoint.tls then "https://" else "http://"
189 }${toString cfg.gotifyEndpoint.host}:${toString cfg.gotifyEndpoint.port}/message";
190 AUTH_USERNAME = cfg.metrics.username;
191 };
192 };
193 };
194}