From 2a2f5a754ebc2a5cb76f4bcca23f73879f69ed90 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 15 Aug 2026 09:31:42 +0200 Subject: [PATCH] feat(otel): point agents at the collector, and stop shipping them the token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent-facing endpoint becomes derived rather than `otel.endpoint` re-defined: with the collector on, agents and hive-c0re's own exporter send to the bridge address; with it off the expression is the old value character for character. `otel.endpoint` keeps meaning "where telemetry ultimately goes" on every hive that already sets it. Suppressing HYPERHIVE_OTEL_HEADERS_CREDENTIAL is the half that actually closes the hole this issue is about. The credential path host -> container is sound (/run/credentials is dr-x------ root), but the hive-otel-header oneshot then writes the value into the agent's own settings.json, mode 0600 and owned by the agent — so today every agent can read the hive's upstream telemetry credential. With a collector there is nothing to forward. The protocol is derived for the same reason the endpoint is: the first hop is now the collector's OTLP/HTTP receiver, while otel.protocol describes the upstream link. --- nix/host-modules/hive-c0re/environment.nix | 30 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/nix/host-modules/hive-c0re/environment.nix b/nix/host-modules/hive-c0re/environment.nix index 3046f46c..ba345ca8 100644 --- a/nix/host-modules/hive-c0re/environment.nix +++ b/nix/host-modules/hive-c0re/environment.nix @@ -81,15 +81,39 @@ in # don't render no-op env lines. let otel = config.services.hyperhive.otel; + # `otel.endpoint` keeps meaning "where telemetry ultimately goes", + # on every hive, whether or not a collector runs. What changes with + # a collector is only where the *first* hop lands — so the + # agent-facing value is DERIVED here rather than by redefining the + # option. Redefining it would migrate the meaning of a value every + # existing hive already has set, silently, while still evaluating. + # With the collector off this expression is `otel.endpoint`, i.e. + # byte-identical to before the collector existed. + collectorOn = otel.collector.enable; in { - HYPERHIVE_OTEL_ENDPOINT = otel.endpoint; - HYPERHIVE_OTEL_PROTOCOL = otel.protocol; + HYPERHIVE_OTEL_ENDPOINT = + if collectorOn then + "http://${config.services.hyperhive.network.bridgeIp}:${toString otel.collector.port}" + else + otel.endpoint; + # The first hop is the collector's OTLP/HTTP receiver, which speaks + # protobuf regardless of what the upstream wants — `otel.protocol` + # describes the *upstream* link, and the collector's own exporter is + # what has to honour it (see nix/host-modules/otel.nix). + HYPERHIVE_OTEL_PROTOCOL = if collectorOn then "http/protobuf" else otel.protocol; } // lib.optionalAttrs (otel.extraResourceAttributes != "") { HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES = otel.extraResourceAttributes; } - // lib.optionalAttrs (otel.headersCredential != null) { + // lib.optionalAttrs (otel.headersCredential != null && !collectorOn) { + # This is the variable that puts the upstream token in an agent's + # own `~/.claude/settings.json` (host_config.rs forwards it into the + # container as an nspawn credential; claude-settings.nix's + # `hive-otel-header` oneshot then writes it into a file the agent + # can read). Not emitting it is what actually removes the token from + # agent containers — the collector holding the credential is only + # half of it, and the half that is invisible in a diff. HYPERHIVE_OTEL_HEADERS_CREDENTIAL = otel.headersCredential; } // lib.optionalAttrs (otel.metricIntervalMs != null) {