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