diff --git a/nix/host-modules/swarm-bao.nix b/nix/host-modules/swarm-bao.nix index 2759df60..b8c95ebb 100644 --- a/nix/host-modules/swarm-bao.nix +++ b/nix/host-modules/swarm-bao.nix @@ -158,6 +158,10 @@ let # # Neither is a superset of the other, which is why this is not one address # with a conditional value. + # ⚠️ openbao logs `unknown or unsupported field ` for each key here. Its + # unknown-field check does not know about named listener blocks; the parser + # does, honours `type`, and configures every one. Not the cause of a store + # that fails to start — look at `advertise` below. listeners = { loopback = { type = "tcp"; @@ -166,6 +170,18 @@ let // listenerTls; } // extraListeners; + + # Raft REFUSES TO START without `cluster_addr`, and the message names neither + # the setting nor the stanza: "cluster address must be set when using raft + # storage". + # + # By name and not by address: this is the URL a reader dials, and the name the + # server certificate has to carry anyway. Cluster traffic is one port up, + # upstream's own convention. + advertise = { + api_addr = "https://${cfg.domain}:${toString cfg.port}"; + cluster_addr = "https://${cfg.domain}:${toString (cfg.port + 1)}"; + }; in { # One service, two namespaces, and the split decides who may set what. @@ -615,6 +631,7 @@ in listener = listeners; storage.raft.path = stateDir; } + // advertise // sealSettings; }; diff --git a/nix/module-eval.nix b/nix/module-eval.nix index ab3de892..6bca89d8 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -105,6 +105,16 @@ let baoNames = machine: machine.services.hyperhive.gateway.localNames; + baoTwoAddresses = hive { + deploy.bao.enable = true; + deploy.bao.extraListenAddresses = [ "10.0.0.1" ]; + }; + + # The config file openbao parses, not the nix that produces it: a setting it + # requires is absent here without anything in the module system minding, so + # the daemon's own startup is otherwise the first reader. + baoSettings = machine: machine.containers.swarm-bao.config.services.openbao.settings; + # 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 @@ -264,6 +274,18 @@ let name = "a hive that does not run the store claims no name for it"; ok = !(builtins.elem "bao.t.local" (baoNames bare)); } + { + # Raft refuses to start without it, and says so in a message that names + # neither the setting nor the stanza. + name = "the store advertises a cluster address"; + ok = lib.hasPrefix "https://" ((baoSettings baoPkcs11).cluster_addr or ""); + } + { + # Control for the case above: these settings are rendered per deployment, + # not constants a passing case could be indifferent to. + name = "a declared extra address renders a second listener beside loopback"; + ok = builtins.length (builtins.attrNames (baoSettings baoTwoAddresses).listener) == 2; + } ]; bad = builtins.filter (c: !c.ok) cases;