{ config, lib, pkgs, ... }: let cfg = config.vhack.monitoring.grafana; in { options.vhack.monitoring.grafana = { enable = lib.mkEnableOption "grafana"; orgId = lib.mkOption { type = lib.types.int; description = "Org ID where all vhack related config will be stored."; default = 1; }; contactPoints = lib.mkOption { type = lib.types.listOf lib.types.str; description = "List of email addresses to send alerts to"; default = []; }; dashboards = lib.mkOption { type = lib.types.listOf lib.types.path; description = "dashboards to provision under 'Vhack' folder."; default = []; }; fqdn = lib.mkOption { type = lib.types.str; description = "Domain under which Grafana will be served."; example = "grafana.example.org"; }; adminPassword = lib.mkOption { type = lib.types.path; description = "The age-encrypted initial admin password; passed to agenix"; }; secretKey = lib.mkOption { type = lib.types.path; description = "The age-encrypted secret key, used for signing; passed to agenix"; }; port = lib.mkOption { type = lib.types.port; description = "Port where Grafana listens to HTTP requests."; default = 3000; }; debugLog = lib.mkOption { type = lib.types.bool; description = "Set to true to enable debug logging of the infrastructure serving Grafana."; default = false; example = true; }; smtp = lib.mkOption { description = "SMTP options."; default = null; type = lib.types.nullOr ( lib.types.submodule { options = { from_address = lib.mkOption { type = lib.types.str; description = "SMTP address from which the emails originate."; example = "vaultwarden@mydomain.com"; }; from_name = lib.mkOption { type = lib.types.str; description = "SMTP name from which the emails originate."; default = "Grafana"; }; host = lib.mkOption { type = lib.types.str; description = "SMTP host to send the emails to."; }; port = lib.mkOption { type = lib.types.port; description = "SMTP port to send the emails to."; default = 25; }; username = lib.mkOption { type = lib.types.str; description = "Username to connect to the SMTP host."; }; passwordFile = lib.mkOption { type = lib.types.str; description = "File containing the password to connect to the SMTP host."; }; }; } ); }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = builtins.length cfg.contactPoints > 0; message = "Must have at least one contact point for alerting"; } ]; age.secrets = { grafanaInitialAdminPw = { file = cfg.adminPassword; mode = "0700"; owner = "grafana"; group = "grafana"; }; grafanaSecretKey = { file = cfg.secretKey; mode = "0700"; owner = "grafana"; group = "grafana"; }; }; vhack = { postgresql.enable = true; monitoring.grafana.dashboards = [ ../dashboards/Errors.json ../dashboards/Performance.json ../dashboards/Scraping_Jobs.json ]; }; vhack.anubis.instances."${cfg.fqdn}".target = "http://${config.services.grafana.settings.server.http_addr}:${toString cfg.port}"; services = { postgresql = { ensureUsers = [ { name = "grafana"; ensureDBOwnership = true; } ]; ensureDatabases = [ "grafana" ]; }; grafana = { enable = true; settings = { database = { host = "/run/postgresql"; user = "grafana"; name = "grafana"; type = "postgres"; }; security = { secret_key = "$__file{${config.age.secrets.grafanaSecretKey.path}}"; disable_initial_admin_creation = false; admin_password = "$__file{${config.age.secrets.grafanaInitialAdminPw.path}}"; }; server = { http_addr = "127.0.0.1"; http_port = cfg.port; domain = cfg.fqdn; root_url = "https://${cfg.fqdn}"; router_logging = cfg.debugLog; }; smtp = lib.mkIf (!(isNull cfg.smtp)) { enabled = true; inherit (cfg.smtp) from_address from_name; host = "${cfg.smtp.host}:${toString cfg.smtp.port}"; user = cfg.smtp.username; password = "$__file{${cfg.smtp.passwordFile}}"; }; }; provision = { dashboards.settings = lib.mkIf (cfg.dashboards != []) { apiVersion = 1; providers = [ { folder = "Vhack"; options.path = pkgs.symlinkJoin { name = "dashboards"; paths = map (p: pkgs.runCommand "davhackoard" {} "mkdir $out; cp ${p} $out") cfg.dashboards; }; allowUiUpdates = true; disableDeletion = true; } ]; }; datasources.settings = { apiVersion = 1; datasources = [ { inherit (cfg) orgId; name = "Prometheus"; type = "prometheus"; url = "http://127.0.0.1:${toString config.services.prometheus.port}"; uid = "df80f9f5-97d7-4112-91d8-72f523a02b09"; isDefault = true; version = 1; } { inherit (cfg) orgId; name = "Loki"; type = "loki"; url = "http://127.0.0.1:${toString config.services.loki.configuration.server.http_listen_port}"; uid = "cd6cc53e-840c-484d-85f7-96fede324006"; version = 1; } ]; deleteDatasources = [ { inherit (cfg) orgId; name = "Prometheus"; } { inherit (cfg) orgId; name = "Loki"; } ]; }; alerting = { contactPoints.settings = { apiVersion = 1; contactPoints = [ { inherit (cfg) orgId; name = "grafana-default-email"; receivers = lib.optionals ((builtins.length cfg.contactPoints) > 0) [ { uid = "sysadmin"; type = "email"; settings.addresses = lib.concatStringsSep ";" cfg.contactPoints; } ]; } ]; }; policies.settings = { apiVersion = 1; policies = [ { inherit (cfg) orgId; receiver = "grafana-default-email"; group_by = [ "grafana_folder" "alertname" ]; group_wait = "30s"; group_interval = "5m"; repeat_interval = "4h"; } ]; # resetPolicies seems to happen after setting the above policies, effectively rolling back # any updates. }; rules.settings = let rules = builtins.fromJSON (builtins.readFile ../rules.json); in { apiVersion = 1; groups = [ { inherit (cfg) orgId; name = "SysAdmin"; folder = "Vhack"; interval = "10m"; inherit rules; } ]; # deleteRules seems to happen after creating the above rules, effectively rolling back # any updates. }; }; }; }; }; }; }