Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/host-modules/hyperhive.nix

228 lines
10 KiB
Nix

# 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 `<hiveName>.<swarm.domain>` formula did NOT move here from
# there and back: it lives once, on `hives.<name>.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 `<name>@<domain>`
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 `<hiveName>.<swarm.domain>` — 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
`<hiveName>.<swarm.domain>` — 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
`<name>.<swarm.domain>` — 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 imports this module, 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
`<hiveName>.<swarm.domain>`, 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/<name>` 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 (`<hiveName>.<swarm.domain>`), 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).
'';
};
}