Watch
0
0
Fork
You've already forked hyperhive
0

nix: gate hive-c0re on deploy.hive-controller.enable, drop hyperhive.enable

`services.hyperhive.enable` and `services.hyperhive.c0re.enable` are gone.
One switch, `services.hyperhive.deploy.hive-controller.enable` (default
false, as the old toggle was), now gates hive-c0re and hive-priv. Both old
paths are `mkRenamedOptionModule` shims in deploy.nix, so a host config
that still sets either evaluates as before and gets a rename warning.

Every other read of the old toggle is resolved, including the 29 made
through the `hyperhiveCfg`/`hiveCfg` aliases:

- Dropped: each swarm service and its glue keeps only its own deploy
  toggle (authelia, bao and its PKI glue, grafana, victorialogs,
  victoriametrics, the secret publisher, swarm-ca, the OIDC client rows,
  the controller/nats/matrix-ctl/publisher/services-issuer identities),
  the forge, and the `domain` deprecation warning.
- To deploy.hive-controller.enable: the queue-agent credential reader and
  its assertion, which feed hive-c0re and write under its state dir, plus
  their policy-order entry; the network identity assertions; hive-tls's
  two writes into hive-c0re's environment.
- hive-tls runs where the gateway runs self-signed
  (`gateway.enable && useSelfSigned`), not on every host.
- The matrix appservice-token reader and its assertion stay on
  `deploy.matrix.enable` plus their client-identity checks. They read
  deploy.matrix's token file and registration script; their deploy.bao
  inputs are the client-half options a hive sets to read a store it does
  not run, so gating on deploy.bao.enable would drop the tested
  remote-reader case.
- The `hiveName` assertion moves from hive-network.nix to hyperhive.nix
  and fires wherever the hive, the store or the homeserver runs: each
  turns the name into an identifier with no fallback.

On a host with `deploy.allSwarmServices` and no hive, the documented
services-host recipe, authelia, bao, grafana, victorialogs,
victoriametrics, the OIDC client rows and the hive CA now render; before,
the old toggle being off left them out.

Refs #4500
This commit is contained in:
atlas 2026-09-26 00:32:32 +02:00
commit 3202cde704
37 changed files with 292 additions and 162 deletions

View file

@ -54,7 +54,7 @@ let
};
boot.loader.grub.enable = false;
system.stateVersion = "25.11";
services.hyperhive.enable = lib.mkForce false;
services.hyperhive.deploy.hive-controller.enable = lib.mkForce false;
services.hyperhive.deploy.matrix.enable = lib.mkForce false;
}
)

View file

@ -1,7 +1,8 @@
# The full hyperhive host stack, pulled together in one place — this
# is what the flake exports as `nixosModules.default` (wrapped with
# the package/source wiring; see flake.nix). One import covers
# everything; `services.hyperhive.enable = true` turns the stack on.
# everything; `services.hyperhive.deploy.hive-controller.enable = true`
# runs a hive on this host.
#
# Every swarm needs the forge — hive-c0re mirrors every agent's applied
# config repo into it and it's the canonical store for the meta flake
@ -9,7 +10,7 @@
# runs it (derived from `deploy.allSwarmServices`, see
# ./swarm-required-services.nix). hive-matrix is opt-in (off by default). All
# subsystems rely on `services.hyperhive.domain`, which is required
# (asserted in hive-network.nix) whenever hyperhive is enabled.
# (asserted in hive-network.nix) wherever this host runs a hive.
{
imports = [
./hyperhive.nix

View file

@ -119,6 +119,15 @@ in
[ "services" "hyperhive" "swarm" "statusPublish" "clientSecretFile" ]
[ "services" "hyperhive" "deploy" "hive-controller" "statusPublish" "clientSecretFile" ]
)
# Both old spellings of "run a hive here" forward to the one switch.
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "enable" ]
[ "services" "hyperhive" "deploy" "hive-controller" "enable" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "c0re" "enable" ]
[ "services" "hyperhive" "deploy" "hive-controller" "enable" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "otel" "enable" ]
[ "services" "hyperhive" "deploy" "swarm-otel" "enable" ]
@ -452,9 +461,9 @@ in
Run the swarm's metrics UI on this host.
Off by default and not derived from
{option}`services.hyperhive.enable`: a swarm has one Grafana, so
running it is a decision about this host rather than about
whether hyperhive is installed.
{option}`services.hyperhive.deploy.hive-controller.enable`: a swarm
has one Grafana, so running it is a decision about this host rather
than about whether it runs a hive.
'';
};
@ -570,6 +579,21 @@ in
'';
};
hive-controller.enable = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
Run a hive on this host: the `hive-c0re` coordinator that owns every
agent container here, its `hive-priv` helper, and the bridge,
resolver and gateway the agents hang off.
Every hive turns this on. A host that only runs the swarm's shared
services ({option}`services.hyperhive.deploy.allSwarmServices`)
leaves it off.
'';
};
swarm-controller.enable = lib.mkOption {
type = lib.types.bool;
default = false;
@ -577,9 +601,9 @@ in
Run the swarm-controller daemon on this host.
Off by default and deliberately not derived from
{option}`services.hyperhive.enable`: a swarm has one controller,
so running it is a decision about this host rather than about
whether hyperhive is installed.
{option}`services.hyperhive.deploy.hive-controller.enable`: a swarm
has one controller, so running it is a decision about this host
rather than about whether it runs a hive.
'';
};

