# "What does THIS host deploy?" # # Separated from `services.hyperhive.swarm.*` because those are two # different kinds of fact and only one of them varies per machine: # # swarm.* — swarm-wide truth. The swarm's name, domain, hives, peers, # and where each service lives. **Identical on every # host**, byte for byte; a hive needs all of it to be a # *client* of the swarm. # deploy.* — this machine's deployment decisions. Necessarily different # on every host, because that is what a deployment is. # # The `enable` toggles live here rather than under `swarm.*` so the # namespace that is identical everywhere does not carry the one thing # that must differ per host. # # Flat and named for the thing deployed — `deploy.forgejo`, not # `deploy.swarmServices.forgejo`: grouping by "swarm service" re-encodes # the service-side taxonomy into a layer that does not care about it. # # ⚠️ Each entry is an attrset with an `enable`, not a bare bool, so a # service that grows a second *deployment* decision has somewhere to put # it — `deploy.nats = { enable; authPackage; }` is then an ordinary # addition rather than a migration, where a bare bool leaves it # unrepresentable. # # ⚠️ The renames below are deliberately in this one file rather than # spread across the service modules, so all of them have a single home # and a single file to delete when the deprecation window closes. { config, lib, ... }: let # The swarm UI's own switch, in both spellings. Accepted and ignored: a # definition only produces a warning, so a host that still sets one keeps # evaluating. `null` is "not set". swarmUiSwitchIgnored = lib.mkOption { type = lib.types.nullOr lib.types.bool; default = null; visible = false; description = '' No effect. swarm-ui runs wherever swarm-controller runs. ''; }; swarmUiSwitchWarning = path: value: lib.optional (value != null) "services.hyperhive.${path} no longer has any effect: swarm-ui now runs wherever swarm-controller runs. Remove this setting; services.hyperhive.deploy.swarm-controller.enable decides it."; # The removed status-publishing secret below. statusSecretRemoved = '' hive-c0re reads this hive's status-publishing client secret from the swarm's secret store, under the hive's own certificate (deploy.bao.clientCertFile), and swarm-secret-publish in authelia's container puts it there. Remove this definition; the file it named is read by nothing now and can be deleted. ''; # Shared by the removed options of the hive's agent queue credential reader. queueAgentCredentialRemoved = '' A hive no longer fetches a shared queue credential for its agents: each agent reads its own secret from the swarm's secret store under its own certificate. Remove this definition; the leaf or directory it named is read by nothing now and can be deleted. ''; in { imports = [ (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "victoriametrics" "port" ] '' VictoriaMetrics listens on a unix socket in a network namespace of its own, and the gateway reaches it there, so it binds no host port. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "otel" "port" ] '' The swarm collector's OTLP receivers listen on unix sockets in a network namespace of its own, and the gateway reaches them there, so there is no receiver port range. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "otel" "producerPort" ] '' The swarm-tier receiver listens on a unix socket, so it binds no port. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "otel" "storeProducerPort" ] '' The store forwarder's receiver listens on a unix socket, so it binds no port. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "authelia" "port" ] '' authelia listens on a unix socket in a network namespace of its own, and the gateway reaches it there, so it binds no port. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "authelia" "metricsPort" ] '' authelia's metrics listener is a unix socket the gateway scrapes, so it binds no port. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "authelia" "bridgePort" ] '' swarm-authelia-bridge listens on a unix socket the gateway proxies to, so it binds no port. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "authelia" "agentClientSuffix" ] '' No client is minted per agent and the queue refuses no client id by suffix, so there is no suffix to name. Remove this definition. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "hive-controller" "statusPublish" "clientSecretFile" ] statusSecretRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "bao" "queueAgentCommonNamePrefix" ] queueAgentCredentialRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "bao" "queueAgentClientCertFile" ] queueAgentCredentialRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "bao" "queueAgentClientKeyFile" ] queueAgentCredentialRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "hive-controller" "queue" "agentCredentialDir" ] queueAgentCredentialRemoved) # Same type, same meaning, new path — so a rename carries it exactly # and existing configs keep evaluating with one warning naming both # paths. # # The runner moved from `deploy.forgejo.ci` to `deploy.ci-runner` with # every option keeping its type and meaning, so the old paths forward. # `hiveName` names the hive a host runs, so it moved under the service # that runs one. Same type and meaning. (lib.mkRenamedOptionModule [ "services" "hyperhive" "hiveName" ] [ "services" "hyperhive" "deploy" "hive-controller" "hiveName" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "ci" "enable" ] [ "services" "hyperhive" "deploy" "ci-runner" "enable" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "ci" "name" ] [ "services" "hyperhive" "deploy" "ci-runner" "name" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "ci" "concurrency" ] [ "services" "hyperhive" "deploy" "ci-runner" "concurrency" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "ci" "labels" ] [ "services" "hyperhive" "deploy" "ci-runner" "labels" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "ci" "package" ] [ "services" "hyperhive" "deploy" "ci-runner" "package" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "ci" "jobTimeout" ] [ "services" "hyperhive" "deploy" "ci-runner" "jobTimeout" ] ) # The gateway's services-issuer pair is a swarm-service identity, not a # hive's, so it sits with the store's other host-unit readers. (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "hive-controller" "tls" "baoClientCertFile" ] [ "services" "hyperhive" "deploy" "bao" "servicesIssuerClientCertFile" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "hive-controller" "tls" "baoClientKeyFile" ] [ "services" "hyperhive" "deploy" "bao" "servicesIssuerClientKeyFile" ] ) # What hive-c0re hands this hive's agents: a per-host choice, so it # sits with the controller's other deployment options. (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "claudeCodePackage" ] [ "services" "hyperhive" "deploy" "hive-controller" "claudeCodePackage" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "dashboardPort" ] [ "services" "hyperhive" "deploy" "hive-controller" "dashboardPort" ] ) # The c0re daemon is the hive-controller service, so its knobs and package # options sit under it. Same types, same meanings. (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "package" ] [ "services" "hyperhive" "deploy" "hive-controller" "package" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "frontend" ] [ "services" "hyperhive" "deploy" "hive-controller" "frontend" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "assets" ] [ "services" "hyperhive" "deploy" "hive-controller" "assets" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "xdgIcons" ] [ "services" "hyperhive" "deploy" "hive-controller" "xdgIcons" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "hyperhiveRev" ] [ "services" "hyperhive" "deploy" "hive-controller" "hyperhiveRev" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "agentBaseToplevel" ] [ "services" "hyperhive" "deploy" "hive-controller" "agentBaseToplevel" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "preBuildAgentTemplates" ] [ "services" "hyperhive" "deploy" "hive-controller" "preBuildAgentTemplates" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "modelPrices" ] [ "services" "hyperhive" "deploy" "hive-controller" "modelPrices" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "agentCpuQuota" ] [ "services" "hyperhive" "deploy" "hive-controller" "agentCpuQuota" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "agentMemoryMax" ] [ "services" "hyperhive" "deploy" "hive-controller" "agentMemoryMax" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "agentCpuWeight" ] [ "services" "hyperhive" "deploy" "hive-controller" "agentCpuWeight" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "agentIoWeight" ] [ "services" "hyperhive" "deploy" "hive-controller" "agentIoWeight" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "buildSlots" ] [ "services" "hyperhive" "deploy" "hive-controller" "buildSlots" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "c0re" "adminUsers" ] [ "services" "hyperhive" "deploy" "hive-controller" "adminUsers" ] ) (lib.mkRenamedOptionModule [ "services" "hyperhive" "otel" "extraResourceAttributes" ] [ "services" "hyperhive" "deploy" "hive-controller" "otel" "extraResourceAttributes" ] ) # The homeserver's port and its firewall switch, removed rather than # renamed: tuwunel listens on a unix socket only the gateway reaches, so # there is no host port to choose or open. (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "matrix" "httpPort" ] '' The homeserver no longer listens on a TCP port: it runs in a network namespace of its own and the gateway reaches it over a unix socket. Remove this definition. Clients and federation peers reach it at services.hyperhive.swarm.matrix.gatewayHost. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "matrix" "openFirewall" ] '' The homeserver no longer listens on a host port, so there is no port to open. Remove this definition. Reach from outside the host goes through the gateway: services.hyperhive.gateway.openFirewall. '') (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "matrix" "gui" "enable" ] '' The matrix web client has no on/off switch: every host that runs the homeserver serves it at services.hyperhive.swarm.matrix.gatewayHost, and the swarm UI links to it. Remove this definition. '') # The hive CA's lifetime and its leaves', removed rather than renamed: no # hive CA is generated, and the store's per-hive role sets the gateway # leaf's lifetime. (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "hive-controller" "tls" "caValidityDays" ] '' No hive CA is generated any more: the gateway leaf is issued by the secret store's pki mount, whose per-hive role sets its lifetime (services.hyperhive.deploy.bao.servicesPkiLeafTtlHours). Remove this definition. See docs/swarm/ca.md. '' ) (lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "hive-controller" "tls" "leafValidityDays" ] '' No hive CA is generated any more: the gateway leaf is issued by the secret store's pki mount, whose per-hive role sets its lifetime (services.hyperhive.deploy.bao.servicesPkiLeafTtlHours). Remove this definition. See docs/swarm/ca.md. '' ) # `deploy.swarm-ui` keeps only its ignored `enable` (declared below); its # `package` is the controller's UI package. (lib.mkRenamedOptionModule [ "services" "hyperhive" "deploy" "swarm-ui" "package" ] [ "services" "hyperhive" "deploy" "swarm-controller" "ui" "package" ] ) ]; # ⚠️ Only `deploy.forgejo.enable` is declared here. The rest of # `deploy.forgejo` is in ./hive-forge/default.nix: the knobs live with the # module that reads them; this file stays the registry of toggles. options.services.hyperhive.swarm.ui.enable = swarmUiSwitchIgnored; config.warnings = swarmUiSwitchWarning "deploy.swarm-ui.enable" config.services.hyperhive.deploy.swarm-ui.enable ++ swarmUiSwitchWarning "swarm.ui.enable" config.services.hyperhive.swarm.ui.enable; options.services.hyperhive.deploy = { swarm-ui.enable = swarmUiSwitchIgnored; forgejo.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the swarm's forge in a `hive-forge` container on this host. A swarm has one forge, so one host turns this on. It is the canonical store for the meta flake and every agent's config repo, so the swarm needs it, but *this* host running it is a decision like any other shared service's. With it off, this hive is a *client*: it reaches the swarm's forge at {option}`services.hyperhive.swarm.forge.domain`, which the operator's DNS must resolve to the forge's host. No container is created, and an existing one's state stays on disk under `/var/lib/nixos-containers/hive-forge/`. {option}`services.hyperhive.deploy.allSwarmServices` turns it on, and `singleHostSwarm` through that. ''; }; grafana.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the swarm's metrics UI on this host. Off by default and not derived from {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. ''; }; victoriametrics.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the swarm's metrics store on this host. Paired with {option}`services.hyperhive.deploy.grafana.enable`: a store with no UI is unreadable and a UI with no store is empty, so there is no sensible deployment that takes one and not the other from that switch. Set either directly to run exactly one. ''; }; victorialogs.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the swarm's log store on this host. A hive that is not the service host is a *client* of this store, not a second one. ''; }; bao.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the swarm's secret store in a `swarm-bao` container on this host. A swarm has one store and it has to exist somewhere. *Where* it runs is a separate question from *that* it runs: set this directly to put the store on a host of its own, and clients still reach it by name at {option}`services.hyperhive.swarm.bao.domain` rather than at a local address. With it off, this hive is a *client*: it still reads its own secrets from whoever runs the store, authenticating with its own client certificate. Every hive needs the client half; only one runs the server half, which is why the two live in different namespaces. ''; }; authelia.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the swarm's authelia in a `swarm-authelia` container on this host. A swarm has one SSO provider, and this says it lives here. With it off, this hive is a *client*: {option}`services.hyperhive.swarm.authelia.url` still points at whoever runs it, and no container is created. That asymmetry is why the two live in different namespaces — every hive needs the client half, only one runs the server half. ''; }; swarm-otel.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the **swarm's** telemetry collector on this host. A swarm has one of these, and it belongs wherever the shared services live rather than on every hive. Named `swarm-otel` rather than `otel` because there are two collectors and the tier is the whole distinction: {option}`services.hyperhive.otel.collector.enable` is the **hive-tier** one, which every hive runs. A bare `deploy.otel` would not say which it meant. A hive that does not run the swarm collector still runs its own, and reaches this one by name at {option}`services.hyperhive.swarm.otel.domain`. ''; }; matrix.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the swarm's matrix homeserver — matrix-tuwunel, in a `hive-matrix` container — on this host. Set it directly to put the homeserver somewhere other than the host holding the rest of the swarm's services. Client-side settings stay in {option}`services.hyperhive.swarm.matrix.*`, which every hive agrees on; this is only the decision to run it here. ''; }; nats.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the swarm's message queue in a `swarm-nats` container on this host. A swarm has one queue, so at most one host turns this on — but *which* host is its own decision, not necessarily the one running the swarm's other shared services. Off by default, and off means *absent*: no container is created and nothing else in the evaluated config changes. ''; }; ci-runner.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the swarm's Forgejo Actions runner in a `swarm-ci-runner` container on this host. It reaches the forge at {option}`services.hyperhive.swarm.forge.domain` and registers with the token swarm-controller publishes to the secret store, so it needs neither the forge nor a hive on this host. Not derived from {option}`services.hyperhive.deploy.allSwarmServices`: a runner executes pull-request code, so running one is a decision of its own. ''; }; review-classifier.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the review classifier in a `swarm-review-classifier` container on this host. It answers the swarm UI's review try-outs with `claude` on the Anthropic API key the operator writes to the secret store at `secret/swarm/services/review-classifier/anthropic-api-key`, field `api_key`. On by default where {option}`services.hyperhive.deploy.allSwarmServices` is set and this host has the classifier's store identity (the pair {option}`services.hyperhive.deploy.bao.reviewClassifierClientCertFile` and `reviewClassifierClientKeyFile`, minted on the store's host). The key is never written for you: until the operator writes it the service runs and every run fails with "no API key…". ''; }; claude-login.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the claude login minter in a `swarm-claude-login` container on this host. It runs `claude auth login` for the agent an operator logs in from its card in the swarm UI, and writes the credentials to that agent's `secret/swarm/agents//claude-credentials`, which the agent's harness installs. Run it on one host: a login in progress lives in the service's memory. On by default where {option}`services.hyperhive.deploy.allSwarmServices` is set and this host has the minter's store identity (the pair {option}`services.hyperhive.deploy.bao.claudeLoginClientCertFile` and `claudeLoginClientKeyFile`, minted on the store's host). ''; }; notify-router.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the notification router in a `swarm-notify-router` container on this host. It reads the forge events swarm-controller keeps from its webhooks and queues todos for the agents they concern: assigned, unassigned, and cleared when the issue or pull request closes. Run it on one host: it handles each issue's events in order. On by default where {option}`services.hyperhive.deploy.allSwarmServices` is set and this host has the router's store identity (the pair {option}`services.hyperhive.deploy.bao.notifyRouterClientCertFile` and `notifyRouterClientKeyFile`, minted on the store's host). ''; }; builder.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the swarm builder in a `swarm-builder` container on this host. It builds each agent config PR's head on this host's agent base, through this host's nix daemon, and swarm-controller writes the result as a `swarm-builder` commit status on the PR. On by default where {option}`services.hyperhive.deploy.allSwarmServices` is set and this host has the builder's store identity (the pair {option}`services.hyperhive.deploy.bao.builderClientCertFile` and `builderClientKeyFile`, minted on the store's host). It evaluates unreviewed config PRs and builds them through this host's nix daemon, from a container with its own network namespace. ''; }; attic.enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run the swarm's binary cache in a `swarm-attic` container on this host: an attic server behind the gateway, with its signing key, its cache and its push and pull tokens created in the secret store on first start. The swarm builder pushes green builds to it. Run it on one host. On by default where {option}`services.hyperhive.deploy.allSwarmServices` is set and this host has the cache's store identity (the pair {option}`services.hyperhive.deploy.bao.atticClientCertFile` and `atticClientKeyFile`, minted on the store's host). {option}`services.hyperhive.swarm.attic.domain` and `cache` default to this cache on every host. For an external cache, override both and leave this unset, or set it to `lib.mkForce false` under `allSwarmServices`. ''; }; 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. ''; }; hive-controller.hiveName = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "pr1ma"; description = '' Name of the hive this host runs, and the key of its entry in {option}`services.hyperhive.swarm.hives`. **Required** when {option}`services.hyperhive.deploy.hive-controller.enable` is set; a host that runs no hive needs none. Not merely a display name: the hive occupies `.`, and the name is the hive's identity to the swarm (its secret-store certificate subject, its OIDC client, its status key). Distinct from {option}`services.hyperhive.domain`, the machine-addressable DNS name. Exposed to agents as `HYPERHIVE_HIVE_NAME`; surfaced in the dashboard chrome and per-agent system prompt. The identity surfaces are listed in `docs/process/conventions.md`, § Hive identity. ''; }; hive-controller.dashboardDomain = lib.mkOption { type = lib.types.nullOr lib.types.str; readOnly = true; internal = true; description = '' Read-only: this hive's entry in {option}`services.hyperhive.swarm.dashboardDomains`, or null where `hiveName` has no entry in {option}`services.hyperhive.swarm.hives` (asserted). ''; }; swarm-controller.enable = lib.mkOption { type = lib.types.bool; default = false; description = '' Run the swarm-controller daemon on this host. Off by default and deliberately not derived from {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. Also serves the swarm UI from this host. The UI is a view onto the controller's state and reaches it over that daemon's unix socket, so the host that runs the controller is the only host that can serve it. ''; }; }; }