diff options
Diffstat (limited to 'nixos/modules/services/monitoring/grafana.nix')
-rw-r--r-- | nixos/modules/services/monitoring/grafana.nix | 239 |
1 files changed, 186 insertions, 53 deletions
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index b0c81a46d4d..e0b2624b6ca 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -5,15 +5,17 @@ with lib; let cfg = config.services.grafana; opt = options.services.grafana; + declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins); envOptions = { PATHS_DATA = cfg.dataDir; - PATHS_PLUGINS = "${cfg.dataDir}/plugins"; + PATHS_PLUGINS = if builtins.isNull cfg.declarativePlugins then "${cfg.dataDir}/plugins" else declarativePlugins; PATHS_LOGS = "${cfg.dataDir}/log"; SERVER_PROTOCOL = cfg.protocol; SERVER_HTTP_ADDR = cfg.addr; SERVER_HTTP_PORT = cfg.port; + SERVER_SOCKET = cfg.socket; SERVER_DOMAIN = cfg.domain; SERVER_ROOT_URL = cfg.rootUrl; SERVER_STATIC_ROOT_PATH = cfg.staticRootPath; @@ -40,6 +42,9 @@ let AUTH_ANONYMOUS_ENABLED = boolToString cfg.auth.anonymous.enable; AUTH_ANONYMOUS_ORG_NAME = cfg.auth.anonymous.org_name; AUTH_ANONYMOUS_ORG_ROLE = cfg.auth.anonymous.org_role; + AUTH_GOOGLE_ENABLED = boolToString cfg.auth.google.enable; + AUTH_GOOGLE_ALLOW_SIGN_UP = boolToString cfg.auth.google.allowSignUp; + AUTH_GOOGLE_CLIENT_ID = cfg.auth.google.clientId; ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable; @@ -64,10 +69,18 @@ let dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration); + notifierConfiguration = { + apiVersion = 1; + notifiers = cfg.provision.notifiers; + }; + + notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); + provisionConfDir = pkgs.runCommand "grafana-provisioning" { } '' - mkdir -p $out/{datasources,dashboards} + mkdir -p $out/{datasources,dashboards,notifiers} ln -sf ${datasourceFile} $out/datasources/datasource.yaml ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml + ln -sf ${notifierFile} $out/notifiers/notifier.yaml ''; # Get a submodule without any embedded metadata: @@ -78,80 +91,80 @@ let options = { name = mkOption { type = types.str; - description = "Name of the datasource. Required"; + description = "Name of the datasource. Required."; }; type = mkOption { - type = types.enum ["graphite" "prometheus" "cloudwatch" "elasticsearch" "influxdb" "opentsdb" "mysql" "mssql" "postgres" "loki"]; - description = "Datasource type. Required"; + type = types.str; + description = "Datasource type. Required."; }; access = mkOption { type = types.enum ["proxy" "direct"]; default = "proxy"; - description = "Access mode. proxy or direct (Server or Browser in the UI). Required"; + description = "Access mode. proxy or direct (Server or Browser in the UI). Required."; }; orgId = mkOption { type = types.int; default = 1; - description = "Org id. will default to orgId 1 if not specified"; + description = "Org id. will default to orgId 1 if not specified."; }; url = mkOption { type = types.str; - description = "Url of the datasource"; + description = "Url of the datasource."; }; password = mkOption { type = types.nullOr types.str; default = null; - description = "Database password, if used"; + description = "Database password, if used."; }; user = mkOption { type = types.nullOr types.str; default = null; - description = "Database user, if used"; + description = "Database user, if used."; }; database = mkOption { type = types.nullOr types.str; default = null; - description = "Database name, if used"; + description = "Database name, if used."; }; basicAuth = mkOption { type = types.nullOr types.bool; default = null; - description = "Enable/disable basic auth"; + description = "Enable/disable basic auth."; }; basicAuthUser = mkOption { type = types.nullOr types.str; default = null; - description = "Basic auth username"; + description = "Basic auth username."; }; basicAuthPassword = mkOption { type = types.nullOr types.str; default = null; - description = "Basic auth password"; + description = "Basic auth password."; }; withCredentials = mkOption { type = types.bool; default = false; - description = "Enable/disable with credentials headers"; + description = "Enable/disable with credentials headers."; }; isDefault = mkOption { type = types.bool; default = false; - description = "Mark as default datasource. Max one per org"; + description = "Mark as default datasource. Max one per org."; }; jsonData = mkOption { type = types.nullOr types.attrs; default = null; - description = "Datasource specific configuration"; + description = "Datasource specific configuration."; }; secureJsonData = mkOption { type = types.nullOr types.attrs; default = null; - description = "Datasource specific secure configuration"; + description = "Datasource specific secure configuration."; }; version = mkOption { type = types.int; default = 1; - description = "Version"; + description = "Version."; }; editable = mkOption { type = types.bool; @@ -167,41 +180,99 @@ let name = mkOption { type = types.str; default = "default"; - description = "Provider name"; + description = "Provider name."; }; orgId = mkOption { type = types.int; default = 1; - description = "Organization ID"; + description = "Organization ID."; }; folder = mkOption { type = types.str; default = ""; - description = "Add dashboards to the specified folder"; + description = "Add dashboards to the specified folder."; }; type = mkOption { type = types.str; default = "file"; - description = "Dashboard provider type"; + description = "Dashboard provider type."; }; disableDeletion = mkOption { type = types.bool; default = false; - description = "Disable deletion when JSON file is removed"; + description = "Disable deletion when JSON file is removed."; }; updateIntervalSeconds = mkOption { type = types.int; default = 10; - description = "How often Grafana will scan for changed dashboards"; + description = "How often Grafana will scan for changed dashboards."; }; options = { path = mkOption { type = types.path; - description = "Path grafana will watch for dashboards"; + description = "Path grafana will watch for dashboards."; }; }; }; }; + + grafanaTypes.notifierConfig = types.submodule { + options = { + name = mkOption { + type = types.str; + default = "default"; + description = "Notifier name."; + }; + type = mkOption { + type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"]; + description = "Notifier type."; + }; + uid = mkOption { + type = types.str; + description = "Unique notifier identifier."; + }; + org_id = mkOption { + type = types.int; + default = 1; + description = "Organization ID."; + }; + org_name = mkOption { + type = types.str; + default = "Main Org."; + description = "Organization name."; + }; + is_default = mkOption { + type = types.bool; + description = "Is the default notifier."; + default = false; + }; + send_reminder = mkOption { + type = types.bool; + default = true; + description = "Should the notifier be sent reminder notifications while alerts continue to fire."; + }; + frequency = mkOption { + type = types.str; + default = "5m"; + description = "How frequently should the notifier be sent reminders."; + }; + disable_resolve_message = mkOption { + type = types.bool; + default = false; + description = "Turn off the message that sends when an alert returns to OK."; + }; + settings = mkOption { + type = types.nullOr types.attrs; + default = null; + description = "Settings for the notifier type."; + }; + secure_settings = mkOption { + type = types.nullOr types.attrs; + default = null; + description = "Secure settings for the notifier type."; + }; + }; + }; in { options.services.grafana = { enable = mkEnableOption "grafana"; @@ -221,7 +292,13 @@ in { port = mkOption { description = "Listening port."; default = 3000; - type = types.int; + type = types.port; + }; + + socket = mkOption { + description = "Listening socket."; + default = "/run/grafana/grafana.sock"; + type = types.str; }; domain = mkOption { @@ -261,6 +338,17 @@ in { type = types.package; }; + declarativePlugins = mkOption { + type = with types; nullOr (listOf path); + default = null; + description = "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed."; + example = literalExample "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]"; + # Make sure each plugin is added only once; otherwise building + # the link farm fails, since the same path is added multiple + # times. + apply = x: if isList x then lib.unique x else x; + }; + dataDir = mkOption { description = "Data directory."; default = "/var/lib/grafana"; @@ -330,17 +418,23 @@ in { provision = { enable = mkEnableOption "provision"; datasources = mkOption { - description = "Grafana datasources configuration"; + description = "Grafana datasources configuration."; default = []; type = types.listOf grafanaTypes.datasourceConfig; apply = x: map _filter x; }; dashboards = mkOption { - description = "Grafana dashboard configuration"; + description = "Grafana dashboard configuration."; default = []; type = types.listOf grafanaTypes.dashboardConfig; apply = x: map _filter x; }; + notifiers = mkOption { + description = "Grafana notifier configuration."; + default = []; + type = types.listOf grafanaTypes.notifierConfig; + apply = x: map _filter x; + }; }; security = { @@ -384,12 +478,12 @@ in { smtp = { enable = mkEnableOption "smtp"; host = mkOption { - description = "Host to connect to"; + description = "Host to connect to."; default = "localhost:25"; type = types.str; }; user = mkOption { - description = "User used for authentication"; + description = "User used for authentication."; default = ""; type = types.str; }; @@ -410,7 +504,7 @@ in { type = types.nullOr types.path; }; fromAddress = mkOption { - description = "Email address used for sending"; + description = "Email address used for sending."; default = "admin@grafana.localhost"; type = types.str; }; @@ -418,7 +512,7 @@ in { users = { allowSignUp = mkOption { - description = "Disable user signup / registration"; + description = "Disable user signup / registration."; default = false; type = types.bool; }; @@ -442,28 +536,51 @@ in { }; }; - auth.anonymous = { - enable = mkOption { - description = "Whether to allow anonymous access"; - default = false; - type = types.bool; - }; - org_name = mkOption { - description = "Which organization to allow anonymous access to"; - default = "Main Org."; - type = types.str; + auth = { + anonymous = { + enable = mkOption { + description = "Whether to allow anonymous access."; + default = false; + type = types.bool; + }; + org_name = mkOption { + description = "Which organization to allow anonymous access to."; + default = "Main Org."; + type = types.str; + }; + org_role = mkOption { + description = "Which role anonymous users have in the organization."; + default = "Viewer"; + type = types.str; + }; }; - org_role = mkOption { - description = "Which role anonymous users have in the organization"; - default = "Viewer"; - type = types.str; + google = { + enable = mkOption { + description = "Whether to allow Google OAuth2."; + default = false; + type = types.bool; + }; + allowSignUp = mkOption { + description = "Whether to allow sign up with Google OAuth2."; + default = false; + type = types.bool; + }; + clientId = mkOption { + description = "Google OAuth2 client ID."; + default = ""; + type = types.str; + }; + clientSecretFile = mkOption { + description = "Google OAuth2 client secret."; + default = null; + type = types.nullOr types.path; + }; }; - }; analytics.reporting = { enable = mkOption { - description = "Whether to allow anonymous usage reporting to stats.grafana.net"; + description = "Whether to allow anonymous usage reporting to stats.grafana.net."; default = true; type = types.bool; }; @@ -489,6 +606,9 @@ in { (optional ( any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources ) "Datasource passwords will be stored as plaintext in the Nix store!") + (optional ( + any (x: x.secure_settings != null) cfg.provision.notifiers + ) "Notifier secure settings will be stored as plaintext in the Nix store!") ]; environment.systemPackages = [ cfg.package ]; @@ -520,17 +640,28 @@ in { QT_QPA_PLATFORM = "offscreen"; } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions; script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + ${optionalString (cfg.auth.google.clientSecretFile != null) '' + GF_AUTH_GOOGLE_CLIENT_SECRET="$(<${escapeShellArg cfg.auth.google.clientSecretFile})" + export GF_AUTH_GOOGLE_CLIENT_SECRET + ''} ${optionalString (cfg.database.passwordFile != null) '' - export GF_DATABASE_PASSWORD="$(cat ${escapeShellArg cfg.database.passwordFile})" + GF_DATABASE_PASSWORD="$(<${escapeShellArg cfg.database.passwordFile})" + export GF_DATABASE_PASSWORD ''} ${optionalString (cfg.security.adminPasswordFile != null) '' - export GF_SECURITY_ADMIN_PASSWORD="$(cat ${escapeShellArg cfg.security.adminPasswordFile})" + GF_SECURITY_ADMIN_PASSWORD="$(<${escapeShellArg cfg.security.adminPasswordFile})" + export GF_SECURITY_ADMIN_PASSWORD ''} ${optionalString (cfg.security.secretKeyFile != null) '' - export GF_SECURITY_SECRET_KEY="$(cat ${escapeShellArg cfg.security.secretKeyFile})" + GF_SECURITY_SECRET_KEY="$(<${escapeShellArg cfg.security.secretKeyFile})" + export GF_SECURITY_SECRET_KEY ''} ${optionalString (cfg.smtp.passwordFile != null) '' - export GF_SMTP_PASSWORD="$(cat ${escapeShellArg cfg.smtp.passwordFile})" + GF_SMTP_PASSWORD="$(<${escapeShellArg cfg.smtp.passwordFile})" + export GF_SMTP_PASSWORD ''} ${optionalString cfg.provision.enable '' export GF_PATHS_PROVISIONING=${provisionConfDir}; @@ -540,6 +671,8 @@ in { serviceConfig = { WorkingDirectory = cfg.dataDir; User = "grafana"; + RuntimeDirectory = "grafana"; + RuntimeDirectoryMode = "0755"; }; preStart = '' ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir} |