View file

@ -31,22 +31,21 @@ let
readersHere = {
# ./glue-matrix-bao-token.nix
swarm-bao-matrix-token =
hyperhiveCfg.enable
&& havePair baoDeploy.matrixTokenClientCertFile baoDeploy.matrixTokenClientKeyFile
havePair baoDeploy.matrixTokenClientCertFile baoDeploy.matrixTokenClientKeyFile
&& deployCfg.matrix.enable;
# ./glue-queue-agent-credential.nix
swarm-bao-queue-agent =
hyperhiveCfg.enable
deployCfg.hive-controller.enable
&& havePair baoDeploy.queueAgentClientCertFile baoDeploy.queueAgentClientKeyFile;
# ./swarm-grafana.nix
swarm-bao-grafana-oidc = hyperhiveCfg.enable && deployCfg.grafana.enable;
swarm-bao-grafana-oidc = deployCfg.grafana.enable;
# ./swarm-otel.nix
swarm-bao-otel-oidc =
deployCfg.swarm-otel.enable
&& havePair baoDeploy.otelOidcClientCertFile baoDeploy.otelOidcClientKeyFile;
# ./swarm-bao.nix: its block is gated on the store, which `orderAfterPolicy`
# already checks.
swarm-bao-forwarder-oidc = hyperhiveCfg.enable;
swarm-bao-forwarder-oidc = true;
# ./swarm-nats.nix: the queue's TLS leaf, not a secret, but the same wait.
swarm-bao-nats-tls = deployCfg.nats.enable;
};

View file

