{ config, lib, pkgs, ... }: let cfg = config.services.burrow.observability; otelCollectorPackage = if builtins.hasAttr "opentelemetry-collector-contrib" pkgs then pkgs."opentelemetry-collector-contrib" else pkgs.opentelemetry-collector; garageCfg = config.services.burrow.garage; headscaleCfg = config.services.burrow.headscale; grafanaHttpPort = config.services.grafana.settings.server.http_port or 3000; grafanaHttpAddress = config.services.grafana.settings.server.http_addr or "127.0.0.1"; tailscaleExporterEnabled = cfg.tailscaleExporterEnvironmentFile != null; in { options.services.burrow.observability = { enable = lib.mkEnableOption "Burrow Prometheus, OpenTelemetry, Jaeger, and Grafana wiring"; prometheusListenAddress = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "Address used by the local Prometheus server."; }; prometheusPort = lib.mkOption { type = lib.types.port; default = 9090; description = "Port used by the local Prometheus server."; }; otelGrpcEndpoint = lib.mkOption { type = lib.types.str; default = "127.0.0.1:4317"; description = "Local OTLP gRPC endpoint accepted by the OpenTelemetry collector."; }; otelHttpEndpoint = lib.mkOption { type = lib.types.str; default = "127.0.0.1:4318"; description = "Local OTLP HTTP endpoint accepted by the OpenTelemetry collector."; }; otelPrometheusEndpoint = lib.mkOption { type = lib.types.str; default = "127.0.0.1:9464"; description = "Prometheus endpoint where the OpenTelemetry collector exports its own metrics."; }; jaegerImage = lib.mkOption { type = lib.types.str; default = "docker.io/jaegertracing/all-in-one:1.57"; description = "OCI image used for the local Jaeger all-in-one service."; }; jaegerUiPort = lib.mkOption { type = lib.types.port; default = 16686; description = "Local Jaeger UI and query API port."; }; jaegerOtlpGrpcPort = lib.mkOption { type = lib.types.port; default = 14317; description = "Local remapped OTLP gRPC port forwarded to Jaeger."; }; jaegerOtlpHttpPort = lib.mkOption { type = lib.types.port; default = 14318; description = "Local remapped OTLP HTTP port forwarded to Jaeger."; }; jaegerAdminPort = lib.mkOption { type = lib.types.port; default = 14269; description = "Local Jaeger admin and metrics port."; }; tailscaleExporterEnvironmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Optional environment file for the Prometheus Tailscale exporter OAuth credentials."; }; }; config = lib.mkIf cfg.enable (lib.mkMerge [ { virtualisation.podman.enable = true; services.grafana = { settings = { metrics = { enabled = true; disable_total_stats = true; }; }; provision = { enable = true; datasources.settings = { apiVersion = 1; prune = true; datasources = [ { name = "Prometheus"; uid = "prometheus"; type = "prometheus"; access = "proxy"; url = "http://${cfg.prometheusListenAddress}:${toString cfg.prometheusPort}"; isDefault = true; jsonData = { timeInterval = "15s"; httpMethod = "POST"; }; } { name = "Jaeger"; uid = "jaeger"; type = "jaeger"; access = "proxy"; url = "http://127.0.0.1:${toString cfg.jaegerUiPort}"; } ]; }; }; }; services.prometheus = { enable = true; listenAddress = cfg.prometheusListenAddress; port = cfg.prometheusPort; retentionTime = "30d"; checkConfig = "syntax-only"; exporters = { node = { enable = true; listenAddress = "127.0.0.1"; }; systemd = { enable = true; listenAddress = "127.0.0.1"; }; }; scrapeConfigs = [ { job_name = "prometheus"; static_configs = [ { targets = [ "${cfg.prometheusListenAddress}:${toString cfg.prometheusPort}" ]; } ]; } { job_name = "node"; static_configs = [ { targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.node.port}" ]; } ]; } { job_name = "systemd"; static_configs = [ { targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.systemd.port}" ]; } ]; } { job_name = "grafana"; metrics_path = "/metrics"; static_configs = [ { targets = [ "${grafanaHttpAddress}:${toString grafanaHttpPort}" ]; } ]; } { job_name = "headscale"; metrics_path = "/metrics"; static_configs = [ { targets = [ "${headscaleCfg.metricsListenAddress}:${toString headscaleCfg.metricsPort}" ]; } ]; } { job_name = "otel-collector"; static_configs = [ { targets = [ cfg.otelPrometheusEndpoint ]; } ]; } { job_name = "jaeger"; metrics_path = "/metrics"; static_configs = [ { targets = [ "127.0.0.1:${toString cfg.jaegerAdminPort}" ]; } ]; } ] ++ lib.optionals garageCfg.enable [ { job_name = "garage"; metrics_path = "/metrics"; static_configs = [ { targets = [ "${garageCfg.adminListenAddress}:${toString garageCfg.adminPort}" ]; } ]; } ] ++ lib.optionals tailscaleExporterEnabled [ { job_name = "tailscale"; static_configs = [ { targets = [ "127.0.0.1:${toString config.services.prometheus.exporters.tailscale.port}" ]; } ]; } ]; }; services.opentelemetry-collector = { enable = true; package = otelCollectorPackage; settings = { receivers = { otlp = { protocols = { grpc.endpoint = cfg.otelGrpcEndpoint; http.endpoint = cfg.otelHttpEndpoint; }; }; }; processors = { resource.attributes = [ { key = "service.namespace"; value = "burrow"; action = "upsert"; } { key = "deployment.environment"; value = config.networking.hostName; action = "upsert"; } ]; batch = { }; }; exporters = { "otlp/jaeger" = { endpoint = "127.0.0.1:${toString cfg.jaegerOtlpGrpcPort}"; tls.insecure = true; }; prometheus.endpoint = cfg.otelPrometheusEndpoint; }; service.pipelines = { traces = { receivers = [ "otlp" ]; processors = [ "resource" "batch" ]; exporters = [ "otlp/jaeger" ]; }; metrics = { receivers = [ "otlp" ]; processors = [ "resource" "batch" ]; exporters = [ "prometheus" ]; }; }; }; }; virtualisation.oci-containers.containers.burrow-jaeger = { image = cfg.jaegerImage; autoStart = true; environment = { COLLECTOR_OTLP_ENABLED = "true"; SPAN_STORAGE_TYPE = "badger"; BADGER_EPHEMERAL = "false"; BADGER_DIRECTORY_VALUE = "/badger/data"; BADGER_DIRECTORY_KEY = "/badger/key"; }; volumes = [ "burrow-jaeger-badger:/badger" ]; ports = [ "127.0.0.1:${toString cfg.jaegerUiPort}:16686" "127.0.0.1:${toString cfg.jaegerOtlpGrpcPort}:4317" "127.0.0.1:${toString cfg.jaegerOtlpHttpPort}:4318" "127.0.0.1:${toString cfg.jaegerAdminPort}:14269" ]; extraOptions = [ "--pull=always" ]; }; systemd.services.opentelemetry-collector.after = [ "podman-burrow-jaeger.service" ]; systemd.services.opentelemetry-collector.wants = [ "podman-burrow-jaeger.service" ]; environment.sessionVariables = { OTEL_EXPORTER_OTLP_ENDPOINT = "http://${cfg.otelGrpcEndpoint}"; OTEL_EXPORTER_OTLP_PROTOCOL = "grpc"; }; } (lib.mkIf tailscaleExporterEnabled { services.prometheus.exporters.tailscale = { enable = true; listenAddress = "127.0.0.1"; environmentFile = cfg.tailscaleExporterEnvironmentFile; }; }) ]); }