python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / monitoring / grafana.nix
blob52d5cab9f5159d1db568967dc6ec9ed533d5caad
1 { options, config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.grafana;
7   opt = options.services.grafana;
8   provisioningSettingsFormat = pkgs.formats.yaml {};
9   declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins);
10   useMysql = cfg.settings.database.type == "mysql";
11   usePostgresql = cfg.settings.database.type == "postgres";
13   settingsFormatIni = pkgs.formats.ini {};
14   configFile = settingsFormatIni.generate "config.ini" cfg.settings;
16   datasourceConfiguration = {
17     apiVersion = 1;
18     datasources = cfg.provision.datasources;
19   };
21   datasourceFileNew = if (cfg.provision.datasources.path == null) then provisioningSettingsFormat.generate "datasource.yaml" cfg.provision.datasources.settings else cfg.provision.datasources.path;
22   datasourceFile = if (builtins.isList cfg.provision.datasources) then provisioningSettingsFormat.generate "datasource.yaml" datasourceConfiguration else datasourceFileNew;
24   dashboardConfiguration = {
25     apiVersion = 1;
26     providers = cfg.provision.dashboards;
27   };
29   dashboardFileNew = if (cfg.provision.dashboards.path == null) then provisioningSettingsFormat.generate "dashboard.yaml" cfg.provision.dashboards.settings else cfg.provision.dashboards.path;
30   dashboardFile = if (builtins.isList cfg.provision.dashboards) then provisioningSettingsFormat.generate "dashboard.yaml" dashboardConfiguration else dashboardFileNew;
32   notifierConfiguration = {
33     apiVersion = 1;
34     notifiers = cfg.provision.notifiers;
35   };
37   notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
39   generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null)
40                                         then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings
41                                         else cfg.provision.alerting."${x}".path;
42   rulesFile = generateAlertingProvisioningYaml "rules";
43   contactPointsFile = generateAlertingProvisioningYaml "contactPoints";
44   policiesFile = generateAlertingProvisioningYaml "policies";
45   templatesFile = generateAlertingProvisioningYaml "templates";
46   muteTimingsFile = generateAlertingProvisioningYaml "muteTimings";
48   provisionConfDir =  pkgs.runCommand "grafana-provisioning" { } ''
49     mkdir -p $out/{datasources,dashboards,notifiers,alerting}
50     ln -sf ${datasourceFile} $out/datasources/datasource.yaml
51     ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml
52     ln -sf ${notifierFile} $out/notifiers/notifier.yaml
53     ln -sf ${rulesFile} $out/alerting/rules.yaml
54     ln -sf ${contactPointsFile} $out/alerting/contactPoints.yaml
55     ln -sf ${policiesFile} $out/alerting/policies.yaml
56     ln -sf ${templatesFile} $out/alerting/templates.yaml
57     ln -sf ${muteTimingsFile} $out/alerting/muteTimings.yaml
58   '';
60   # Get a submodule without any embedded metadata:
61   _filter = x: filterAttrs (k: v: k != "_module") x;
63   # http://docs.grafana.org/administration/provisioning/#datasources
64   grafanaTypes.datasourceConfig = types.submodule {
65     freeformType = provisioningSettingsFormat.type;
67     options = {
68       name = mkOption {
69         type = types.str;
70         description = lib.mdDoc "Name of the datasource. Required.";
71       };
72       type = mkOption {
73         type = types.str;
74         description = lib.mdDoc "Datasource type. Required.";
75       };
76       access = mkOption {
77         type = types.enum ["proxy" "direct"];
78         default = "proxy";
79         description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required.";
80       };
81       uid = mkOption {
82         type = types.nullOr types.str;
83         default = null;
84         description = lib.mdDoc "Custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically.";
85       };
86       url = mkOption {
87         type = types.str;
88         default = "localhost";
89         description = lib.mdDoc "Url of the datasource.";
90       };
91       editable = mkOption {
92         type = types.bool;
93         default = false;
94         description = lib.mdDoc "Allow users to edit datasources from the UI.";
95       };
96       password = mkOption {
97         type = types.nullOr types.str;
98         default = null;
99         description = lib.mdDoc ''
100           Database password, if used. Please note that the contents of this option
101           will end up in a world-readable Nix store. Use the file provider
102           pointing at a reasonably secured file in the local filesystem
103           to work around that. Look at the documentation for details:
104           <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
105         '';
106       };
107       basicAuthPassword = mkOption {
108         type = types.nullOr types.str;
109         default = null;
110         description = lib.mdDoc ''
111           Basic auth password. Please note that the contents of this option
112           will end up in a world-readable Nix store. Use the file provider
113           pointing at a reasonably secured file in the local filesystem
114           to work around that. Look at the documentation for details:
115           <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
116         '';
117       };
118       secureJsonData = mkOption {
119         type = types.nullOr types.attrs;
120         default = null;
121         description = lib.mdDoc ''
122           Datasource specific secure configuration. Please note that the contents of this option
123           will end up in a world-readable Nix store. Use the file provider
124           pointing at a reasonably secured file in the local filesystem
125           to work around that. Look at the documentation for details:
126           <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
127         '';
128       };
129     };
130   };
132   # http://docs.grafana.org/administration/provisioning/#dashboards
133   grafanaTypes.dashboardConfig = types.submodule {
134     freeformType = provisioningSettingsFormat.type;
136     options = {
137       name = mkOption {
138         type = types.str;
139         default = "default";
140         description = lib.mdDoc "A unique provider name.";
141       };
142       type = mkOption {
143         type = types.str;
144         default = "file";
145         description = lib.mdDoc "Dashboard provider type.";
146       };
147       options.path = mkOption {
148         type = types.path;
149         description = lib.mdDoc "Path grafana will watch for dashboards. Required when using the 'file' type.";
150       };
151     };
152   };
154   grafanaTypes.notifierConfig = types.submodule {
155     options = {
156       name = mkOption {
157         type = types.str;
158         default = "default";
159         description = lib.mdDoc "Notifier name.";
160       };
161       type = mkOption {
162         type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"];
163         description = lib.mdDoc "Notifier type.";
164       };
165       uid = mkOption {
166         type = types.str;
167         description = lib.mdDoc "Unique notifier identifier.";
168       };
169       org_id = mkOption {
170         type = types.int;
171         default = 1;
172         description = lib.mdDoc "Organization ID.";
173       };
174       org_name = mkOption {
175         type = types.str;
176         default = "Main Org.";
177         description = lib.mdDoc "Organization name.";
178       };
179       is_default = mkOption {
180         type = types.bool;
181         description = lib.mdDoc "Is the default notifier.";
182         default = false;
183       };
184       send_reminder = mkOption {
185         type = types.bool;
186         default = true;
187         description = lib.mdDoc "Should the notifier be sent reminder notifications while alerts continue to fire.";
188       };
189       frequency = mkOption {
190         type = types.str;
191         default = "5m";
192         description = lib.mdDoc "How frequently should the notifier be sent reminders.";
193       };
194       disable_resolve_message = mkOption {
195         type = types.bool;
196         default = false;
197         description = lib.mdDoc "Turn off the message that sends when an alert returns to OK.";
198       };
199       settings = mkOption {
200         type = types.nullOr types.attrs;
201         default = null;
202         description = lib.mdDoc "Settings for the notifier type.";
203       };
204       secure_settings = mkOption {
205         type = types.nullOr types.attrs;
206         default = null;
207         description = lib.mdDoc ''
208           Secure settings for the notifier type. Please note that the contents of this option
209           will end up in a world-readable Nix store. Use the file provider
210           pointing at a reasonably secured file in the local filesystem
211           to work around that. Look at the documentation for details:
212           <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
213         '';
214       };
215     };
216   };
217 in {
218   imports = [
219     (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ])
220     (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ])
221     (mkRenamedOptionModule [ "services" "grafana" "port" ] [ "services" "grafana" "settings" "server" "http_port" ])
222     (mkRenamedOptionModule [ "services" "grafana" "domain" ] [ "services" "grafana" "settings" "server" "domain" ])
223     (mkRenamedOptionModule [ "services" "grafana" "rootUrl" ] [ "services" "grafana" "settings" "server" "root_url" ])
224     (mkRenamedOptionModule [ "services" "grafana" "staticRootPath" ] [ "services" "grafana" "settings" "server" "static_root_path" ])
225     (mkRenamedOptionModule [ "services" "grafana" "certFile" ] [ "services" "grafana" "settings" "server" "cert_file" ])
226     (mkRenamedOptionModule [ "services" "grafana" "certKey" ] [ "services" "grafana" "settings" "server" "cert_key" ])
227     (mkRenamedOptionModule [ "services" "grafana" "socket" ] [ "services" "grafana" "settings" "server" "socket" ])
228     (mkRenamedOptionModule [ "services" "grafana" "database" "type" ] [ "services" "grafana" "settings" "database" "type" ])
229     (mkRenamedOptionModule [ "services" "grafana" "database" "host" ] [ "services" "grafana" "settings" "database" "host" ])
230     (mkRenamedOptionModule [ "services" "grafana" "database" "name" ] [ "services" "grafana" "settings" "database" "name" ])
231     (mkRenamedOptionModule [ "services" "grafana" "database" "user" ] [ "services" "grafana" "settings" "database" "user" ])
232     (mkRenamedOptionModule [ "services" "grafana" "database" "password" ] [ "services" "grafana" "settings" "database" "password" ])
233     (mkRenamedOptionModule [ "services" "grafana" "database" "path" ] [ "services" "grafana" "settings" "database" "path" ])
234     (mkRenamedOptionModule [ "services" "grafana" "database" "connMaxLifetime" ] [ "services" "grafana" "settings" "database" "conn_max_lifetime" ])
235     (mkRenamedOptionModule [ "services" "grafana" "security" "adminUser" ] [ "services" "grafana" "settings" "security" "admin_user" ])
236     (mkRenamedOptionModule [ "services" "grafana" "security" "adminPassword" ] [ "services" "grafana" "settings" "security" "admin_password" ])
237     (mkRenamedOptionModule [ "services" "grafana" "security" "secretKey" ] [ "services" "grafana" "settings" "security" "secret_key" ])
238     (mkRenamedOptionModule [ "services" "grafana" "server" "serveFromSubPath" ] [ "services" "grafana" "settings" "server" "serve_from_sub_path" ])
239     (mkRenamedOptionModule [ "services" "grafana" "smtp" "enable" ] [ "services" "grafana" "settings" "smtp" "enabled" ])
240     (mkRenamedOptionModule [ "services" "grafana" "smtp" "user" ] [ "services" "grafana" "settings" "smtp" "user" ])
241     (mkRenamedOptionModule [ "services" "grafana" "smtp" "password" ] [ "services" "grafana" "settings" "smtp" "password" ])
242     (mkRenamedOptionModule [ "services" "grafana" "smtp" "fromAddress" ] [ "services" "grafana" "settings" "smtp" "from_address" ])
243     (mkRenamedOptionModule [ "services" "grafana" "users" "allowSignUp" ] [ "services" "grafana" "settings" "users" "allow_sign_up" ])
244     (mkRenamedOptionModule [ "services" "grafana" "users" "allowOrgCreate" ] [ "services" "grafana" "settings" "users" "allow_org_create" ])
245     (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrg" ] [ "services" "grafana" "settings" "users" "auto_assign_org" ])
246     (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrgRole" ] [ "services" "grafana" "settings" "users" "auto_assign_org_role" ])
247     (mkRenamedOptionModule [ "services" "grafana" "auth" "disableLoginForm" ] [ "services" "grafana" "settings" "auth" "disable_login_form" ])
248     (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "enable" ] [ "services" "grafana" "settings" "auth.anonymous" "enabled" ])
249     (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_name" ] [ "services" "grafana" "settings" "auth.anonymous" "org_name" ])
250     (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_role" ] [ "services" "grafana" "settings" "auth.anonymous" "org_role" ])
251     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "enable" ] [ "services" "grafana" "settings" "auth.azuread" "enabled" ])
252     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowSignUp" ] [ "services" "grafana" "settings" "auth.azuread" "allow_sign_up" ])
253     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "clientId" ] [ "services" "grafana" "settings" "auth.azuread" "client_id" ])
254     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedDomains" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_domains" ])
255     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedGroups" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_groups" ])
256     (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "enable" ] [ "services" "grafana" "settings" "auth.google" "enabled" ])
257     (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "allowSignUp" ] [ "services" "grafana" "settings" "auth.google" "allow_sign_up" ])
258     (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "clientId" ] [ "services" "grafana" "settings" "auth.google" "client_id" ])
259     (mkRenamedOptionModule [ "services" "grafana" "analytics" "reporting" "enable" ] [ "services" "grafana" "settings" "analytics" "reporting_enabled" ])
261     (mkRemovedOptionModule [ "services" "grafana" "database" "passwordFile" ] ''
262       This option has been removed. Use 'services.grafana.settings.database.password' with file provider instead.
263     '')
264     (mkRemovedOptionModule [ "services" "grafana" "security" "adminPasswordFile" ] ''
265       This option has been removed. Use 'services.grafana.settings.security.admin_password' with file provider instead.
266     '')
267     (mkRemovedOptionModule [ "services" "grafana" "security" "secretKeyFile" ] ''
268       This option has been removed. Use 'services.grafana.settings.security.secret_key' with file provider instead.
269     '')
270     (mkRemovedOptionModule [ "services" "grafana" "smtp" "passwordFile" ] ''
271       This option has been removed. Use 'services.grafana.settings.smtp.password' with file provider instead.
272     '')
273     (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "clientSecretFile" ] ''
274       This option has been removed. Use 'services.grafana.settings.azuread.client_secret' with file provider instead.
275     '')
276     (mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] ''
277       This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead.
278     '')
280     (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
281   ];
283   options.services.grafana = {
284     enable = mkEnableOption (lib.mdDoc "grafana");
286     declarativePlugins = mkOption {
287       type = with types; nullOr (listOf path);
288       default = null;
289       description = lib.mdDoc "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
290       example = literalExpression "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
291       # Make sure each plugin is added only once; otherwise building
292       # the link farm fails, since the same path is added multiple
293       # times.
294       apply = x: if isList x then lib.unique x else x;
295     };
297     package = mkOption {
298       description = lib.mdDoc "Package to use.";
299       default = pkgs.grafana;
300       defaultText = literalExpression "pkgs.grafana";
301       type = types.package;
302     };
304     dataDir = mkOption {
305       description = lib.mdDoc "Data directory.";
306       default = "/var/lib/grafana";
307       type = types.path;
308     };
310     settings = mkOption {
311       description = lib.mdDoc ''
312         Grafana settings. See <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/>
313         for available options. INI format is used.
314       '';
315       type = types.submodule {
316         freeformType = settingsFormatIni.type;
318         options = {
319           paths = {
320             plugins = mkOption {
321               description = lib.mdDoc "Directory where grafana will automatically scan and look for plugins";
322               default = if (cfg.declarativePlugins == null) then "${cfg.dataDir}/plugins" else declarativePlugins;
323               defaultText = literalExpression "if (cfg.declarativePlugins == null) then \"\${cfg.dataDir}/plugins\" else declarativePlugins";
324               type = types.path;
325             };
327             provisioning = mkOption {
328               description = lib.mdDoc ''
329                 Folder that contains provisioning config files that grafana will apply on startup and while running.
330                 Don't change the value of this option if you are planning to use `services.grafana.provision` options.
331               '';
332               default = provisionConfDir;
333               defaultText = literalExpression ''
334                 pkgs.runCommand "grafana-provisioning" { } \'\'
335                   mkdir -p $out/{datasources,dashboards,notifiers,alerting}
336                   ln -sf ''${datasourceFile} $out/datasources/datasource.yaml
337                   ln -sf ''${dashboardFile} $out/dashboards/dashboard.yaml
338                   ln -sf ''${notifierFile} $out/notifiers/notifier.yaml
339                   ln -sf ''${rulesFile} $out/alerting/rules.yaml
340                   ln -sf ''${contactPointsFile} $out/alerting/contactPoints.yaml
341                   ln -sf ''${policiesFile} $out/alerting/policies.yaml
342                   ln -sf ''${templatesFile} $out/alerting/templates.yaml
343                   ln -sf ''${muteTimingsFile} $out/alerting/muteTimings.yaml
344                   \'\'
345               '';
346               type = types.path;
347             };
348           };
350           server = {
351             protocol = mkOption {
352               description = lib.mdDoc "Which protocol to listen.";
353               default = "http";
354               type = types.enum ["http" "https" "h2" "socket"];
355             };
357             http_addr = mkOption {
358               description = lib.mdDoc "Listening address.";
359               default = "";
360               type = types.str;
361             };
363             http_port = mkOption {
364               description = lib.mdDoc "Listening port.";
365               default = 3000;
366               type = types.port;
367             };
369             domain = mkOption {
370               description = lib.mdDoc "The public facing domain name used to access grafana from a browser.";
371               default = "localhost";
372               type = types.str;
373             };
375             root_url = mkOption {
376               description = lib.mdDoc "Full public facing url.";
377               default = "%(protocol)s://%(domain)s:%(http_port)s/";
378               type = types.str;
379             };
381             static_root_path = mkOption {
382               description = lib.mdDoc "Root path for static assets.";
383               default = "${cfg.package}/share/grafana/public";
384               defaultText = literalExpression ''"''${package}/share/grafana/public"'';
385               type = types.str;
386             };
388             enable_gzip = mkOption {
389               description = lib.mdDoc ''
390                 Set this option to true to enable HTTP compression, this can improve transfer speed and bandwidth utilization.
391                 It is recommended that most users set it to true. By default it is set to false for compatibility reasons.
392               '';
393               default = false;
394               type = types.bool;
395             };
397             cert_file = mkOption {
398               description = lib.mdDoc "Cert file for ssl.";
399               default = "";
400               type = types.str;
401             };
403             cert_key = mkOption {
404               description = lib.mdDoc "Cert key for ssl.";
405               default = "";
406               type = types.str;
407             };
409             socket = mkOption {
410               description = lib.mdDoc "Path where the socket should be created when protocol=socket. Make sure that Grafana has appropriate permissions before you change this setting.";
411               default = "/run/grafana/grafana.sock";
412               type = types.str;
413             };
414           };
416           database = {
417             type = mkOption {
418               description = lib.mdDoc "Database type.";
419               default = "sqlite3";
420               type = types.enum ["mysql" "sqlite3" "postgres"];
421             };
423             host = mkOption {
424               description = lib.mdDoc "Database host.";
425               default = "127.0.0.1:3306";
426               type = types.str;
427             };
429             name = mkOption {
430               description = lib.mdDoc "Database name.";
431               default = "grafana";
432               type = types.str;
433             };
435             user = mkOption {
436               description = lib.mdDoc "Database user.";
437               default = "root";
438               type = types.str;
439             };
441             password = mkOption {
442               description = lib.mdDoc ''
443                 Database password. Please note that the contents of this option
444                 will end up in a world-readable Nix store. Use the file provider
445                 pointing at a reasonably secured file in the local filesystem
446                 to work around that. Look at the documentation for details:
447                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
448               '';
449               default = "";
450               type = types.str;
451             };
453             path = mkOption {
454               description = lib.mdDoc "Only applicable to sqlite3 database. The file path where the database will be stored.";
455               default = "${cfg.dataDir}/data/grafana.db";
456               defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
457               type = types.path;
458             };
459           };
461           security = {
462             admin_user = mkOption {
463               description = lib.mdDoc "Default admin username.";
464               default = "admin";
465               type = types.str;
466             };
468             admin_password = mkOption {
469               description = lib.mdDoc ''
470                 Default admin password. Please note that the contents of this option
471                 will end up in a world-readable Nix store. Use the file provider
472                 pointing at a reasonably secured file in the local filesystem
473                 to work around that. Look at the documentation for details:
474                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
475               '';
476               default = "admin";
477               type = types.str;
478             };
480             secret_key = mkOption {
481               description = lib.mdDoc ''
482                 Secret key used for signing. Please note that the contents of this option
483                 will end up in a world-readable Nix store. Use the file provider
484                 pointing at a reasonably secured file in the local filesystem
485                 to work around that. Look at the documentation for details:
486                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
487               '';
488               default = "SW2YcwTIb9zpOOhoPsMm";
489               type = types.str;
490             };
491           };
493           smtp = {
494             enabled = mkOption {
495               description = lib.mdDoc "Whether to enable SMTP.";
496               default = false;
497               type = types.bool;
498             };
499             host = mkOption {
500               description = lib.mdDoc "Host to connect to.";
501               default = "localhost:25";
502               type = types.str;
503             };
504             user = mkOption {
505               description = lib.mdDoc "User used for authentication.";
506               default = "";
507               type = types.str;
508             };
509             password = mkOption {
510               description = lib.mdDoc ''
511                 Password used for authentication. Please note that the contents of this option
512                 will end up in a world-readable Nix store. Use the file provider
513                 pointing at a reasonably secured file in the local filesystem
514                 to work around that. Look at the documentation for details:
515                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
516               '';
517               default = "";
518               type = types.str;
519             };
520             from_address = mkOption {
521               description = lib.mdDoc "Email address used for sending.";
522               default = "admin@grafana.localhost";
523               type = types.str;
524             };
525           };
527           users = {
528             allow_sign_up = mkOption {
529               description = lib.mdDoc "Disable user signup / registration.";
530               default = false;
531               type = types.bool;
532             };
534             allow_org_create = mkOption {
535               description = lib.mdDoc "Whether user is allowed to create organizations.";
536               default = false;
537               type = types.bool;
538             };
540             auto_assign_org = mkOption {
541               description = lib.mdDoc "Whether to automatically assign new users to default org.";
542               default = true;
543               type = types.bool;
544             };
546             auto_assign_org_role = mkOption {
547               description = lib.mdDoc "Default role new users will be auto assigned.";
548               default = "Viewer";
549               type = types.enum ["Viewer" "Editor"];
550             };
551           };
553           analytics.reporting_enabled = mkOption {
554             description = lib.mdDoc "Whether to allow anonymous usage reporting to stats.grafana.net.";
555             default = true;
556             type = types.bool;
557           };
558         };
559       };
560     };
562     provision = {
563       enable = mkEnableOption (lib.mdDoc "provision");
565       datasources = mkOption {
566         description = lib.mdDoc ''
567           Deprecated option for Grafana datasource configuration. Use either
568           `services.grafana.provision.datasources.settings` or
569           `services.grafana.provision.datasources.path` instead.
570         '';
571         default = [];
572         apply = x: if (builtins.isList x) then map _filter x else x;
573         type = with types; either (listOf grafanaTypes.datasourceConfig) (submodule {
574           options.settings = mkOption {
575             description = lib.mdDoc ''
576               Grafana datasource configuration in Nix. Can't be used with
577               `services.grafana.provision.datasources.path` simultaneously. See
578               <https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources>
579               for supported options.
580             '';
581             default = null;
582             type = types.nullOr (types.submodule {
583               options = {
584                 apiVersion = mkOption {
585                   description = lib.mdDoc "Config file version.";
586                   default = 1;
587                   type = types.int;
588                 };
590                 datasources = mkOption {
591                   description = lib.mdDoc "List of datasources to insert/update.";
592                   default = [];
593                   type = types.listOf grafanaTypes.datasourceConfig;
594                 };
596                 deleteDatasources = mkOption {
597                   description = lib.mdDoc "List of datasources that should be deleted from the database.";
598                   default = [];
599                   type = types.listOf (types.submodule {
600                     options.name = mkOption {
601                       description = lib.mdDoc "Name of the datasource to delete.";
602                       type = types.str;
603                     };
605                     options.orgId = mkOption {
606                       description = lib.mdDoc "Organization ID of the datasource to delete.";
607                       type = types.int;
608                     };
609                   });
610                 };
611               };
612             });
613             example = literalExpression ''
614               {
615                 apiVersion = 1;
617                 datasources = [{
618                   name = "Graphite";
619                   type = "graphite";
620                 }];
622                 deleteDatasources = [{
623                   name = "Graphite";
624                   orgId = 1;
625                 }];
626               }
627             '';
628           };
630           options.path = mkOption {
631             description = lib.mdDoc ''
632               Path to YAML datasource configuration. Can't be used with
633               `services.grafana.provision.datasources.settings` simultaneously.
634             '';
635             default = null;
636             type = types.nullOr types.path;
637           };
638         });
639       };
642       dashboards = mkOption {
643         description = lib.mdDoc ''
644           Deprecated option for Grafana dashboard configuration. Use either
645           `services.grafana.provision.dashboards.settings` or
646           `services.grafana.provision.dashboards.path` instead.
647         '';
648         default = [];
649         apply = x: if (builtins.isList x) then map _filter x else x;
650         type = with types; either (listOf grafanaTypes.dashboardConfig) (submodule {
651           options.settings = mkOption {
652             description = lib.mdDoc ''
653               Grafana dashboard configuration in Nix. Can't be used with
654               `services.grafana.provision.dashboards.path` simultaneously. See
655               <https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards>
656               for supported options.
657             '';
658             default = null;
659             type = types.nullOr (types.submodule {
660               options.apiVersion = mkOption {
661                 description = lib.mdDoc "Config file version.";
662                 default = 1;
663                 type = types.int;
664               };
666               options.providers = mkOption {
667                 description = lib.mdDoc "List of dashboards to insert/update.";
668                 default = [];
669                 type = types.listOf grafanaTypes.dashboardConfig;
670               };
671             });
672             example = literalExpression ''
673               {
674                 apiVersion = 1;
676                 providers = [{
677                     name = "default";
678                     options.path = "/var/lib/grafana/dashboards";
679                 }];
680               }
681             '';
682           };
684           options.path = mkOption {
685             description = lib.mdDoc ''
686               Path to YAML dashboard configuration. Can't be used with
687               `services.grafana.provision.dashboards.settings` simultaneously.
688             '';
689             default = null;
690             type = types.nullOr types.path;
691           };
692         });
693       };
696       notifiers = mkOption {
697         description = lib.mdDoc "Grafana notifier configuration.";
698         default = [];
699         type = types.listOf grafanaTypes.notifierConfig;
700         apply = x: map _filter x;
701       };
704       alerting = {
705         rules = {
706           path = mkOption {
707             description = lib.mdDoc ''
708               Path to YAML rules configuration. Can't be used with
709               `services.grafana.provision.alerting.rules.settings` simultaneously.
710             '';
711             default = null;
712             type = types.nullOr types.path;
713           };
715           settings = mkOption {
716             description = lib.mdDoc ''
717               Grafana rules configuration in Nix. Can't be used with
718               `services.grafana.provision.alerting.rules.path` simultaneously. See
719               <https://grafana.com/docs/grafana/latest/administration/provisioning/#rules>
720               for supported options.
721             '';
722             default = null;
723             type = types.nullOr (types.submodule {
724               options = {
725                 apiVersion = mkOption {
726                   description = lib.mdDoc "Config file version.";
727                   default = 1;
728                   type = types.int;
729                 };
731                 groups = mkOption {
732                   description = lib.mdDoc "List of rule groups to import or update.";
733                   default = [];
734                   type = types.listOf (types.submodule {
735                     freeformType = provisioningSettingsFormat.type;
737                     options.name = mkOption {
738                       description = lib.mdDoc "Name of the rule group. Required.";
739                       type = types.str;
740                     };
742                     options.folder = mkOption {
743                       description = lib.mdDoc "Name of the folder the rule group will be stored in. Required.";
744                       type = types.str;
745                     };
747                     options.interval = mkOption {
748                       description = lib.mdDoc "Interval that the rule group should be evaluated at. Required.";
749                       type = types.str;
750                     };
751                   });
752                 };
754                 deleteRules = mkOption {
755                   description = lib.mdDoc "List of alert rule UIDs that should be deleted.";
756                   default = [];
757                   type = types.listOf (types.submodule {
758                     options.orgId = mkOption {
759                       description = lib.mdDoc "Organization ID, default = 1";
760                       default = 1;
761                       type = types.int;
762                     };
764                     options.uid = mkOption {
765                       description = lib.mdDoc "Unique identifier for the rule. Required.";
766                       type = types.str;
767                     };
768                   });
769                 };
770               };
771             });
772             example = literalExpression ''
773               {
774                 apiVersion = 1;
776                 groups = [{
777                   orgId = 1;
778                   name = "my_rule_group";
779                   folder = "my_first_folder";
780                   interval = "60s";
781                   rules = [{
782                     uid = "my_id_1";
783                     title = "my_first_rule";
784                     condition = "A";
785                     data = [{
786                       refId = "A";
787                       datasourceUid = "-100";
788                       model = {
789                         conditions = [{
790                           evaluator = {
791                             params = [ 3 ];
792                             type = "git";
793                           };
794                           operator.type = "and";
795                           query.params = [ "A" ];
796                           reducer.type = "last";
797                           type = "query";
798                         }];
799                         datasource = {
800                           type = "__expr__";
801                           uid = "-100";
802                         };
803                         expression = "1==0";
804                         intervalMs = 1000;
805                         maxDataPoints = 43200;
806                         refId = "A";
807                         type = "math";
808                       };
809                     }];
810                     dashboardUid = "my_dashboard";
811                     panelId = 123;
812                     noDataState = "Alerting";
813                     for = "60s";
814                     annotations.some_key = "some_value";
815                     labels.team = "sre_team1";
816                   }];
817                 }];
819                 deleteRules = [{
820                   orgId = 1;
821                   uid = "my_id_1";
822                 }];
823               }
824             '';
825           };
826         };
828         contactPoints = {
829           path = mkOption {
830             description = lib.mdDoc ''
831               Path to YAML contact points configuration. Can't be used with
832               `services.grafana.provision.alerting.contactPoints.settings` simultaneously.
833             '';
834             default = null;
835             type = types.nullOr types.path;
836           };
838           settings = mkOption {
839             description = lib.mdDoc ''
840               Grafana contact points configuration in Nix. Can't be used with
841               `services.grafana.provision.alerting.contactPoints.path` simultaneously. See
842               <https://grafana.com/docs/grafana/latest/administration/provisioning/#contact-points>
843               for supported options.
844             '';
845             default = null;
846             type = types.nullOr (types.submodule {
847               options = {
848                 apiVersion = mkOption {
849                   description = lib.mdDoc "Config file version.";
850                   default = 1;
851                   type = types.int;
852                 };
854                 contactPoints = mkOption {
855                   description = lib.mdDoc "List of contact points to import or update. Please note that sensitive data will end up in world-readable Nix store.";
856                   default = [];
857                   type = types.listOf (types.submodule {
858                     freeformType = provisioningSettingsFormat.type;
860                     options.name = mkOption {
861                       description = lib.mdDoc "Name of the contact point. Required.";
862                       type = types.str;
863                     };
864                   });
865                 };
867                 deleteContactPoints = mkOption {
868                   description = lib.mdDoc "List of receivers that should be deleted.";
869                   default = [];
870                   type = types.listOf (types.submodule {
871                     options.orgId = mkOption {
872                       description = lib.mdDoc "Organization ID, default = 1.";
873                       default = 1;
874                       type = types.int;
875                     };
877                     options.uid = mkOption {
878                       description = lib.mdDoc "Unique identifier for the receiver. Required.";
879                       type = types.str;
880                     };
881                   });
882                 };
883               };
884             });
885             example = literalExpression ''
886               {
887                 apiVersion = 1;
889                 contactPoints = [{
890                   orgId = 1;
891                   name = "cp_1";
892                   receivers = [{
893                     uid = "first_uid";
894                     type = "prometheus-alertmanager";
895                     settings.url = "http://test:9000";
896                   }];
897                 }];
899                 deleteContactPoints = [{
900                   orgId = 1;
901                   uid = "first_uid";
902                 }];
903               }
904             '';
905           };
906         };
908         policies = {
909           path = mkOption {
910             description = lib.mdDoc ''
911               Path to YAML notification policies configuration. Can't be used with
912               `services.grafana.provision.alerting.policies.settings` simultaneously.
913             '';
914             default = null;
915             type = types.nullOr types.path;
916           };
918           settings = mkOption {
919             description = lib.mdDoc ''
920               Grafana notification policies configuration in Nix. Can't be used with
921               `services.grafana.provision.alerting.policies.path` simultaneously. See
922               <https://grafana.com/docs/grafana/latest/administration/provisioning/#notification-policies>
923               for supported options.
924             '';
925             default = null;
926             type = types.nullOr (types.submodule {
927               options = {
928                 apiVersion = mkOption {
929                   description = lib.mdDoc "Config file version.";
930                   default = 1;
931                   type = types.int;
932                 };
934                 policies = mkOption {
935                   description = lib.mdDoc "List of contact points to import or update.";
936                   default = [];
937                   type = types.listOf (types.submodule {
938                     freeformType = provisioningSettingsFormat.type;
939                   });
940                 };
942                 resetPolicies = mkOption {
943                   description = lib.mdDoc "List of orgIds that should be reset to the default policy.";
944                   default = [];
945                   type = types.listOf types.int;
946                 };
947               };
948             });
949             example = literalExpression ''
950               {
951                 apiVersion = 1;
953                 policies = [{
954                   orgId = 1;
955                   receiver = "grafana-default-email";
956                   group_by = [ "..." ];
957                   matchers = [
958                     "alertname = Watchdog"
959                     "severity =~ \"warning|critical\""
960                   ];
961                   mute_time_intervals = [
962                     "abc"
963                   ];
964                   group_wait = "30s";
965                   group_interval = "5m";
966                   repeat_interval = "4h";
967                 }];
969                 resetPolicies = [
970                   1
971                 ];
972               }
973             '';
974           };
975         };
977         templates = {
978           path = mkOption {
979             description = lib.mdDoc ''
980               Path to YAML templates configuration. Can't be used with
981               `services.grafana.provision.alerting.templates.settings` simultaneously.
982             '';
983             default = null;
984             type = types.nullOr types.path;
985           };
987           settings = mkOption {
988             description = lib.mdDoc ''
989               Grafana templates configuration in Nix. Can't be used with
990               `services.grafana.provision.alerting.templates.path` simultaneously. See
991               <https://grafana.com/docs/grafana/latest/administration/provisioning/#templates>
992               for supported options.
993             '';
994             default = null;
995             type = types.nullOr (types.submodule {
996               options = {
997                 apiVersion = mkOption {
998                   description = lib.mdDoc "Config file version.";
999                   default = 1;
1000                   type = types.int;
1001                 };
1003                 templates = mkOption {
1004                   description = lib.mdDoc "List of templates to import or update.";
1005                   default = [];
1006                   type = types.listOf (types.submodule {
1007                     freeformType = provisioningSettingsFormat.type;
1009                     options.name = mkOption {
1010                       description = lib.mdDoc "Name of the template, must be unique. Required.";
1011                       type = types.str;
1012                     };
1014                     options.template = mkOption {
1015                       description = lib.mdDoc "Alerting with a custom text template";
1016                       type = types.str;
1017                     };
1018                   });
1019                 };
1021                 deleteTemplates = mkOption {
1022                   description = lib.mdDoc "List of alert rule UIDs that should be deleted.";
1023                   default = [];
1024                   type = types.listOf (types.submodule {
1025                     options.orgId = mkOption {
1026                       description = lib.mdDoc "Organization ID, default = 1.";
1027                       default = 1;
1028                       type = types.int;
1029                     };
1031                     options.name = mkOption {
1032                       description = lib.mdDoc "Name of the template, must be unique. Required.";
1033                       type = types.str;
1034                     };
1035                   });
1036                 };
1037               };
1038             });
1039             example = literalExpression ''
1040               {
1041                 apiVersion = 1;
1043                 templates = [{
1044                   orgId = 1;
1045                   name = "my_first_template";
1046                   template = "Alerting with a custom text template";
1047                 }];
1049                 deleteTemplates = [{
1050                   orgId = 1;
1051                   name = "my_first_template";
1052                 }];
1053               }
1054             '';
1055           };
1056         };
1058         muteTimings = {
1059           path = mkOption {
1060             description = lib.mdDoc ''
1061               Path to YAML mute timings configuration. Can't be used with
1062               `services.grafana.provision.alerting.muteTimings.settings` simultaneously.
1063             '';
1064             default = null;
1065             type = types.nullOr types.path;
1066           };
1068           settings = mkOption {
1069             description = lib.mdDoc ''
1070               Grafana mute timings configuration in Nix. Can't be used with
1071               `services.grafana.provision.alerting.muteTimings.path` simultaneously. See
1072               <https://grafana.com/docs/grafana/latest/administration/provisioning/#mute-timings>
1073               for supported options.
1074             '';
1075             default = null;
1076             type = types.nullOr (types.submodule {
1077               options = {
1078                 apiVersion = mkOption {
1079                   description = lib.mdDoc "Config file version.";
1080                   default = 1;
1081                   type = types.int;
1082                 };
1084                 muteTimes = mkOption {
1085                   description = lib.mdDoc "List of mute time intervals to import or update.";
1086                   default = [];
1087                   type = types.listOf (types.submodule {
1088                     freeformType = provisioningSettingsFormat.type;
1090                     options.name = mkOption {
1091                       description = lib.mdDoc "Name of the mute time interval, must be unique. Required.";
1092                       type = types.str;
1093                     };
1094                   });
1095                 };
1097                 deleteMuteTimes = mkOption {
1098                   description = lib.mdDoc "List of mute time intervals that should be deleted.";
1099                   default = [];
1100                   type = types.listOf (types.submodule {
1101                     options.orgId = mkOption {
1102                       description = lib.mdDoc "Organization ID, default = 1.";
1103                       default = 1;
1104                       type = types.int;
1105                     };
1107                     options.name = mkOption {
1108                       description = lib.mdDoc "Name of the mute time interval, must be unique. Required.";
1109                       type = types.str;
1110                     };
1111                   });
1112                 };
1113               };
1114             });
1115             example = literalExpression ''
1116               {
1117                 apiVersion = 1;
1119                 muteTimes = [{
1120                   orgId = 1;
1121                   name = "mti_1";
1122                   time_intervals = [{
1123                     times = [{
1124                       start_time = "06:00";
1125                       end_time = "23:59";
1126                     }];
1127                     weekdays = [
1128                       "monday:wednesday"
1129                       "saturday"
1130                       "sunday"
1131                     ];
1132                     months = [
1133                       "1:3"
1134                       "may:august"
1135                       "december"
1136                     ];
1137                     years = [
1138                       "2020:2022"
1139                       "2030"
1140                     ];
1141                     days_of_month = [
1142                       "1:5"
1143                       "-3:-1"
1144                     ];
1145                   }];
1146                 }];
1148                 deleteMuteTimes = [{
1149                   orgId = 1;
1150                   name = "mti_1";
1151                 }];
1152               }
1153             '';
1154           };
1155         };
1156       };
1157     };
1158   };
1160   config = mkIf cfg.enable {
1161     warnings = let
1162       usesFileProvider = opt: defaultValue: builtins.match "^${defaultValue}$|^\\$__file\\{.*}$" opt != null;
1163     in flatten [
1164       (optional (
1165         ! usesFileProvider cfg.settings.database.password "" ||
1166         ! usesFileProvider cfg.settings.security.admin_password "admin"
1167       ) "Grafana passwords will be stored as plaintext in the Nix store! Use file provider instead.")
1168       (optional (
1169         let
1170           checkOpts = opt: any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) opt;
1171           datasourcesUsed = if (cfg.provision.datasources.settings == null) then [] else cfg.provision.datasources.settings.datasources;
1172         in if (builtins.isList cfg.provision.datasources) then checkOpts cfg.provision.datasources else checkOpts datasourcesUsed
1173         ) ''
1174           Datasource passwords will be stored as plaintext in the Nix store!
1175           It is not possible to use file provider in provisioning; please provision
1176           datasources via `services.grafana.provision.datasources.path` instead.
1177         '')
1178       (optional (
1179         any (x: x.secure_settings != null) cfg.provision.notifiers
1180       ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.")
1181       (optional (
1182         builtins.isList cfg.provision.datasources && cfg.provision.datasources != []
1183       ) ''
1184           Provisioning Grafana datasources with options has been deprecated.
1185           Use `services.grafana.provision.datasources.settings` or
1186           `services.grafana.provision.datasources.path` instead.
1187         '')
1188       (optional (
1189         builtins.isList cfg.provision.datasources && cfg.provision.dashboards != []
1190       ) ''
1191           Provisioning Grafana dashboards with options has been deprecated.
1192           Use `services.grafana.provision.dashboards.settings` or
1193           `services.grafana.provision.dashboards.path` instead.
1194         '')
1195       (optional (
1196         cfg.provision.notifiers != []
1197         ) ''
1198             Notifiers are deprecated upstream and will be removed in Grafana 10.
1199             Use `services.grafana.provision.alerting.contactPoints` instead.
1200         '')
1201     ];
1203     environment.systemPackages = [ cfg.package ];
1205     assertions = [
1206       {
1207         assertion = if (builtins.isList cfg.provision.datasources) then true else cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
1208         message = "Cannot set both datasources settings and datasources path";
1209       }
1210       {
1211         assertion = let
1212           prometheusIsNotDirect = opt: all
1213           ({ type, access, ... }: type == "prometheus" -> access != "direct")
1214           opt;
1215         in
1216           if (builtins.isList cfg.provision.datasources) then prometheusIsNotDirect cfg.provision.datasources
1217           else cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
1218         message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
1219       }
1220       {
1221         assertion = if (builtins.isList cfg.provision.dashboards) then true else cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
1222         message = "Cannot set both dashboards settings and dashboards path";
1223       }
1224       {
1225         assertion = cfg.provision.alerting.rules.settings == null || cfg.provision.alerting.rules.path == null;
1226         message = "Cannot set both rules settings and rules path";
1227       }
1228       {
1229         assertion = cfg.provision.alerting.contactPoints.settings == null || cfg.provision.alerting.contactPoints.path == null;
1230         message = "Cannot set both contact points settings and contact points path";
1231       }
1232       {
1233         assertion = cfg.provision.alerting.policies.settings == null || cfg.provision.alerting.policies.path == null;
1234         message = "Cannot set both policies settings and policies path";
1235       }
1236       {
1237         assertion = cfg.provision.alerting.templates.settings == null || cfg.provision.alerting.templates.path == null;
1238         message = "Cannot set both templates settings and templates path";
1239       }
1240       {
1241         assertion = cfg.provision.alerting.muteTimings.settings == null || cfg.provision.alerting.muteTimings.path == null;
1242         message = "Cannot set both mute timings settings and mute timings path";
1243       }
1244     ];
1246     systemd.services.grafana = {
1247       description = "Grafana Service Daemon";
1248       wantedBy = ["multi-user.target"];
1249       after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
1250       script = ''
1251         set -o errexit -o pipefail -o nounset -o errtrace
1252         shopt -s inherit_errexit
1254         exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} -config ${configFile}
1255       '';
1256       serviceConfig = {
1257         WorkingDirectory = cfg.dataDir;
1258         User = "grafana";
1259         RuntimeDirectory = "grafana";
1260         RuntimeDirectoryMode = "0755";
1261         # Hardening
1262         AmbientCapabilities = lib.mkIf (cfg.settings.server.http_port < 1024) [ "CAP_NET_BIND_SERVICE" ];
1263         CapabilityBoundingSet = if (cfg.settings.server.http_port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
1264         DeviceAllow = [ "" ];
1265         LockPersonality = true;
1266         NoNewPrivileges = true;
1267         PrivateDevices = true;
1268         PrivateTmp = true;
1269         ProtectClock = true;
1270         ProtectControlGroups = true;
1271         ProtectHome = true;
1272         ProtectHostname = true;
1273         ProtectKernelLogs = true;
1274         ProtectKernelModules = true;
1275         ProtectKernelTunables = true;
1276         ProtectProc = "invisible";
1277         ProtectSystem = "full";
1278         RemoveIPC = true;
1279         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
1280         RestrictNamespaces = true;
1281         RestrictRealtime = true;
1282         RestrictSUIDSGID = true;
1283         SystemCallArchitectures = "native";
1284         # Upstream grafana is not setting SystemCallFilter for compatibility
1285         # reasons, see https://github.com/grafana/grafana/pull/40176
1286         SystemCallFilter = [ "@system-service" "~@privileged" ];
1287         UMask = "0027";
1288       };
1289       preStart = ''
1290         ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
1291         ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir}
1292       '';
1293     };
1295     users.users.grafana = {
1296       uid = config.ids.uids.grafana;
1297       description = "Grafana user";
1298       home = cfg.dataDir;
1299       createHome = true;
1300       group = "grafana";
1301     };
1302     users.groups.grafana = {};
1303   };