···55let
66 cfg = config.services.grafana;
77 opt = options.services.grafana;
88- provisioningSettingsFormat = pkgs.formats.yaml {};
88+ provisioningSettingsFormat = pkgs.formats.yaml { };
99 declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins);
1010 useMysql = cfg.settings.database.type == "mysql";
1111 usePostgresql = cfg.settings.database.type == "postgres";
12121313- settingsFormatIni = pkgs.formats.ini {};
1313+ settingsFormatIni = pkgs.formats.ini { };
1414 configFile = settingsFormatIni.generate "config.ini" cfg.settings;
15151616 mkProvisionCfg = name: attr: provisionCfg:
1717 if provisionCfg.path != null
1818- then provisionCfg.path
1818+ then provisionCfg.path
1919 else
2020 provisioningSettingsFormat.generate "${name}.yaml"
2121 (if provisionCfg.settings != null
2222- then provisionCfg.settings
2323- else {
2424- apiVersion = 1;
2525- ${attr} = [];
2626- });
2222+ then provisionCfg.settings
2323+ else {
2424+ apiVersion = 1;
2525+ ${attr} = [ ];
2626+ });
27272828 datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources;
2929 dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards;
···35353636 notifierFileOrDir = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
37373838- generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null)
3939- then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings
4040- else cfg.provision.alerting."${x}".path;
3838+ generateAlertingProvisioningYaml = x:
3939+ if (cfg.provision.alerting."${x}".path == null)
4040+ then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings
4141+ else cfg.provision.alerting."${x}".path;
4142 rulesFileOrDir = generateAlertingProvisioningYaml "rules";
4243 contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints";
4344 policiesFileOrDir = generateAlertingProvisioningYaml "policies";
···102103 description = lib.mdDoc "Datasource type. Required.";
103104 };
104105 access = mkOption {
105105- type = types.enum ["proxy" "direct"];
106106+ type = types.enum [ "proxy" "direct" ];
106107 default = "proxy";
107108 description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required.";
108109 };
···170171 description = lib.mdDoc "Notifier name.";
171172 };
172173 type = mkOption {
173173- type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"];
174174+ type = types.enum [ "dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook" ];
174175 description = lib.mdDoc "Notifier type.";
175176 };
176177 uid = mkOption {
···225226 };
226227 };
227228 };
228228-in {
229229+in
230230+{
229231 imports = [
230232 (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ])
231233 (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ])
···354356 protocol = mkOption {
355357 description = lib.mdDoc "Which protocol to listen.";
356358 default = "http";
357357- type = types.enum ["http" "https" "h2" "socket"];
359359+ type = types.enum [ "http" "https" "h2" "socket" ];
358360 };
359361360362 http_addr = mkOption {
···376378 };
377379378380 domain = mkOption {
379379- description = lib.mdDoc "The public facing domain name used to access grafana from a browser.";
381381+ description = lib.mdDoc ''
382382+ The public facing domain name used to access grafana from a browser.
383383+384384+ This setting is only used in the default value of the `root_url` setting.
385385+ If you set the latter manually, this option does not have to be specified.
386386+ '';
380387 default = "localhost";
381388 type = types.str;
389389+ };
390390+391391+ enforce_domain = mkOption {
392392+ description = lib.mdDoc ''
393393+ Redirect to correct domain if the host header does not match the domain.
394394+ Prevents DNS rebinding attacks.
395395+ '';
396396+ default = false;
397397+ type = types.bool;
382398 };
383399384400 root_url = mkOption {
385385- description = lib.mdDoc "Full public facing url.";
401401+ description = lib.mdDoc ''
402402+ This is the full URL used to access Grafana from a web browser.
403403+ This is important if you use Google or GitHub OAuth authentication (for the callback URL to be correct).
404404+405405+ This setting is also important if you have a reverse proxy in front of Grafana that exposes it through a subpath.
406406+ In that case add the subpath to the end of this URL setting.
407407+ '';
408408+ # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L54
386409 default = "%(protocol)s://%(domain)s:%(http_port)s/";
387410 type = types.str;
388411 };
389412413413+ serve_from_sub_path = mkOption {
414414+ description = lib.mdDoc ''
415415+ Serve Grafana from subpath specified in the `root_url` setting.
416416+ By default it is set to `false` for compatibility reasons.
417417+418418+ By enabling this setting and using a subpath in `root_url` above,
419419+ e.g. `root_url = "http://localhost:3000/grafana"`,
420420+ Grafana is accessible on `http://localhost:3000/grafana`.
421421+ If accessed without subpath, Grafana will redirect to an URL with the subpath.
422422+ '';
423423+ default = false;
424424+ type = types.bool;
425425+ };
426426+427427+ router_logging = mkOption {
428428+ description = lib.mdDoc ''
429429+ Set to `true` for Grafana to log all HTTP requests (not just errors).
430430+ These are logged as Info level events to the Grafana log.
431431+ '';
432432+ default = false;
433433+ type = types.bool;
434434+ };
435435+390436 static_root_path = mkOption {
391437 description = lib.mdDoc "Root path for static assets.";
392438 default = "${cfg.package}/share/grafana/public";
···396442397443 enable_gzip = mkOption {
398444 description = lib.mdDoc ''
399399- Set this option to true to enable HTTP compression, this can improve transfer speed and bandwidth utilization.
400400- It is recommended that most users set it to true. By default it is set to false for compatibility reasons.
445445+ Set this option to `true` to enable HTTP compression, this can improve transfer speed and bandwidth utilization.
446446+ It is recommended that most users set it to `true`. By default it is set to `false` for compatibility reasons.
401447 '';
402448 default = false;
403449 type = types.bool;
404450 };
405451406452 cert_file = mkOption {
407407- description = lib.mdDoc "Cert file for ssl.";
453453+ description = lib.mdDoc ''
454454+ Path to the certificate file (if `protocol` is set to `https` or `h2`).
455455+ '';
408456 default = "";
409457 type = types.str;
410458 };
411459412460 cert_key = mkOption {
413413- description = lib.mdDoc "Cert key for ssl.";
461461+ description = lib.mdDoc ''
462462+ Path to the certificate key file (if `protocol` is set to `https` or `h2`).
463463+ '';
414464 default = "";
415465 type = types.str;
416466 };
417467468468+ socket_gid = mkOption {
469469+ description = lib.mdDoc ''
470470+ GID where the socket should be set when `protocol=socket`.
471471+ 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.
472472+ It is recommended to set the gid as http server user gid.
473473+ Not set when the value is -1.
474474+ '';
475475+ default = -1;
476476+ type = types.int;
477477+ };
478478+479479+ socket_mode = mkOption {
480480+ description = lib.mdDoc ''
481481+ Mode where the socket should be set when `protocol=socket`.
482482+ Make sure that Grafana process is the file owner before you change this setting.
483483+ '';
484484+ # I assume this value is interpreted as octal literal by grafana.
485485+ # If this was an int, people following tutorials or porting their
486486+ # old config could stumble across nix not having octal literals.
487487+ default = "0660";
488488+ type = types.str;
489489+ };
490490+418491 socket = mkOption {
419419- description = lib.mdDoc "Path where the socket should be created when protocol=socket. Make sure that Grafana has appropriate permissions before you change this setting.";
492492+ description = lib.mdDoc ''
493493+ Path where the socket should be created when `protocol=socket`.
494494+ Make sure that Grafana has appropriate permissions before you change this setting.
495495+ '';
420496 default = "/run/grafana/grafana.sock";
497497+ type = types.str;
498498+ };
499499+500500+ cdn_url = mkOption {
501501+ description = lib.mdDoc ''
502502+ Specify a full HTTP URL address to the root of your Grafana CDN assets.
503503+ Grafana will add edition and version paths.
504504+505505+ For example, given a cdn url like `https://cdn.myserver.com`
506506+ grafana will try to load a javascript file from `http://cdn.myserver.com/grafana-oss/7.4.0/public/build/app.<hash>.js`.
507507+ '';
508508+ default = "";
509509+ type = types.str;
510510+ };
511511+512512+ read_timeout = mkOption {
513513+ description = lib.mdDoc ''
514514+ Sets the maximum time using a duration format (5s/5m/5ms)
515515+ before timing out read of an incoming request and closing idle connections.
516516+ 0 means there is no timeout for reading the request.
517517+ '';
518518+ default = "0";
421519 type = types.str;
422520 };
423521 };
···426524 type = mkOption {
427525 description = lib.mdDoc "Database type.";
428526 default = "sqlite3";
429429- type = types.enum ["mysql" "sqlite3" "postgres"];
527527+ type = types.enum [ "mysql" "sqlite3" "postgres" ];
430528 };
431529432530 host = mkOption {
433433- description = lib.mdDoc "Database host.";
531531+ description = lib.mdDoc ''
532532+ Only applicable to MySQL or Postgres.
533533+ Includes IP or hostname and port or in case of Unix sockets the path to it.
534534+ For example, for MySQL running on the same host as Grafana: `host = "127.0.0.1:3306"`
535535+ or with Unix sockets: `host = "/var/run/mysqld/mysqld.sock"`
536536+ '';
434537 default = "127.0.0.1:3306";
435538 type = types.str;
436539 };
437540438541 name = mkOption {
439439- description = lib.mdDoc "Database name.";
542542+ description = lib.mdDoc "The name of the Grafana database.";
440543 default = "grafana";
441544 type = types.str;
442545 };
443546444547 user = mkOption {
445445- description = lib.mdDoc "Database user.";
548548+ description = lib.mdDoc "The database user (not applicable for `sqlite3`).";
446549 default = "root";
447550 type = types.str;
448551 };
449552450553 password = mkOption {
451554 description = lib.mdDoc ''
452452- Database password. Please note that the contents of this option
555555+ The database user's password (not applicable for `sqlite3`).
556556+557557+ Please note that the contents of this option
453558 will end up in a world-readable Nix store. Use the file provider
454559 pointing at a reasonably secured file in the local filesystem
455560 to work around that. Look at the documentation for details:
···459564 type = types.str;
460565 };
461566567567+ max_idle_conn = mkOption {
568568+ description = lib.mdDoc "The maximum number of connections in the idle connection pool.";
569569+ default = 2;
570570+ type = types.int;
571571+ };
572572+573573+ max_open_conn = mkOption {
574574+ description = lib.mdDoc "The maximum number of open connections to the database.";
575575+ default = 0; # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L123-L124
576576+ type = types.int;
577577+ };
578578+579579+ conn_max_lifetime = mkOption {
580580+ description = lib.mdDoc ''
581581+ Sets the maximum amount of time a connection may be reused.
582582+ The default is 14400 (which means 14400 seconds or 4 hours).
583583+ For MySQL, this setting should be shorter than the `wait_timeout` variable.
584584+ '';
585585+ default = 14400;
586586+ type = types.int;
587587+ };
588588+589589+ locking_attempt_timeout_sec = mkOption {
590590+ description = lib.mdDoc ''
591591+ For `mysql`, if the `migrationLocking` feature toggle is set,
592592+ specify the time (in seconds) to wait before failing to lock the database for the migrations.
593593+ '';
594594+ default = 0;
595595+ type = types.int;
596596+ };
597597+598598+ log_queries = mkOption {
599599+ description = lib.mdDoc "Set to `true` to log the sql calls and execution times";
600600+ default = false;
601601+ type = types.bool;
602602+ };
603603+604604+ ssl_mode = mkOption {
605605+ description = lib.mdDoc ''
606606+ For Postgres, use either `disable`, `require` or `verify-full`.
607607+ For MySQL, use either `true`, `false`, or `skip-verify`.
608608+ '';
609609+ default = "disable"; # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L134
610610+ type = types.enum [ "disable" "require" "verify-full" "true" "false" "skip-verify" ];
611611+ };
612612+613613+ isolation_level = mkOption {
614614+ description = lib.mdDoc ''
615615+ Only the MySQL driver supports isolation levels in Grafana.
616616+ In case the value is empty, the driver's default isolation level is applied.
617617+ '';
618618+ default = null;
619619+ type = types.nullOr (types.enum [ "READ-UNCOMMITTED" "READ-COMMITTED" "REPEATABLE-READ" "SERIALIZABLE" ]);
620620+ };
621621+622622+ ca_cert_path = mkOption {
623623+ description = lib.mdDoc "The path to the CA certificate to use.";
624624+ default = "";
625625+ type = types.str;
626626+ };
627627+628628+ client_key_path = mkOption {
629629+ description = lib.mdDoc "The path to the client key. Only if server requires client authentication.";
630630+ default = "";
631631+ type = types.str;
632632+ };
633633+634634+ client_cert_path = mkOption {
635635+ description = lib.mdDoc "The path to the client cert. Only if server requires client authentication.";
636636+ default = "";
637637+ type = types.str;
638638+ };
639639+640640+ server_cert_name = mkOption {
641641+ description = lib.mdDoc ''
642642+ The common name field of the certificate used by the `mysql` or `postgres` server.
643643+ Not necessary if `ssl_mode` is set to `skip-verify`.
644644+ '';
645645+ default = "";
646646+ type = types.str;
647647+ };
648648+462649 path = mkOption {
463463- description = lib.mdDoc "Only applicable to sqlite3 database. The file path where the database will be stored.";
650650+ description = lib.mdDoc "Only applicable to `sqlite3` database. The file path where the database will be stored.";
464651 default = "${cfg.dataDir}/data/grafana.db";
465652 defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
466653 type = types.path;
467654 };
655655+656656+ cache_mode = mkOption {
657657+ description = lib.mdDoc ''
658658+ For `sqlite3` only.
659659+ [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database.
660660+ '';
661661+ default = "private";
662662+ type = types.enum [ "private" "shared" ];
663663+ };
664664+665665+ wal = mkOption {
666666+ description = lib.mdDoc ''
667667+ For `sqlite3` only.
668668+ Setting to enable/disable [Write-Ahead Logging](https://sqlite.org/wal.html).
669669+ '';
670670+ default = false;
671671+ type = types.bool;
672672+ };
673673+674674+ query_retries = mkOption {
675675+ description = lib.mdDoc ''
676676+ This setting applies to `sqlite3` only and controls the number of times the system retries a query when the database is locked.
677677+ '';
678678+ default = 0;
679679+ type = types.int;
680680+ };
681681+682682+ transaction_retries = mkOption {
683683+ description = lib.mdDoc ''
684684+ This setting applies to `sqlite3` only and controls the number of times the system retries a transaction when the database is locked.
685685+ '';
686686+ default = 5;
687687+ type = types.int;
688688+ };
689689+690690+ # TODO Add "instrument_queries" option when upgrading to grafana 10.0
691691+ # instrument_queries = mkOption {
692692+ # description = lib.mdDoc "Set to `true` to add metrics and tracing for database queries.";
693693+ # default = false;
694694+ # type = types.bool;
695695+ # };
468696 };
469697470698 security = {
699699+ disable_initial_admin_creation = mkOption {
700700+ description = lib.mdDoc "Disable creation of admin user on first start of Grafana.";
701701+ default = false;
702702+ type = types.bool;
703703+ };
704704+471705 admin_user = mkOption {
472706 description = lib.mdDoc "Default admin username.";
473707 default = "admin";
···486720 type = types.str;
487721 };
488722723723+ admin_email = mkOption {
724724+ description = lib.mdDoc "The email of the default Grafana Admin, created on startup.";
725725+ default = "admin@localhost";
726726+ type = types.str;
727727+ };
728728+489729 secret_key = mkOption {
490730 description = lib.mdDoc ''
491731 Secret key used for signing. Please note that the contents of this option
···497737 default = "SW2YcwTIb9zpOOhoPsMm";
498738 type = types.str;
499739 };
740740+741741+ disable_gravatar = mkOption {
742742+ description = lib.mdDoc "Set to `true` to disable the use of Gravatar for user profile images.";
743743+ default = false;
744744+ type = types.bool;
745745+ };
746746+747747+ data_source_proxy_whitelist = mkOption {
748748+ description = lib.mdDoc ''
749749+ Define a whitelist of allowed IP addresses or domains, with ports,
750750+ to be used in data source URLs with the Grafana data source proxy.
751751+ Format: `ip_or_domain:port` separated by spaces.
752752+ PostgreSQL, MySQL, and MSSQL data sources do not use the proxy and are therefore unaffected by this setting.
753753+ '';
754754+ default = "";
755755+ type = types.str;
756756+ };
757757+758758+ disable_brute_force_login_protection = mkOption {
759759+ description = lib.mdDoc "Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout).";
760760+ default = false;
761761+ type = types.bool;
762762+ };
763763+764764+ cookie_secure = mkOption {
765765+ description = lib.mdDoc "Set to `true` if you host Grafana behind HTTPS.";
766766+ default = false;
767767+ type = types.bool;
768768+ };
769769+770770+ cookie_samesite = mkOption {
771771+ description = lib.mdDoc ''
772772+ Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests.
773773+ The main goal is to mitigate the risk of cross-origin information leakage.
774774+ This setting also provides some protection against cross-site request forgery attacks (CSRF),
775775+ [read more about SameSite here](https://owasp.org/www-community/SameSite).
776776+ Using value `disabled` does not add any `SameSite` attribute to cookies.
777777+ '';
778778+ default = "lax";
779779+ type = types.enum [ "lax" "strict" "none" "disabled" ];
780780+ };
781781+782782+ allow_embedding = mkOption {
783783+ description = lib.mdDoc ''
784784+ When `false`, the HTTP header `X-Frame-Options: deny` will be set in Grafana HTTP responses
785785+ which will instruct browsers to not allow rendering Grafana in a `<frame>`, `<iframe>`, `<embed>` or `<object>`.
786786+ The main goal is to mitigate the risk of [Clickjacking](https://owasp.org/www-community/attacks/Clickjacking).
787787+ '';
788788+ default = false;
789789+ type = types.bool;
790790+ };
791791+792792+ strict_transport_security = mkOption {
793793+ description = lib.mdDoc ''
794794+ Set to `true` if you want to enable HTTP `Strict-Transport-Security` (HSTS) response header.
795795+ Only use this when HTTPS is enabled in your configuration,
796796+ or when there is another upstream system that ensures your application does HTTPS (like a frontend load balancer).
797797+ HSTS tells browsers that the site should only be accessed using HTTPS.
798798+ '';
799799+ default = false;
800800+ type = types.bool;
801801+ };
802802+803803+ strict_transport_security_max_age_seconds = mkOption {
804804+ description = lib.mdDoc ''
805805+ Sets how long a browser should cache HSTS in seconds.
806806+ Only applied if `strict_transport_security` is enabled.
807807+ '';
808808+ default = 86400;
809809+ type = types.int;
810810+ };
811811+812812+ strict_transport_security_preload = mkOption {
813813+ description = lib.mdDoc ''
814814+ Set to `true` to enable HSTS `preloading` option.
815815+ Only applied if `strict_transport_security` is enabled.
816816+ '';
817817+ default = false;
818818+ type = types.bool;
819819+ };
820820+821821+ strict_transport_security_subdomains = mkOption {
822822+ description = lib.mdDoc ''
823823+ Set to `true` to enable HSTS `includeSubDomains` option.
824824+ Only applied if `strict_transport_security` is enabled.
825825+ '';
826826+ default = false;
827827+ type = types.bool;
828828+ };
829829+830830+ x_content_type_options = mkOption {
831831+ description = lib.mdDoc ''
832832+ Set to `false` to disable the `X-Content-Type-Options` response header.
833833+ The `X-Content-Type-Options` response HTTP header is a marker used by the server
834834+ to indicate that the MIME types advertised in the `Content-Type` headers should not be changed and be followed.
835835+ '';
836836+ default = true;
837837+ type = types.bool;
838838+ };
839839+840840+ x_xss_protection = mkOption {
841841+ description = lib.mdDoc ''
842842+ Set to `false` to disable the `X-XSS-Protection` header,
843843+ which tells browsers to stop pages from loading when they detect reflected cross-site scripting (XSS) attacks.
844844+ '';
845845+ default = true;
846846+ type = types.bool;
847847+ };
848848+849849+ content_security_policy = mkOption {
850850+ description = lib.mdDoc ''
851851+ Set to `true` to add the `Content-Security-Policy` header to your requests.
852852+ CSP allows to control resources that the user agent can load and helps prevent XSS attacks.
853853+ '';
854854+ default = false;
855855+ type = types.bool;
856856+ };
857857+858858+ content_security_policy_report_only = mkOption {
859859+ description = lib.mdDoc ''
860860+ Set to `true` to add the `Content-Security-Policy-Report-Only` header to your requests.
861861+ CSP in Report Only mode enables you to experiment with policies by monitoring their effects without enforcing them.
862862+ You can enable both policies simultaneously.
863863+ '';
864864+ default = false;
865865+ type = types.bool;
866866+ };
867867+868868+ # The options content_security_policy_template and
869869+ # content_security_policy_template are missing because I'm not sure
870870+ # how exactly the quoting of the default value works. See also
871871+ # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L364
872872+ # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L373
500873 };
501874502875 smtp = {
···505878 default = false;
506879 type = types.bool;
507880 };
881881+508882 host = mkOption {
509883 description = lib.mdDoc "Host to connect to.";
510884 default = "localhost:25";
511885 type = types.str;
512886 };
887887+513888 user = mkOption {
514889 description = lib.mdDoc "User used for authentication.";
515890 default = "";
516891 type = types.str;
517892 };
893893+518894 password = mkOption {
519895 description = lib.mdDoc ''
520896 Password used for authentication. Please note that the contents of this option
···526902 default = "";
527903 type = types.str;
528904 };
905905+906906+ cert_file = mkOption {
907907+ description = lib.mdDoc "File path to a cert file.";
908908+ default = "";
909909+ type = types.str;
910910+ };
911911+912912+ key_file = mkOption {
913913+ description = lib.mdDoc "File path to a key file.";
914914+ default = "";
915915+ type = types.str;
916916+ };
917917+918918+ skip_verify = mkOption {
919919+ description = lib.mdDoc "Verify SSL for SMTP server.";
920920+ default = false;
921921+ type = types.bool;
922922+ };
923923+529924 from_address = mkOption {
530530- description = lib.mdDoc "Email address used for sending.";
925925+ description = lib.mdDoc "Address used when sending out emails.";
531926 default = "admin@grafana.localhost";
532927 type = types.str;
533928 };
929929+930930+ from_name = mkOption {
931931+ description = lib.mdDoc "Name to be used as client identity for EHLO in SMTP dialog.";
932932+ default = "Grafana";
933933+ type = types.str;
934934+ };
935935+936936+ startTLS_policy = mkOption {
937937+ description = lib.mdDoc "StartTLS policy when connecting to server.";
938938+ default = null;
939939+ type = types.nullOr (types.enum [ "OpportunisticStartTLS" "MandatoryStartTLS" "NoStartTLS" ]);
940940+ };
534941 };
535942536943 users = {
537944 allow_sign_up = mkOption {
538538- description = lib.mdDoc "Disable user signup / registration.";
945945+ description = lib.mdDoc ''
946946+ Set to false to prohibit users from being able to sign up / create user accounts.
947947+ The admin user can still create users.
948948+ '';
539949 default = false;
540950 type = types.bool;
541951 };
542952543953 allow_org_create = mkOption {
544544- description = lib.mdDoc "Whether user is allowed to create organizations.";
954954+ description = lib.mdDoc "Set to `false` to prohibit users from creating new organizations.";
545955 default = false;
546956 type = types.bool;
547957 };
548958549959 auto_assign_org = mkOption {
550550- description = lib.mdDoc "Whether to automatically assign new users to default org.";
960960+ description = lib.mdDoc ''
961961+ Set to `true` to automatically add new users to the main organization (id 1).
962962+ When set to `false,` new users automatically cause a new organization to be created for that new user.
963963+ The organization will be created even if the `allow_org_create` setting is set to `false`.
964964+ '';
551965 default = true;
552966 type = types.bool;
553967 };
554968969969+ auto_assign_org_id = mkOption {
970970+ description = lib.mdDoc ''
971971+ Set this value to automatically add new users to the provided org.
972972+ This requires `auto_assign_org` to be set to `true`.
973973+ Please make sure that this organization already exists.
974974+ '';
975975+ default = 1;
976976+ type = types.int;
977977+ };
978978+555979 auto_assign_org_role = mkOption {
556556- description = lib.mdDoc "Default role new users will be auto assigned.";
980980+ description = lib.mdDoc ''
981981+ The role new users will be assigned for the main organization (if the `auto_assign_org` setting is set to `true`).
982982+ '';
557983 default = "Viewer";
558558- type = types.enum ["Viewer" "Editor" "Admin"];
984984+ type = types.enum [ "Viewer" "Editor" "Admin" ];
985985+ };
986986+987987+ verify_email_enabled = mkOption {
988988+ description = lib.mdDoc "Require email validation before sign up completes.";
989989+ default = false;
990990+ type = types.bool;
991991+ };
992992+993993+ login_hint = mkOption {
994994+ description = lib.mdDoc "Text used as placeholder text on login page for login/username input.";
995995+ default = "email or username";
996996+ type = types.str;
997997+ };
998998+999999+ password_hint = mkOption {
10001000+ description = lib.mdDoc "Text used as placeholder text on login page for password input.";
10011001+ default = "password";
10021002+ type = types.str;
10031003+ };
10041004+10051005+ default_theme = mkOption {
10061006+ description = lib.mdDoc "Sets the default UI theme. `system` matches the user's system theme.";
10071007+ default = "dark";
10081008+ type = types.enum [ "dark" "light" "system" ];
10091009+ };
10101010+10111011+ default_language = mkOption {
10121012+ description = lib.mdDoc "This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`.";
10131013+ default = "en-US";
10141014+ type = types.str;
10151015+ };
10161016+10171017+ home_page = mkOption {
10181018+ description = lib.mdDoc ''
10191019+ Path to a custom home page.
10201020+ Users are only redirected to this if the default home dashboard is used.
10211021+ It should match a frontend route and contain a leading slash.
10221022+ '';
10231023+ default = "";
10241024+ type = types.str;
10251025+ };
10261026+10271027+ viewers_can_edit = mkOption {
10281028+ description = lib.mdDoc ''
10291029+ Viewers can access and use Explore and perform temporary edits on panels in dashboards they have access to.
10301030+ They cannot save their changes.
10311031+ '';
10321032+ default = false;
10331033+ type = types.bool;
10341034+ };
10351035+10361036+ editors_can_admin = mkOption {
10371037+ description = lib.mdDoc "Editors can administrate dashboards, folders and teams they create.";
10381038+ default = false;
10391039+ type = types.bool;
10401040+ };
10411041+10421042+ user_invite_max_lifetime_duration = mkOption {
10431043+ description = lib.mdDoc ''
10441044+ The duration in time a user invitation remains valid before expiring.
10451045+ This setting should be expressed as a duration.
10461046+ Examples: `6h` (hours), `2d` (days), `1w` (week).
10471047+ The minimum supported duration is `15m` (15 minutes).
10481048+ '';
10491049+ default = "24h";
10501050+ type = types.str;
10511051+ };
10521052+10531053+ hidden_users = mkOption {
10541054+ description = lib.mdDoc ''
10551055+ This is a comma-separated list of usernames.
10561056+ Users specified here are hidden in the Grafana UI.
10571057+ They are still visible to Grafana administrators and to themselves.
10581058+ '';
10591059+ default = "";
10601060+ type = types.str;
5591061 };
5601062 };
5611063562562- analytics.reporting_enabled = mkOption {
563563- description = lib.mdDoc "Whether to allow anonymous usage reporting to stats.grafana.net.";
564564- default = true;
565565- type = types.bool;
10641064+ analytics = {
10651065+ reporting_enabled = mkOption {
10661066+ description = lib.mdDoc ''
10671067+ When enabled Grafana will send anonymous usage statistics to `stats.grafana.org`.
10681068+ No IP addresses are being tracked, only simple counters to track running instances, versions, dashboard and error counts.
10691069+ Counters are sent every 24 hours.
10701070+ '';
10711071+ default = true;
10721072+ type = types.bool;
10731073+ };
10741074+10751075+ check_for_updates = mkOption {
10761076+ description = lib.mdDoc ''
10771077+ When set to `false`, disables checking for new versions of Grafana from Grafana's GitHub repository.
10781078+ When enabled, the check for a new version runs every 10 minutes.
10791079+ It will notify, via the UI, when a new version is available.
10801080+ The check itself will not prompt any auto-updates of the Grafana software, nor will it send any sensitive information.
10811081+ '';
10821082+ default = true;
10831083+ type = types.bool;
10841084+ };
10851085+10861086+ check_for_plugin_updates = mkOption {
10871087+ description = lib.mdDoc ''
10881088+ When set to `false`, disables checking for new versions of installed plugins from https://grafana.com.
10891089+ When enabled, the check for a new plugin runs every 10 minutes.
10901090+ It will notify, via the UI, when a new plugin update exists.
10911091+ The check itself will not prompt any auto-updates of the plugin, nor will it send any sensitive information.
10921092+ '';
10931093+ default = cfg.declarativePlugins == null;
10941094+ defaultText = literalExpression "cfg.declarativePlugins == null";
10951095+ type = types.bool;
10961096+ };
10971097+10981098+ feedback_links_enabled = mkOption {
10991099+ description = lib.mdDoc "Set to `false` to remove all feedback links from the UI.";
11001100+ default = true;
11011101+ type = types.bool;
11021102+ };
5661103 };
5671104 };
5681105 };
···5751112 description = lib.mdDoc ''
5761113 Declaratively provision Grafana's datasources.
5771114 '';
578578- default = {};
11151115+ default = { };
5791116 type = submodule' {
5801117 options.settings = mkOption {
5811118 description = lib.mdDoc ''
···59511325961133 datasources = mkOption {
5971134 description = lib.mdDoc "List of datasources to insert/update.";
598598- default = [];
11351135+ default = [ ];
5991136 type = types.listOf grafanaTypes.datasourceConfig;
6001137 };
60111386021139 deleteDatasources = mkOption {
6031140 description = lib.mdDoc "List of datasources that should be deleted from the database.";
604604- default = [];
11411141+ default = [ ];
6051142 type = types.listOf (types.submodule {
6061143 options.name = mkOption {
6071144 description = lib.mdDoc "Name of the datasource to delete.";
···6501187 description = lib.mdDoc ''
6511188 Declaratively provision Grafana's dashboards.
6521189 '';
653653- default = {};
11901190+ default = { };
6541191 type = submodule' {
6551192 options.settings = mkOption {
6561193 description = lib.mdDoc ''
···66912066701207 options.providers = mkOption {
6711208 description = lib.mdDoc "List of dashboards to insert/update.";
672672- default = [];
12091209+ default = [ ];
6731210 type = types.listOf grafanaTypes.dashboardConfig;
6741211 };
6751212 });
···70012377011238 notifiers = mkOption {
7021239 description = lib.mdDoc "Grafana notifier configuration.";
703703- default = [];
12401240+ default = [ ];
7041241 type = types.listOf grafanaTypes.notifierConfig;
7051242 apply = x: map _filter x;
7061243 };
···73612737371274 groups = mkOption {
7381275 description = lib.mdDoc "List of rule groups to import or update.";
739739- default = [];
12761276+ default = [ ];
7401277 type = types.listOf (types.submodule {
7411278 freeformType = provisioningSettingsFormat.type;
7421279···75912967601297 deleteRules = mkOption {
7611298 description = lib.mdDoc "List of alert rule UIDs that should be deleted.";
762762- default = [];
12991299+ default = [ ];
7631300 type = types.listOf (types.submodule {
7641301 options.orgId = mkOption {
7651302 description = lib.mdDoc "Organization ID, default = 1";
···86013978611398 contactPoints = mkOption {
8621399 description = lib.mdDoc "List of contact points to import or update.";
863863- default = [];
14001400+ default = [ ];
8641401 type = types.listOf (types.submodule {
8651402 freeformType = provisioningSettingsFormat.type;
8661403···87314108741411 deleteContactPoints = mkOption {
8751412 description = lib.mdDoc "List of receivers that should be deleted.";
876876- default = [];
14131413+ default = [ ];
8771414 type = types.listOf (types.submodule {
8781415 options.orgId = mkOption {
8791416 description = lib.mdDoc "Organization ID, default = 1.";
···94114789421479 policies = mkOption {
9431480 description = lib.mdDoc "List of contact points to import or update.";
944944- default = [];
14811481+ default = [ ];
9451482 type = types.listOf (types.submodule {
9461483 freeformType = provisioningSettingsFormat.type;
9471484 });
···94914869501487 resetPolicies = mkOption {
9511488 description = lib.mdDoc "List of orgIds that should be reset to the default policy.";
952952- default = [];
14891489+ default = [ ];
9531490 type = types.listOf types.int;
9541491 };
9551492 };
···1011154810121549 templates = mkOption {
10131550 description = lib.mdDoc "List of templates to import or update.";
10141014- default = [];
15511551+ default = [ ];
10151552 type = types.listOf (types.submodule {
10161553 freeformType = provisioningSettingsFormat.type;
10171554···1029156610301567 deleteTemplates = mkOption {
10311568 description = lib.mdDoc "List of alert rule UIDs that should be deleted.";
10321032- default = [];
15691569+ default = [ ];
10331570 type = types.listOf (types.submodule {
10341571 options.orgId = mkOption {
10351572 description = lib.mdDoc "Organization ID, default = 1.";
···1093163010941631 muteTimes = mkOption {
10951632 description = lib.mdDoc "List of mute time intervals to import or update.";
10961096- default = [];
16331633+ default = [ ];
10971634 type = types.listOf (types.submodule {
10981635 freeformType = provisioningSettingsFormat.type;
10991636···1106164311071644 deleteMuteTimes = mkOption {
11081645 description = lib.mdDoc "List of mute time intervals that should be deleted.";
11091109- default = [];
16461646+ default = [ ];
11101647 type = types.listOf (types.submodule {
11111648 options.orgId = mkOption {
11121649 description = lib.mdDoc "Organization ID, default = 1.";
···11681705 };
1169170611701707 config = mkIf cfg.enable {
11711171- warnings = let
11721172- doesntUseFileProvider = opt: defaultValue:
11731173- let
11741174- regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$";
11751175- in builtins.match regex opt == null;
11761176- in
11771177- # Ensure that no custom credentials are leaked into the Nix store. Unless the default value
11781178- # is specified, this can be achieved by using the file/env provider:
11791179- # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
11801180- (optional (
11811181- doesntUseFileProvider cfg.settings.database.password "" ||
11821182- doesntUseFileProvider cfg.settings.security.admin_password "admin"
11831183- ) ''
11841184- Grafana passwords will be stored as plaintext in the Nix store!
11851185- Use file provider or an env-var instead.
11861186- '')
11871187- # Warn about deprecated notifiers.
11881188- ++ (optional (cfg.provision.notifiers != []) ''
11891189- Notifiers are deprecated upstream and will be removed in Grafana 10.
11901190- Use `services.grafana.provision.alerting.contactPoints` instead.
11911191- '')
11921192- # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
11931193- # only uses file/env providers.
11941194- ++ (optional (
11951195- let
11961196- datasourcesToCheck = optionals
11971197- (cfg.provision.datasources.settings != null)
11981198- cfg.provision.datasources.settings.datasources;
11991199- declarationUnsafe = { secureJsonData, ... }:
12001200- secureJsonData != null
12011201- && any (flip doesntUseFileProvider null) (attrValues secureJsonData);
12021202- in any declarationUnsafe datasourcesToCheck
12031203- ) ''
12041204- Declarations in the `secureJsonData`-block of a datasource will be leaked to the
12051205- Nix store unless a file-provider or an env-var is used!
12061206- '')
12071207- ++ (optional (
12081208- any (x: x.secure_settings != null) cfg.provision.notifiers
12091209- ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.");
17081708+ warnings =
17091709+ let
17101710+ doesntUseFileProvider = opt: defaultValue:
17111711+ let regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$";
17121712+ in builtins.match regex opt == null;
17131713+17141714+ # Ensure that no custom credentials are leaked into the Nix store. Unless the default value
17151715+ # is specified, this can be achieved by using the file/env provider:
17161716+ # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
17171717+ passwordWithoutFileProvider = optional
17181718+ (
17191719+ doesntUseFileProvider cfg.settings.database.password "" ||
17201720+ doesntUseFileProvider cfg.settings.security.admin_password "admin"
17211721+ )
17221722+ ''
17231723+ Grafana passwords will be stored as plaintext in the Nix store!
17241724+ Use file provider or an env-var instead.
17251725+ '';
17261726+17271727+ # Warn about deprecated notifiers.
17281728+ deprecatedNotifiers = optional (cfg.provision.notifiers != [ ]) ''
17291729+ Notifiers are deprecated upstream and will be removed in Grafana 10.
17301730+ Use `services.grafana.provision.alerting.contactPoints` instead.
17311731+ '';
17321732+17331733+ # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
17341734+ # only uses file/env providers.
17351735+ secureJsonDataWithoutFileProvider = optional
17361736+ (
17371737+ let
17381738+ datasourcesToCheck = optionals
17391739+ (cfg.provision.datasources.settings != null)
17401740+ cfg.provision.datasources.settings.datasources;
17411741+ declarationUnsafe = { secureJsonData, ... }:
17421742+ secureJsonData != null
17431743+ && any (flip doesntUseFileProvider null) (attrValues secureJsonData);
17441744+ in
17451745+ any declarationUnsafe datasourcesToCheck
17461746+ )
17471747+ ''
17481748+ Declarations in the `secureJsonData`-block of a datasource will be leaked to the
17491749+ Nix store unless a file-provider or an env-var is used!
17501750+ '';
17511751+17521752+ notifierSecureSettingsWithoutFileProvider = optional
17531753+ (any (x: x.secure_settings != null) cfg.provision.notifiers)
17541754+ "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.";
17551755+ in
17561756+ passwordWithoutFileProvider
17571757+ ++ deprecatedNotifiers
17581758+ ++ secureJsonDataWithoutFileProvider
17591759+ ++ notifierSecureSettingsWithoutFileProvider;
1210176012111761 environment.systemPackages = [ cfg.package ];
12121762···12161766 message = "Cannot set both datasources settings and datasources path";
12171767 }
12181768 {
12191219- assertion = let
12201220- prometheusIsNotDirect = opt: all
12211221- ({ type, access, ... }: type == "prometheus" -> access != "direct")
12221222- opt;
12231223- in
17691769+ assertion =
17701770+ let
17711771+ prometheusIsNotDirect = opt: all
17721772+ ({ type, access, ... }: type == "prometheus" -> access != "direct")
17731773+ opt;
17741774+ in
12241775 cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
12251776 message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
12261777 }
···1252180312531804 systemd.services.grafana = {
12541805 description = "Grafana Service Daemon";
12551255- wantedBy = ["multi-user.target"];
12561256- after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
18061806+ wantedBy = [ "multi-user.target" ];
18071807+ after = [ "networking.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
12571808 script = ''
12581809 set -o errexit -o pipefail -o nounset -o errtrace
12591810 shopt -s inherit_errexit
···13091860 createHome = true;
13101861 group = "grafana";
13111862 };
13121312- users.groups.grafana = {};
18631863+ users.groups.grafana = { };
13131864 };
13141865}