Watch
0
0
Fork
You've already forked hyperhive
0

swarm-otel: ship the whole host journal, drop user sessions after it

The swarm collector's journald receiver read only the units listed in
`services.hyperhive.swarm.otel.journaldUnits`. A unit nobody listed
never reached the store, and a misspelt entry shipped nothing without
an error. The list existed to keep an operator's desktop session out of
a store every swarm operator can read, but the receiver can only match
positively, so the only way to express "not user sessions" was to name
every service instead.

The receiver now reads the whole host journal, and a new
`filter/exclude-user-sessions` processor in the `logs/<swarm>` pipeline
drops records whose `_SYSTEMD_SLICE` is `user-<uid>.slice` (session
scopes and `user@<uid>.service`). The per-hive `logs/<hive>` pipelines
carry agent-container journals only and get no filter.

`journaldUnits` is removed with `mkRemovedOptionModule`, together with
its non-empty assertion and the entry each host module added. The four
module-eval membership checks go with it, replaced by one structural
case in swarm-otel-core.

Closes #3646
This commit is contained in:
atlas 2026-09-30 22:51:52 +02:00 • committed by mara
commit 6b1e825c0a
25 changed files with 72 additions and 324 deletions

View file

@ -160,14 +160,6 @@ let
&& lib.hasInfix "${dir}/secret" s
&& lib.hasInfix "${dir}/client_id" s;
}
{
# A reader off the store's host is a reader whose journal is the only
# record of why a hive's agents never connected, so the collector has to
# be told the unit exists. Nothing else can say it: the store's module
# does not know who holds a certificate.
name = "the queue credential reader's journal reaches the collector";
ok = builtins.elem "swarm-bao-queue-agent" baoRemoteReader.services.hyperhive.swarm.otel.journaldUnits;
}
{
# No agent container may render before this unit has had its attempts,
# and the edge that guarantees it must delay hive-c0re rather than sink

View file

@ -282,10 +282,9 @@ let
ok = !(lib.elem "--link-journal=host" (baoWithCollector.containers.swarm-bao.extraFlags or [ ]));
}
{
# The whole journal, which is what the shared collector's unit allowlist
# is not. A `units` list here would render and deploy perfectly while
# shipping only the units someone remembered to name — the failure this
# forwarder exists to end.
# The whole journal. A `units` list here would render and deploy
# perfectly while shipping only the units someone remembered to name —
# the failure this forwarder exists to end.
name = "the store's forwarder filters no units";
ok = !((baoForwarder baoWithCollector).settings.receivers.journald ? units);
}

View file

@ -92,10 +92,6 @@ let
lib.attrValues (removeAttrs units [ triggeredName ])
));
}
{
name = "the renewal's journal ships beside the boot issuance's";
ok = lib.elem triggeredName allLocal.services.hyperhive.swarm.otel.journaldUnits;
}
{
# Moved with the role, in both places: the threshold is half of what the
# store grants, and the store grants what the option says.

View file

@ -62,53 +62,24 @@ let
deploy.bao.otelOidcClientKeyFile = "/etc/pki/bao-otel-oidc-key.pem";
};
# The collector beside the store holding a bootstrap token: the one shape in
# which every unit an apply can leave failed renders on the same host.
otelApplyPath = hive {
deploy.swarm-otel.enable = true;
deploy.authelia.enable = true;
deploy.bao.enable = true;
deploy.bao.bootstrapTokenFile = "/run/secrets/bao-bootstrap.token";
};
# Units on the path a deploy takes to TLS, the store's grants and the
# collector itself. A failure among them silences ingest, so without their
# journals the store can show that ingest stopped but not which unit
# stopped it.
applyPathUnits = [
"container@swarm-otel"
"hive-tls-ca"
"swarm-services-cert"
"hive-gateway-self-signed-cert"
"swarm-bao-granter-role"
"swarm-bao-controller-policy"
"swarm-bao-secret-publisher-policy"
"swarm-bao-matrix-ctl-policy"
"swarm-bao-matrix-token-policy"
"swarm-bao-queue-agent-policy"
"swarm-bao-grafana-oidc-policy"
"swarm-bao-otel-oidc-policy"
"swarm-bao-services-issuer-policy"
];
cases = [
{
# Listed AND defined, because a name that matches nothing is not an
# error anywhere: a unit renamed out from under its entry would pass a
# membership check and still never reach the store.
name = "every unit on the apply path is defined and ships its journal";
# The host journal is read whole, so the filter is the only thing
# keeping an operator's desktop session out of the store. A processor a
# pipeline names but the config does not define is a collector that
# refuses to start, hence both halves.
name = "the host journal ships every unit, through the user-session filter";
ok =
let
m = otelApplyPath;
s = otelSettings otelNoStores;
journalPipelines = lib.filter (p: builtins.elem "journald" p.receivers) (
lib.attrValues s.service.pipelines
);
in
lib.all (
u: builtins.elem u m.services.hyperhive.swarm.otel.journaldUnits && m.systemd.services ? ${u}
) applyPathUnits;
}
{
# Transient, so nothing here defines it and only membership can be
# pinned: nixos-rebuild names the unit it runs the activation in.
name = "the activation's journal ships beside the units it starts";
ok = builtins.elem "nixos-rebuild-switch-to-configuration" otelApplyPath.services.hyperhive.swarm.otel.journaldUnits;
!(s.receivers.journald ? units)
&& journalPipelines != [ ]
&& lib.all (p: builtins.elem "filter/exclude-user-sessions" p.processors) journalPipelines
&& s.processors."filter/exclude-user-sessions".logs.log_record or [ ] != [ ];
}
{
# 🩸 The arm that guards the ruling this slice landed under, the