Merge pull request #238472 from Garmelon/grafana-config-options

nixos/grafana: Update and add config options

authored by

Pol Dellaiera and committed by
GitHub
604ee3fa 43a2991d

+652 -101
+652 -101
nixos/modules/services/monitoring/grafana.nix
··· 5 5 let 6 6 cfg = config.services.grafana; 7 7 opt = options.services.grafana; 8 - provisioningSettingsFormat = pkgs.formats.yaml {}; 8 + provisioningSettingsFormat = pkgs.formats.yaml { }; 9 9 declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins); 10 10 useMysql = cfg.settings.database.type == "mysql"; 11 11 usePostgresql = cfg.settings.database.type == "postgres"; 12 12 13 - settingsFormatIni = pkgs.formats.ini {}; 13 + settingsFormatIni = pkgs.formats.ini { }; 14 14 configFile = settingsFormatIni.generate "config.ini" cfg.settings; 15 15 16 16 mkProvisionCfg = name: attr: provisionCfg: 17 17 if provisionCfg.path != null 18 - then provisionCfg.path 18 + then provisionCfg.path 19 19 else 20 20 provisioningSettingsFormat.generate "${name}.yaml" 21 21 (if provisionCfg.settings != null 22 - then provisionCfg.settings 23 - else { 24 - apiVersion = 1; 25 - ${attr} = []; 26 - }); 22 + then provisionCfg.settings 23 + else { 24 + apiVersion = 1; 25 + ${attr} = [ ]; 26 + }); 27 27 28 28 datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources; 29 29 dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards; ··· 35 35 36 36 notifierFileOrDir = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); 37 37 38 - generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null) 39 - then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings 40 - else cfg.provision.alerting."${x}".path; 38 + generateAlertingProvisioningYaml = x: 39 + if (cfg.provision.alerting."${x}".path == null) 40 + then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings 41 + else cfg.provision.alerting."${x}".path; 41 42 rulesFileOrDir = generateAlertingProvisioningYaml "rules"; 42 43 contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints"; 43 44 policiesFileOrDir = generateAlertingProvisioningYaml "policies"; ··· 102 103 description = lib.mdDoc "Datasource type. Required."; 103 104 }; 104 105 access = mkOption { 105 - type = types.enum ["proxy" "direct"]; 106 + type = types.enum [ "proxy" "direct" ]; 106 107 default = "proxy"; 107 108 description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required."; 108 109 }; ··· 170 171 description = lib.mdDoc "Notifier name."; 171 172 }; 172 173 type = mkOption { 173 - type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"]; 174 + type = types.enum [ "dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook" ]; 174 175 description = lib.mdDoc "Notifier type."; 175 176 }; 176 177 uid = mkOption { ··· 225 226 }; 226 227 }; 227 228 }; 228 - in { 229 + in 230 + { 229 231 imports = [ 230 232 (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ]) 231 233 (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ]) ··· 354 356 protocol = mkOption { 355 357 description = lib.mdDoc "Which protocol to listen."; 356 358 default = "http"; 357 - type = types.enum ["http" "https" "h2" "socket"]; 359 + type = types.enum [ "http" "https" "h2" "socket" ]; 358 360 }; 359 361 360 362 http_addr = mkOption { ··· 376 378 }; 377 379 378 380 domain = mkOption { 379 - description = lib.mdDoc "The public facing domain name used to access grafana from a browser."; 381 + description = lib.mdDoc '' 382 + The public facing domain name used to access grafana from a browser. 383 + 384 + This setting is only used in the default value of the `root_url` setting. 385 + If you set the latter manually, this option does not have to be specified. 386 + ''; 380 387 default = "localhost"; 381 388 type = types.str; 389 + }; 390 + 391 + enforce_domain = mkOption { 392 + description = lib.mdDoc '' 393 + Redirect to correct domain if the host header does not match the domain. 394 + Prevents DNS rebinding attacks. 395 + ''; 396 + default = false; 397 + type = types.bool; 382 398 }; 383 399 384 400 root_url = mkOption { 385 - description = lib.mdDoc "Full public facing url."; 401 + description = lib.mdDoc '' 402 + This is the full URL used to access Grafana from a web browser. 403 + This is important if you use Google or GitHub OAuth authentication (for the callback URL to be correct). 404 + 405 + This setting is also important if you have a reverse proxy in front of Grafana that exposes it through a subpath. 406 + In that case add the subpath to the end of this URL setting. 407 + ''; 408 + # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L54 386 409 default = "%(protocol)s://%(domain)s:%(http_port)s/"; 387 410 type = types.str; 388 411 }; 389 412 413 + serve_from_sub_path = mkOption { 414 + description = lib.mdDoc '' 415 + Serve Grafana from subpath specified in the `root_url` setting. 416 + By default it is set to `false` for compatibility reasons. 417 + 418 + By enabling this setting and using a subpath in `root_url` above, 419 + e.g. `root_url = "http://localhost:3000/grafana"`, 420 + Grafana is accessible on `http://localhost:3000/grafana`. 421 + If accessed without subpath, Grafana will redirect to an URL with the subpath. 422 + ''; 423 + default = false; 424 + type = types.bool; 425 + }; 426 + 427 + router_logging = mkOption { 428 + description = lib.mdDoc '' 429 + Set to `true` for Grafana to log all HTTP requests (not just errors). 430 + These are logged as Info level events to the Grafana log. 431 + ''; 432 + default = false; 433 + type = types.bool; 434 + }; 435 + 390 436 static_root_path = mkOption { 391 437 description = lib.mdDoc "Root path for static assets."; 392 438 default = "${cfg.package}/share/grafana/public"; ··· 396 442 397 443 enable_gzip = mkOption { 398 444 description = lib.mdDoc '' 399 - Set this option to true to enable HTTP compression, this can improve transfer speed and bandwidth utilization. 400 - It is recommended that most users set it to true. By default it is set to false for compatibility reasons. 445 + Set this option to `true` to enable HTTP compression, this can improve transfer speed and bandwidth utilization. 446 + It is recommended that most users set it to `true`. By default it is set to `false` for compatibility reasons. 401 447 ''; 402 448 default = false; 403 449 type = types.bool; 404 450 }; 405 451 406 452 cert_file = mkOption { 407 - description = lib.mdDoc "Cert file for ssl."; 453 + description = lib.mdDoc '' 454 + Path to the certificate file (if `protocol` is set to `https` or `h2`). 455 + ''; 408 456 default = ""; 409 457 type = types.str; 410 458 }; 411 459 412 460 cert_key = mkOption { 413 - description = lib.mdDoc "Cert key for ssl."; 461 + description = lib.mdDoc '' 462 + Path to the certificate key file (if `protocol` is set to `https` or `h2`). 463 + ''; 414 464 default = ""; 415 465 type = types.str; 416 466 }; 417 467 468 + socket_gid = mkOption { 469 + description = lib.mdDoc '' 470 + GID where the socket should be set when `protocol=socket`. 471 + 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. 472 + It is recommended to set the gid as http server user gid. 473 + Not set when the value is -1. 474 + ''; 475 + default = -1; 476 + type = types.int; 477 + }; 478 + 479 + socket_mode = mkOption { 480 + description = lib.mdDoc '' 481 + Mode where the socket should be set when `protocol=socket`. 482 + Make sure that Grafana process is the file owner before you change this setting. 483 + ''; 484 + # I assume this value is interpreted as octal literal by grafana. 485 + # If this was an int, people following tutorials or porting their 486 + # old config could stumble across nix not having octal literals. 487 + default = "0660"; 488 + type = types.str; 489 + }; 490 + 418 491 socket = mkOption { 419 - 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."; 492 + description = lib.mdDoc '' 493 + Path where the socket should be created when `protocol=socket`. 494 + Make sure that Grafana has appropriate permissions before you change this setting. 495 + ''; 420 496 default = "/run/grafana/grafana.sock"; 497 + type = types.str; 498 + }; 499 + 500 + cdn_url = mkOption { 501 + description = lib.mdDoc '' 502 + Specify a full HTTP URL address to the root of your Grafana CDN assets. 503 + Grafana will add edition and version paths. 504 + 505 + For example, given a cdn url like `https://cdn.myserver.com` 506 + grafana will try to load a javascript file from `http://cdn.myserver.com/grafana-oss/7.4.0/public/build/app.<hash>.js`. 507 + ''; 508 + default = ""; 509 + type = types.str; 510 + }; 511 + 512 + read_timeout = mkOption { 513 + description = lib.mdDoc '' 514 + Sets the maximum time using a duration format (5s/5m/5ms) 515 + before timing out read of an incoming request and closing idle connections. 516 + 0 means there is no timeout for reading the request. 517 + ''; 518 + default = "0"; 421 519 type = types.str; 422 520 }; 423 521 }; ··· 426 524 type = mkOption { 427 525 description = lib.mdDoc "Database type."; 428 526 default = "sqlite3"; 429 - type = types.enum ["mysql" "sqlite3" "postgres"]; 527 + type = types.enum [ "mysql" "sqlite3" "postgres" ]; 430 528 }; 431 529 432 530 host = mkOption { 433 - description = lib.mdDoc "Database host."; 531 + description = lib.mdDoc '' 532 + Only applicable to MySQL or Postgres. 533 + Includes IP or hostname and port or in case of Unix sockets the path to it. 534 + For example, for MySQL running on the same host as Grafana: `host = "127.0.0.1:3306"` 535 + or with Unix sockets: `host = "/var/run/mysqld/mysqld.sock"` 536 + ''; 434 537 default = "127.0.0.1:3306"; 435 538 type = types.str; 436 539 }; 437 540 438 541 name = mkOption { 439 - description = lib.mdDoc "Database name."; 542 + description = lib.mdDoc "The name of the Grafana database."; 440 543 default = "grafana"; 441 544 type = types.str; 442 545 }; 443 546 444 547 user = mkOption { 445 - description = lib.mdDoc "Database user."; 548 + description = lib.mdDoc "The database user (not applicable for `sqlite3`)."; 446 549 default = "root"; 447 550 type = types.str; 448 551 }; 449 552 450 553 password = mkOption { 451 554 description = lib.mdDoc '' 452 - Database password. Please note that the contents of this option 555 + The database user's password (not applicable for `sqlite3`). 556 + 557 + Please note that the contents of this option 453 558 will end up in a world-readable Nix store. Use the file provider 454 559 pointing at a reasonably secured file in the local filesystem 455 560 to work around that. Look at the documentation for details: ··· 459 564 type = types.str; 460 565 }; 461 566 567 + max_idle_conn = mkOption { 568 + description = lib.mdDoc "The maximum number of connections in the idle connection pool."; 569 + default = 2; 570 + type = types.int; 571 + }; 572 + 573 + max_open_conn = mkOption { 574 + description = lib.mdDoc "The maximum number of open connections to the database."; 575 + default = 0; # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L123-L124 576 + type = types.int; 577 + }; 578 + 579 + conn_max_lifetime = mkOption { 580 + description = lib.mdDoc '' 581 + Sets the maximum amount of time a connection may be reused. 582 + The default is 14400 (which means 14400 seconds or 4 hours). 583 + For MySQL, this setting should be shorter than the `wait_timeout` variable. 584 + ''; 585 + default = 14400; 586 + type = types.int; 587 + }; 588 + 589 + locking_attempt_timeout_sec = mkOption { 590 + description = lib.mdDoc '' 591 + For `mysql`, if the `migrationLocking` feature toggle is set, 592 + specify the time (in seconds) to wait before failing to lock the database for the migrations. 593 + ''; 594 + default = 0; 595 + type = types.int; 596 + }; 597 + 598 + log_queries = mkOption { 599 + description = lib.mdDoc "Set to `true` to log the sql calls and execution times"; 600 + default = false; 601 + type = types.bool; 602 + }; 603 + 604 + ssl_mode = mkOption { 605 + description = lib.mdDoc '' 606 + For Postgres, use either `disable`, `require` or `verify-full`. 607 + For MySQL, use either `true`, `false`, or `skip-verify`. 608 + ''; 609 + default = "disable"; # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L134 610 + type = types.enum [ "disable" "require" "verify-full" "true" "false" "skip-verify" ]; 611 + }; 612 + 613 + isolation_level = mkOption { 614 + description = lib.mdDoc '' 615 + Only the MySQL driver supports isolation levels in Grafana. 616 + In case the value is empty, the driver's default isolation level is applied. 617 + ''; 618 + default = null; 619 + type = types.nullOr (types.enum [ "READ-UNCOMMITTED" "READ-COMMITTED" "REPEATABLE-READ" "SERIALIZABLE" ]); 620 + }; 621 + 622 + ca_cert_path = mkOption { 623 + description = lib.mdDoc "The path to the CA certificate to use."; 624 + default = ""; 625 + type = types.str; 626 + }; 627 + 628 + client_key_path = mkOption { 629 + description = lib.mdDoc "The path to the client key. Only if server requires client authentication."; 630 + default = ""; 631 + type = types.str; 632 + }; 633 + 634 + client_cert_path = mkOption { 635 + description = lib.mdDoc "The path to the client cert. Only if server requires client authentication."; 636 + default = ""; 637 + type = types.str; 638 + }; 639 + 640 + server_cert_name = mkOption { 641 + description = lib.mdDoc '' 642 + The common name field of the certificate used by the `mysql` or `postgres` server. 643 + Not necessary if `ssl_mode` is set to `skip-verify`. 644 + ''; 645 + default = ""; 646 + type = types.str; 647 + }; 648 + 462 649 path = mkOption { 463 - description = lib.mdDoc "Only applicable to sqlite3 database. The file path where the database will be stored."; 650 + description = lib.mdDoc "Only applicable to `sqlite3` database. The file path where the database will be stored."; 464 651 default = "${cfg.dataDir}/data/grafana.db"; 465 652 defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"''; 466 653 type = types.path; 467 654 }; 655 + 656 + cache_mode = mkOption { 657 + description = lib.mdDoc '' 658 + For `sqlite3` only. 659 + [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database. 660 + ''; 661 + default = "private"; 662 + type = types.enum [ "private" "shared" ]; 663 + }; 664 + 665 + wal = mkOption { 666 + description = lib.mdDoc '' 667 + For `sqlite3` only. 668 + Setting to enable/disable [Write-Ahead Logging](https://sqlite.org/wal.html). 669 + ''; 670 + default = false; 671 + type = types.bool; 672 + }; 673 + 674 + query_retries = mkOption { 675 + description = lib.mdDoc '' 676 + This setting applies to `sqlite3` only and controls the number of times the system retries a query when the database is locked. 677 + ''; 678 + default = 0; 679 + type = types.int; 680 + }; 681 + 682 + transaction_retries = mkOption { 683 + description = lib.mdDoc '' 684 + This setting applies to `sqlite3` only and controls the number of times the system retries a transaction when the database is locked. 685 + ''; 686 + default = 5; 687 + type = types.int; 688 + }; 689 + 690 + # TODO Add "instrument_queries" option when upgrading to grafana 10.0 691 + # instrument_queries = mkOption { 692 + # description = lib.mdDoc "Set to `true` to add metrics and tracing for database queries."; 693 + # default = false; 694 + # type = types.bool; 695 + # }; 468 696 }; 469 697 470 698 security = { 699 + disable_initial_admin_creation = mkOption { 700 + description = lib.mdDoc "Disable creation of admin user on first start of Grafana."; 701 + default = false; 702 + type = types.bool; 703 + }; 704 + 471 705 admin_user = mkOption { 472 706 description = lib.mdDoc "Default admin username."; 473 707 default = "admin"; ··· 486 720 type = types.str; 487 721 }; 488 722 723 + admin_email = mkOption { 724 + description = lib.mdDoc "The email of the default Grafana Admin, created on startup."; 725 + default = "admin@localhost"; 726 + type = types.str; 727 + }; 728 + 489 729 secret_key = mkOption { 490 730 description = lib.mdDoc '' 491 731 Secret key used for signing. Please note that the contents of this option ··· 497 737 default = "SW2YcwTIb9zpOOhoPsMm"; 498 738 type = types.str; 499 739 }; 740 + 741 + disable_gravatar = mkOption { 742 + description = lib.mdDoc "Set to `true` to disable the use of Gravatar for user profile images."; 743 + default = false; 744 + type = types.bool; 745 + }; 746 + 747 + data_source_proxy_whitelist = mkOption { 748 + description = lib.mdDoc '' 749 + Define a whitelist of allowed IP addresses or domains, with ports, 750 + to be used in data source URLs with the Grafana data source proxy. 751 + Format: `ip_or_domain:port` separated by spaces. 752 + PostgreSQL, MySQL, and MSSQL data sources do not use the proxy and are therefore unaffected by this setting. 753 + ''; 754 + default = ""; 755 + type = types.str; 756 + }; 757 + 758 + disable_brute_force_login_protection = mkOption { 759 + description = lib.mdDoc "Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout)."; 760 + default = false; 761 + type = types.bool; 762 + }; 763 + 764 + cookie_secure = mkOption { 765 + description = lib.mdDoc "Set to `true` if you host Grafana behind HTTPS."; 766 + default = false; 767 + type = types.bool; 768 + }; 769 + 770 + cookie_samesite = mkOption { 771 + description = lib.mdDoc '' 772 + Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. 773 + The main goal is to mitigate the risk of cross-origin information leakage. 774 + This setting also provides some protection against cross-site request forgery attacks (CSRF), 775 + [read more about SameSite here](https://owasp.org/www-community/SameSite). 776 + Using value `disabled` does not add any `SameSite` attribute to cookies. 777 + ''; 778 + default = "lax"; 779 + type = types.enum [ "lax" "strict" "none" "disabled" ]; 780 + }; 781 + 782 + allow_embedding = mkOption { 783 + description = lib.mdDoc '' 784 + When `false`, the HTTP header `X-Frame-Options: deny` will be set in Grafana HTTP responses 785 + which will instruct browsers to not allow rendering Grafana in a `<frame>`, `<iframe>`, `<embed>` or `<object>`. 786 + The main goal is to mitigate the risk of [Clickjacking](https://owasp.org/www-community/attacks/Clickjacking). 787 + ''; 788 + default = false; 789 + type = types.bool; 790 + }; 791 + 792 + strict_transport_security = mkOption { 793 + description = lib.mdDoc '' 794 + Set to `true` if you want to enable HTTP `Strict-Transport-Security` (HSTS) response header. 795 + Only use this when HTTPS is enabled in your configuration, 796 + or when there is another upstream system that ensures your application does HTTPS (like a frontend load balancer). 797 + HSTS tells browsers that the site should only be accessed using HTTPS. 798 + ''; 799 + default = false; 800 + type = types.bool; 801 + }; 802 + 803 + strict_transport_security_max_age_seconds = mkOption { 804 + description = lib.mdDoc '' 805 + Sets how long a browser should cache HSTS in seconds. 806 + Only applied if `strict_transport_security` is enabled. 807 + ''; 808 + default = 86400; 809 + type = types.int; 810 + }; 811 + 812 + strict_transport_security_preload = mkOption { 813 + description = lib.mdDoc '' 814 + Set to `true` to enable HSTS `preloading` option. 815 + Only applied if `strict_transport_security` is enabled. 816 + ''; 817 + default = false; 818 + type = types.bool; 819 + }; 820 + 821 + strict_transport_security_subdomains = mkOption { 822 + description = lib.mdDoc '' 823 + Set to `true` to enable HSTS `includeSubDomains` option. 824 + Only applied if `strict_transport_security` is enabled. 825 + ''; 826 + default = false; 827 + type = types.bool; 828 + }; 829 + 830 + x_content_type_options = mkOption { 831 + description = lib.mdDoc '' 832 + Set to `false` to disable the `X-Content-Type-Options` response header. 833 + The `X-Content-Type-Options` response HTTP header is a marker used by the server 834 + to indicate that the MIME types advertised in the `Content-Type` headers should not be changed and be followed. 835 + ''; 836 + default = true; 837 + type = types.bool; 838 + }; 839 + 840 + x_xss_protection = mkOption { 841 + description = lib.mdDoc '' 842 + Set to `false` to disable the `X-XSS-Protection` header, 843 + which tells browsers to stop pages from loading when they detect reflected cross-site scripting (XSS) attacks. 844 + ''; 845 + default = true; 846 + type = types.bool; 847 + }; 848 + 849 + content_security_policy = mkOption { 850 + description = lib.mdDoc '' 851 + Set to `true` to add the `Content-Security-Policy` header to your requests. 852 + CSP allows to control resources that the user agent can load and helps prevent XSS attacks. 853 + ''; 854 + default = false; 855 + type = types.bool; 856 + }; 857 + 858 + content_security_policy_report_only = mkOption { 859 + description = lib.mdDoc '' 860 + Set to `true` to add the `Content-Security-Policy-Report-Only` header to your requests. 861 + CSP in Report Only mode enables you to experiment with policies by monitoring their effects without enforcing them. 862 + You can enable both policies simultaneously. 863 + ''; 864 + default = false; 865 + type = types.bool; 866 + }; 867 + 868 + # The options content_security_policy_template and 869 + # content_security_policy_template are missing because I'm not sure 870 + # how exactly the quoting of the default value works. See also 871 + # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L364 872 + # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L373 500 873 }; 501 874 502 875 smtp = { ··· 505 878 default = false; 506 879 type = types.bool; 507 880 }; 881 + 508 882 host = mkOption { 509 883 description = lib.mdDoc "Host to connect to."; 510 884 default = "localhost:25"; 511 885 type = types.str; 512 886 }; 887 + 513 888 user = mkOption { 514 889 description = lib.mdDoc "User used for authentication."; 515 890 default = ""; 516 891 type = types.str; 517 892 }; 893 + 518 894 password = mkOption { 519 895 description = lib.mdDoc '' 520 896 Password used for authentication. Please note that the contents of this option ··· 526 902 default = ""; 527 903 type = types.str; 528 904 }; 905 + 906 + cert_file = mkOption { 907 + description = lib.mdDoc "File path to a cert file."; 908 + default = ""; 909 + type = types.str; 910 + }; 911 + 912 + key_file = mkOption { 913 + description = lib.mdDoc "File path to a key file."; 914 + default = ""; 915 + type = types.str; 916 + }; 917 + 918 + skip_verify = mkOption { 919 + description = lib.mdDoc "Verify SSL for SMTP server."; 920 + default = false; 921 + type = types.bool; 922 + }; 923 + 529 924 from_address = mkOption { 530 - description = lib.mdDoc "Email address used for sending."; 925 + description = lib.mdDoc "Address used when sending out emails."; 531 926 default = "admin@grafana.localhost"; 532 927 type = types.str; 533 928 }; 929 + 930 + from_name = mkOption { 931 + description = lib.mdDoc "Name to be used as client identity for EHLO in SMTP dialog."; 932 + default = "Grafana"; 933 + type = types.str; 934 + }; 935 + 936 + startTLS_policy = mkOption { 937 + description = lib.mdDoc "StartTLS policy when connecting to server."; 938 + default = null; 939 + type = types.nullOr (types.enum [ "OpportunisticStartTLS" "MandatoryStartTLS" "NoStartTLS" ]); 940 + }; 534 941 }; 535 942 536 943 users = { 537 944 allow_sign_up = mkOption { 538 - description = lib.mdDoc "Disable user signup / registration."; 945 + description = lib.mdDoc '' 946 + Set to false to prohibit users from being able to sign up / create user accounts. 947 + The admin user can still create users. 948 + ''; 539 949 default = false; 540 950 type = types.bool; 541 951 }; 542 952 543 953 allow_org_create = mkOption { 544 - description = lib.mdDoc "Whether user is allowed to create organizations."; 954 + description = lib.mdDoc "Set to `false` to prohibit users from creating new organizations."; 545 955 default = false; 546 956 type = types.bool; 547 957 }; 548 958 549 959 auto_assign_org = mkOption { 550 - description = lib.mdDoc "Whether to automatically assign new users to default org."; 960 + description = lib.mdDoc '' 961 + Set to `true` to automatically add new users to the main organization (id 1). 962 + When set to `false,` new users automatically cause a new organization to be created for that new user. 963 + The organization will be created even if the `allow_org_create` setting is set to `false`. 964 + ''; 551 965 default = true; 552 966 type = types.bool; 553 967 }; 554 968 969 + auto_assign_org_id = mkOption { 970 + description = lib.mdDoc '' 971 + Set this value to automatically add new users to the provided org. 972 + This requires `auto_assign_org` to be set to `true`. 973 + Please make sure that this organization already exists. 974 + ''; 975 + default = 1; 976 + type = types.int; 977 + }; 978 + 555 979 auto_assign_org_role = mkOption { 556 - description = lib.mdDoc "Default role new users will be auto assigned."; 980 + description = lib.mdDoc '' 981 + The role new users will be assigned for the main organization (if the `auto_assign_org` setting is set to `true`). 982 + ''; 557 983 default = "Viewer"; 558 - type = types.enum ["Viewer" "Editor" "Admin"]; 984 + type = types.enum [ "Viewer" "Editor" "Admin" ]; 985 + }; 986 + 987 + verify_email_enabled = mkOption { 988 + description = lib.mdDoc "Require email validation before sign up completes."; 989 + default = false; 990 + type = types.bool; 991 + }; 992 + 993 + login_hint = mkOption { 994 + description = lib.mdDoc "Text used as placeholder text on login page for login/username input."; 995 + default = "email or username"; 996 + type = types.str; 997 + }; 998 + 999 + password_hint = mkOption { 1000 + description = lib.mdDoc "Text used as placeholder text on login page for password input."; 1001 + default = "password"; 1002 + type = types.str; 1003 + }; 1004 + 1005 + default_theme = mkOption { 1006 + description = lib.mdDoc "Sets the default UI theme. `system` matches the user's system theme."; 1007 + default = "dark"; 1008 + type = types.enum [ "dark" "light" "system" ]; 1009 + }; 1010 + 1011 + default_language = mkOption { 1012 + description = lib.mdDoc "This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`."; 1013 + default = "en-US"; 1014 + type = types.str; 1015 + }; 1016 + 1017 + home_page = mkOption { 1018 + description = lib.mdDoc '' 1019 + Path to a custom home page. 1020 + Users are only redirected to this if the default home dashboard is used. 1021 + It should match a frontend route and contain a leading slash. 1022 + ''; 1023 + default = ""; 1024 + type = types.str; 1025 + }; 1026 + 1027 + viewers_can_edit = mkOption { 1028 + description = lib.mdDoc '' 1029 + Viewers can access and use Explore and perform temporary edits on panels in dashboards they have access to. 1030 + They cannot save their changes. 1031 + ''; 1032 + default = false; 1033 + type = types.bool; 1034 + }; 1035 + 1036 + editors_can_admin = mkOption { 1037 + description = lib.mdDoc "Editors can administrate dashboards, folders and teams they create."; 1038 + default = false; 1039 + type = types.bool; 1040 + }; 1041 + 1042 + user_invite_max_lifetime_duration = mkOption { 1043 + description = lib.mdDoc '' 1044 + The duration in time a user invitation remains valid before expiring. 1045 + This setting should be expressed as a duration. 1046 + Examples: `6h` (hours), `2d` (days), `1w` (week). 1047 + The minimum supported duration is `15m` (15 minutes). 1048 + ''; 1049 + default = "24h"; 1050 + type = types.str; 1051 + }; 1052 + 1053 + hidden_users = mkOption { 1054 + description = lib.mdDoc '' 1055 + This is a comma-separated list of usernames. 1056 + Users specified here are hidden in the Grafana UI. 1057 + They are still visible to Grafana administrators and to themselves. 1058 + ''; 1059 + default = ""; 1060 + type = types.str; 559 1061 }; 560 1062 }; 561 1063 562 - analytics.reporting_enabled = mkOption { 563 - description = lib.mdDoc "Whether to allow anonymous usage reporting to stats.grafana.net."; 564 - default = true; 565 - type = types.bool; 1064 + analytics = { 1065 + reporting_enabled = mkOption { 1066 + description = lib.mdDoc '' 1067 + When enabled Grafana will send anonymous usage statistics to `stats.grafana.org`. 1068 + No IP addresses are being tracked, only simple counters to track running instances, versions, dashboard and error counts. 1069 + Counters are sent every 24 hours. 1070 + ''; 1071 + default = true; 1072 + type = types.bool; 1073 + }; 1074 + 1075 + check_for_updates = mkOption { 1076 + description = lib.mdDoc '' 1077 + When set to `false`, disables checking for new versions of Grafana from Grafana's GitHub repository. 1078 + When enabled, the check for a new version runs every 10 minutes. 1079 + It will notify, via the UI, when a new version is available. 1080 + The check itself will not prompt any auto-updates of the Grafana software, nor will it send any sensitive information. 1081 + ''; 1082 + default = true; 1083 + type = types.bool; 1084 + }; 1085 + 1086 + check_for_plugin_updates = mkOption { 1087 + description = lib.mdDoc '' 1088 + When set to `false`, disables checking for new versions of installed plugins from https://grafana.com. 1089 + When enabled, the check for a new plugin runs every 10 minutes. 1090 + It will notify, via the UI, when a new plugin update exists. 1091 + The check itself will not prompt any auto-updates of the plugin, nor will it send any sensitive information. 1092 + ''; 1093 + default = cfg.declarativePlugins == null; 1094 + defaultText = literalExpression "cfg.declarativePlugins == null"; 1095 + type = types.bool; 1096 + }; 1097 + 1098 + feedback_links_enabled = mkOption { 1099 + description = lib.mdDoc "Set to `false` to remove all feedback links from the UI."; 1100 + default = true; 1101 + type = types.bool; 1102 + }; 566 1103 }; 567 1104 }; 568 1105 }; ··· 575 1112 description = lib.mdDoc '' 576 1113 Declaratively provision Grafana's datasources. 577 1114 ''; 578 - default = {}; 1115 + default = { }; 579 1116 type = submodule' { 580 1117 options.settings = mkOption { 581 1118 description = lib.mdDoc '' ··· 595 1132 596 1133 datasources = mkOption { 597 1134 description = lib.mdDoc "List of datasources to insert/update."; 598 - default = []; 1135 + default = [ ]; 599 1136 type = types.listOf grafanaTypes.datasourceConfig; 600 1137 }; 601 1138 602 1139 deleteDatasources = mkOption { 603 1140 description = lib.mdDoc "List of datasources that should be deleted from the database."; 604 - default = []; 1141 + default = [ ]; 605 1142 type = types.listOf (types.submodule { 606 1143 options.name = mkOption { 607 1144 description = lib.mdDoc "Name of the datasource to delete."; ··· 650 1187 description = lib.mdDoc '' 651 1188 Declaratively provision Grafana's dashboards. 652 1189 ''; 653 - default = {}; 1190 + default = { }; 654 1191 type = submodule' { 655 1192 options.settings = mkOption { 656 1193 description = lib.mdDoc '' ··· 669 1206 670 1207 options.providers = mkOption { 671 1208 description = lib.mdDoc "List of dashboards to insert/update."; 672 - default = []; 1209 + default = [ ]; 673 1210 type = types.listOf grafanaTypes.dashboardConfig; 674 1211 }; 675 1212 }); ··· 700 1237 701 1238 notifiers = mkOption { 702 1239 description = lib.mdDoc "Grafana notifier configuration."; 703 - default = []; 1240 + default = [ ]; 704 1241 type = types.listOf grafanaTypes.notifierConfig; 705 1242 apply = x: map _filter x; 706 1243 }; ··· 736 1273 737 1274 groups = mkOption { 738 1275 description = lib.mdDoc "List of rule groups to import or update."; 739 - default = []; 1276 + default = [ ]; 740 1277 type = types.listOf (types.submodule { 741 1278 freeformType = provisioningSettingsFormat.type; 742 1279 ··· 759 1296 760 1297 deleteRules = mkOption { 761 1298 description = lib.mdDoc "List of alert rule UIDs that should be deleted."; 762 - default = []; 1299 + default = [ ]; 763 1300 type = types.listOf (types.submodule { 764 1301 options.orgId = mkOption { 765 1302 description = lib.mdDoc "Organization ID, default = 1"; ··· 860 1397 861 1398 contactPoints = mkOption { 862 1399 description = lib.mdDoc "List of contact points to import or update."; 863 - default = []; 1400 + default = [ ]; 864 1401 type = types.listOf (types.submodule { 865 1402 freeformType = provisioningSettingsFormat.type; 866 1403 ··· 873 1410 874 1411 deleteContactPoints = mkOption { 875 1412 description = lib.mdDoc "List of receivers that should be deleted."; 876 - default = []; 1413 + default = [ ]; 877 1414 type = types.listOf (types.submodule { 878 1415 options.orgId = mkOption { 879 1416 description = lib.mdDoc "Organization ID, default = 1."; ··· 941 1478 942 1479 policies = mkOption { 943 1480 description = lib.mdDoc "List of contact points to import or update."; 944 - default = []; 1481 + default = [ ]; 945 1482 type = types.listOf (types.submodule { 946 1483 freeformType = provisioningSettingsFormat.type; 947 1484 }); ··· 949 1486 950 1487 resetPolicies = mkOption { 951 1488 description = lib.mdDoc "List of orgIds that should be reset to the default policy."; 952 - default = []; 1489 + default = [ ]; 953 1490 type = types.listOf types.int; 954 1491 }; 955 1492 }; ··· 1011 1548 1012 1549 templates = mkOption { 1013 1550 description = lib.mdDoc "List of templates to import or update."; 1014 - default = []; 1551 + default = [ ]; 1015 1552 type = types.listOf (types.submodule { 1016 1553 freeformType = provisioningSettingsFormat.type; 1017 1554 ··· 1029 1566 1030 1567 deleteTemplates = mkOption { 1031 1568 description = lib.mdDoc "List of alert rule UIDs that should be deleted."; 1032 - default = []; 1569 + default = [ ]; 1033 1570 type = types.listOf (types.submodule { 1034 1571 options.orgId = mkOption { 1035 1572 description = lib.mdDoc "Organization ID, default = 1."; ··· 1093 1630 1094 1631 muteTimes = mkOption { 1095 1632 description = lib.mdDoc "List of mute time intervals to import or update."; 1096 - default = []; 1633 + default = [ ]; 1097 1634 type = types.listOf (types.submodule { 1098 1635 freeformType = provisioningSettingsFormat.type; 1099 1636 ··· 1106 1643 1107 1644 deleteMuteTimes = mkOption { 1108 1645 description = lib.mdDoc "List of mute time intervals that should be deleted."; 1109 - default = []; 1646 + default = [ ]; 1110 1647 type = types.listOf (types.submodule { 1111 1648 options.orgId = mkOption { 1112 1649 description = lib.mdDoc "Organization ID, default = 1."; ··· 1168 1705 }; 1169 1706 1170 1707 config = mkIf cfg.enable { 1171 - warnings = let 1172 - doesntUseFileProvider = opt: defaultValue: 1173 - let 1174 - regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$"; 1175 - in builtins.match regex opt == null; 1176 - in 1177 - # Ensure that no custom credentials are leaked into the Nix store. Unless the default value 1178 - # is specified, this can be achieved by using the file/env provider: 1179 - # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion 1180 - (optional ( 1181 - doesntUseFileProvider cfg.settings.database.password "" || 1182 - doesntUseFileProvider cfg.settings.security.admin_password "admin" 1183 - ) '' 1184 - Grafana passwords will be stored as plaintext in the Nix store! 1185 - Use file provider or an env-var instead. 1186 - '') 1187 - # Warn about deprecated notifiers. 1188 - ++ (optional (cfg.provision.notifiers != []) '' 1189 - Notifiers are deprecated upstream and will be removed in Grafana 10. 1190 - Use `services.grafana.provision.alerting.contactPoints` instead. 1191 - '') 1192 - # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings` 1193 - # only uses file/env providers. 1194 - ++ (optional ( 1195 - let 1196 - datasourcesToCheck = optionals 1197 - (cfg.provision.datasources.settings != null) 1198 - cfg.provision.datasources.settings.datasources; 1199 - declarationUnsafe = { secureJsonData, ... }: 1200 - secureJsonData != null 1201 - && any (flip doesntUseFileProvider null) (attrValues secureJsonData); 1202 - in any declarationUnsafe datasourcesToCheck 1203 - ) '' 1204 - Declarations in the `secureJsonData`-block of a datasource will be leaked to the 1205 - Nix store unless a file-provider or an env-var is used! 1206 - '') 1207 - ++ (optional ( 1208 - any (x: x.secure_settings != null) cfg.provision.notifiers 1209 - ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead."); 1708 + warnings = 1709 + let 1710 + doesntUseFileProvider = opt: defaultValue: 1711 + let regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$"; 1712 + in builtins.match regex opt == null; 1713 + 1714 + # Ensure that no custom credentials are leaked into the Nix store. Unless the default value 1715 + # is specified, this can be achieved by using the file/env provider: 1716 + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion 1717 + passwordWithoutFileProvider = optional 1718 + ( 1719 + doesntUseFileProvider cfg.settings.database.password "" || 1720 + doesntUseFileProvider cfg.settings.security.admin_password "admin" 1721 + ) 1722 + '' 1723 + Grafana passwords will be stored as plaintext in the Nix store! 1724 + Use file provider or an env-var instead. 1725 + ''; 1726 + 1727 + # Warn about deprecated notifiers. 1728 + deprecatedNotifiers = optional (cfg.provision.notifiers != [ ]) '' 1729 + Notifiers are deprecated upstream and will be removed in Grafana 10. 1730 + Use `services.grafana.provision.alerting.contactPoints` instead. 1731 + ''; 1732 + 1733 + # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings` 1734 + # only uses file/env providers. 1735 + secureJsonDataWithoutFileProvider = optional 1736 + ( 1737 + let 1738 + datasourcesToCheck = optionals 1739 + (cfg.provision.datasources.settings != null) 1740 + cfg.provision.datasources.settings.datasources; 1741 + declarationUnsafe = { secureJsonData, ... }: 1742 + secureJsonData != null 1743 + && any (flip doesntUseFileProvider null) (attrValues secureJsonData); 1744 + in 1745 + any declarationUnsafe datasourcesToCheck 1746 + ) 1747 + '' 1748 + Declarations in the `secureJsonData`-block of a datasource will be leaked to the 1749 + Nix store unless a file-provider or an env-var is used! 1750 + ''; 1751 + 1752 + notifierSecureSettingsWithoutFileProvider = optional 1753 + (any (x: x.secure_settings != null) cfg.provision.notifiers) 1754 + "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead."; 1755 + in 1756 + passwordWithoutFileProvider 1757 + ++ deprecatedNotifiers 1758 + ++ secureJsonDataWithoutFileProvider 1759 + ++ notifierSecureSettingsWithoutFileProvider; 1210 1760 1211 1761 environment.systemPackages = [ cfg.package ]; 1212 1762 ··· 1216 1766 message = "Cannot set both datasources settings and datasources path"; 1217 1767 } 1218 1768 { 1219 - assertion = let 1220 - prometheusIsNotDirect = opt: all 1221 - ({ type, access, ... }: type == "prometheus" -> access != "direct") 1222 - opt; 1223 - in 1769 + assertion = 1770 + let 1771 + prometheusIsNotDirect = opt: all 1772 + ({ type, access, ... }: type == "prometheus" -> access != "direct") 1773 + opt; 1774 + in 1224 1775 cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources; 1225 1776 message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)"; 1226 1777 } ··· 1252 1803 1253 1804 systemd.services.grafana = { 1254 1805 description = "Grafana Service Daemon"; 1255 - wantedBy = ["multi-user.target"]; 1256 - after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; 1806 + wantedBy = [ "multi-user.target" ]; 1807 + after = [ "networking.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; 1257 1808 script = '' 1258 1809 set -o errexit -o pipefail -o nounset -o errtrace 1259 1810 shopt -s inherit_errexit ··· 1309 1860 createHome = true; 1310 1861 group = "grafana"; 1311 1862 }; 1312 - users.groups.grafana = {}; 1863 + users.groups.grafana = { }; 1313 1864 }; 1314 1865 }