Files
nix-config/modules/nixos/services/grafana/default.nix
mjallen18 7798684d29 grafana
2026-03-24 10:20:46 -05:00

346 lines
12 KiB
Nix
Executable File

{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
name = "grafana";
cfg = config.${namespace}.services.${name};
# ---------------------------------------------------------------------------
# Community dashboards — fetched at build time, pinned by hash.
# ---------------------------------------------------------------------------
communityDashboards = pkgs.linkFarm "grafana-community-dashboards" [
{
# Node Exporter Full — https://grafana.com/grafana/dashboards/1860
name = "node-exporter-full.json";
path = pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/1860/revisions/latest/download";
sha256 = "sha256-pNgn6xgZBEu6LW0lc0cXX2gRkQ8lg/rer34SPE3yEl4=";
};
}
{
# PostgreSQL Database — https://grafana.com/grafana/dashboards/9628
name = "postgresql.json";
path = pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/9628/revisions/latest/download";
sha256 = "sha256-UhusNAZbyt7fJV/DhFUK4FKOmnTpG0R15YO2r+nDnMc=";
};
}
{
# Redis Dashboard for prometheus-redis-exporter 1.x — https://grafana.com/grafana/dashboards/763
name = "redis.json";
path = pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/763/revisions/latest/download";
sha256 = "sha256-pThz+zHjcTT9vf8fpUuZK/ejNnH9GwEZVXOY27c9Aw8=";
};
}
{
# MySQL Overview — https://grafana.com/grafana/dashboards/7362
name = "mysql.json";
path = pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/7362/revisions/latest/download";
sha256 = "sha256-WW7g60KY20XAdyUpumA0hBrjFC9MQGuGjiJKUhSVBXI=";
};
}
{
# Nextcloud — https://grafana.com/grafana/dashboards/9632
name = "nextcloud.json";
path = pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/9632/revisions/latest/download";
sha256 = "sha256-Z28Q/sMg3jxglkszAs83IpL8f4p9loNnTQzjc3S/SAQ=";
};
}
];
# ---------------------------------------------------------------------------
# Custom dashboards — maintained in this repo under dashboards/
# ---------------------------------------------------------------------------
customDashboards = pkgs.linkFarm "grafana-custom-dashboards" [
{
name = "nut.json";
path = ./dashboards/nut.json;
}
{
name = "caddy.json";
path = ./dashboards/caddy.json;
}
{
name = "gitea.json";
path = ./dashboards/gitea.json;
}
{
name = "nas-overview.json";
path = ./dashboards/nas-overview.json;
}
];
# Minimal .my.cnf for the mysqld exporter. No credentials are needed
# because runAsLocalSuperUser = true runs as the mysql OS user, which
# MariaDB authenticates via the unix_socket plugin automatically.
mysqldExporterCnf = pkgs.writeText "prometheus-mysqld-exporter.cnf" ''
[client]
user=root
socket=/run/mysqld/mysqld.sock
'';
giteaPort = config.${namespace}.services.gitea.port;
resticPort = config.${namespace}.services.restic.port;
nextcloudPort = config.${namespace}.services.nextcloud.port;
grafanaConfig = lib.${namespace}.mkModule {
inherit config name;
description = "grafana";
options = { };
moduleConfig = {
services = {
prometheus = {
enable = true;
# bearer_token_file paths (e.g. Gitea metrics key) are SOPS secrets
# that only exist at runtime, not in the Nix build sandbox.
# "syntax-only" still catches config errors without stat-ing the files.
checkConfig = "syntax-only";
exporters = {
node = {
enable = true;
enabledCollectors = [
"filesystem"
"diskstats"
"meminfo"
"cpu"
"systemd"
"processes"
];
extraFlags = [
"--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run)($|/)"
];
};
libvirt = {
enable = false;
openFirewall = true;
};
nut = {
enable = true;
openFirewall = true;
passwordPath = config.sops.secrets."jallen-nas/ups_password".path;
nutUser = upsUser;
};
# PostgreSQL — runs as the local postgres superuser via peer auth
# (Unix socket, no password required).
postgres = {
enable = true;
runAsLocalSuperUser = true;
};
# Redis — single exporter instance covering all four Redis servers
# via the multi-target scrape pattern (/scrape?target=<addr>).
# The exporter needs AF_INET to reach TCP Redis instances.
redis = {
enable = true;
# No fixed --redis.addr: multi-target mode uses ?target= param.
};
# MariaDB — runs as the mysql OS user so it can connect via the
# Unix socket without a password (unix_socket auth).
mysqld = {
enable = true;
runAsLocalSuperUser = true;
configFile = mysqldExporterCnf;
};
# Nextcloud — authenticates with the admin account.
# passwordFile must be readable by the prometheus-nextcloud-exporter
# user; sops mode 0440 + group keys covers that.
nextcloud = {
enable = true;
url = "http://localhost:${toString nextcloudPort}";
username = "mjallen";
passwordFile = config.sops.secrets."jallen-nas/nextcloud/adminpassword".path;
};
};
scrapeConfigs = [
# ── System ──────────────────────────────────────────────────────────
{
job_name = "node";
static_configs = [
{
targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ];
}
];
}
# ── UPS (NUT) ────────────────────────────────────────────────────────
{
job_name = "nut";
static_configs = [
{
targets = [ "localhost:${toString config.services.prometheus.exporters.nut.port}" ];
}
];
}
# ── Databases ────────────────────────────────────────────────────────
{
job_name = "postgres";
static_configs = [
{
targets = [ "localhost:${toString config.services.prometheus.exporters.postgres.port}" ];
}
];
}
{
# Redis multi-target: one exporter, four Redis instances.
# The redis_exporter's /scrape?target= endpoint proxies each target
# so a single exporter process covers all servers.
job_name = "redis";
metrics_path = "/scrape";
static_configs = [
{
targets = [
"redis://localhost:6379" # authentik
"redis://localhost:6363" # ccache
"redis://localhost:6380" # manyfold
"redis://localhost:6381" # onlyoffice
];
}
];
relabel_configs = [
{
source_labels = [ "__address__" ];
target_label = "__param_target";
}
{
source_labels = [ "__param_target" ];
target_label = "instance";
}
{
target_label = "__address__";
replacement = "localhost:${toString config.services.prometheus.exporters.redis.port}";
}
];
}
{
job_name = "mysqld";
static_configs = [
{
targets = [ "localhost:${toString config.services.prometheus.exporters.mysqld.port}" ];
}
];
}
# ── Application services ─────────────────────────────────────────────
{
# Caddy exposes its built-in Prometheus endpoint on port 2019.
job_name = "caddy";
static_configs = [
{
targets = [ "localhost:2019" ];
}
];
}
{
# Gitea's /metrics endpoint is protected by a Bearer token.
job_name = "gitea";
metrics_path = "/metrics";
bearer_token_file = config.sops.secrets."jallen-nas/gitea/metrics-key".path;
static_configs = [
{
targets = [ "localhost:${toString giteaPort}" ];
}
];
}
{
# restic REST server exposes Prometheus metrics at /metrics.
job_name = "restic";
metrics_path = "/metrics";
static_configs = [
{
targets = [ "localhost:${toString resticPort}" ];
}
];
}
{
job_name = "nextcloud";
static_configs = [
{
targets = [ "localhost:${toString config.services.prometheus.exporters.nextcloud.port}" ];
}
];
}
];
};
grafana = {
enable = true;
settings = {
server = {
http_port = cfg.port;
http_addr = "0.0.0.0";
};
security = {
# Read the secret key from a SOPS-managed file at runtime so it
# never appears in the Nix store. The "$__file{}" syntax is
# Grafana's built-in file provider.
secret_key = "$__file{${config.sops.secrets."jallen-nas/grafana/secret-key".path}}";
};
};
dataDir = "${cfg.configDir}/grafana";
provision = {
enable = true;
datasources.settings.datasources = [
{
name = "Prometheus";
type = "prometheus";
access = "proxy";
url = "http://localhost:${toString config.services.prometheus.port}";
}
];
dashboards.settings.providers = [
{
name = "community";
orgId = 1;
type = "file";
disableDeletion = true;
updateIntervalSeconds = 60;
allowUiUpdates = false;
options.path = communityDashboards;
}
{
name = "custom";
orgId = 1;
type = "file";
disableDeletion = true;
updateIntervalSeconds = 60;
allowUiUpdates = false;
options.path = customDashboards;
}
];
};
};
};
# The redis exporter needs AF_INET to reach TCP Redis instances.
# The default systemd hardening only allows AF_UNIX.
systemd.services.prometheus-redis-exporter.serviceConfig.RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
};
};
upsUser = "nas-admin";
in
{
imports = [ grafanaConfig ];
}