about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/monitoring/grafana.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/monitoring/grafana.nix')
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana.nix1888
1 files changed, 1888 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana.nix b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
new file mode 100644
index 000000000000..9d453c539482
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
@@ -0,0 +1,1888 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grafana;
+  opt = options.services.grafana;
+  provisioningSettingsFormat = pkgs.formats.yaml { };
+  declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins);
+  useMysql = cfg.settings.database.type == "mysql";
+  usePostgresql = cfg.settings.database.type == "postgres";
+
+  # Prefer using the values from the default config file[0] directly. This way,
+  # people reading the NixOS manual can see them without cross-referencing the
+  # official documentation.
+  #
+  # However, if there is no default entry or if the setting is optional, use
+  # `null` as the default value. It will be turned into the empty string.
+  #
+  # If a setting is a list, always allow setting it as a plain string as well.
+  #
+  # [0]: https://github.com/grafana/grafana/blob/main/conf/defaults.ini
+  settingsFormatIni = pkgs.formats.ini {
+    listToValue = concatMapStringsSep " " (generators.mkValueStringDefault { });
+    mkKeyValue = generators.mkKeyValueDefault
+      {
+        mkValueString = v:
+          if v == null then ""
+          else generators.mkValueStringDefault { } v;
+      }
+      "=";
+  };
+  configFile = settingsFormatIni.generate "config.ini" cfg.settings;
+
+  mkProvisionCfg = name: attr: provisionCfg:
+    if provisionCfg.path != null
+    then provisionCfg.path
+    else
+      provisioningSettingsFormat.generate "${name}.yaml"
+        (if provisionCfg.settings != null
+        then provisionCfg.settings
+        else {
+          apiVersion = 1;
+          ${attr} = [ ];
+        });
+
+  datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources;
+  dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards;
+
+  notifierConfiguration = {
+    apiVersion = 1;
+    notifiers = cfg.provision.notifiers;
+  };
+
+  notifierFileOrDir = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
+
+  generateAlertingProvisioningYaml = x:
+    if (cfg.provision.alerting."${x}".path == null)
+    then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings
+    else cfg.provision.alerting."${x}".path;
+  rulesFileOrDir = generateAlertingProvisioningYaml "rules";
+  contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints";
+  policiesFileOrDir = generateAlertingProvisioningYaml "policies";
+  templatesFileOrDir = generateAlertingProvisioningYaml "templates";
+  muteTimingsFileOrDir = generateAlertingProvisioningYaml "muteTimings";
+
+  ln = { src, dir, filename }: ''
+    if [[ -d "${src}" ]]; then
+      pushd $out/${dir} &>/dev/null
+        lndir "${src}"
+      popd &>/dev/null
+    else
+      ln -sf ${src} $out/${dir}/${filename}.yaml
+    fi
+  '';
+  provisionConfDir = pkgs.runCommand "grafana-provisioning" { nativeBuildInputs = [ pkgs.xorg.lndir ]; } ''
+    mkdir -p $out/{alerting,datasources,dashboards,notifiers,plugins}
+    ${ln { src = datasourceFileOrDir;    dir = "datasources"; filename = "datasource"; }}
+    ${ln { src = dashboardFileOrDir;     dir = "dashboards";  filename = "dashboard"; }}
+    ${ln { src = notifierFileOrDir;      dir = "notifiers";   filename = "notifier"; }}
+    ${ln { src = rulesFileOrDir;         dir = "alerting";    filename = "rules"; }}
+    ${ln { src = contactPointsFileOrDir; dir = "alerting";    filename = "contactPoints"; }}
+    ${ln { src = policiesFileOrDir;      dir = "alerting";    filename = "policies"; }}
+    ${ln { src = templatesFileOrDir;     dir = "alerting";    filename = "templates"; }}
+    ${ln { src = muteTimingsFileOrDir;   dir = "alerting";    filename = "muteTimings"; }}
+  '';
+
+  # Get a submodule without any embedded metadata:
+  _filter = x: filterAttrs (k: v: k != "_module") x;
+
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources
+  grafanaTypes.datasourceConfig = types.submodule {
+    freeformType = provisioningSettingsFormat.type;
+
+    options = {
+      name = mkOption {
+        type = types.str;
+        description = "Name of the datasource. Required.";
+      };
+      type = mkOption {
+        type = types.str;
+        description = "Datasource type. Required.";
+      };
+      access = mkOption {
+        type = types.enum [ "proxy" "direct" ];
+        default = "proxy";
+        description = "Access mode. proxy or direct (Server or Browser in the UI). Required.";
+      };
+      uid = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically.";
+      };
+      url = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Url of the datasource.";
+      };
+      editable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Allow users to edit datasources from the UI.";
+      };
+      jsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Extra data for datasource plugins.";
+      };
+      secureJsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = ''
+          Datasource specific secure configuration. Please note that the contents of this option
+          will end up in a world-readable Nix store. Use the file provider
+          pointing at a reasonably secured file in the local filesystem
+          to work around that. Look at the documentation for details:
+          <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+        '';
+      };
+    };
+  };
+
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
+  grafanaTypes.dashboardConfig = types.submodule {
+    freeformType = provisioningSettingsFormat.type;
+
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = "default";
+        description = "A unique provider name.";
+      };
+      type = mkOption {
+        type = types.str;
+        default = "file";
+        description = "Dashboard provider type.";
+      };
+      options.path = mkOption {
+        type = types.path;
+        description = "Path grafana will watch for dashboards. Required when using the 'file' type.";
+      };
+    };
+  };
+
+  grafanaTypes.notifierConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = "default";
+        description = "Notifier name.";
+      };
+      type = mkOption {
+        type = types.enum [ "dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook" ];
+        description = "Notifier type.";
+      };
+      uid = mkOption {
+        type = types.str;
+        description = "Unique notifier identifier.";
+      };
+      org_id = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Organization ID.";
+      };
+      org_name = mkOption {
+        type = types.str;
+        default = "Main Org.";
+        description = "Organization name.";
+      };
+      is_default = mkOption {
+        type = types.bool;
+        description = "Is the default notifier.";
+        default = false;
+      };
+      send_reminder = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Should the notifier be sent reminder notifications while alerts continue to fire.";
+      };
+      frequency = mkOption {
+        type = types.str;
+        default = "5m";
+        description = "How frequently should the notifier be sent reminders.";
+      };
+      disable_resolve_message = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Turn off the message that sends when an alert returns to OK.";
+      };
+      settings = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Settings for the notifier type.";
+      };
+      secure_settings = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = ''
+          Secure settings for the notifier type. Please note that the contents of this option
+          will end up in a world-readable Nix store. Use the file provider
+          pointing at a reasonably secured file in the local filesystem
+          to work around that. Look at the documentation for details:
+          <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+        '';
+      };
+    };
+  };
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ])
+    (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ])
+    (mkRenamedOptionModule [ "services" "grafana" "port" ] [ "services" "grafana" "settings" "server" "http_port" ])
+    (mkRenamedOptionModule [ "services" "grafana" "domain" ] [ "services" "grafana" "settings" "server" "domain" ])
+    (mkRenamedOptionModule [ "services" "grafana" "rootUrl" ] [ "services" "grafana" "settings" "server" "root_url" ])
+    (mkRenamedOptionModule [ "services" "grafana" "staticRootPath" ] [ "services" "grafana" "settings" "server" "static_root_path" ])
+    (mkRenamedOptionModule [ "services" "grafana" "certFile" ] [ "services" "grafana" "settings" "server" "cert_file" ])
+    (mkRenamedOptionModule [ "services" "grafana" "certKey" ] [ "services" "grafana" "settings" "server" "cert_key" ])
+    (mkRenamedOptionModule [ "services" "grafana" "socket" ] [ "services" "grafana" "settings" "server" "socket" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "type" ] [ "services" "grafana" "settings" "database" "type" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "host" ] [ "services" "grafana" "settings" "database" "host" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "name" ] [ "services" "grafana" "settings" "database" "name" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "user" ] [ "services" "grafana" "settings" "database" "user" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "password" ] [ "services" "grafana" "settings" "database" "password" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "path" ] [ "services" "grafana" "settings" "database" "path" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "connMaxLifetime" ] [ "services" "grafana" "settings" "database" "conn_max_lifetime" ])
+    (mkRenamedOptionModule [ "services" "grafana" "security" "adminUser" ] [ "services" "grafana" "settings" "security" "admin_user" ])
+    (mkRenamedOptionModule [ "services" "grafana" "security" "adminPassword" ] [ "services" "grafana" "settings" "security" "admin_password" ])
+    (mkRenamedOptionModule [ "services" "grafana" "security" "secretKey" ] [ "services" "grafana" "settings" "security" "secret_key" ])
+    (mkRenamedOptionModule [ "services" "grafana" "server" "serveFromSubPath" ] [ "services" "grafana" "settings" "server" "serve_from_sub_path" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "enable" ] [ "services" "grafana" "settings" "smtp" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "user" ] [ "services" "grafana" "settings" "smtp" "user" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "password" ] [ "services" "grafana" "settings" "smtp" "password" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "fromAddress" ] [ "services" "grafana" "settings" "smtp" "from_address" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "allowSignUp" ] [ "services" "grafana" "settings" "users" "allow_sign_up" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "allowOrgCreate" ] [ "services" "grafana" "settings" "users" "allow_org_create" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrg" ] [ "services" "grafana" "settings" "users" "auto_assign_org" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrgRole" ] [ "services" "grafana" "settings" "users" "auto_assign_org_role" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "disableLoginForm" ] [ "services" "grafana" "settings" "auth" "disable_login_form" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "enable" ] [ "services" "grafana" "settings" "auth.anonymous" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_name" ] [ "services" "grafana" "settings" "auth.anonymous" "org_name" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_role" ] [ "services" "grafana" "settings" "auth.anonymous" "org_role" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "enable" ] [ "services" "grafana" "settings" "auth.azuread" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowSignUp" ] [ "services" "grafana" "settings" "auth.azuread" "allow_sign_up" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "clientId" ] [ "services" "grafana" "settings" "auth.azuread" "client_id" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedDomains" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_domains" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedGroups" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_groups" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "enable" ] [ "services" "grafana" "settings" "auth.google" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "allowSignUp" ] [ "services" "grafana" "settings" "auth.google" "allow_sign_up" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "clientId" ] [ "services" "grafana" "settings" "auth.google" "client_id" ])
+    (mkRenamedOptionModule [ "services" "grafana" "analytics" "reporting" "enable" ] [ "services" "grafana" "settings" "analytics" "reporting_enabled" ])
+
+    (mkRemovedOptionModule [ "services" "grafana" "database" "passwordFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.database.password' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "security" "adminPasswordFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.security.admin_password' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "security" "secretKeyFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.security.secret_key' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "smtp" "passwordFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.smtp.password' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "clientSecretFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.azuread.client_secret' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "extraOptions" ] ''
+      This option has been removed. Use 'services.grafana.settings' instead. For a detailed migration guide, please
+      review the release notes of NixOS 22.11.
+    '')
+
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
+  ];
+
+  options.services.grafana = {
+    enable = mkEnableOption "grafana";
+
+    declarativePlugins = mkOption {
+      type = with types; nullOr (listOf path);
+      default = null;
+      description = "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
+      example = literalExpression "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
+      # Make sure each plugin is added only once; otherwise building
+      # the link farm fails, since the same path is added multiple
+      # times.
+      apply = x: if isList x then lib.unique x else x;
+    };
+
+    package = mkPackageOption pkgs "grafana" { };
+
+    dataDir = mkOption {
+      description = "Data directory.";
+      default = "/var/lib/grafana";
+      type = types.path;
+    };
+
+    settings = mkOption {
+      description = ''
+        Grafana settings. See <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/>
+        for available options. INI format is used.
+      '';
+      type = types.submodule {
+        freeformType = settingsFormatIni.type;
+
+        options = {
+          paths = {
+            plugins = mkOption {
+              description = "Directory where grafana will automatically scan and look for plugins";
+              default = if (cfg.declarativePlugins == null) then "${cfg.dataDir}/plugins" else declarativePlugins;
+              defaultText = literalExpression "if (cfg.declarativePlugins == null) then \"\${cfg.dataDir}/plugins\" else declarativePlugins";
+              type = types.path;
+            };
+
+            provisioning = mkOption {
+              description = ''
+                Folder that contains provisioning config files that grafana will apply on startup and while running.
+                Don't change the value of this option if you are planning to use `services.grafana.provision` options.
+              '';
+              default = provisionConfDir;
+              defaultText = "directory with links to files generated from services.grafana.provision";
+              type = types.path;
+            };
+          };
+
+          server = {
+            protocol = mkOption {
+              description = "Which protocol to listen.";
+              default = "http";
+              type = types.enum [ "http" "https" "h2" "socket" ];
+            };
+
+            http_addr = mkOption {
+              type = types.str;
+              default = "127.0.0.1";
+              description = ''
+                Listening address.
+
+                ::: {.note}
+                This setting intentionally varies from upstream's default to be a bit more secure by default.
+                :::
+              '';
+            };
+
+            http_port = mkOption {
+              description = "Listening port.";
+              default = 3000;
+              type = types.port;
+            };
+
+            domain = mkOption {
+              description = ''
+                The public facing domain name used to access grafana from a browser.
+
+                This setting is only used in the default value of the `root_url` setting.
+                If you set the latter manually, this option does not have to be specified.
+              '';
+              default = "localhost";
+              type = types.str;
+            };
+
+            enforce_domain = mkOption {
+              description = ''
+                Redirect to correct domain if the host header does not match the domain.
+                Prevents DNS rebinding attacks.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            root_url = mkOption {
+              description = ''
+                This is the full URL used to access Grafana from a web browser.
+                This is important if you use Google or GitHub OAuth authentication (for the callback URL to be correct).
+
+                This setting is also important if you have a reverse proxy in front of Grafana that exposes it through a subpath.
+                In that case add the subpath to the end of this URL setting.
+              '';
+              default = "%(protocol)s://%(domain)s:%(http_port)s/";
+              type = types.str;
+            };
+
+            serve_from_sub_path = mkOption {
+              description = ''
+                Serve Grafana from subpath specified in the `root_url` setting.
+                By default it is set to `false` for compatibility reasons.
+
+                By enabling this setting and using a subpath in `root_url` above,
+                e.g. `root_url = "http://localhost:3000/grafana"`,
+                Grafana is accessible on `http://localhost:3000/grafana`.
+                If accessed without subpath, Grafana will redirect to an URL with the subpath.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            router_logging = mkOption {
+              description = ''
+                Set to `true` for Grafana to log all HTTP requests (not just errors).
+                These are logged as Info level events to the Grafana log.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            static_root_path = mkOption {
+              description = "Root path for static assets.";
+              default = "${cfg.package}/share/grafana/public";
+              defaultText = literalExpression ''"''${package}/share/grafana/public"'';
+              type = types.str;
+            };
+
+            enable_gzip = mkOption {
+              description = ''
+                Set this option to `true` to enable HTTP compression, this can improve transfer speed and bandwidth utilization.
+                It is recommended that most users set it to `true`. By default it is set to `false` for compatibility reasons.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            cert_file = mkOption {
+              description = ''
+                Path to the certificate file (if `protocol` is set to `https` or `h2`).
+              '';
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            cert_key = mkOption {
+              description = ''
+                Path to the certificate key file (if `protocol` is set to `https` or `h2`).
+              '';
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            socket_gid = mkOption {
+              description = ''
+                GID where the socket should be set when `protocol=socket`.
+                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.
+                It is recommended to set the gid as http server user gid.
+                Not set when the value is -1.
+              '';
+              default = -1;
+              type = types.int;
+            };
+
+            socket_mode = mkOption {
+              description = ''
+                Mode where the socket should be set when `protocol=socket`.
+                Make sure that Grafana process is the file owner before you change this setting.
+              '';
+              # I assume this value is interpreted as octal literal by grafana.
+              # If this was an int, people following tutorials or porting their
+              # old config could stumble across nix not having octal literals.
+              default = "0660";
+              type = types.str;
+            };
+
+            socket = mkOption {
+              description = ''
+                Path where the socket should be created when `protocol=socket`.
+                Make sure that Grafana has appropriate permissions before you change this setting.
+              '';
+              default = "/run/grafana/grafana.sock";
+              type = types.str;
+            };
+
+            cdn_url = mkOption {
+              description = ''
+                Specify a full HTTP URL address to the root of your Grafana CDN assets.
+                Grafana will add edition and version paths.
+
+                For example, given a cdn url like `https://cdn.myserver.com`
+                grafana will try to load a javascript file from `http://cdn.myserver.com/grafana-oss/7.4.0/public/build/app.<hash>.js`.
+              '';
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            read_timeout = mkOption {
+              description = ''
+                Sets the maximum time using a duration format (5s/5m/5ms)
+                before timing out read of an incoming request and closing idle connections.
+                0 means there is no timeout for reading the request.
+              '';
+              default = "0";
+              type = types.str;
+            };
+          };
+
+          database = {
+            type = mkOption {
+              description = "Database type.";
+              default = "sqlite3";
+              type = types.enum [ "mysql" "sqlite3" "postgres" ];
+            };
+
+            host = mkOption {
+              description = ''
+                Only applicable to MySQL or Postgres.
+                Includes IP or hostname and port or in case of Unix sockets the path to it.
+                For example, for MySQL running on the same host as Grafana: `host = "127.0.0.1:3306"`
+                or with Unix sockets: `host = "/var/run/mysqld/mysqld.sock"`
+              '';
+              default = "127.0.0.1:3306";
+              type = types.str;
+            };
+
+            name = mkOption {
+              description = "The name of the Grafana database.";
+              default = "grafana";
+              type = types.str;
+            };
+
+            user = mkOption {
+              description = "The database user (not applicable for `sqlite3`).";
+              default = "root";
+              type = types.str;
+            };
+
+            password = mkOption {
+              description = ''
+                The database user's password (not applicable for `sqlite3`).
+
+                Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "";
+              type = types.str;
+            };
+
+            max_idle_conn = mkOption {
+              description = "The maximum number of connections in the idle connection pool.";
+              default = 2;
+              type = types.int;
+            };
+
+            max_open_conn = mkOption {
+              description = "The maximum number of open connections to the database.";
+              default = 0;
+              type = types.int;
+            };
+
+            conn_max_lifetime = mkOption {
+              description = ''
+                Sets the maximum amount of time a connection may be reused.
+                The default is 14400 (which means 14400 seconds or 4 hours).
+                For MySQL, this setting should be shorter than the `wait_timeout` variable.
+              '';
+              default = 14400;
+              type = types.int;
+            };
+
+            locking_attempt_timeout_sec = mkOption {
+              description = ''
+                For `mysql`, if the `migrationLocking` feature toggle is set,
+                specify the time (in seconds) to wait before failing to lock the database for the migrations.
+              '';
+              default = 0;
+              type = types.int;
+            };
+
+            log_queries = mkOption {
+              description = "Set to `true` to log the sql calls and execution times";
+              default = false;
+              type = types.bool;
+            };
+
+            ssl_mode = mkOption {
+              description = ''
+                For Postgres, use either `disable`, `require` or `verify-full`.
+                For MySQL, use either `true`, `false`, or `skip-verify`.
+              '';
+              default = "disable";
+              type = types.enum [ "disable" "require" "verify-full" "true" "false" "skip-verify" ];
+            };
+
+            isolation_level = mkOption {
+              description = ''
+                Only the MySQL driver supports isolation levels in Grafana.
+                In case the value is empty, the driver's default isolation level is applied.
+              '';
+              default = null;
+              type = types.nullOr (types.enum [ "READ-UNCOMMITTED" "READ-COMMITTED" "REPEATABLE-READ" "SERIALIZABLE" ]);
+            };
+
+            ca_cert_path = mkOption {
+              description = "The path to the CA certificate to use.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            client_key_path = mkOption {
+              description = "The path to the client key. Only if server requires client authentication.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            client_cert_path = mkOption {
+              description = "The path to the client cert. Only if server requires client authentication.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            server_cert_name = mkOption {
+              description = ''
+                The common name field of the certificate used by the `mysql` or `postgres` server.
+                Not necessary if `ssl_mode` is set to `skip-verify`.
+              '';
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            path = mkOption {
+              description = "Only applicable to `sqlite3` database. The file path where the database will be stored.";
+              default = "${cfg.dataDir}/data/grafana.db";
+              defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
+              type = types.path;
+            };
+
+            cache_mode = mkOption {
+              description = ''
+                For `sqlite3` only.
+                [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database.
+              '';
+              default = "private";
+              type = types.enum [ "private" "shared" ];
+            };
+
+            wal = mkOption {
+              description = ''
+                For `sqlite3` only.
+                Setting to enable/disable [Write-Ahead Logging](https://sqlite.org/wal.html).
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            query_retries = mkOption {
+              description = ''
+                This setting applies to `sqlite3` only and controls the number of times the system retries a query when the database is locked.
+              '';
+              default = 0;
+              type = types.int;
+            };
+
+            transaction_retries = mkOption {
+              description = ''
+                This setting applies to `sqlite3` only and controls the number of times the system retries a transaction when the database is locked.
+              '';
+              default = 5;
+              type = types.int;
+            };
+
+            # TODO Add "instrument_queries" option when upgrading to grafana 10.0
+            # instrument_queries = mkOption {
+            #   description = "Set to `true` to add metrics and tracing for database queries.";
+            #   default = false;
+            #   type = types.bool;
+            # };
+          };
+
+          security = {
+            disable_initial_admin_creation = mkOption {
+              description = "Disable creation of admin user on first start of Grafana.";
+              default = false;
+              type = types.bool;
+            };
+
+            admin_user = mkOption {
+              description = "Default admin username.";
+              default = "admin";
+              type = types.str;
+            };
+
+            admin_password = mkOption {
+              description = ''
+                Default admin password. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "admin";
+              type = types.str;
+            };
+
+            admin_email = mkOption {
+              description = "The email of the default Grafana Admin, created on startup.";
+              default = "admin@localhost";
+              type = types.str;
+            };
+
+            secret_key = mkOption {
+              description = ''
+                Secret key used for signing. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "SW2YcwTIb9zpOOhoPsMm";
+              type = types.str;
+            };
+
+            disable_gravatar = mkOption {
+              description = "Set to `true` to disable the use of Gravatar for user profile images.";
+              default = false;
+              type = types.bool;
+            };
+
+            data_source_proxy_whitelist = mkOption {
+              description = ''
+                Define a whitelist of allowed IP addresses or domains, with ports,
+                to be used in data source URLs with the Grafana data source proxy.
+                Format: `ip_or_domain:port` separated by spaces.
+                PostgreSQL, MySQL, and MSSQL data sources do not use the proxy and are therefore unaffected by this setting.
+              '';
+              default = [ ];
+              type = types.oneOf [ types.str (types.listOf types.str) ];
+            };
+
+            disable_brute_force_login_protection = mkOption {
+              description = "Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout).";
+              default = false;
+              type = types.bool;
+            };
+
+            cookie_secure = mkOption {
+              description = "Set to `true` if you host Grafana behind HTTPS.";
+              default = false;
+              type = types.bool;
+            };
+
+            cookie_samesite = mkOption {
+              description = ''
+                Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests.
+                The main goal is to mitigate the risk of cross-origin information leakage.
+                This setting also provides some protection against cross-site request forgery attacks (CSRF),
+                [read more about SameSite here](https://owasp.org/www-community/SameSite).
+                Using value `disabled` does not add any `SameSite` attribute to cookies.
+              '';
+              default = "lax";
+              type = types.enum [ "lax" "strict" "none" "disabled" ];
+            };
+
+            allow_embedding = mkOption {
+              description = ''
+                When `false`, the HTTP header `X-Frame-Options: deny` will be set in Grafana HTTP responses
+                which will instruct browsers to not allow rendering Grafana in a `<frame>`, `<iframe>`, `<embed>` or `<object>`.
+                The main goal is to mitigate the risk of [Clickjacking](https://owasp.org/www-community/attacks/Clickjacking).
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            strict_transport_security = mkOption {
+              description = ''
+                Set to `true` if you want to enable HTTP `Strict-Transport-Security` (HSTS) response header.
+                Only use this when HTTPS is enabled in your configuration,
+                or when there is another upstream system that ensures your application does HTTPS (like a frontend load balancer).
+                HSTS tells browsers that the site should only be accessed using HTTPS.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            strict_transport_security_max_age_seconds = mkOption {
+              description = ''
+                Sets how long a browser should cache HSTS in seconds.
+                Only applied if `strict_transport_security` is enabled.
+              '';
+              default = 86400;
+              type = types.int;
+            };
+
+            strict_transport_security_preload = mkOption {
+              description = ''
+                Set to `true` to enable HSTS `preloading` option.
+                Only applied if `strict_transport_security` is enabled.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            strict_transport_security_subdomains = mkOption {
+              description = ''
+                Set to `true` to enable HSTS `includeSubDomains` option.
+                Only applied if `strict_transport_security` is enabled.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            x_content_type_options = mkOption {
+              description = ''
+                Set to `false` to disable the `X-Content-Type-Options` response header.
+                The `X-Content-Type-Options` response HTTP header is a marker used by the server
+                to indicate that the MIME types advertised in the `Content-Type` headers should not be changed and be followed.
+              '';
+              default = true;
+              type = types.bool;
+            };
+
+            x_xss_protection = mkOption {
+              description = ''
+                Set to `false` to disable the `X-XSS-Protection` header,
+                which tells browsers to stop pages from loading when they detect reflected cross-site scripting (XSS) attacks.
+              '';
+              default = true;
+              type = types.bool;
+            };
+
+            content_security_policy = mkOption {
+              description = ''
+                Set to `true` to add the `Content-Security-Policy` header to your requests.
+                CSP allows to control resources that the user agent can load and helps prevent XSS attacks.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            content_security_policy_report_only = mkOption {
+              description = ''
+                Set to `true` to add the `Content-Security-Policy-Report-Only` header to your requests.
+                CSP in Report Only mode enables you to experiment with policies by monitoring their effects without enforcing them.
+                You can enable both policies simultaneously.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            # The options content_security_policy_template and
+            # content_security_policy_template are missing because I'm not sure
+            # how exactly the quoting of the default value works. See also
+            # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L364
+            # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L373
+
+            # These two options are lists joined with spaces:
+            # https://github.com/grafana/grafana/blob/916d9793aa81c2990640b55a15dee0db6b525e41/pkg/middleware/csrf/csrf.go#L37-L38
+
+            csrf_trusted_origins = mkOption {
+              description = ''
+                List of additional allowed URLs to pass by the CSRF check.
+                Suggested when authentication comes from an IdP.
+              '';
+              default = [ ];
+              type = types.oneOf [ types.str (types.listOf types.str) ];
+            };
+
+            csrf_additional_headers = mkOption {
+              description = ''
+                List of allowed headers to be set by the user.
+                Suggested to use for if authentication lives behind reverse proxies.
+              '';
+              default = [ ];
+              type = types.oneOf [ types.str (types.listOf types.str) ];
+            };
+          };
+
+          smtp = {
+            enabled = mkOption {
+              description = "Whether to enable SMTP.";
+              default = false;
+              type = types.bool;
+            };
+
+            host = mkOption {
+              description = "Host to connect to.";
+              default = "localhost:25";
+              type = types.str;
+            };
+
+            user = mkOption {
+              description = "User used for authentication.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            password = mkOption {
+              description = ''
+                Password used for authentication. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "";
+              type = types.str;
+            };
+
+            cert_file = mkOption {
+              description = "File path to a cert file.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            key_file = mkOption {
+              description = "File path to a key file.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            skip_verify = mkOption {
+              description = "Verify SSL for SMTP server.";
+              default = false;
+              type = types.bool;
+            };
+
+            from_address = mkOption {
+              description = "Address used when sending out emails.";
+              default = "admin@grafana.localhost";
+              type = types.str;
+            };
+
+            from_name = mkOption {
+              description = "Name to be used as client identity for EHLO in SMTP dialog.";
+              default = "Grafana";
+              type = types.str;
+            };
+
+            ehlo_identity = mkOption {
+              description = "Name to be used as client identity for EHLO in SMTP dialog.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            startTLS_policy = mkOption {
+              description = "StartTLS policy when connecting to server.";
+              default = null;
+              type = types.nullOr (types.enum [ "OpportunisticStartTLS" "MandatoryStartTLS" "NoStartTLS" ]);
+            };
+          };
+
+          users = {
+            allow_sign_up = mkOption {
+              description = ''
+                Set to false to prohibit users from being able to sign up / create user accounts.
+                The admin user can still create users.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            allow_org_create = mkOption {
+              description = "Set to `false` to prohibit users from creating new organizations.";
+              default = false;
+              type = types.bool;
+            };
+
+            auto_assign_org = mkOption {
+              description = ''
+                Set to `true` to automatically add new users to the main organization (id 1).
+                When set to `false,` new users automatically cause a new organization to be created for that new user.
+                The organization will be created even if the `allow_org_create` setting is set to `false`.
+              '';
+              default = true;
+              type = types.bool;
+            };
+
+            auto_assign_org_id = mkOption {
+              description = ''
+                Set this value to automatically add new users to the provided org.
+                This requires `auto_assign_org` to be set to `true`.
+                Please make sure that this organization already exists.
+              '';
+              default = 1;
+              type = types.int;
+            };
+
+            auto_assign_org_role = mkOption {
+              description = ''
+                The role new users will be assigned for the main organization (if the `auto_assign_org` setting is set to `true`).
+              '';
+              default = "Viewer";
+              type = types.enum [ "Viewer" "Editor" "Admin" ];
+            };
+
+            verify_email_enabled = mkOption {
+              description = "Require email validation before sign up completes.";
+              default = false;
+              type = types.bool;
+            };
+
+            login_hint = mkOption {
+              description = "Text used as placeholder text on login page for login/username input.";
+              default = "email or username";
+              type = types.str;
+            };
+
+            password_hint = mkOption {
+              description = "Text used as placeholder text on login page for password input.";
+              default = "password";
+              type = types.str;
+            };
+
+            default_theme = mkOption {
+              description = "Sets the default UI theme. `system` matches the user's system theme.";
+              default = "dark";
+              type = types.enum [ "dark" "light" "system" ];
+            };
+
+            default_language = mkOption {
+              description = "This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`.";
+              default = "en-US";
+              type = types.str;
+            };
+
+            home_page = mkOption {
+              description = ''
+                Path to a custom home page.
+                Users are only redirected to this if the default home dashboard is used.
+                It should match a frontend route and contain a leading slash.
+              '';
+              default = "";
+              type = types.str;
+            };
+
+            viewers_can_edit = mkOption {
+              description = ''
+                Viewers can access and use Explore and perform temporary edits on panels in dashboards they have access to.
+                They cannot save their changes.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            editors_can_admin = mkOption {
+              description = "Editors can administrate dashboards, folders and teams they create.";
+              default = false;
+              type = types.bool;
+            };
+
+            user_invite_max_lifetime_duration = mkOption {
+              description = ''
+                The duration in time a user invitation remains valid before expiring.
+                This setting should be expressed as a duration.
+                Examples: `6h` (hours), `2d` (days), `1w` (week).
+                The minimum supported duration is `15m` (15 minutes).
+              '';
+              default = "24h";
+              type = types.str;
+            };
+
+            # Lists are joined via space, so this option can't be a list.
+            # Users have to manually join their values.
+            hidden_users = mkOption {
+              description = ''
+                This is a comma-separated list of usernames.
+                Users specified here are hidden in the Grafana UI.
+                They are still visible to Grafana administrators and to themselves.
+              '';
+              default = "";
+              type = types.str;
+            };
+          };
+
+          analytics = {
+            reporting_enabled = mkOption {
+              description = ''
+                When enabled Grafana will send anonymous usage statistics to `stats.grafana.org`.
+                No IP addresses are being tracked, only simple counters to track running instances, versions, dashboard and error counts.
+                Counters are sent every 24 hours.
+              '';
+              default = true;
+              type = types.bool;
+            };
+
+            check_for_updates = mkOption {
+              description = ''
+                When set to `false`, disables checking for new versions of Grafana from Grafana's GitHub repository.
+                When enabled, the check for a new version runs every 10 minutes.
+                It will notify, via the UI, when a new version is available.
+                The check itself will not prompt any auto-updates of the Grafana software, nor will it send any sensitive information.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            check_for_plugin_updates = mkOption {
+              description = ''
+                When set to `false`, disables checking for new versions of installed plugins from https://grafana.com.
+                When enabled, the check for a new plugin runs every 10 minutes.
+                It will notify, via the UI, when a new plugin update exists.
+                The check itself will not prompt any auto-updates of the plugin, nor will it send any sensitive information.
+              '';
+              default = cfg.declarativePlugins == null;
+              defaultText = literalExpression "cfg.declarativePlugins == null";
+              type = types.bool;
+            };
+
+            feedback_links_enabled = mkOption {
+              description = "Set to `false` to remove all feedback links from the UI.";
+              default = true;
+              type = types.bool;
+            };
+          };
+        };
+      };
+    };
+
+    provision = {
+      enable = mkEnableOption "provision";
+
+      datasources = mkOption {
+        description = ''
+          Declaratively provision Grafana's datasources.
+        '';
+        default = { };
+        type = types.submodule {
+          options.settings = mkOption {
+            description = ''
+              Grafana datasource configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.datasources.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                datasources = mkOption {
+                  description = "List of datasources to insert/update.";
+                  default = [ ];
+                  type = types.listOf grafanaTypes.datasourceConfig;
+                };
+
+                deleteDatasources = mkOption {
+                  description = "List of datasources that should be deleted from the database.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    options.name = mkOption {
+                      description = "Name of the datasource to delete.";
+                      type = types.str;
+                    };
+
+                    options.orgId = mkOption {
+                      description = "Organization ID of the datasource to delete.";
+                      type = types.int;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                datasources = [{
+                  name = "Graphite";
+                  type = "graphite";
+                }];
+
+                deleteDatasources = [{
+                  name = "Graphite";
+                  orgId = 1;
+                }];
+              }
+            '';
+          };
+
+          options.path = mkOption {
+            description = ''
+              Path to YAML datasource configuration. Can't be used with
+              [](#opt-services.grafana.provision.datasources.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+        };
+      };
+
+
+      dashboards = mkOption {
+        description = ''
+          Declaratively provision Grafana's dashboards.
+        '';
+        default = { };
+        type = types.submodule {
+          options.settings = mkOption {
+            description = ''
+              Grafana dashboard configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.dashboards.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options.apiVersion = mkOption {
+                description = "Config file version.";
+                default = 1;
+                type = types.int;
+              };
+
+              options.providers = mkOption {
+                description = "List of dashboards to insert/update.";
+                default = [ ];
+                type = types.listOf grafanaTypes.dashboardConfig;
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                providers = [{
+                    name = "default";
+                    options.path = "/var/lib/grafana/dashboards";
+                }];
+              }
+            '';
+          };
+
+          options.path = mkOption {
+            description = ''
+              Path to YAML dashboard configuration. Can't be used with
+              [](#opt-services.grafana.provision.dashboards.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+        };
+      };
+
+
+      notifiers = mkOption {
+        description = "Grafana notifier configuration.";
+        default = [ ];
+        type = types.listOf grafanaTypes.notifierConfig;
+        apply = x: map _filter x;
+      };
+
+
+      alerting = {
+        rules = {
+          path = mkOption {
+            description = ''
+              Path to YAML rules configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.rules.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = ''
+              Grafana rules configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.rules.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#rules>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                groups = mkOption {
+                  description = "List of rule groups to import or update.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = "Name of the rule group. Required.";
+                      type = types.str;
+                    };
+
+                    options.folder = mkOption {
+                      description = "Name of the folder the rule group will be stored in. Required.";
+                      type = types.str;
+                    };
+
+                    options.interval = mkOption {
+                      description = "Interval that the rule group should be evaluated at. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteRules = mkOption {
+                  description = "List of alert rule UIDs that should be deleted.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = "Organization ID, default = 1";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.uid = mkOption {
+                      description = "Unique identifier for the rule. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                groups = [{
+                  orgId = 1;
+                  name = "my_rule_group";
+                  folder = "my_first_folder";
+                  interval = "60s";
+                  rules = [{
+                    uid = "my_id_1";
+                    title = "my_first_rule";
+                    condition = "A";
+                    data = [{
+                      refId = "A";
+                      datasourceUid = "-100";
+                      model = {
+                        conditions = [{
+                          evaluator = {
+                            params = [ 3 ];
+                            type = "git";
+                          };
+                          operator.type = "and";
+                          query.params = [ "A" ];
+                          reducer.type = "last";
+                          type = "query";
+                        }];
+                        datasource = {
+                          type = "__expr__";
+                          uid = "-100";
+                        };
+                        expression = "1==0";
+                        intervalMs = 1000;
+                        maxDataPoints = 43200;
+                        refId = "A";
+                        type = "math";
+                      };
+                    }];
+                    dashboardUid = "my_dashboard";
+                    panelId = 123;
+                    noDataState = "Alerting";
+                    for = "60s";
+                    annotations.some_key = "some_value";
+                    labels.team = "sre_team1";
+                  }];
+                }];
+
+                deleteRules = [{
+                  orgId = 1;
+                  uid = "my_id_1";
+                }];
+              }
+            '';
+          };
+        };
+
+        contactPoints = {
+          path = mkOption {
+            description = ''
+              Path to YAML contact points configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.contactPoints.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = ''
+              Grafana contact points configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.contactPoints.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#contact-points>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                contactPoints = mkOption {
+                  description = "List of contact points to import or update.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = "Name of the contact point. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteContactPoints = mkOption {
+                  description = "List of receivers that should be deleted.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = "Organization ID, default = 1.";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.uid = mkOption {
+                      description = "Unique identifier for the receiver. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                contactPoints = [{
+                  orgId = 1;
+                  name = "cp_1";
+                  receivers = [{
+                    uid = "first_uid";
+                    type = "prometheus-alertmanager";
+                    settings.url = "http://test:9000";
+                  }];
+                }];
+
+                deleteContactPoints = [{
+                  orgId = 1;
+                  uid = "first_uid";
+                }];
+              }
+            '';
+          };
+        };
+
+        policies = {
+          path = mkOption {
+            description = ''
+              Path to YAML notification policies configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.policies.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = ''
+              Grafana notification policies configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.policies.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#notification-policies>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                policies = mkOption {
+                  description = "List of contact points to import or update.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+                  });
+                };
+
+                resetPolicies = mkOption {
+                  description = "List of orgIds that should be reset to the default policy.";
+                  default = [ ];
+                  type = types.listOf types.int;
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                policies = [{
+                  orgId = 1;
+                  receiver = "grafana-default-email";
+                  group_by = [ "..." ];
+                  matchers = [
+                    "alertname = Watchdog"
+                    "severity =~ \"warning|critical\""
+                  ];
+                  mute_time_intervals = [
+                    "abc"
+                  ];
+                  group_wait = "30s";
+                  group_interval = "5m";
+                  repeat_interval = "4h";
+                }];
+
+                resetPolicies = [
+                  1
+                ];
+              }
+            '';
+          };
+        };
+
+        templates = {
+          path = mkOption {
+            description = ''
+              Path to YAML templates configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.templates.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = ''
+              Grafana templates configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.templates.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#templates>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                templates = mkOption {
+                  description = "List of templates to import or update.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = "Name of the template, must be unique. Required.";
+                      type = types.str;
+                    };
+
+                    options.template = mkOption {
+                      description = "Alerting with a custom text template";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteTemplates = mkOption {
+                  description = "List of alert rule UIDs that should be deleted.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = "Organization ID, default = 1.";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.name = mkOption {
+                      description = "Name of the template, must be unique. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                templates = [{
+                  orgId = 1;
+                  name = "my_first_template";
+                  template = "Alerting with a custom text template";
+                }];
+
+                deleteTemplates = [{
+                  orgId = 1;
+                  name = "my_first_template";
+                }];
+              }
+            '';
+          };
+        };
+
+        muteTimings = {
+          path = mkOption {
+            description = ''
+              Path to YAML mute timings configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.muteTimings.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = ''
+              Grafana mute timings configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.muteTimings.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#mute-timings>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                muteTimes = mkOption {
+                  description = "List of mute time intervals to import or update.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = "Name of the mute time interval, must be unique. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteMuteTimes = mkOption {
+                  description = "List of mute time intervals that should be deleted.";
+                  default = [ ];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = "Organization ID, default = 1.";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.name = mkOption {
+                      description = "Name of the mute time interval, must be unique. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                muteTimes = [{
+                  orgId = 1;
+                  name = "mti_1";
+                  time_intervals = [{
+                    times = [{
+                      start_time = "06:00";
+                      end_time = "23:59";
+                    }];
+                    weekdays = [
+                      "monday:wednesday"
+                      "saturday"
+                      "sunday"
+                    ];
+                    months = [
+                      "1:3"
+                      "may:august"
+                      "december"
+                    ];
+                    years = [
+                      "2020:2022"
+                      "2030"
+                    ];
+                    days_of_month = [
+                      "1:5"
+                      "-3:-1"
+                    ];
+                  }];
+                }];
+
+                deleteMuteTimes = [{
+                  orgId = 1;
+                  name = "mti_1";
+                }];
+              }
+            '';
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    warnings =
+      let
+        doesntUseFileProvider = opt: defaultValue:
+          let regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$";
+          in builtins.match regex opt == null;
+
+        # Ensure that no custom credentials are leaked into the Nix store. Unless the default value
+        # is specified, this can be achieved by using the file/env provider:
+        # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
+        passwordWithoutFileProvider = optional
+          (
+            doesntUseFileProvider cfg.settings.database.password "" ||
+            doesntUseFileProvider cfg.settings.security.admin_password "admin"
+          )
+          ''
+            Grafana passwords will be stored as plaintext in the Nix store!
+            Use file provider or an env-var instead.
+          '';
+
+        # Warn about deprecated notifiers.
+        deprecatedNotifiers = optional (cfg.provision.notifiers != [ ]) ''
+          Notifiers are deprecated upstream and will be removed in Grafana 11.
+          Use `services.grafana.provision.alerting.contactPoints` instead.
+        '';
+
+        # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
+        # only uses file/env providers.
+        secureJsonDataWithoutFileProvider = optional
+          (
+            let
+              datasourcesToCheck = optionals
+                (cfg.provision.datasources.settings != null)
+                cfg.provision.datasources.settings.datasources;
+              declarationUnsafe = { secureJsonData, ... }:
+                secureJsonData != null
+                && any (flip doesntUseFileProvider null) (attrValues secureJsonData);
+            in
+            any declarationUnsafe datasourcesToCheck
+          )
+          ''
+            Declarations in the `secureJsonData`-block of a datasource will be leaked to the
+            Nix store unless a file-provider or an env-var is used!
+          '';
+
+        notifierSecureSettingsWithoutFileProvider = optional
+          (any (x: x.secure_settings != null) cfg.provision.notifiers)
+          "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.";
+      in
+      passwordWithoutFileProvider
+      ++ deprecatedNotifiers
+      ++ secureJsonDataWithoutFileProvider
+      ++ notifierSecureSettingsWithoutFileProvider;
+
+    environment.systemPackages = [ cfg.package ];
+
+    assertions = [
+      {
+        assertion = cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
+        message = "Cannot set both datasources settings and datasources path";
+      }
+      {
+        assertion =
+          let
+            prometheusIsNotDirect = opt: all
+              ({ type, access, ... }: type == "prometheus" -> access != "direct")
+              opt;
+          in
+          cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
+        message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
+      }
+      {
+        assertion = cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
+        message = "Cannot set both dashboards settings and dashboards path";
+      }
+      {
+        assertion = cfg.provision.alerting.rules.settings == null || cfg.provision.alerting.rules.path == null;
+        message = "Cannot set both rules settings and rules path";
+      }
+      {
+        assertion = cfg.provision.alerting.contactPoints.settings == null || cfg.provision.alerting.contactPoints.path == null;
+        message = "Cannot set both contact points settings and contact points path";
+      }
+      {
+        assertion = cfg.provision.alerting.policies.settings == null || cfg.provision.alerting.policies.path == null;
+        message = "Cannot set both policies settings and policies path";
+      }
+      {
+        assertion = cfg.provision.alerting.templates.settings == null || cfg.provision.alerting.templates.path == null;
+        message = "Cannot set both templates settings and templates path";
+      }
+      {
+        assertion = cfg.provision.alerting.muteTimings.settings == null || cfg.provision.alerting.muteTimings.path == null;
+        message = "Cannot set both mute timings settings and mute timings path";
+      }
+    ];
+
+    systemd.services.grafana = {
+      description = "Grafana Service Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
+      script = ''
+        set -o errexit -o pipefail -o nounset -o errtrace
+        shopt -s inherit_errexit
+
+        exec ${cfg.package}/bin/grafana server -homepath ${cfg.dataDir} -config ${configFile}
+      '';
+      serviceConfig = {
+        WorkingDirectory = cfg.dataDir;
+        User = "grafana";
+        Restart = "on-failure";
+        RuntimeDirectory = "grafana";
+        RuntimeDirectoryMode = "0755";
+        # Hardening
+        AmbientCapabilities = lib.mkIf (cfg.settings.server.http_port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = if (cfg.settings.server.http_port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
+        DeviceAllow = [ "" ];
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "full";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        # Upstream grafana is not setting SystemCallFilter for compatibility
+        # reasons, see https://github.com/grafana/grafana/pull/40176
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ];
+        UMask = "0027";
+      };
+      preStart = ''
+        ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
+        ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir}
+      '';
+    };
+
+    users.users.grafana = {
+      uid = config.ids.uids.grafana;
+      description = "Grafana user";
+      home = cfg.dataDir;
+      createHome = true;
+      group = "grafana";
+    };
+    users.groups.grafana = { };
+  };
+}