grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / monitoring / grafana.nix
blobeae2658b7ffb806143e787b7cd76894ed511acef
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   # Prefer using the values from the default config file[0] directly. This way,
14   # people reading the NixOS manual can see them without cross-referencing the
15   # official documentation.
16   #
17   # However, if there is no default entry or if the setting is optional, use
18   # `null` as the default value. It will be turned into the empty string.
19   #
20   # If a setting is a list, always allow setting it as a plain string as well.
21   #
22   # [0]: https://github.com/grafana/grafana/blob/main/conf/defaults.ini
23   settingsFormatIni = pkgs.formats.ini {
24     listToValue = concatMapStringsSep " " (generators.mkValueStringDefault { });
25     mkKeyValue = generators.mkKeyValueDefault
26       {
27         mkValueString = v:
28           if v == null then ""
29           else generators.mkValueStringDefault { } v;
30       }
31       "=";
32   };
33   configFile = settingsFormatIni.generate "config.ini" cfg.settings;
35   mkProvisionCfg = name: attr: provisionCfg:
36     if provisionCfg.path != null
37     then provisionCfg.path
38     else
39       provisioningSettingsFormat.generate "${name}.yaml"
40         (if provisionCfg.settings != null
41         then provisionCfg.settings
42         else {
43           apiVersion = 1;
44           ${attr} = [ ];
45         });
47   datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources;
48   dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards;
50   generateAlertingProvisioningYaml = x:
51     if (cfg.provision.alerting."${x}".path == null)
52     then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings
53     else cfg.provision.alerting."${x}".path;
54   rulesFileOrDir = generateAlertingProvisioningYaml "rules";
55   contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints";
56   policiesFileOrDir = generateAlertingProvisioningYaml "policies";
57   templatesFileOrDir = generateAlertingProvisioningYaml "templates";
58   muteTimingsFileOrDir = generateAlertingProvisioningYaml "muteTimings";
60   ln = { src, dir, filename }: ''
61     if [[ -d "${src}" ]]; then
62       pushd $out/${dir} &>/dev/null
63         lndir "${src}"
64       popd &>/dev/null
65     else
66       ln -sf ${src} $out/${dir}/${filename}.yaml
67     fi
68   '';
69   provisionConfDir = pkgs.runCommand "grafana-provisioning" { nativeBuildInputs = [ pkgs.xorg.lndir ]; } ''
70     mkdir -p $out/{alerting,datasources,dashboards,plugins}
71     ${ln { src = datasourceFileOrDir;    dir = "datasources"; filename = "datasource"; }}
72     ${ln { src = dashboardFileOrDir;     dir = "dashboards";  filename = "dashboard"; }}
73     ${ln { src = rulesFileOrDir;         dir = "alerting";    filename = "rules"; }}
74     ${ln { src = contactPointsFileOrDir; dir = "alerting";    filename = "contactPoints"; }}
75     ${ln { src = policiesFileOrDir;      dir = "alerting";    filename = "policies"; }}
76     ${ln { src = templatesFileOrDir;     dir = "alerting";    filename = "templates"; }}
77     ${ln { src = muteTimingsFileOrDir;   dir = "alerting";    filename = "muteTimings"; }}
78   '';
80   # Get a submodule without any embedded metadata:
81   _filter = x: filterAttrs (k: v: k != "_module") x;
83   # https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources
84   grafanaTypes.datasourceConfig = types.submodule {
85     freeformType = provisioningSettingsFormat.type;
87     options = {
88       name = mkOption {
89         type = types.str;
90         description = "Name of the datasource. Required.";
91       };
92       type = mkOption {
93         type = types.str;
94         description = "Datasource type. Required.";
95       };
96       access = mkOption {
97         type = types.enum [ "proxy" "direct" ];
98         default = "proxy";
99         description = "Access mode. proxy or direct (Server or Browser in the UI). Required.";
100       };
101       uid = mkOption {
102         type = types.nullOr types.str;
103         default = null;
104         description = "Custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically.";
105       };
106       url = mkOption {
107         type = types.str;
108         default = "";
109         description = "Url of the datasource.";
110       };
111       editable = mkOption {
112         type = types.bool;
113         default = false;
114         description = "Allow users to edit datasources from the UI.";
115       };
116       jsonData = mkOption {
117         type = types.nullOr types.attrs;
118         default = null;
119         description = "Extra data for datasource plugins.";
120       };
121       secureJsonData = mkOption {
122         type = types.nullOr types.attrs;
123         default = null;
124         description = ''
125           Datasource specific secure configuration. Please note that the contents of this option
126           will end up in a world-readable Nix store. Use the file provider
127           pointing at a reasonably secured file in the local filesystem
128           to work around that. Look at the documentation for details:
129           <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
130         '';
131       };
132     };
133   };
135   # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
136   grafanaTypes.dashboardConfig = types.submodule {
137     freeformType = provisioningSettingsFormat.type;
139     options = {
140       name = mkOption {
141         type = types.str;
142         default = "default";
143         description = "A unique provider name.";
144       };
145       type = mkOption {
146         type = types.str;
147         default = "file";
148         description = "Dashboard provider type.";
149       };
150       options.path = mkOption {
151         type = types.path;
152         description = "Path grafana will watch for dashboards. Required when using the 'file' type.";
153       };
154     };
155   };
158   imports = [
159     (mkRemovedOptionModule [ "services" "grafana" "provision" "notifiers" ] ''
160       Notifiers (services.grafana.provision.notifiers) were removed in Grafana 11.
161     '')
163     (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ])
164     (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ])
165     (mkRenamedOptionModule [ "services" "grafana" "port" ] [ "services" "grafana" "settings" "server" "http_port" ])
166     (mkRenamedOptionModule [ "services" "grafana" "domain" ] [ "services" "grafana" "settings" "server" "domain" ])
167     (mkRenamedOptionModule [ "services" "grafana" "rootUrl" ] [ "services" "grafana" "settings" "server" "root_url" ])
168     (mkRenamedOptionModule [ "services" "grafana" "staticRootPath" ] [ "services" "grafana" "settings" "server" "static_root_path" ])
169     (mkRenamedOptionModule [ "services" "grafana" "certFile" ] [ "services" "grafana" "settings" "server" "cert_file" ])
170     (mkRenamedOptionModule [ "services" "grafana" "certKey" ] [ "services" "grafana" "settings" "server" "cert_key" ])
171     (mkRenamedOptionModule [ "services" "grafana" "socket" ] [ "services" "grafana" "settings" "server" "socket" ])
172     (mkRenamedOptionModule [ "services" "grafana" "database" "type" ] [ "services" "grafana" "settings" "database" "type" ])
173     (mkRenamedOptionModule [ "services" "grafana" "database" "host" ] [ "services" "grafana" "settings" "database" "host" ])
174     (mkRenamedOptionModule [ "services" "grafana" "database" "name" ] [ "services" "grafana" "settings" "database" "name" ])
175     (mkRenamedOptionModule [ "services" "grafana" "database" "user" ] [ "services" "grafana" "settings" "database" "user" ])
176     (mkRenamedOptionModule [ "services" "grafana" "database" "password" ] [ "services" "grafana" "settings" "database" "password" ])
177     (mkRenamedOptionModule [ "services" "grafana" "database" "path" ] [ "services" "grafana" "settings" "database" "path" ])
178     (mkRenamedOptionModule [ "services" "grafana" "database" "connMaxLifetime" ] [ "services" "grafana" "settings" "database" "conn_max_lifetime" ])
179     (mkRenamedOptionModule [ "services" "grafana" "security" "adminUser" ] [ "services" "grafana" "settings" "security" "admin_user" ])
180     (mkRenamedOptionModule [ "services" "grafana" "security" "adminPassword" ] [ "services" "grafana" "settings" "security" "admin_password" ])
181     (mkRenamedOptionModule [ "services" "grafana" "security" "secretKey" ] [ "services" "grafana" "settings" "security" "secret_key" ])
182     (mkRenamedOptionModule [ "services" "grafana" "server" "serveFromSubPath" ] [ "services" "grafana" "settings" "server" "serve_from_sub_path" ])
183     (mkRenamedOptionModule [ "services" "grafana" "smtp" "enable" ] [ "services" "grafana" "settings" "smtp" "enabled" ])
184     (mkRenamedOptionModule [ "services" "grafana" "smtp" "user" ] [ "services" "grafana" "settings" "smtp" "user" ])
185     (mkRenamedOptionModule [ "services" "grafana" "smtp" "password" ] [ "services" "grafana" "settings" "smtp" "password" ])
186     (mkRenamedOptionModule [ "services" "grafana" "smtp" "fromAddress" ] [ "services" "grafana" "settings" "smtp" "from_address" ])
187     (mkRenamedOptionModule [ "services" "grafana" "users" "allowSignUp" ] [ "services" "grafana" "settings" "users" "allow_sign_up" ])
188     (mkRenamedOptionModule [ "services" "grafana" "users" "allowOrgCreate" ] [ "services" "grafana" "settings" "users" "allow_org_create" ])
189     (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrg" ] [ "services" "grafana" "settings" "users" "auto_assign_org" ])
190     (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrgRole" ] [ "services" "grafana" "settings" "users" "auto_assign_org_role" ])
191     (mkRenamedOptionModule [ "services" "grafana" "auth" "disableLoginForm" ] [ "services" "grafana" "settings" "auth" "disable_login_form" ])
192     (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "enable" ] [ "services" "grafana" "settings" "auth.anonymous" "enabled" ])
193     (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_name" ] [ "services" "grafana" "settings" "auth.anonymous" "org_name" ])
194     (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_role" ] [ "services" "grafana" "settings" "auth.anonymous" "org_role" ])
195     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "enable" ] [ "services" "grafana" "settings" "auth.azuread" "enabled" ])
196     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowSignUp" ] [ "services" "grafana" "settings" "auth.azuread" "allow_sign_up" ])
197     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "clientId" ] [ "services" "grafana" "settings" "auth.azuread" "client_id" ])
198     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedDomains" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_domains" ])
199     (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedGroups" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_groups" ])
200     (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "enable" ] [ "services" "grafana" "settings" "auth.google" "enabled" ])
201     (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "allowSignUp" ] [ "services" "grafana" "settings" "auth.google" "allow_sign_up" ])
202     (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "clientId" ] [ "services" "grafana" "settings" "auth.google" "client_id" ])
203     (mkRenamedOptionModule [ "services" "grafana" "analytics" "reporting" "enable" ] [ "services" "grafana" "settings" "analytics" "reporting_enabled" ])
205     (mkRemovedOptionModule [ "services" "grafana" "database" "passwordFile" ] ''
206       This option has been removed. Use 'services.grafana.settings.database.password' with file provider instead.
207     '')
208     (mkRemovedOptionModule [ "services" "grafana" "security" "adminPasswordFile" ] ''
209       This option has been removed. Use 'services.grafana.settings.security.admin_password' with file provider instead.
210     '')
211     (mkRemovedOptionModule [ "services" "grafana" "security" "secretKeyFile" ] ''
212       This option has been removed. Use 'services.grafana.settings.security.secret_key' with file provider instead.
213     '')
214     (mkRemovedOptionModule [ "services" "grafana" "smtp" "passwordFile" ] ''
215       This option has been removed. Use 'services.grafana.settings.smtp.password' with file provider instead.
216     '')
217     (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "clientSecretFile" ] ''
218       This option has been removed. Use 'services.grafana.settings.azuread.client_secret' with file provider instead.
219     '')
220     (mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] ''
221       This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead.
222     '')
223     (mkRemovedOptionModule [ "services" "grafana" "extraOptions" ] ''
224       This option has been removed. Use 'services.grafana.settings' instead. For a detailed migration guide, please
225       review the release notes of NixOS 22.11.
226     '')
228     (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
229   ];
231   options.services.grafana = {
232     enable = mkEnableOption "grafana";
234     declarativePlugins = mkOption {
235       type = with types; nullOr (listOf path);
236       default = null;
237       description = "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
238       example = literalExpression "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
239       # Make sure each plugin is added only once; otherwise building
240       # the link farm fails, since the same path is added multiple
241       # times.
242       apply = x: if isList x then lib.unique x else x;
243     };
245     package = mkPackageOption pkgs "grafana" { };
247     dataDir = mkOption {
248       description = "Data directory.";
249       default = "/var/lib/grafana";
250       type = types.path;
251     };
253     settings = mkOption {
254       description = ''
255         Grafana settings. See <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/>
256         for available options. INI format is used.
257       '';
258       type = types.submodule {
259         freeformType = settingsFormatIni.type;
261         options = {
262           paths = {
263             plugins = mkOption {
264               description = "Directory where grafana will automatically scan and look for plugins";
265               default = if (cfg.declarativePlugins == null) then "${cfg.dataDir}/plugins" else declarativePlugins;
266               defaultText = literalExpression "if (cfg.declarativePlugins == null) then \"\${cfg.dataDir}/plugins\" else declarativePlugins";
267               type = types.path;
268             };
270             provisioning = mkOption {
271               description = ''
272                 Folder that contains provisioning config files that grafana will apply on startup and while running.
273                 Don't change the value of this option if you are planning to use `services.grafana.provision` options.
274               '';
275               default = provisionConfDir;
276               defaultText = "directory with links to files generated from services.grafana.provision";
277               type = types.path;
278             };
279           };
281           server = {
282             protocol = mkOption {
283               description = "Which protocol to listen.";
284               default = "http";
285               type = types.enum [ "http" "https" "h2" "socket" ];
286             };
288             http_addr = mkOption {
289               type = types.str;
290               default = "127.0.0.1";
291               description = ''
292                 Listening address.
294                 ::: {.note}
295                 This setting intentionally varies from upstream's default to be a bit more secure by default.
296                 :::
297               '';
298             };
300             http_port = mkOption {
301               description = "Listening port.";
302               default = 3000;
303               type = types.port;
304             };
306             domain = mkOption {
307               description = ''
308                 The public facing domain name used to access grafana from a browser.
310                 This setting is only used in the default value of the `root_url` setting.
311                 If you set the latter manually, this option does not have to be specified.
312               '';
313               default = "localhost";
314               type = types.str;
315             };
317             enforce_domain = mkOption {
318               description = ''
319                 Redirect to correct domain if the host header does not match the domain.
320                 Prevents DNS rebinding attacks.
321               '';
322               default = false;
323               type = types.bool;
324             };
326             root_url = mkOption {
327               description = ''
328                 This is the full URL used to access Grafana from a web browser.
329                 This is important if you use Google or GitHub OAuth authentication (for the callback URL to be correct).
331                 This setting is also important if you have a reverse proxy in front of Grafana that exposes it through a subpath.
332                 In that case add the subpath to the end of this URL setting.
333               '';
334               default = "%(protocol)s://%(domain)s:%(http_port)s/";
335               type = types.str;
336             };
338             serve_from_sub_path = mkOption {
339               description = ''
340                 Serve Grafana from subpath specified in the `root_url` setting.
341                 By default it is set to `false` for compatibility reasons.
343                 By enabling this setting and using a subpath in `root_url` above,
344                 e.g. `root_url = "http://localhost:3000/grafana"`,
345                 Grafana is accessible on `http://localhost:3000/grafana`.
346                 If accessed without subpath, Grafana will redirect to an URL with the subpath.
347               '';
348               default = false;
349               type = types.bool;
350             };
352             router_logging = mkOption {
353               description = ''
354                 Set to `true` for Grafana to log all HTTP requests (not just errors).
355                 These are logged as Info level events to the Grafana log.
356               '';
357               default = false;
358               type = types.bool;
359             };
361             static_root_path = mkOption {
362               description = "Root path for static assets.";
363               default = "${cfg.package}/share/grafana/public";
364               defaultText = literalExpression ''"''${package}/share/grafana/public"'';
365               type = types.str;
366             };
368             enable_gzip = mkOption {
369               description = ''
370                 Set this option to `true` to enable HTTP compression, this can improve transfer speed and bandwidth utilization.
371                 It is recommended that most users set it to `true`. By default it is set to `false` for compatibility reasons.
372               '';
373               default = false;
374               type = types.bool;
375             };
377             cert_file = mkOption {
378               description = ''
379                 Path to the certificate file (if `protocol` is set to `https` or `h2`).
380               '';
381               default = null;
382               type = types.nullOr types.str;
383             };
385             cert_key = mkOption {
386               description = ''
387                 Path to the certificate key file (if `protocol` is set to `https` or `h2`).
388               '';
389               default = null;
390               type = types.nullOr types.str;
391             };
393             socket_gid = mkOption {
394               description = ''
395                 GID where the socket should be set when `protocol=socket`.
396                 Make sure that the target group is in the group of Grafana process and that Grafana process is the file owner before you change this setting.
397                 It is recommended to set the gid as http server user gid.
398                 Not set when the value is -1.
399               '';
400               default = -1;
401               type = types.int;
402             };
404             socket_mode = mkOption {
405               description = ''
406                 Mode where the socket should be set when `protocol=socket`.
407                 Make sure that Grafana process is the file owner before you change this setting.
408               '';
409               # I assume this value is interpreted as octal literal by grafana.
410               # If this was an int, people following tutorials or porting their
411               # old config could stumble across nix not having octal literals.
412               default = "0660";
413               type = types.str;
414             };
416             socket = mkOption {
417               description = ''
418                 Path where the socket should be created when `protocol=socket`.
419                 Make sure that Grafana has appropriate permissions before you change this setting.
420               '';
421               default = "/run/grafana/grafana.sock";
422               type = types.str;
423             };
425             cdn_url = mkOption {
426               description = ''
427                 Specify a full HTTP URL address to the root of your Grafana CDN assets.
428                 Grafana will add edition and version paths.
430                 For example, given a cdn url like `https://cdn.myserver.com`
431                 grafana will try to load a javascript file from `http://cdn.myserver.com/grafana-oss/7.4.0/public/build/app.<hash>.js`.
432               '';
433               default = null;
434               type = types.nullOr types.str;
435             };
437             read_timeout = mkOption {
438               description = ''
439                 Sets the maximum time using a duration format (5s/5m/5ms)
440                 before timing out read of an incoming request and closing idle connections.
441                 0 means there is no timeout for reading the request.
442               '';
443               default = "0";
444               type = types.str;
445             };
446           };
448           database = {
449             type = mkOption {
450               description = "Database type.";
451               default = "sqlite3";
452               type = types.enum [ "mysql" "sqlite3" "postgres" ];
453             };
455             host = mkOption {
456               description = ''
457                 Only applicable to MySQL or Postgres.
458                 Includes IP or hostname and port or in case of Unix sockets the path to it.
459                 For example, for MySQL running on the same host as Grafana: `host = "127.0.0.1:3306"`
460                 or with Unix sockets: `host = "/var/run/mysqld/mysqld.sock"`
461               '';
462               default = "127.0.0.1:3306";
463               type = types.str;
464             };
466             name = mkOption {
467               description = "The name of the Grafana database.";
468               default = "grafana";
469               type = types.str;
470             };
472             user = mkOption {
473               description = "The database user (not applicable for `sqlite3`).";
474               default = "root";
475               type = types.str;
476             };
478             password = mkOption {
479               description = ''
480                 The database user's password (not applicable for `sqlite3`).
482                 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 = "";
489               type = types.str;
490             };
492             max_idle_conn = mkOption {
493               description = "The maximum number of connections in the idle connection pool.";
494               default = 2;
495               type = types.int;
496             };
498             max_open_conn = mkOption {
499               description = "The maximum number of open connections to the database.";
500               default = 0;
501               type = types.int;
502             };
504             conn_max_lifetime = mkOption {
505               description = ''
506                 Sets the maximum amount of time a connection may be reused.
507                 The default is 14400 (which means 14400 seconds or 4 hours).
508                 For MySQL, this setting should be shorter than the `wait_timeout` variable.
509               '';
510               default = 14400;
511               type = types.int;
512             };
514             locking_attempt_timeout_sec = mkOption {
515               description = ''
516                 For `mysql`, if the `migrationLocking` feature toggle is set,
517                 specify the time (in seconds) to wait before failing to lock the database for the migrations.
518               '';
519               default = 0;
520               type = types.int;
521             };
523             log_queries = mkOption {
524               description = "Set to `true` to log the sql calls and execution times";
525               default = false;
526               type = types.bool;
527             };
529             ssl_mode = mkOption {
530               description = ''
531                 For Postgres, use either `disable`, `require` or `verify-full`.
532                 For MySQL, use either `true`, `false`, or `skip-verify`.
533               '';
534               default = "disable";
535               type = types.enum [ "disable" "require" "verify-full" "true" "false" "skip-verify" ];
536             };
538             isolation_level = mkOption {
539               description = ''
540                 Only the MySQL driver supports isolation levels in Grafana.
541                 In case the value is empty, the driver's default isolation level is applied.
542               '';
543               default = null;
544               type = types.nullOr (types.enum [ "READ-UNCOMMITTED" "READ-COMMITTED" "REPEATABLE-READ" "SERIALIZABLE" ]);
545             };
547             ca_cert_path = mkOption {
548               description = "The path to the CA certificate to use.";
549               default = null;
550               type = types.nullOr types.str;
551             };
553             client_key_path = mkOption {
554               description = "The path to the client key. Only if server requires client authentication.";
555               default = null;
556               type = types.nullOr types.str;
557             };
559             client_cert_path = mkOption {
560               description = "The path to the client cert. Only if server requires client authentication.";
561               default = null;
562               type = types.nullOr types.str;
563             };
565             server_cert_name = mkOption {
566               description = ''
567                 The common name field of the certificate used by the `mysql` or `postgres` server.
568                 Not necessary if `ssl_mode` is set to `skip-verify`.
569               '';
570               default = null;
571               type = types.nullOr types.str;
572             };
574             path = mkOption {
575               description = "Only applicable to `sqlite3` database. The file path where the database will be stored.";
576               default = "${cfg.dataDir}/data/grafana.db";
577               defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
578               type = types.path;
579             };
581             cache_mode = mkOption {
582               description = ''
583                 For `sqlite3` only.
584                 [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database.
585               '';
586               default = "private";
587               type = types.enum [ "private" "shared" ];
588             };
590             wal = mkOption {
591               description = ''
592                 For `sqlite3` only.
593                 Setting to enable/disable [Write-Ahead Logging](https://sqlite.org/wal.html).
594               '';
595               default = false;
596               type = types.bool;
597             };
599             query_retries = mkOption {
600               description = ''
601                 This setting applies to `sqlite3` only and controls the number of times the system retries a query when the database is locked.
602               '';
603               default = 0;
604               type = types.int;
605             };
607             transaction_retries = mkOption {
608               description = ''
609                 This setting applies to `sqlite3` only and controls the number of times the system retries a transaction when the database is locked.
610               '';
611               default = 5;
612               type = types.int;
613             };
615             # TODO Add "instrument_queries" option when upgrading to grafana 10.0
616             # instrument_queries = mkOption {
617             #   description = "Set to `true` to add metrics and tracing for database queries.";
618             #   default = false;
619             #   type = types.bool;
620             # };
621           };
623           security = {
624             disable_initial_admin_creation = mkOption {
625               description = "Disable creation of admin user on first start of Grafana.";
626               default = false;
627               type = types.bool;
628             };
630             admin_user = mkOption {
631               description = "Default admin username.";
632               default = "admin";
633               type = types.str;
634             };
636             admin_password = mkOption {
637               description = ''
638                 Default admin password. Please note that the contents of this option
639                 will end up in a world-readable Nix store. Use the file provider
640                 pointing at a reasonably secured file in the local filesystem
641                 to work around that. Look at the documentation for details:
642                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
643               '';
644               default = "admin";
645               type = types.str;
646             };
648             admin_email = mkOption {
649               description = "The email of the default Grafana Admin, created on startup.";
650               default = "admin@localhost";
651               type = types.str;
652             };
654             secret_key = mkOption {
655               description = ''
656                 Secret key used for signing. Please note that the contents of this option
657                 will end up in a world-readable Nix store. Use the file provider
658                 pointing at a reasonably secured file in the local filesystem
659                 to work around that. Look at the documentation for details:
660                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
661               '';
662               default = "SW2YcwTIb9zpOOhoPsMm";
663               type = types.str;
664             };
666             disable_gravatar = mkOption {
667               description = "Set to `true` to disable the use of Gravatar for user profile images.";
668               default = false;
669               type = types.bool;
670             };
672             data_source_proxy_whitelist = mkOption {
673               description = ''
674                 Define a whitelist of allowed IP addresses or domains, with ports,
675                 to be used in data source URLs with the Grafana data source proxy.
676                 Format: `ip_or_domain:port` separated by spaces.
677                 PostgreSQL, MySQL, and MSSQL data sources do not use the proxy and are therefore unaffected by this setting.
678               '';
679               default = [ ];
680               type = types.oneOf [ types.str (types.listOf types.str) ];
681             };
683             disable_brute_force_login_protection = mkOption {
684               description = "Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout).";
685               default = false;
686               type = types.bool;
687             };
689             cookie_secure = mkOption {
690               description = "Set to `true` if you host Grafana behind HTTPS.";
691               default = false;
692               type = types.bool;
693             };
695             cookie_samesite = mkOption {
696               description = ''
697                 Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests.
698                 The main goal is to mitigate the risk of cross-origin information leakage.
699                 This setting also provides some protection against cross-site request forgery attacks (CSRF),
700                 [read more about SameSite here](https://owasp.org/www-community/SameSite).
701                 Using value `disabled` does not add any `SameSite` attribute to cookies.
702               '';
703               default = "lax";
704               type = types.enum [ "lax" "strict" "none" "disabled" ];
705             };
707             allow_embedding = mkOption {
708               description = ''
709                 When `false`, the HTTP header `X-Frame-Options: deny` will be set in Grafana HTTP responses
710                 which will instruct browsers to not allow rendering Grafana in a `<frame>`, `<iframe>`, `<embed>` or `<object>`.
711                 The main goal is to mitigate the risk of [Clickjacking](https://owasp.org/www-community/attacks/Clickjacking).
712               '';
713               default = false;
714               type = types.bool;
715             };
717             strict_transport_security = mkOption {
718               description = ''
719                 Set to `true` if you want to enable HTTP `Strict-Transport-Security` (HSTS) response header.
720                 Only use this when HTTPS is enabled in your configuration,
721                 or when there is another upstream system that ensures your application does HTTPS (like a frontend load balancer).
722                 HSTS tells browsers that the site should only be accessed using HTTPS.
723               '';
724               default = false;
725               type = types.bool;
726             };
728             strict_transport_security_max_age_seconds = mkOption {
729               description = ''
730                 Sets how long a browser should cache HSTS in seconds.
731                 Only applied if `strict_transport_security` is enabled.
732               '';
733               default = 86400;
734               type = types.int;
735             };
737             strict_transport_security_preload = mkOption {
738               description = ''
739                 Set to `true` to enable HSTS `preloading` option.
740                 Only applied if `strict_transport_security` is enabled.
741               '';
742               default = false;
743               type = types.bool;
744             };
746             strict_transport_security_subdomains = mkOption {
747               description = ''
748                 Set to `true` to enable HSTS `includeSubDomains` option.
749                 Only applied if `strict_transport_security` is enabled.
750               '';
751               default = false;
752               type = types.bool;
753             };
755             x_content_type_options = mkOption {
756               description = ''
757                 Set to `false` to disable the `X-Content-Type-Options` response header.
758                 The `X-Content-Type-Options` response HTTP header is a marker used by the server
759                 to indicate that the MIME types advertised in the `Content-Type` headers should not be changed and be followed.
760               '';
761               default = true;
762               type = types.bool;
763             };
765             x_xss_protection = mkOption {
766               description = ''
767                 Set to `false` to disable the `X-XSS-Protection` header,
768                 which tells browsers to stop pages from loading when they detect reflected cross-site scripting (XSS) attacks.
769               '';
770               default = true;
771               type = types.bool;
772             };
774             content_security_policy = mkOption {
775               description = ''
776                 Set to `true` to add the `Content-Security-Policy` header to your requests.
777                 CSP allows to control resources that the user agent can load and helps prevent XSS attacks.
778               '';
779               default = false;
780               type = types.bool;
781             };
783             content_security_policy_report_only = mkOption {
784               description = ''
785                 Set to `true` to add the `Content-Security-Policy-Report-Only` header to your requests.
786                 CSP in Report Only mode enables you to experiment with policies by monitoring their effects without enforcing them.
787                 You can enable both policies simultaneously.
788               '';
789               default = false;
790               type = types.bool;
791             };
793             # The options content_security_policy_template and
794             # content_security_policy_template are missing because I'm not sure
795             # how exactly the quoting of the default value works. See also
796             # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L364
797             # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L373
799             # These two options are lists joined with spaces:
800             # https://github.com/grafana/grafana/blob/916d9793aa81c2990640b55a15dee0db6b525e41/pkg/middleware/csrf/csrf.go#L37-L38
802             csrf_trusted_origins = mkOption {
803               description = ''
804                 List of additional allowed URLs to pass by the CSRF check.
805                 Suggested when authentication comes from an IdP.
806               '';
807               default = [ ];
808               type = types.oneOf [ types.str (types.listOf types.str) ];
809             };
811             csrf_additional_headers = mkOption {
812               description = ''
813                 List of allowed headers to be set by the user.
814                 Suggested to use for if authentication lives behind reverse proxies.
815               '';
816               default = [ ];
817               type = types.oneOf [ types.str (types.listOf types.str) ];
818             };
819           };
821           smtp = {
822             enabled = mkOption {
823               description = "Whether to enable SMTP.";
824               default = false;
825               type = types.bool;
826             };
828             host = mkOption {
829               description = "Host to connect to.";
830               default = "localhost:25";
831               type = types.str;
832             };
834             user = mkOption {
835               description = "User used for authentication.";
836               default = null;
837               type = types.nullOr types.str;
838             };
840             password = mkOption {
841               description = ''
842                 Password used for authentication. Please note that the contents of this option
843                 will end up in a world-readable Nix store. Use the file provider
844                 pointing at a reasonably secured file in the local filesystem
845                 to work around that. Look at the documentation for details:
846                 <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
847               '';
848               default = "";
849               type = types.str;
850             };
852             cert_file = mkOption {
853               description = "File path to a cert file.";
854               default = null;
855               type = types.nullOr types.str;
856             };
858             key_file = mkOption {
859               description = "File path to a key file.";
860               default = null;
861               type = types.nullOr types.str;
862             };
864             skip_verify = mkOption {
865               description = "Verify SSL for SMTP server.";
866               default = false;
867               type = types.bool;
868             };
870             from_address = mkOption {
871               description = "Address used when sending out emails.";
872               default = "admin@grafana.localhost";
873               type = types.str;
874             };
876             from_name = mkOption {
877               description = "Name to be used as client identity for EHLO in SMTP dialog.";
878               default = "Grafana";
879               type = types.str;
880             };
882             ehlo_identity = mkOption {
883               description = "Name to be used as client identity for EHLO in SMTP dialog.";
884               default = null;
885               type = types.nullOr types.str;
886             };
888             startTLS_policy = mkOption {
889               description = "StartTLS policy when connecting to server.";
890               default = null;
891               type = types.nullOr (types.enum [ "OpportunisticStartTLS" "MandatoryStartTLS" "NoStartTLS" ]);
892             };
893           };
895           users = {
896             allow_sign_up = mkOption {
897               description = ''
898                 Set to false to prohibit users from being able to sign up / create user accounts.
899                 The admin user can still create users.
900               '';
901               default = false;
902               type = types.bool;
903             };
905             allow_org_create = mkOption {
906               description = "Set to `false` to prohibit users from creating new organizations.";
907               default = false;
908               type = types.bool;
909             };
911             auto_assign_org = mkOption {
912               description = ''
913                 Set to `true` to automatically add new users to the main organization (id 1).
914                 When set to `false,` new users automatically cause a new organization to be created for that new user.
915                 The organization will be created even if the `allow_org_create` setting is set to `false`.
916               '';
917               default = true;
918               type = types.bool;
919             };
921             auto_assign_org_id = mkOption {
922               description = ''
923                 Set this value to automatically add new users to the provided org.
924                 This requires `auto_assign_org` to be set to `true`.
925                 Please make sure that this organization already exists.
926               '';
927               default = 1;
928               type = types.int;
929             };
931             auto_assign_org_role = mkOption {
932               description = ''
933                 The role new users will be assigned for the main organization (if the `auto_assign_org` setting is set to `true`).
934               '';
935               default = "Viewer";
936               type = types.enum [ "Viewer" "Editor" "Admin" ];
937             };
939             verify_email_enabled = mkOption {
940               description = "Require email validation before sign up completes.";
941               default = false;
942               type = types.bool;
943             };
945             login_hint = mkOption {
946               description = "Text used as placeholder text on login page for login/username input.";
947               default = "email or username";
948               type = types.str;
949             };
951             password_hint = mkOption {
952               description = "Text used as placeholder text on login page for password input.";
953               default = "password";
954               type = types.str;
955             };
957             default_theme = mkOption {
958               description = "Sets the default UI theme. `system` matches the user's system theme.";
959               default = "dark";
960               type = types.enum [ "dark" "light" "system" ];
961             };
963             default_language = mkOption {
964               description = "This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`.";
965               default = "en-US";
966               type = types.str;
967             };
969             home_page = mkOption {
970               description = ''
971                 Path to a custom home page.
972                 Users are only redirected to this if the default home dashboard is used.
973                 It should match a frontend route and contain a leading slash.
974               '';
975               default = "";
976               type = types.str;
977             };
979             viewers_can_edit = mkOption {
980               description = ''
981                 Viewers can access and use Explore and perform temporary edits on panels in dashboards they have access to.
982                 They cannot save their changes.
983               '';
984               default = false;
985               type = types.bool;
986             };
988             editors_can_admin = mkOption {
989               description = "Editors can administrate dashboards, folders and teams they create.";
990               default = false;
991               type = types.bool;
992             };
994             user_invite_max_lifetime_duration = mkOption {
995               description = ''
996                 The duration in time a user invitation remains valid before expiring.
997                 This setting should be expressed as a duration.
998                 Examples: `6h` (hours), `2d` (days), `1w` (week).
999                 The minimum supported duration is `15m` (15 minutes).
1000               '';
1001               default = "24h";
1002               type = types.str;
1003             };
1005             # Lists are joined via space, so this option can't be a list.
1006             # Users have to manually join their values.
1007             hidden_users = mkOption {
1008               description = ''
1009                 This is a comma-separated list of usernames.
1010                 Users specified here are hidden in the Grafana UI.
1011                 They are still visible to Grafana administrators and to themselves.
1012               '';
1013               default = "";
1014               type = types.str;
1015             };
1016           };
1018           analytics = {
1019             reporting_enabled = mkOption {
1020               description = ''
1021                 When enabled Grafana will send anonymous usage statistics to `stats.grafana.org`.
1022                 No IP addresses are being tracked, only simple counters to track running instances, versions, dashboard and error counts.
1023                 Counters are sent every 24 hours.
1024               '';
1025               default = true;
1026               type = types.bool;
1027             };
1029             check_for_updates = mkOption {
1030               description = ''
1031                 When set to `false`, disables checking for new versions of Grafana from Grafana's GitHub repository.
1032                 When enabled, the check for a new version runs every 10 minutes.
1033                 It will notify, via the UI, when a new version is available.
1034                 The check itself will not prompt any auto-updates of the Grafana software, nor will it send any sensitive information.
1035               '';
1036               default = false;
1037               type = types.bool;
1038             };
1040             check_for_plugin_updates = mkOption {
1041               description = ''
1042                 When set to `false`, disables checking for new versions of installed plugins from https://grafana.com.
1043                 When enabled, the check for a new plugin runs every 10 minutes.
1044                 It will notify, via the UI, when a new plugin update exists.
1045                 The check itself will not prompt any auto-updates of the plugin, nor will it send any sensitive information.
1046               '';
1047               default = cfg.declarativePlugins == null;
1048               defaultText = literalExpression "cfg.declarativePlugins == null";
1049               type = types.bool;
1050             };
1052             feedback_links_enabled = mkOption {
1053               description = "Set to `false` to remove all feedback links from the UI.";
1054               default = true;
1055               type = types.bool;
1056             };
1057           };
1058         };
1059       };
1060     };
1062     provision = {
1063       enable = mkEnableOption "provision";
1065       datasources = mkOption {
1066         description = ''
1067           Declaratively provision Grafana's datasources.
1068         '';
1069         default = { };
1070         type = types.submodule {
1071           options.settings = mkOption {
1072             description = ''
1073               Grafana datasource configuration in Nix. Can't be used with
1074               [](#opt-services.grafana.provision.datasources.path) simultaneously. See
1075               <https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources>
1076               for supported options.
1077             '';
1078             default = null;
1079             type = types.nullOr (types.submodule {
1080               options = {
1081                 apiVersion = mkOption {
1082                   description = "Config file version.";
1083                   default = 1;
1084                   type = types.int;
1085                 };
1087                 datasources = mkOption {
1088                   description = "List of datasources to insert/update.";
1089                   default = [ ];
1090                   type = types.listOf grafanaTypes.datasourceConfig;
1091                 };
1093                 deleteDatasources = mkOption {
1094                   description = "List of datasources that should be deleted from the database.";
1095                   default = [ ];
1096                   type = types.listOf (types.submodule {
1097                     options.name = mkOption {
1098                       description = "Name of the datasource to delete.";
1099                       type = types.str;
1100                     };
1102                     options.orgId = mkOption {
1103                       description = "Organization ID of the datasource to delete.";
1104                       type = types.int;
1105                     };
1106                   });
1107                 };
1108               };
1109             });
1110             example = literalExpression ''
1111               {
1112                 apiVersion = 1;
1114                 datasources = [{
1115                   name = "Graphite";
1116                   type = "graphite";
1117                 }];
1119                 deleteDatasources = [{
1120                   name = "Graphite";
1121                   orgId = 1;
1122                 }];
1123               }
1124             '';
1125           };
1127           options.path = mkOption {
1128             description = ''
1129               Path to YAML datasource configuration. Can't be used with
1130               [](#opt-services.grafana.provision.datasources.settings) simultaneously.
1131               Can be either a directory or a single YAML file. Will end up in the store.
1132             '';
1133             default = null;
1134             type = types.nullOr types.path;
1135           };
1136         };
1137       };
1140       dashboards = mkOption {
1141         description = ''
1142           Declaratively provision Grafana's dashboards.
1143         '';
1144         default = { };
1145         type = types.submodule {
1146           options.settings = mkOption {
1147             description = ''
1148               Grafana dashboard configuration in Nix. Can't be used with
1149               [](#opt-services.grafana.provision.dashboards.path) simultaneously. See
1150               <https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards>
1151               for supported options.
1152             '';
1153             default = null;
1154             type = types.nullOr (types.submodule {
1155               options.apiVersion = mkOption {
1156                 description = "Config file version.";
1157                 default = 1;
1158                 type = types.int;
1159               };
1161               options.providers = mkOption {
1162                 description = "List of dashboards to insert/update.";
1163                 default = [ ];
1164                 type = types.listOf grafanaTypes.dashboardConfig;
1165               };
1166             });
1167             example = literalExpression ''
1168               {
1169                 apiVersion = 1;
1171                 providers = [{
1172                     name = "default";
1173                     options.path = "/var/lib/grafana/dashboards";
1174                 }];
1175               }
1176             '';
1177           };
1179           options.path = mkOption {
1180             description = ''
1181               Path to YAML dashboard configuration. Can't be used with
1182               [](#opt-services.grafana.provision.dashboards.settings) simultaneously.
1183               Can be either a directory or a single YAML file. Will end up in the store.
1184             '';
1185             default = null;
1186             type = types.nullOr types.path;
1187           };
1188         };
1189       };
1191       alerting = {
1192         rules = {
1193           path = mkOption {
1194             description = ''
1195               Path to YAML rules configuration. Can't be used with
1196               [](#opt-services.grafana.provision.alerting.rules.settings) simultaneously.
1197               Can be either a directory or a single YAML file. Will end up in the store.
1198             '';
1199             default = null;
1200             type = types.nullOr types.path;
1201           };
1203           settings = mkOption {
1204             description = ''
1205               Grafana rules configuration in Nix. Can't be used with
1206               [](#opt-services.grafana.provision.alerting.rules.path) simultaneously. See
1207               <https://grafana.com/docs/grafana/latest/administration/provisioning/#rules>
1208               for supported options.
1209             '';
1210             default = null;
1211             type = types.nullOr (types.submodule {
1212               options = {
1213                 apiVersion = mkOption {
1214                   description = "Config file version.";
1215                   default = 1;
1216                   type = types.int;
1217                 };
1219                 groups = mkOption {
1220                   description = "List of rule groups to import or update.";
1221                   default = [ ];
1222                   type = types.listOf (types.submodule {
1223                     freeformType = provisioningSettingsFormat.type;
1225                     options.name = mkOption {
1226                       description = "Name of the rule group. Required.";
1227                       type = types.str;
1228                     };
1230                     options.folder = mkOption {
1231                       description = "Name of the folder the rule group will be stored in. Required.";
1232                       type = types.str;
1233                     };
1235                     options.interval = mkOption {
1236                       description = "Interval that the rule group should be evaluated at. Required.";
1237                       type = types.str;
1238                     };
1239                   });
1240                 };
1242                 deleteRules = mkOption {
1243                   description = "List of alert rule UIDs that should be deleted.";
1244                   default = [ ];
1245                   type = types.listOf (types.submodule {
1246                     options.orgId = mkOption {
1247                       description = "Organization ID, default = 1";
1248                       default = 1;
1249                       type = types.int;
1250                     };
1252                     options.uid = mkOption {
1253                       description = "Unique identifier for the rule. Required.";
1254                       type = types.str;
1255                     };
1256                   });
1257                 };
1258               };
1259             });
1260             example = literalExpression ''
1261               {
1262                 apiVersion = 1;
1264                 groups = [{
1265                   orgId = 1;
1266                   name = "my_rule_group";
1267                   folder = "my_first_folder";
1268                   interval = "60s";
1269                   rules = [{
1270                     uid = "my_id_1";
1271                     title = "my_first_rule";
1272                     condition = "A";
1273                     data = [{
1274                       refId = "A";
1275                       datasourceUid = "-100";
1276                       model = {
1277                         conditions = [{
1278                           evaluator = {
1279                             params = [ 3 ];
1280                             type = "git";
1281                           };
1282                           operator.type = "and";
1283                           query.params = [ "A" ];
1284                           reducer.type = "last";
1285                           type = "query";
1286                         }];
1287                         datasource = {
1288                           type = "__expr__";
1289                           uid = "-100";
1290                         };
1291                         expression = "1==0";
1292                         intervalMs = 1000;
1293                         maxDataPoints = 43200;
1294                         refId = "A";
1295                         type = "math";
1296                       };
1297                     }];
1298                     dashboardUid = "my_dashboard";
1299                     panelId = 123;
1300                     noDataState = "Alerting";
1301                     for = "60s";
1302                     annotations.some_key = "some_value";
1303                     labels.team = "sre_team1";
1304                   }];
1305                 }];
1307                 deleteRules = [{
1308                   orgId = 1;
1309                   uid = "my_id_1";
1310                 }];
1311               }
1312             '';
1313           };
1314         };
1316         contactPoints = {
1317           path = mkOption {
1318             description = ''
1319               Path to YAML contact points configuration. Can't be used with
1320               [](#opt-services.grafana.provision.alerting.contactPoints.settings) simultaneously.
1321               Can be either a directory or a single YAML file. Will end up in the store.
1322             '';
1323             default = null;
1324             type = types.nullOr types.path;
1325           };
1327           settings = mkOption {
1328             description = ''
1329               Grafana contact points configuration in Nix. Can't be used with
1330               [](#opt-services.grafana.provision.alerting.contactPoints.path) simultaneously. See
1331               <https://grafana.com/docs/grafana/latest/administration/provisioning/#contact-points>
1332               for supported options.
1333             '';
1334             default = null;
1335             type = types.nullOr (types.submodule {
1336               options = {
1337                 apiVersion = mkOption {
1338                   description = "Config file version.";
1339                   default = 1;
1340                   type = types.int;
1341                 };
1343                 contactPoints = mkOption {
1344                   description = "List of contact points to import or update.";
1345                   default = [ ];
1346                   type = types.listOf (types.submodule {
1347                     freeformType = provisioningSettingsFormat.type;
1349                     options.name = mkOption {
1350                       description = "Name of the contact point. Required.";
1351                       type = types.str;
1352                     };
1353                   });
1354                 };
1356                 deleteContactPoints = mkOption {
1357                   description = "List of receivers that should be deleted.";
1358                   default = [ ];
1359                   type = types.listOf (types.submodule {
1360                     options.orgId = mkOption {
1361                       description = "Organization ID, default = 1.";
1362                       default = 1;
1363                       type = types.int;
1364                     };
1366                     options.uid = mkOption {
1367                       description = "Unique identifier for the receiver. Required.";
1368                       type = types.str;
1369                     };
1370                   });
1371                 };
1372               };
1373             });
1374             example = literalExpression ''
1375               {
1376                 apiVersion = 1;
1378                 contactPoints = [{
1379                   orgId = 1;
1380                   name = "cp_1";
1381                   receivers = [{
1382                     uid = "first_uid";
1383                     type = "prometheus-alertmanager";
1384                     settings.url = "http://test:9000";
1385                   }];
1386                 }];
1388                 deleteContactPoints = [{
1389                   orgId = 1;
1390                   uid = "first_uid";
1391                 }];
1392               }
1393             '';
1394           };
1395         };
1397         policies = {
1398           path = mkOption {
1399             description = ''
1400               Path to YAML notification policies configuration. Can't be used with
1401               [](#opt-services.grafana.provision.alerting.policies.settings) simultaneously.
1402               Can be either a directory or a single YAML file. Will end up in the store.
1403             '';
1404             default = null;
1405             type = types.nullOr types.path;
1406           };
1408           settings = mkOption {
1409             description = ''
1410               Grafana notification policies configuration in Nix. Can't be used with
1411               [](#opt-services.grafana.provision.alerting.policies.path) simultaneously. See
1412               <https://grafana.com/docs/grafana/latest/administration/provisioning/#notification-policies>
1413               for supported options.
1414             '';
1415             default = null;
1416             type = types.nullOr (types.submodule {
1417               options = {
1418                 apiVersion = mkOption {
1419                   description = "Config file version.";
1420                   default = 1;
1421                   type = types.int;
1422                 };
1424                 policies = mkOption {
1425                   description = "List of contact points to import or update.";
1426                   default = [ ];
1427                   type = types.listOf (types.submodule {
1428                     freeformType = provisioningSettingsFormat.type;
1429                   });
1430                 };
1432                 resetPolicies = mkOption {
1433                   description = "List of orgIds that should be reset to the default policy.";
1434                   default = [ ];
1435                   type = types.listOf types.int;
1436                 };
1437               };
1438             });
1439             example = literalExpression ''
1440               {
1441                 apiVersion = 1;
1443                 policies = [{
1444                   orgId = 1;
1445                   receiver = "grafana-default-email";
1446                   group_by = [ "..." ];
1447                   matchers = [
1448                     "alertname = Watchdog"
1449                     "severity =~ \"warning|critical\""
1450                   ];
1451                   mute_time_intervals = [
1452                     "abc"
1453                   ];
1454                   group_wait = "30s";
1455                   group_interval = "5m";
1456                   repeat_interval = "4h";
1457                 }];
1459                 resetPolicies = [
1460                   1
1461                 ];
1462               }
1463             '';
1464           };
1465         };
1467         templates = {
1468           path = mkOption {
1469             description = ''
1470               Path to YAML templates configuration. Can't be used with
1471               [](#opt-services.grafana.provision.alerting.templates.settings) simultaneously.
1472               Can be either a directory or a single YAML file. Will end up in the store.
1473             '';
1474             default = null;
1475             type = types.nullOr types.path;
1476           };
1478           settings = mkOption {
1479             description = ''
1480               Grafana templates configuration in Nix. Can't be used with
1481               [](#opt-services.grafana.provision.alerting.templates.path) simultaneously. See
1482               <https://grafana.com/docs/grafana/latest/administration/provisioning/#templates>
1483               for supported options.
1484             '';
1485             default = null;
1486             type = types.nullOr (types.submodule {
1487               options = {
1488                 apiVersion = mkOption {
1489                   description = "Config file version.";
1490                   default = 1;
1491                   type = types.int;
1492                 };
1494                 templates = mkOption {
1495                   description = "List of templates to import or update.";
1496                   default = [ ];
1497                   type = types.listOf (types.submodule {
1498                     freeformType = provisioningSettingsFormat.type;
1500                     options.name = mkOption {
1501                       description = "Name of the template, must be unique. Required.";
1502                       type = types.str;
1503                     };
1505                     options.template = mkOption {
1506                       description = "Alerting with a custom text template";
1507                       type = types.str;
1508                     };
1509                   });
1510                 };
1512                 deleteTemplates = mkOption {
1513                   description = "List of alert rule UIDs that should be deleted.";
1514                   default = [ ];
1515                   type = types.listOf (types.submodule {
1516                     options.orgId = mkOption {
1517                       description = "Organization ID, default = 1.";
1518                       default = 1;
1519                       type = types.int;
1520                     };
1522                     options.name = mkOption {
1523                       description = "Name of the template, must be unique. Required.";
1524                       type = types.str;
1525                     };
1526                   });
1527                 };
1528               };
1529             });
1530             example = literalExpression ''
1531               {
1532                 apiVersion = 1;
1534                 templates = [{
1535                   orgId = 1;
1536                   name = "my_first_template";
1537                   template = "Alerting with a custom text template";
1538                 }];
1540                 deleteTemplates = [{
1541                   orgId = 1;
1542                   name = "my_first_template";
1543                 }];
1544               }
1545             '';
1546           };
1547         };
1549         muteTimings = {
1550           path = mkOption {
1551             description = ''
1552               Path to YAML mute timings configuration. Can't be used with
1553               [](#opt-services.grafana.provision.alerting.muteTimings.settings) simultaneously.
1554               Can be either a directory or a single YAML file. Will end up in the store.
1555             '';
1556             default = null;
1557             type = types.nullOr types.path;
1558           };
1560           settings = mkOption {
1561             description = ''
1562               Grafana mute timings configuration in Nix. Can't be used with
1563               [](#opt-services.grafana.provision.alerting.muteTimings.path) simultaneously. See
1564               <https://grafana.com/docs/grafana/latest/administration/provisioning/#mute-timings>
1565               for supported options.
1566             '';
1567             default = null;
1568             type = types.nullOr (types.submodule {
1569               options = {
1570                 apiVersion = mkOption {
1571                   description = "Config file version.";
1572                   default = 1;
1573                   type = types.int;
1574                 };
1576                 muteTimes = mkOption {
1577                   description = "List of mute time intervals to import or update.";
1578                   default = [ ];
1579                   type = types.listOf (types.submodule {
1580                     freeformType = provisioningSettingsFormat.type;
1582                     options.name = mkOption {
1583                       description = "Name of the mute time interval, must be unique. Required.";
1584                       type = types.str;
1585                     };
1586                   });
1587                 };
1589                 deleteMuteTimes = mkOption {
1590                   description = "List of mute time intervals that should be deleted.";
1591                   default = [ ];
1592                   type = types.listOf (types.submodule {
1593                     options.orgId = mkOption {
1594                       description = "Organization ID, default = 1.";
1595                       default = 1;
1596                       type = types.int;
1597                     };
1599                     options.name = mkOption {
1600                       description = "Name of the mute time interval, must be unique. Required.";
1601                       type = types.str;
1602                     };
1603                   });
1604                 };
1605               };
1606             });
1607             example = literalExpression ''
1608               {
1609                 apiVersion = 1;
1611                 muteTimes = [{
1612                   orgId = 1;
1613                   name = "mti_1";
1614                   time_intervals = [{
1615                     times = [{
1616                       start_time = "06:00";
1617                       end_time = "23:59";
1618                     }];
1619                     weekdays = [
1620                       "monday:wednesday"
1621                       "saturday"
1622                       "sunday"
1623                     ];
1624                     months = [
1625                       "1:3"
1626                       "may:august"
1627                       "december"
1628                     ];
1629                     years = [
1630                       "2020:2022"
1631                       "2030"
1632                     ];
1633                     days_of_month = [
1634                       "1:5"
1635                       "-3:-1"
1636                     ];
1637                   }];
1638                 }];
1640                 deleteMuteTimes = [{
1641                   orgId = 1;
1642                   name = "mti_1";
1643                 }];
1644               }
1645             '';
1646           };
1647         };
1648       };
1649     };
1650   };
1652   config = mkIf cfg.enable {
1653     warnings =
1654       let
1655         doesntUseFileProvider = opt: defaultValue:
1656           let regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$";
1657           in builtins.match regex opt == null;
1659         # Ensure that no custom credentials are leaked into the Nix store. Unless the default value
1660         # is specified, this can be achieved by using the file/env provider:
1661         # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
1662         passwordWithoutFileProvider = optional
1663           (
1664             doesntUseFileProvider cfg.settings.database.password "" ||
1665             doesntUseFileProvider cfg.settings.security.admin_password "admin"
1666           )
1667           ''
1668             Grafana passwords will be stored as plaintext in the Nix store!
1669             Use file provider or an env-var instead.
1670           '';
1672         # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
1673         # only uses file/env providers.
1674         secureJsonDataWithoutFileProvider = optional
1675           (
1676             let
1677               datasourcesToCheck = optionals
1678                 (cfg.provision.datasources.settings != null)
1679                 cfg.provision.datasources.settings.datasources;
1680               declarationUnsafe = { secureJsonData, ... }:
1681                 secureJsonData != null
1682                 && any (flip doesntUseFileProvider null) (attrValues secureJsonData);
1683             in
1684             any declarationUnsafe datasourcesToCheck
1685           )
1686           ''
1687             Declarations in the `secureJsonData`-block of a datasource will be leaked to the
1688             Nix store unless a file-provider or an env-var is used!
1689           '';
1690       in
1691       passwordWithoutFileProvider
1692       ++ secureJsonDataWithoutFileProvider
1693       ;
1695     environment.systemPackages = [ cfg.package ];
1697     assertions = [
1698       {
1699         assertion = cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
1700         message = "Cannot set both datasources settings and datasources path";
1701       }
1702       {
1703         assertion =
1704           let
1705             prometheusIsNotDirect = opt: all
1706               ({ type, access, ... }: type == "prometheus" -> access != "direct")
1707               opt;
1708           in
1709           cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
1710         message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
1711       }
1712       {
1713         assertion = cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
1714         message = "Cannot set both dashboards settings and dashboards path";
1715       }
1716       {
1717         assertion = cfg.provision.alerting.rules.settings == null || cfg.provision.alerting.rules.path == null;
1718         message = "Cannot set both rules settings and rules path";
1719       }
1720       {
1721         assertion = cfg.provision.alerting.contactPoints.settings == null || cfg.provision.alerting.contactPoints.path == null;
1722         message = "Cannot set both contact points settings and contact points path";
1723       }
1724       {
1725         assertion = cfg.provision.alerting.policies.settings == null || cfg.provision.alerting.policies.path == null;
1726         message = "Cannot set both policies settings and policies path";
1727       }
1728       {
1729         assertion = cfg.provision.alerting.templates.settings == null || cfg.provision.alerting.templates.path == null;
1730         message = "Cannot set both templates settings and templates path";
1731       }
1732       {
1733         assertion = cfg.provision.alerting.muteTimings.settings == null || cfg.provision.alerting.muteTimings.path == null;
1734         message = "Cannot set both mute timings settings and mute timings path";
1735       }
1736     ];
1738     systemd.services.grafana = {
1739       description = "Grafana Service Daemon";
1740       wantedBy = [ "multi-user.target" ];
1741       after = [ "networking.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
1742       script = ''
1743         set -o errexit -o pipefail -o nounset -o errtrace
1744         shopt -s inherit_errexit
1746         exec ${cfg.package}/bin/grafana server -homepath ${cfg.dataDir} -config ${configFile}
1747       '';
1748       serviceConfig = {
1749         WorkingDirectory = cfg.dataDir;
1750         User = "grafana";
1751         Restart = "on-failure";
1752         RuntimeDirectory = "grafana";
1753         RuntimeDirectoryMode = "0755";
1754         # Hardening
1755         AmbientCapabilities = lib.mkIf (cfg.settings.server.http_port < 1024) [ "CAP_NET_BIND_SERVICE" ];
1756         CapabilityBoundingSet = if (cfg.settings.server.http_port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
1757         DeviceAllow = [ "" ];
1758         LockPersonality = true;
1759         NoNewPrivileges = true;
1760         PrivateDevices = true;
1761         PrivateTmp = true;
1762         ProtectClock = true;
1763         ProtectControlGroups = true;
1764         ProtectHome = true;
1765         ProtectHostname = true;
1766         ProtectKernelLogs = true;
1767         ProtectKernelModules = true;
1768         ProtectKernelTunables = true;
1769         ProtectProc = "invisible";
1770         ProtectSystem = "full";
1771         RemoveIPC = true;
1772         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
1773         RestrictNamespaces = true;
1774         RestrictRealtime = true;
1775         RestrictSUIDSGID = true;
1776         SystemCallArchitectures = "native";
1777         # Upstream grafana is not setting SystemCallFilter for compatibility
1778         # reasons, see https://github.com/grafana/grafana/pull/40176
1779         SystemCallFilter = [
1780           "@system-service"
1781           "~@privileged"
1782         ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ];
1783         UMask = "0027";
1784       };
1785       preStart = ''
1786         ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
1787         ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir}
1788       '';
1789     };
1791     users.users.grafana = {
1792       uid = config.ids.uids.grafana;
1793       description = "Grafana user";
1794       home = cfg.dataDir;
1795       createHome = true;
1796       group = "grafana";
1797     };
1798     users.groups.grafana = { };
1799   };