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