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