1 { options, config, lib, pkgs, ... }:
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.
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.
20 # If a setting is a list, always allow setting it as a plain string as well.
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
29 else generators.mkValueStringDefault { } v;
33 configFile = settingsFormatIni.generate "config.ini" cfg.settings;
35 mkProvisionCfg = name: attr: provisionCfg:
36 if provisionCfg.path != null
37 then provisionCfg.path
39 provisioningSettingsFormat.generate "${name}.yaml"
40 (if provisionCfg.settings != null
41 then provisionCfg.settings
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
66 ln -sf ${src} $out/${dir}/${filename}.yaml
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"; }}
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;
90 description = "Name of the datasource. Required.";
94 description = "Datasource type. Required.";
97 type = types.enum [ "proxy" "direct" ];
99 description = "Access mode. proxy or direct (Server or Browser in the UI). Required.";
102 type = types.nullOr types.str;
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.";
109 description = "Url of the datasource.";
111 editable = mkOption {
114 description = "Allow users to edit datasources from the UI.";
116 jsonData = mkOption {
117 type = types.nullOr types.attrs;
119 description = "Extra data for datasource plugins.";
121 secureJsonData = mkOption {
122 type = types.nullOr types.attrs;
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>
135 # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
136 grafanaTypes.dashboardConfig = types.submodule {
137 freeformType = provisioningSettingsFormat.type;
143 description = "A unique provider name.";
148 description = "Dashboard provider type.";
150 options.path = mkOption {
152 description = "Path grafana will watch for dashboards. Required when using the 'file' type.";
159 (mkRemovedOptionModule [ "services" "grafana" "provision" "notifiers" ] ''
160 Notifiers (services.grafana.provision.notifiers) were removed in Grafana 11.
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.
208 (mkRemovedOptionModule [ "services" "grafana" "security" "adminPasswordFile" ] ''
209 This option has been removed. Use 'services.grafana.settings.security.admin_password' with file provider instead.
211 (mkRemovedOptionModule [ "services" "grafana" "security" "secretKeyFile" ] ''
212 This option has been removed. Use 'services.grafana.settings.security.secret_key' with file provider instead.
214 (mkRemovedOptionModule [ "services" "grafana" "smtp" "passwordFile" ] ''
215 This option has been removed. Use 'services.grafana.settings.smtp.password' with file provider instead.
217 (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "clientSecretFile" ] ''
218 This option has been removed. Use 'services.grafana.settings.azuread.client_secret' with file provider instead.
220 (mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] ''
221 This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead.
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.
228 (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
231 options.services.grafana = {
232 enable = mkEnableOption "grafana";
234 declarativePlugins = mkOption {
235 type = with types; nullOr (listOf path);
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
242 apply = x: if isList x then lib.unique x else x;
245 package = mkPackageOption pkgs "grafana" { };
248 description = "Data directory.";
249 default = "/var/lib/grafana";
253 settings = mkOption {
255 Grafana settings. See <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/>
256 for available options. INI format is used.
258 type = types.submodule {
259 freeformType = settingsFormatIni.type;
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";
270 provisioning = mkOption {
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.
275 default = provisionConfDir;
276 defaultText = "directory with links to files generated from services.grafana.provision";
282 protocol = mkOption {
283 description = "Which protocol to listen.";
285 type = types.enum [ "http" "https" "h2" "socket" ];
288 http_addr = mkOption {
290 default = "127.0.0.1";
295 This setting intentionally varies from upstream's default to be a bit more secure by default.
300 http_port = mkOption {
301 description = "Listening port.";
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.
313 default = "localhost";
317 enforce_domain = mkOption {
319 Redirect to correct domain if the host header does not match the domain.
320 Prevents DNS rebinding attacks.
326 root_url = mkOption {
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.
334 default = "%(protocol)s://%(domain)s:%(http_port)s/";
338 serve_from_sub_path = mkOption {
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.
352 router_logging = mkOption {
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.
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"'';
368 enable_gzip = mkOption {
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.
377 cert_file = mkOption {
379 Path to the certificate file (if `protocol` is set to `https` or `h2`).
382 type = types.nullOr types.str;
385 cert_key = mkOption {
387 Path to the certificate key file (if `protocol` is set to `https` or `h2`).
390 type = types.nullOr types.str;
393 socket_gid = mkOption {
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.
404 socket_mode = mkOption {
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.
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.
418 Path where the socket should be created when `protocol=socket`.
419 Make sure that Grafana has appropriate permissions before you change this setting.
421 default = "/run/grafana/grafana.sock";
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`.
434 type = types.nullOr types.str;
437 read_timeout = mkOption {
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.
450 description = "Database type.";
452 type = types.enum [ "mysql" "sqlite3" "postgres" ];
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"`
462 default = "127.0.0.1:3306";
467 description = "The name of the Grafana database.";
473 description = "The database user (not applicable for `sqlite3`).";
478 password = mkOption {
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>
492 max_idle_conn = mkOption {
493 description = "The maximum number of connections in the idle connection pool.";
498 max_open_conn = mkOption {
499 description = "The maximum number of open connections to the database.";
504 conn_max_lifetime = mkOption {
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.
514 locking_attempt_timeout_sec = mkOption {
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.
523 log_queries = mkOption {
524 description = "Set to `true` to log the sql calls and execution times";
529 ssl_mode = mkOption {
531 For Postgres, use either `disable`, `require` or `verify-full`.
532 For MySQL, use either `true`, `false`, or `skip-verify`.
535 type = types.enum [ "disable" "require" "verify-full" "true" "false" "skip-verify" ];
538 isolation_level = mkOption {
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.
544 type = types.nullOr (types.enum [ "READ-UNCOMMITTED" "READ-COMMITTED" "REPEATABLE-READ" "SERIALIZABLE" ]);
547 ca_cert_path = mkOption {
548 description = "The path to the CA certificate to use.";
550 type = types.nullOr types.str;
553 client_key_path = mkOption {
554 description = "The path to the client key. Only if server requires client authentication.";
556 type = types.nullOr types.str;
559 client_cert_path = mkOption {
560 description = "The path to the client cert. Only if server requires client authentication.";
562 type = types.nullOr types.str;
565 server_cert_name = mkOption {
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`.
571 type = types.nullOr types.str;
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"'';
581 cache_mode = mkOption {
584 [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database.
587 type = types.enum [ "private" "shared" ];
593 Setting to enable/disable [Write-Ahead Logging](https://sqlite.org/wal.html).
599 query_retries = mkOption {
601 This setting applies to `sqlite3` only and controls the number of times the system retries a query when the database is locked.
607 transaction_retries = mkOption {
609 This setting applies to `sqlite3` only and controls the number of times the system retries a transaction when the database is locked.
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.";
624 disable_initial_admin_creation = mkOption {
625 description = "Disable creation of admin user on first start of Grafana.";
630 admin_user = mkOption {
631 description = "Default admin username.";
636 admin_password = mkOption {
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>
648 admin_email = mkOption {
649 description = "The email of the default Grafana Admin, created on startup.";
650 default = "admin@localhost";
654 secret_key = mkOption {
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>
662 default = "SW2YcwTIb9zpOOhoPsMm";
666 disable_gravatar = mkOption {
667 description = "Set to `true` to disable the use of Gravatar for user profile images.";
672 data_source_proxy_whitelist = mkOption {
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.
680 type = types.oneOf [ types.str (types.listOf types.str) ];
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).";
689 cookie_secure = mkOption {
690 description = "Set to `true` if you host Grafana behind HTTPS.";
695 cookie_samesite = mkOption {
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.
704 type = types.enum [ "lax" "strict" "none" "disabled" ];
707 allow_embedding = mkOption {
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).
717 strict_transport_security = mkOption {
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.
728 strict_transport_security_max_age_seconds = mkOption {
730 Sets how long a browser should cache HSTS in seconds.
731 Only applied if `strict_transport_security` is enabled.
737 strict_transport_security_preload = mkOption {
739 Set to `true` to enable HSTS `preloading` option.
740 Only applied if `strict_transport_security` is enabled.
746 strict_transport_security_subdomains = mkOption {
748 Set to `true` to enable HSTS `includeSubDomains` option.
749 Only applied if `strict_transport_security` is enabled.
755 x_content_type_options = mkOption {
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.
765 x_xss_protection = mkOption {
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.
774 content_security_policy = mkOption {
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.
783 content_security_policy_report_only = mkOption {
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.
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 {
804 List of additional allowed URLs to pass by the CSRF check.
805 Suggested when authentication comes from an IdP.
808 type = types.oneOf [ types.str (types.listOf types.str) ];
811 csrf_additional_headers = mkOption {
813 List of allowed headers to be set by the user.
814 Suggested to use for if authentication lives behind reverse proxies.
817 type = types.oneOf [ types.str (types.listOf types.str) ];
823 description = "Whether to enable SMTP.";
829 description = "Host to connect to.";
830 default = "localhost:25";
835 description = "User used for authentication.";
837 type = types.nullOr types.str;
840 password = mkOption {
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>
852 cert_file = mkOption {
853 description = "File path to a cert file.";
855 type = types.nullOr types.str;
858 key_file = mkOption {
859 description = "File path to a key file.";
861 type = types.nullOr types.str;
864 skip_verify = mkOption {
865 description = "Verify SSL for SMTP server.";
870 from_address = mkOption {
871 description = "Address used when sending out emails.";
872 default = "admin@grafana.localhost";
876 from_name = mkOption {
877 description = "Name to be used as client identity for EHLO in SMTP dialog.";
882 ehlo_identity = mkOption {
883 description = "Name to be used as client identity for EHLO in SMTP dialog.";
885 type = types.nullOr types.str;
888 startTLS_policy = mkOption {
889 description = "StartTLS policy when connecting to server.";
891 type = types.nullOr (types.enum [ "OpportunisticStartTLS" "MandatoryStartTLS" "NoStartTLS" ]);
896 allow_sign_up = mkOption {
898 Set to false to prohibit users from being able to sign up / create user accounts.
899 The admin user can still create users.
905 allow_org_create = mkOption {
906 description = "Set to `false` to prohibit users from creating new organizations.";
911 auto_assign_org = mkOption {
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`.
921 auto_assign_org_id = mkOption {
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.
931 auto_assign_org_role = mkOption {
933 The role new users will be assigned for the main organization (if the `auto_assign_org` setting is set to `true`).
936 type = types.enum [ "Viewer" "Editor" "Admin" ];
939 verify_email_enabled = mkOption {
940 description = "Require email validation before sign up completes.";
945 login_hint = mkOption {
946 description = "Text used as placeholder text on login page for login/username input.";
947 default = "email or username";
951 password_hint = mkOption {
952 description = "Text used as placeholder text on login page for password input.";
953 default = "password";
957 default_theme = mkOption {
958 description = "Sets the default UI theme. `system` matches the user's system theme.";
960 type = types.enum [ "dark" "light" "system" ];
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`.";
969 home_page = mkOption {
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.
979 viewers_can_edit = mkOption {
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.
988 editors_can_admin = mkOption {
989 description = "Editors can administrate dashboards, folders and teams they create.";
994 user_invite_max_lifetime_duration = mkOption {
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).
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 {
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.
1019 reporting_enabled = mkOption {
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.
1029 check_for_updates = mkOption {
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.
1040 check_for_plugin_updates = mkOption {
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.
1047 default = cfg.declarativePlugins == null;
1048 defaultText = literalExpression "cfg.declarativePlugins == null";
1052 feedback_links_enabled = mkOption {
1053 description = "Set to `false` to remove all feedback links from the UI.";
1063 enable = mkEnableOption "provision";
1065 datasources = mkOption {
1067 Declaratively provision Grafana's datasources.
1070 type = types.submodule {
1071 options.settings = mkOption {
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.
1079 type = types.nullOr (types.submodule {
1081 apiVersion = mkOption {
1082 description = "Config file version.";
1087 datasources = mkOption {
1088 description = "List of datasources to insert/update.";
1090 type = types.listOf grafanaTypes.datasourceConfig;
1093 deleteDatasources = mkOption {
1094 description = "List of datasources that should be deleted from the database.";
1096 type = types.listOf (types.submodule {
1097 options.name = mkOption {
1098 description = "Name of the datasource to delete.";
1102 options.orgId = mkOption {
1103 description = "Organization ID of the datasource to delete.";
1110 example = literalExpression ''
1119 deleteDatasources = [{
1127 options.path = mkOption {
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.
1134 type = types.nullOr types.path;
1140 dashboards = mkOption {
1142 Declaratively provision Grafana's dashboards.
1145 type = types.submodule {
1146 options.settings = mkOption {
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.
1154 type = types.nullOr (types.submodule {
1155 options.apiVersion = mkOption {
1156 description = "Config file version.";
1161 options.providers = mkOption {
1162 description = "List of dashboards to insert/update.";
1164 type = types.listOf grafanaTypes.dashboardConfig;
1167 example = literalExpression ''
1173 options.path = "/var/lib/grafana/dashboards";
1179 options.path = mkOption {
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.
1186 type = types.nullOr types.path;
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.
1200 type = types.nullOr types.path;
1203 settings = mkOption {
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.
1211 type = types.nullOr (types.submodule {
1213 apiVersion = mkOption {
1214 description = "Config file version.";
1220 description = "List of rule groups to import or update.";
1222 type = types.listOf (types.submodule {
1223 freeformType = provisioningSettingsFormat.type;
1225 options.name = mkOption {
1226 description = "Name of the rule group. Required.";
1230 options.folder = mkOption {
1231 description = "Name of the folder the rule group will be stored in. Required.";
1235 options.interval = mkOption {
1236 description = "Interval that the rule group should be evaluated at. Required.";
1242 deleteRules = mkOption {
1243 description = "List of alert rule UIDs that should be deleted.";
1245 type = types.listOf (types.submodule {
1246 options.orgId = mkOption {
1247 description = "Organization ID, default = 1";
1252 options.uid = mkOption {
1253 description = "Unique identifier for the rule. Required.";
1260 example = literalExpression ''
1266 name = "my_rule_group";
1267 folder = "my_first_folder";
1271 title = "my_first_rule";
1275 datasourceUid = "-100";
1282 operator.type = "and";
1283 query.params = [ "A" ];
1284 reducer.type = "last";
1291 expression = "1==0";
1293 maxDataPoints = 43200;
1298 dashboardUid = "my_dashboard";
1300 noDataState = "Alerting";
1302 annotations.some_key = "some_value";
1303 labels.team = "sre_team1";
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.
1324 type = types.nullOr types.path;
1327 settings = mkOption {
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.
1335 type = types.nullOr (types.submodule {
1337 apiVersion = mkOption {
1338 description = "Config file version.";
1343 contactPoints = mkOption {
1344 description = "List of contact points to import or update.";
1346 type = types.listOf (types.submodule {
1347 freeformType = provisioningSettingsFormat.type;
1349 options.name = mkOption {
1350 description = "Name of the contact point. Required.";
1356 deleteContactPoints = mkOption {
1357 description = "List of receivers that should be deleted.";
1359 type = types.listOf (types.submodule {
1360 options.orgId = mkOption {
1361 description = "Organization ID, default = 1.";
1366 options.uid = mkOption {
1367 description = "Unique identifier for the receiver. Required.";
1374 example = literalExpression ''
1383 type = "prometheus-alertmanager";
1384 settings.url = "http://test:9000";
1388 deleteContactPoints = [{
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.
1405 type = types.nullOr types.path;
1408 settings = mkOption {
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.
1416 type = types.nullOr (types.submodule {
1418 apiVersion = mkOption {
1419 description = "Config file version.";
1424 policies = mkOption {
1425 description = "List of contact points to import or update.";
1427 type = types.listOf (types.submodule {
1428 freeformType = provisioningSettingsFormat.type;
1432 resetPolicies = mkOption {
1433 description = "List of orgIds that should be reset to the default policy.";
1435 type = types.listOf types.int;
1439 example = literalExpression ''
1445 receiver = "grafana-default-email";
1446 group_by = [ "..." ];
1448 "alertname = Watchdog"
1449 "severity =~ \"warning|critical\""
1451 mute_time_intervals = [
1455 group_interval = "5m";
1456 repeat_interval = "4h";
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.
1475 type = types.nullOr types.path;
1478 settings = mkOption {
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.
1486 type = types.nullOr (types.submodule {
1488 apiVersion = mkOption {
1489 description = "Config file version.";
1494 templates = mkOption {
1495 description = "List of templates to import or update.";
1497 type = types.listOf (types.submodule {
1498 freeformType = provisioningSettingsFormat.type;
1500 options.name = mkOption {
1501 description = "Name of the template, must be unique. Required.";
1505 options.template = mkOption {
1506 description = "Alerting with a custom text template";
1512 deleteTemplates = mkOption {
1513 description = "List of alert rule UIDs that should be deleted.";
1515 type = types.listOf (types.submodule {
1516 options.orgId = mkOption {
1517 description = "Organization ID, default = 1.";
1522 options.name = mkOption {
1523 description = "Name of the template, must be unique. Required.";
1530 example = literalExpression ''
1536 name = "my_first_template";
1537 template = "Alerting with a custom text template";
1540 deleteTemplates = [{
1542 name = "my_first_template";
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.
1557 type = types.nullOr types.path;
1560 settings = mkOption {
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.
1568 type = types.nullOr (types.submodule {
1570 apiVersion = mkOption {
1571 description = "Config file version.";
1576 muteTimes = mkOption {
1577 description = "List of mute time intervals to import or update.";
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.";
1589 deleteMuteTimes = mkOption {
1590 description = "List of mute time intervals that should be deleted.";
1592 type = types.listOf (types.submodule {
1593 options.orgId = mkOption {
1594 description = "Organization ID, default = 1.";
1599 options.name = mkOption {
1600 description = "Name of the mute time interval, must be unique. Required.";
1607 example = literalExpression ''
1616 start_time = "06:00";
1640 deleteMuteTimes = [{
1652 config = mkIf cfg.enable {
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
1664 doesntUseFileProvider cfg.settings.database.password "" ||
1665 doesntUseFileProvider cfg.settings.security.admin_password "admin"
1668 Grafana passwords will be stored as plaintext in the Nix store!
1669 Use file provider or an env-var instead.
1672 # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
1673 # only uses file/env providers.
1674 secureJsonDataWithoutFileProvider = optional
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);
1684 any declarationUnsafe datasourcesToCheck
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!
1691 passwordWithoutFileProvider
1692 ++ secureJsonDataWithoutFileProvider
1695 environment.systemPackages = [ cfg.package ];
1699 assertion = cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
1700 message = "Cannot set both datasources settings and datasources path";
1705 prometheusIsNotDirect = opt: all
1706 ({ type, access, ... }: type == "prometheus" -> access != "direct")
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)";
1713 assertion = cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
1714 message = "Cannot set both dashboards settings and dashboards path";
1717 assertion = cfg.provision.alerting.rules.settings == null || cfg.provision.alerting.rules.path == null;
1718 message = "Cannot set both rules settings and rules path";
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";
1725 assertion = cfg.provision.alerting.policies.settings == null || cfg.provision.alerting.policies.path == null;
1726 message = "Cannot set both policies settings and policies path";
1729 assertion = cfg.provision.alerting.templates.settings == null || cfg.provision.alerting.templates.path == null;
1730 message = "Cannot set both templates settings and templates path";
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";
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";
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}
1749 WorkingDirectory = cfg.dataDir;
1751 Restart = "on-failure";
1752 RuntimeDirectory = "grafana";
1753 RuntimeDirectoryMode = "0755";
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;
1762 ProtectClock = true;
1763 ProtectControlGroups = true;
1765 ProtectHostname = true;
1766 ProtectKernelLogs = true;
1767 ProtectKernelModules = true;
1768 ProtectKernelTunables = true;
1769 ProtectProc = "invisible";
1770 ProtectSystem = "full";
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 = [
1782 ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ];
1786 ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
1787 ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir}
1791 users.users.grafana = {
1792 uid = config.ids.uids.grafana;
1793 description = "Grafana user";
1798 users.groups.grafana = { };