# Top-level, cross-cutting hyperhive options: the hive's identity # (domain + display names) and hive-wide feature toggles read by several # subsystem modules. Imported by the ./default.nix aggregator. { lib, config, options, ... }: let hiveCfg = config.services.hyperhive; hiveName = hiveCfg.deploy.hive-controller.hiveName; in { imports = [ (lib.mkRemovedOptionModule [ "services" "hyperhive" "ruthless" ] '' hive-c0re no longer creates ruth at boot. Remove this definition, and create ruth from the swarm dashboard like any other agent; a ruth container that already exists keeps starting with the hive. '') (lib.mkRenamedOptionModule [ "services" "hyperhive" "github" "enable" ] [ "services" "hyperhive" "swarm" "github" "enable" ] ) ]; # Canonical hive DNS domain shared by every subsystem that needs a # stable hostname. Typed nullOr (default null) so the option always # exists, but it's REQUIRED wherever this host runs a hive — an # assertion in hive-network.nix fails eval when it's unset, since # matrix bakes it in on first boot and the gateway/forge/agent URLs all # derive from it (no safe default). Full identity-surface # context (HYPERHIVE_HIVE_DOMAIN / HIVE_NAME / SWARM_NAME env-var # chain → identity.rs → claude prompt): docs/process/conventions.md:: # Hive identity (label + domain + display names). options.services.hyperhive.domain = lib.mkOption { type = lib.types.nullOr lib.types.str; # Read out of the swarm directory rather than derived here. The # directory is what every OTHER hive reads this hive's address from, # so deriving it separately gave two homes for one value with # nothing asserting they agreed — and a disagreement surfaces as # "the other hives can't reach me", on a machine other than the # misconfigured one. # # ⚠️ The `.` formula lives once, on # `hives..domain`, which this reads. A direct fallback here # would recreate the second path (and, since that default reads # `swarm.domain` too, a value that can differ from the directory's). default = if hiveName != null && hiveCfg.swarm.hives ? ${hiveName} then hiveCfg.swarm.hives.${hiveName}.domain else null; defaultText = lib.literalExpression "services.hyperhive.swarm.hives.\${hiveName}.domain, or null when there is no entry for this hive"; example = "darkest.space"; description = '' Canonical host domain for hyperhive subsystems that need a stable name (currently: `services.hyperhive.swarm.matrix.serverName` derives from this, defaulting to `matrix.''${services.hyperhive.domain}` when `serverName` is null). **Required** when `services.hyperhive.deploy.hive-controller.enable` — eval fails with a helpful message if it's unset (it's baked into matrix on first boot and drives the gateway/forge/agent URLs, with no safe default; changing it later is destructive). Exposed to agents as `HYPERHIVE_HIVE_DOMAIN`; consumed by `hive-agent::identity::hive_domain()` for `@` qualified labels. **Deprecated as a place to write.** It is read from this hive's own entry in `services.hyperhive.swarm.hives`, whose `domain` defaults to `.` — so a conventional hive states nothing at all, and a non-conventional one states its address in the directory every other hive reads. Setting it here still wins and still works, with a warning: the directory is shared, this option is not, so a value written only here is invisible to the rest of the swarm. ''; }; # Deprecation warning for the shorthand above, fired on PRIORITY. # # ⚠️ Not `isDefined`, and not `files`: the module system injects an # option's own `default` as a definition attributed to the declaring # file, so both say "defined, in hyperhive.nix" for a config that set # nothing — measured, after this warning fired on the conventional # case. `mkOptionDefault` is priority 1500, so anything lower is a # definition someone actually wrote (100 plain, 1000 mkDefault). config.warnings = lib.optional (options.services.hyperhive.domain.highestPrio < 1500) '' services.hyperhive.domain is set explicitly and is deprecated. This hive's address belongs in the swarm directory, which every hive in the swarm shares a copy of: services.hyperhive.swarm.hives."${toString hiveName}".domain = "${toString hiveCfg.domain}"; (Or drop the value entirely if it is the conventional `.` — that is the directory entry's own default.) Setting it here still wins, so nothing is broken right now. What it does not do is tell the other hives: they read this hive's address out of their copy of the directory, so a value written only here leaves them pointing somewhere else with nothing detecting it. ''; # Where the swarm lives. Declared beside the hive's own identity # because it is what that identity is derived FROM — every hive in a # swarm is a sub-domain of it. No alias: this option has no earlier # spelling. options.services.hyperhive.swarm.domain = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "darkest.space"; description = '' DNS domain of the wider swarm this hive belongs to. Each hive occupies its own sub-domain of it, which is why every entry in `services.hyperhive.swarm.hives` defaults its `domain` to `.` — set this plus `services.hyperhive.deploy.hive-controller.hiveName`, list the hives by name, and no hive in the swarm states an address at all. **Required** on every host that runs a hyperhive service (a `services.hyperhive.deploy.*` service, the gateway, the bridge, `otel` or the snapshot store), with the same value on every host of the swarm, and deliberately not defaulted. A guessed swarm domain is a wrong hostname that evaluates cleanly and deploys, which is worse than an eval failure telling an operator to write down the one address their swarm answers to. ''; }; # The hive's name is `deploy.hive-controller.hiveName`, declared in # ./deploy.nix beside the switch that makes a host run a hive. The # identity surfaces (label, domain, display names, env-var chain) are in # docs/process/conventions.md::Hive identity. # # A hive turns the name into an identifier with no fallback: hive-c0re's # OIDC client and matrix localpart, the CN of the hive's `client.pem` # (./glue-bao-tls.nix), and the `hives/` path of its secrets. A null # there renders as an empty string that evaluates and deploys. config.assertions = [ { assertion = !hiveCfg.deploy.hive-controller.enable || hiveName != null; message = '' hyperhive requires services.hyperhive.deploy.hive-controller.hiveName to be set on a host that runs a hive — it is this hive's label within the swarm, and the leftmost part of the domain it is addressed by (`.`), not only a display name. Set it (`services.hyperhive.deploy.hive-controller.hiveName = "pr1ma";`). ''; } { # An EMPTY `hives` fires this too, deliberately: since # `swarm.domain` is required, every hive is in a swarm — a swarm of # one is still a swarm — so a directory with no entry for this host # is missing one either way. It also has to fire here, because this # host's own domain is read out of the directory: without the entry # `services.hyperhive.domain` is null and the generic required-domain # assertion in ./hive-network.nix would fire instead, naming an # option the operator should not be setting. # # Guarded on `hiveName != null` so the required-hiveName assertion # above is what fires for that case — two assertions naming the same # missing value is noise. assertion = !hiveCfg.deploy.hive-controller.enable || hiveName == null || hiveCfg.swarm.hives ? ${hiveName}; message = '' services.hyperhive.swarm.hives has no entry for this hive (services.hyperhive.deploy.hive-controller.hiveName = "${toString hiveName}"). `hives` describes every hive in the swarm including this one, so that every host can share one identical attrset, and this hive's own domain is derived from its entry. Add: services.hyperhive.swarm.hives."${toString hiveName}" = { }; No fields are needed: `domain` defaults to `.`. Set it in the entry if this hive is addressed by something else. Declared hives: ${lib.concatStringsSep ", " (lib.attrNames hiveCfg.swarm.hives)} ''; } ]; config.services.hyperhive.deploy.hive-controller.dashboardDomain = if hiveName != null && hiveCfg.swarm.dashboardDomains ? ${hiveName} then hiveCfg.swarm.dashboardDomains.${hiveName} else null; # The one hive-level option that describes something ABOVE the hive, # which is why it sits under `swarm` with the swarm-global services # rather than beside the hive's name. options.services.hyperhive.swarm.name = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "constellat1on"; description = '' Human-readable name of the wider swarm this hive belongs to. Hives at different DNS domains can share a swarm name when they federate together. Exposed to agents as `HYPERHIVE_SWARM_NAME`; surfaced in the dashboard chrome and per-agent system prompt when set. ''; }; # `singleHostSwarm` is declared in ./local-defaults.nix, with # the values it asserts. It is a deployment mode rather than a setting # this module's options read, so it lives with its consequences. options.services.hyperhive.swarm.github.enable = lib.mkOption { type = lib.types.bool; default = true; example = false; description = '' Swarm-wide switch for the per-agent GitHub integration (the `gh` CLI wrapper + git credential helper, per `services.hyperhive.agent.github.enable`). On by default: every agent gets the integration, inert until an operator links a PAT for it in the swarm UI. Set `false` to turn it off for the whole swarm --- the meta-flake renderer (`hive-c0re/src/meta.rs`) then injects `services.hyperhive.agent.github.enable = false` into every agent. Exposed to hive-c0re as `HYPERHIVE_GITHUB_DISABLED` (set only when the integration is off). ''; }; }