# 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; in { # The swarm's display name moved under `swarm` when the swarm-global # settings were consolidated; the hive's own name and domain stayed put, # because they describe this hive rather than the swarm it joins. imports = [ (lib.mkRenamedOptionModule [ "services" "hyperhive" "swarmName" ] [ "services" "hyperhive" "swarm" "name" ] ) ]; # 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 did NOT move here from # there and back: it lives once, on `hives..domain`, which # this reads. Restoring 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 hiveCfg.hiveName != null && hiveCfg.swarm.hives ? ${hiveCfg.hiveName} then hiveCfg.swarm.hives.${hiveCfg.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 hiveCfg.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. Unlike the renamed options nearby, this # is genuinely new: nothing moved here, so there is no alias. 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.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. ''; }; # Human display name for this hive. Distinct from the DNS domain # above (machine-readable) — see docs/process/conventions.md::Hive identity # for the domain-vs-name-vs-swarm distinction + the env-var # propagation chain. The swarm's display name is # `services.hyperhive.swarm.name`, one level out: this hive is named # here, the swarm it belongs to is named there. options.services.hyperhive.hiveName = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "pr1ma"; description = '' Human-readable name of this single-host hive instance. **Required** when this host runs a hive, the swarm's secret store or its homeserver (`services.hyperhive.deploy.hive-controller.enable`, `deploy.bao.enable`, `deploy.matrix.enable`). Distinct from `services.hyperhive.domain` (the machine-addressable DNS name) but no longer merely cosmetic: a hive occupies `.`, so this is the label the hive is *addressed* by as well as the one it is called. Exposed to agents as `HYPERHIVE_HIVE_NAME`; surfaced in the dashboard chrome and per-agent system prompt. ''; }; # Each of these deployments turns `hiveName` into an identifier with no # fallback: hive-c0re's OIDC client and matrix localpart, the CN of the # store's `client.pem` (./glue-bao-tls.nix), and the `hives/` path # the matrix token and queue credential readers fetch. A null there renders # as an empty string that evaluates and deploys. config.assertions = [ { assertion = !( hiveCfg.deploy.hive-controller.enable || hiveCfg.deploy.bao.enable || hiveCfg.deploy.matrix.enable ) || hiveCfg.hiveName != null; message = '' hyperhive requires services.hyperhive.hiveName to be set on a host that runs a hive, the swarm's secret store or its homeserver — 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.hiveName = "pr1ma";`). ''; } ]; # 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 `hiveName`. 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. # Whether this hive runs "ruthless" — with no root/manager agent at # all. Some hives don't want a root agent — see issue tracker # "scope concept: special agents". options.services.hyperhive.ruthless = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run this hive "ruthless" — with no root (manager) agent at all (no ruth). When `true`, hive-c0re skips the root-agent auto-management sweep entirely (it otherwise creates the root agent's container when missing and restarts it when present but stopped). Defaults to `false` (the root agent is auto-managed as required infrastructure). Exposed to hive-c0re as `HYPERHIVE_RUTHLESS`. ''; }; options.services.hyperhive.github.enable = lib.mkOption { type = lib.types.bool; default = true; example = false; description = '' Hive-wide switch for the per-agent GitHub integration (the `gh` CLI wrapper + git credential helper, per `hyperhive.github.enable`). On by default: every agent gets the integration, inert until a PAT is provisioned via the dashboard credentials tab or `hivectl github set-token`. Set `false` to turn it off for the whole hive --- the meta-flake renderer (`hive-c0re/src/meta.rs`) then injects `hyperhive.github.enable = false` into every agent. Exposed to hive-c0re as `HYPERHIVE_GITHUB_DISABLED` (set only when the integration is off). ''; }; }