From f3ce94b4f6e1ae3a1b66056aa181d1f98e525e6b Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 31 Aug 2026 21:15:21 +0200 Subject: [PATCH] swarm-bao: make the store's name resolve where the store runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing resolved `bao.`. It is absent from `swarm.serviceDomains` and, unlike every other swarm service, contributed no `gateway.localNames` entry — so the name a reader dials answered only if an operator had published an external record, which nothing asks them to do. Cross-hive traffic goes via the domain either way; only what it resolves to varies, and for a multi-host swarm that is the operator's upstream DNS. This covers the deployment with no upstream record to configure. DNS only, and that asymmetry is deliberate: bao stays out of `serviceDomains` and gets no vhost. Its leaf comes from its own CA, so a services-CA entry would mint one nothing uses, and nginx terminating TLS would strip the client certificate the store authenticates every hive with. `swarm-nats.nix` documents the same exclusion for itself. module-eval gains the presence and absence arms: claiming a name this host does not serve would point every local reader at the wrong machine. Refs #3862 --- nix/host-modules/swarm-bao.nix | 12 ++++++++++++ nix/module-eval.nix | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/nix/host-modules/swarm-bao.nix b/nix/host-modules/swarm-bao.nix index a5b3d7c6..2759df60 100644 --- a/nix/host-modules/swarm-bao.nix +++ b/nix/host-modules/swarm-bao.nix @@ -418,6 +418,18 @@ in ''; } ]; + + # The name every reader dials, made resolvable where the store runs. + # Cross-hive traffic always goes via the domain; only what it resolves + # to varies, and a multi-host swarm is the operator's upstream DNS. This + # covers the case that has no upstream record to configure. + # + # ⚠️ DNS only. Bao is deliberately absent from `swarm.serviceDomains` + # and gets no vhost: nginx terminating TLS would strip the client + # certificate, which is how the store authenticates every hive — see + # this file's header. `localNames` is the one half of the sibling + # pattern that applies. + services.hyperhive.gateway.localNames = [ cfg.domain ]; }) (lib.mkIf (hyperhiveCfg.enable && deployCfg.bao.enable && haveServerTls) { diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 73b49a84..ab3de892 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -103,6 +103,8 @@ let # is deployed" from "this host can authenticate to the store". matrixNoBaoIdentity = hive { deploy.matrix.enable = true; }; + baoNames = machine: machine.services.hyperhive.gateway.localNames; + # A priority collision is a property of the *option*, not # of the merged value's interior — nix throws the moment the value is # demanded at all, so `seq`-ing each `serviceConfig` value to WHNF is @@ -250,6 +252,18 @@ let in s ? swarm-bao-matrix-token && s.swarm-bao-matrix-token.requires == [ "swarm-bao-pki.service" ]; } + { + # The name a reader dials has to resolve where the store runs; a + # multi-host swarm resolves it upstream instead. + name = "the store's host answers for the store's name"; + ok = builtins.elem "bao.t.local" (baoNames baoPkcs11); + } + { + # Absence arm, and the one that matters: claiming a name this host does + # not serve points every local reader at the wrong machine. + name = "a hive that does not run the store claims no name for it"; + ok = !(builtins.elem "bao.t.local" (baoNames bare)); + } ]; bad = builtins.filter (c: !c.ok) cases;