nixos-config/config/service/metrics.nix

348 lines
12 KiB
Nix

{ config, lib, pkgs, ... }@toplevel:
with lib;
let
hostname = config.instance.hostname;
domain-name = config.fudo.hosts."${hostname}".domain;
domain = config.fudo.domains."${domain-name}";
host-secrets = config.fudo.secrets.host-secrets."${hostname}";
notEmpty = lst: (length lst) > 0;
metricsEnabled = notEmpty domain.prometheus-hosts;
metricsScraper = elem hostname domain.prometheus-hosts;
metricsMonitor = elem hostname domain.grafana-hosts;
prometheus-cfg = config.fudo.services.metrics.prometheus;
grafana-cfg = config.fudo.services.metrics.grafana;
host-fqdn = hostname:
let host-domain = config.fudo.hosts.${hostname}.domain;
in "${hostname}.${host-domain}";
host-auth-fqdn = hostname: "${host-fqdn hostname}.";
make-alias-map = type: hosts:
listToAttrs
(imap0 (i: hostname: nameValuePair hostname "${type}-${toString i}") hosts);
headOrNull = lst: if notEmpty lst then head lst else null;
metrics-master = headOrNull domain.prometheus-hosts;
monitor-master = headOrNull domain.grafana-hosts;
metrics-alias-map = make-alias-map "metrics" domain.prometheus-hosts;
monitor-alias-map = make-alias-map "monitor" domain.grafana-hosts;
alias-map-to-cnames =
mapAttrs' (hostname: alias: nameValuePair alias (host-auth-fqdn hostname));
alias-map-to-hostnames =
mapAttrsToList (hostname: alias: "${alias}.${domain-name}");
grafana-smtp-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-smtp-passwd"
config.instance.build-seed;
grafana-auth-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-auth-passwd"
config.instance.build-seed;
grafana-admin-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-admin-passwd"
config.instance.build-seed;
grafana-secret-key-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-secret-key"
config.instance.build-seed;
grafana-database-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-database-postgres"
config.instance.build-seed;
site = let site-name = config.fudo.hosts."${hostname}".site;
in config.fudo.sites."${site-name}";
is-private-network = site.local-gateway != null;
domainToBaseDn = domain:
concatStringsSep "," (map (el: "dc=${el}") (splitString "." domain));
ldapEnabled = domain.ldap-servers != [ ];
isPostgresServer = config.instance.hostname == domain.postgresql-server;
postgresServer = pkgs.lib.getHostFqdn domain.postgresql-server;
in {
options.fudo.services.metrics = with types; {
prometheus = {
static-targets = mkOption {
type = attrsOf (listOf str);
description =
"A map of exporter type to a list of host:ports from which to collect metrics.";
example = { dovecot = [ "my.host.name:1111" ]; };
default = { };
};
state-directory = mkOption {
type = str;
description = "Path at which to store Prometheus state.";
default = "/var/lib/prometheus";
};
};
grafana = {
smtp = {
username = mkOption {
type = str;
description = "Username from which to send Grafana alerts.";
default = "monitor";
};
hostname = mkOption {
type = str;
description = "Hostname of the SMTP host.";
default = "mail.${toplevel.config.instance.local-domain}";
};
};
ldap = let base-dn = domainToBaseDn config.instance.local-domain;
in {
base-dn = mkOption {
type = str;
description = "DN under which to search for users.";
default = base-dn;
};
bind-user = mkOption {
type = str;
description = "DN as which to bind to the LDAP server.";
default = "grafana_reader";
};
bind-passwd = mkOption {
type = nullOr str;
description = "Path to file with bind password. Generated if null.";
default = null;
};
};
# database = {
# hostname = mkOption {
# type = str;
# description = "Hostname of the postgresql database.";
# default = "localhost";
# };
# user = mkOption {
# type = str;
# description =
# "Username as which to authenticate to the postgresql database.";
# };
# password-file = mkOption {
# type = str;
# description =
# "Password file (on the target host) which to authenticate to the postgresql database.";
# };
# name = mkOption {
# type = str;
# description = "Database name.";
# default = "grafana";
# };
# };
state-directory = mkOption {
type = str;
description = "Path at which to store Grafana state.";
default = "/var/lib/grafana";
};
};
};
config = mkIf metricsEnabled {
fudo = {
system-users = {
"${grafana-cfg.smtp.username}" = {
description = "Grafana Alerts";
ldap-hashed-password =
pkgs.lib.passwd.hash-ldap-passwd "grafana-smtp-passwd"
grafana-smtp-password-file;
};
"${grafana-cfg.ldap.bind-user}" = mkIf ((domain.ldap-servers != [ ])
&& (grafana-cfg.ldap.bind-passwd == null)) {
description = "Grafana Authentication Reader";
ldap-hashed-password =
pkgs.lib.passwd.hash-ldap-passwd "grafana-auth-passwd"
grafana-auth-password-file;
};
};
secrets.host-secrets =
let grafana-user = config.systemd.services.grafana.serviceConfig.User;
in {
"${hostname}" = {
grafana-smtp-password = mkIf metricsMonitor {
source-file = grafana-smtp-password-file;
target-file = "/run/metrics/grafana/smtp.passwd";
user = grafana-user;
};
grafana-admin-password = mkIf metricsMonitor {
source-file = grafana-admin-password-file;
target-file = "/run/metrics/grafana/admin.passwd";
user = grafana-user;
};
grafana-secret-key = mkIf metricsMonitor {
source-file = grafana-secret-key-file;
target-file = "/run/metrics/grafana/secret.key";
user = grafana-user;
};
grafana-postgresql-password = mkIf metricsMonitor {
source-file = grafana-database-password-file;
target-file = "/run/metrics/grafana/postgres.passwd";
user = grafana-user;
};
postgresql-grafana-password = mkIf isPostgresServer {
source-file = grafana-database-password-file;
target-file = "/run/postgres-users/grafana.passwd";
user = config.systemd.services.postgresql.serviceConfig.User;
};
};
};
zones."${domain.zone}" = {
aliases = let
metrics-aliases = alias-map-to-cnames metrics-alias-map;
monitor-aliases = alias-map-to-cnames monitor-alias-map;
metrics-master-cname = optionalAttrs (metrics-master != null) {
metrics = "${metrics-master}.${domain-name}.";
};
monitor-master-cname = optionalAttrs (monitor-master != null) {
monitor = "${monitor-master}.${domain-name}.";
};
in metrics-aliases // monitor-aliases // metrics-master-cname
// monitor-master-cname;
metric-records = let
domain-hosts = filterAttrs (hostname: hostOpts:
hostOpts.domain == domain-name && hostOpts.nixos-system)
config.fudo.hosts;
in {
node = map (hostname: {
host = "${hostname}.${domain-name}";
port = if is-private-network then 80 else 443;
}) (attrNames domain-hosts);
};
};
postgresql = mkIf isPostgresServer {
users.grafana = {
password-file = host-secrets.postgresql-grafana-password.target-file;
databases.grafana = {
access = "CONNECT";
entity-access = {
"ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
# "SELECT,INSERT,UPDATE,DELETE";
"ALL SEQUENCES IN SCHEMA public" = "ALL PRIVILEGES";
# "SELECT, UPDATE";
};
};
};
databases.grafana.users = config.instance.local-admins;
};
metrics = {
node-exporter = {
enable = true;
hostname = host-fqdn hostname;
private-network = is-private-network;
};
prometheus = mkIf metricsScraper {
enable = true;
service-discovery-dns = {
node = [ "node._metrics._tcp.${domain-name}" ];
};
static-targets = prometheus-cfg.static-targets;
hostname = let alias = metrics-alias-map.${hostname};
in "${alias}.${domain-name}";
state-directory = prometheus-cfg.state-directory;
private-network = is-private-network;
};
grafana = mkIf metricsMonitor {
enable = true;
hostname = let alias = monitor-alias-map.${hostname};
in "${alias}.${domain-name}";
smtp = let cfg = grafana-cfg.smtp;
in {
username = cfg.username;
password-file = host-secrets.grafana-smtp-password.target-file;
hostname = cfg.hostname;
email = "${cfg.username}@${domain-name}";
};
database = let cfg = grafana-cfg.database;
in {
name = "grafana";
user = "grafana";
password-file =
host-secrets.grafana-postgresql-password.target-file;
hostname = postgresServer;
};
ldap = mkIf (domain.ldap-servers != [ ]) {
hosts = map host-fqdn domain.ldap-servers;
base-dn = grafana-cfg.ldap.base-dn;
bind-dn =
"cn=${grafana-cfg.ldap.bind-user},${grafana-cfg.ldap.base-dn}";
bind-passwd = if (grafana-cfg.ldap.bind-passwd != null) then
grafana-cfg.ldap.bind-passwd
else
(readFile grafana-auth-password-file);
};
admin-password-file = host-secrets.grafana-admin-password.target-file;
secret-key-file = host-secrets.grafana-secret-key.target-file;
datasources = let
scheme = if is-private-network then "http" else "https";
host-config = hostname: {
url = "${scheme}://${hostname}.${domain-name}";
type = "prometheus";
default = hostname == "metrics-0";
};
in listToAttrs
(map (host: nameValuePair "prometheus-${host}" (host-config host))
(attrValues metrics-alias-map));
state-directory = grafana-cfg.state-directory;
private-network = is-private-network;
};
};
};
services.nginx =
mkIf (hostname == metrics-master || hostname == monitor-master) {
enable = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
virtualHosts =
let scheme = if is-private-network then "http" else "https";
in {
"metrics.${domain-name}" = mkIf (hostname == metrics-master) {
enableACME = !is-private-network;
forceSSL = !is-private-network;
locations."/".return = let alias = metrics-alias-map.${hostname};
in "301 ${scheme}://${alias}.${domain-name}$request_uri";
};
"monitor.${domain-name}" = mkIf (hostname == monitor-master) {
enableACME = !is-private-network;
forceSSL = !is-private-network;
locations."/".return = let alias = monitor-alias-map.${hostname};
in "301 ${scheme}://${alias}.${domain-name}$request_uri";
};
};
};
};
}