@ -43,9 +43,10 @@ let
# What a reader calls itself to the store. The hive's name, because a bao
# cert-auth role matches on the CN — this is an interface, not a label.
# No fallback: `hiveName` is asserted set for every hyperhive host, which is
# the same condition this file's `config` is gated on. A fallback here reads
# as a second supported spelling and there is no such thing.
# No fallback: `hiveName` is asserted set wherever `deploy.bao.enable` is
# (./hyperhive.nix), the same condition this file's `config` is gated on. A
# fallback here reads as a second supported spelling and there is no such
# thing.
clientCn = hyperhiveCfg.hiveName;
# $1 dir $2 basename $3 CN $4 SAN or "" $5 EKU
@ -71,7 +72,7 @@ let
'';
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.bao.enable) {
config = lib.mkIf deployCfg.bao.enable {
services.hyperhive.deploy.bao = {
serverCertFile = lib.mkDefault "${pkiDir}/server.pem";
serverKeyFile = lib.mkDefault "${pkiDir}/server-key.pem";

View file

@ -32,7 +32,7 @@ let
pkiDir = if haveMintedPki then builtins.dirOf baoDeploy.clientCertFile else null;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.swarm-controller.enable && haveMintedPki) {
config = lib.mkIf (deployCfg.swarm-controller.enable && haveMintedPki) {
services.hyperhive.deploy.swarm-controller = {
baoClientCertFile = lib.mkDefault "${pkiDir}/controller.pem";
baoClientKeyFile = lib.mkDefault "${pkiDir}/controller-key.pem";

View file

@ -24,7 +24,7 @@ let
forgeCfg = hyperhiveCfg.swarm.forge;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia.enable) {
config = lib.mkIf deployCfg.authelia.enable {
# One declaration, two readers. The forge knows its own callback URL;
# making the operator restate it in authelia's client list would be a
# second source of truth for a string whose mismatch is a silent

View file

@ -32,7 +32,7 @@ let
grafanaCfg = hyperhiveCfg.swarm.grafana;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia.enable) {
config = lib.mkIf deployCfg.authelia.enable {
# One declaration, two readers. Grafana's callback URL is format-locked to
# its own root URL, and the read-only option it is taken from is where that
# format is spelled — restating it here would be a second source of truth

View file

@ -69,7 +69,8 @@ let
# pieces; this literal is the nix half of that one agreement.
#
# `hiveName` has no fallback here for the reason ./glue-bao-tls.nix gives at
# its own use of it: it is asserted set for every hyperhive host.
# its own use of it: it is asserted set on every host that runs the
# homeserver.
tokenPath = "secret/swarm/hives/${hyperhiveCfg.hiveName}/matrix/appservice-token";
# A literal, not an option — ./hive-matrix.nix names its container
@ -94,7 +95,7 @@ in
# is a hive that has no store, which is supported. So this one additionally
# requires `hiveReaderIdentity`: the host demonstrably reads the store, and
# named every option but this pair.
(lib.mkIf (hyperhiveCfg.enable && deployCfg.matrix.enable && hiveReaderIdentity) {
(lib.mkIf (deployCfg.matrix.enable && hiveReaderIdentity) {
assertions = [
{
assertion = haveClientIdentity;
@ -124,7 +125,7 @@ in
];
})
(lib.mkIf (hyperhiveCfg.enable && haveClientIdentity && deployCfg.matrix.enable) {
(lib.mkIf (haveClientIdentity && deployCfg.matrix.enable) {
# Same rule as the unit's own gate: this reader exists on a host that has a
# client identity and a homeserver, which is not every host that runs the
# store, so the store's module cannot name it.

View file

@ -31,7 +31,7 @@ let
pkiDir = if haveMintedPki then builtins.dirOf baoDeploy.clientCertFile else null;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.matrix.enable && haveMintedPki) {
config = lib.mkIf (deployCfg.matrix.enable && haveMintedPki) {
services.hyperhive.deploy.matrix = {
ctlBaoClientCertFile = lib.mkDefault "${pkiDir}/matrix-ctl.pem";
ctlBaoClientKeyFile = lib.mkDefault "${pkiDir}/matrix-ctl-key.pem";

View file

@ -31,7 +31,7 @@ let
pkiDir = if haveMintedPki then builtins.dirOf baoDeploy.clientCertFile else null;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.nats.enable && haveMintedPki) {
config = lib.mkIf (deployCfg.nats.enable && haveMintedPki) {
services.hyperhive.deploy.nats = {
baoClientCertFile = lib.mkDefault "${pkiDir}/nats.pem";
baoClientKeyFile = lib.mkDefault "${pkiDir}/nats-key.pem";

View file

@ -20,10 +20,10 @@
# into one that is refused, which reaches the agent as a timeout.
#
# 📌 This runs wherever a client identity is configured, NOT only where the
# store is — the rule ./glue-matrix-bao-token.nix states in full. There is no
# "this hive runs agents" condition to gate it on as well: agent containers
# are created at runtime by hive-c0re, so every hyperhive host is a host that
# may run one.
# store is — the rule ./glue-matrix-bao-token.nix states in full — and
# wherever this host runs a hive (`deploy.hive-controller.enable`): the
# credential is for the agents hive-c0re creates here at runtime, and the unit
# writes under hive-c0re's own state directory.
{
pkgs,
lib,
@ -74,7 +74,8 @@ let
# one agreement.
#
# `hiveName` has no fallback here for the reason ./glue-bao-tls.nix gives at
# its own use of it: it is asserted set for every hyperhive host.
# its own use of it: it is asserted set wherever
# `deploy.hive-controller.enable` is.
credentialPath = "secret/swarm/hives/${hyperhiveCfg.hiveName}/queue/agent";
in
{
@ -110,11 +111,11 @@ in
#
# Shaped after ./swarm-grafana.nix's `haveClientIdentity` assertion — the
# same refusal, named to this principal's own pair. Where Grafana's gate
# is `deploy.grafana.enable`, this reader has no toggle of its own to
# check: every hive runs one for its own agents, so reading the store at
# all is what asks for it. Hence `hiveReaderIdentity` alone — a host
# holding no hive leaf has no store to read and nothing is missing.
(lib.mkIf (hyperhiveCfg.enable && hiveReaderIdentity) {
# is `deploy.grafana.enable`, this reader's is the hive's own: every hive
# runs one for its own agents, so on a hive, reading the store at all is
# what asks for it. Hence `hiveReaderIdentity` — a host holding no hive
# leaf has no store to read and nothing is missing.
(lib.mkIf (deployCfg.hive-controller.enable && hiveReaderIdentity) {
assertions = [
{
assertion = haveClientIdentity;
@ -149,7 +150,7 @@ in
];
})
(lib.mkIf (hyperhiveCfg.enable && haveClientIdentity) {
(lib.mkIf (deployCfg.hive-controller.enable && haveClientIdentity) {
# Same rule as the unit's own gate: this reader exists on any host holding
# a client identity, which is not every host that runs the store, so the
# store's module cannot name it.

View file

@ -31,12 +31,10 @@ let
pkiDir = if haveMintedPki then builtins.dirOf baoDeploy.clientCertFile else null;
in
{
config =
lib.mkIf (hyperhiveCfg.enable && deployCfg.swarm-secret-publisher.enable && haveMintedPki)
{
services.hyperhive.deploy.swarm-secret-publisher = {
baoClientCertFile = lib.mkDefault "${pkiDir}/secret-publisher.pem";
baoClientKeyFile = lib.mkDefault "${pkiDir}/secret-publisher-key.pem";
};
};
config = lib.mkIf (deployCfg.swarm-secret-publisher.enable && haveMintedPki) {
services.hyperhive.deploy.swarm-secret-publisher = {
baoClientCertFile = lib.mkDefault "${pkiDir}/secret-publisher.pem";
baoClientKeyFile = lib.mkDefault "${pkiDir}/secret-publisher-key.pem";
};
};
}

View file

@ -34,7 +34,7 @@ let
pkiDir = if haveMintedPki then builtins.dirOf baoDeploy.clientCertFile else null;
in
{
config = lib.mkIf (hyperhiveCfg.enable && haveMintedPki) {
config = lib.mkIf haveMintedPki {
services.hyperhive.deploy.hive-controller.tls = {
baoClientCertFile = lib.mkDefault "${pkiDir}/services-issuer.pem";
baoClientKeyFile = lib.mkDefault "${pkiDir}/services-issuer-key.pem";

View file

@ -29,7 +29,7 @@ let
baoCfg = hyperhiveCfg.swarm.bao;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia.enable) {
config = lib.mkIf deployCfg.authelia.enable {
# One declaration, two readers: `clientId` is a read-only option
# ./swarm-bao.nix owns, and ./swarm-otel.nix builds this principal's
# authenticator audience from the same option.

View file

@ -32,7 +32,7 @@ let
otelCfg = hyperhiveCfg.swarm.otel;
in
{
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia.enable) {
config = lib.mkIf deployCfg.authelia.enable {
# One declaration, two readers: `clientId` and `audience` are read-only
# options ./swarm-otel.nix derives from the scrape/push targets it owns,
# so this file states neither formula a second time.

View file

@ -153,11 +153,11 @@ in
name = "hive-c0re";
consumers = [ "hive-c0re" ];
hostUnit = true;
enable = cfg.enable;
enable = config.services.hyperhive.deploy.hive-controller.enable;
})
];
config = lib.mkIf cfg.enable {
config = lib.mkIf config.services.hyperhive.deploy.hive-controller.enable {
# This module is the host's only knowledge that agent containers
# exist: they hang off the bridge, resolve through dnsmasq, and their
# UIs — plus the operator dashboard — are served by the gateway.

View file

@ -7,17 +7,10 @@
{
pkgs,
lib,
config,
...
}:
{
options.services.hyperhive.c0re = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.hyperhive.enable;
defaultText = lib.literalExpression "config.services.hyperhive.enable";
description = "Enable hive-c0re coordinator daemon (auto-enabled by services.hyperhive.enable).";
};
package = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.default";

View file

@ -459,7 +459,7 @@ in
};
};
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.forgejo.enable) {
config = lib.mkIf deployCfg.forgejo.enable {
# Same principle as the vhost below — this service's own surface lives
# with the service. The SSO source is named alongside forgejo because
# its failure mode is a login that silently falls back, not an error.

View file

@ -86,7 +86,8 @@ let
# saying so, as the token path below.
#
# `hiveName` has no fallback here for the reason ./glue-matrix-bao-token.nix
# gives at its own use of it: it is asserted set for every hyperhive host.
# gives at its own use of it: it is asserted set on every host that runs the
# homeserver.
hiveLocalpart = "hive-${toString config.services.hyperhive.hiveName}";
# The `as_token`, and the `hs_token` the spec requires alongside it. Both

Some files were not shown because too many files have changed in this diff Show more