# "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. ''; 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" "deploy" "hive-controller" "statusPublish" "clientSecretFile" ] statusSecretRemoved) # 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. (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" ] ) (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). ''; }; 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. ''; }; 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; 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. ''; }; }; }