# Glue: declare the loopback scrape targets of authelia, grafana and nats on the # swarm's OTEL collector, wherever one of them shares a host with it. # # ONE FILE, THREE PAIRINGS — each service ← the collector, with the same shape. # Deleting this leaves the collector scraping none of them: no error, no # metrics. # # Each target is gated on the service AND the collector being enabled here. The # loopback address is only honest when both run on this host; a target nobody # can read asserts a collection that is not happening. Both services default # from `deploy.allSwarmServices`, but `mkDefault` is an invitation to override, # so co-location is a property of the auto-deployed topology, not the modules. # # ⚠️ Silent when split: separate hosts are separate evaluations with no shared # context, so a host running a service and no collector declares nothing and # the collector's host never learns of the service. No error, no warning — a # healthy exporter and an empty dashboard. Not guardable from here. # # ⚠️ `swarm.otel`, not `hyperhive.otel` — two collectors one word apart, and only # this one reads `scrapeTargets`. { lib, config, ... }: let hyperhiveCfg = config.services.hyperhive; deployCfg = hyperhiveCfg.deploy; swarmCfg = hyperhiveCfg.swarm; withCollector = service: lib.mkIf (deployCfg.${service}.enable && deployCfg.swarm-otel.enable); in { config.services.hyperhive.swarm.otel.scrapeTargets = { authelia = withCollector "authelia" "127.0.0.1:${toString swarmCfg.authelia.metricsPort}"; grafana = withCollector "grafana" "127.0.0.1:${toString swarmCfg.grafana.metricsPort}"; nats = withCollector "nats" "127.0.0.1:${toString swarmCfg.nats.metricsPort}"; }; }