Feature Specification: Setups and wiring

Created: 2026-09-22

Status: Implemented

Input: Reconstructed from the setups of core at 0bf4ecd36, pull requests #1818, #1822, #1828, #1831, #1836 and #2243, commit ba2b92c41, issues #527 and #2236, and the design notes of the maintainers.

User Scenarios & Testing (mandatory)

User Story 1 - Generate a full deployment from a minimal configuration (Priority: P1)

A hosting provider gives a group of machines, an apex domain and a hosting configuration. The setups generate the full NixOS configuration of each machine: DNS records and zones, TLS certificates and trust, SMTP, secrets, workload identity, the database roster, and the routing of resources to their providers.

Why this priority: without the setups, each provider writes this wiring by hand, and the deployment programs have nothing to deploy.

Independent Test: evaluate the NixOS configurations of the operator group with the default configuration. Each machine evaluates, and the zone has a record for each published name.

Acceptance Scenarios:

  1. Given a group with published and internal machines, When the setups evaluate, Then only the published machines get public DNS names.
  2. Given an apex domain, When the setups evaluate, Then all names are under that apex.
  3. Given a component that provides a capability (for example OIDC or SMTP), When another component needs it, Then the wiring connects them, whatever the node names are.

User Story 2 - Change one wiring decision with confidence (Priority: P2)

A developer changes how one decision is made (for example which node gets a TLS certificate). The decision is a pure function, and a unit test asserts on the same expression that the deployment uses.

Independent Test: change a decision function so it gives a wrong result. Its unit test fails.

Edge Cases

  • A node has no address. The zones do not include it.
  • The public uplink value is not a CIDR. The evaluation fails.
  • The apex is not set. The evaluation fails with an error that names the missing input.

Requirements (mandatory)

Functional Requirements

  • FR-001: The setups MUST define the groups (operator, hosting-provider, dev, hypervisor), each with published and internal machines.
  • FR-002: The setups MUST generate the NixOS configuration of each machine from the group, the apex and the hosting configuration.
  • FR-003: Each wiring decision MUST be a pure function with its own unit test that asserts on the expression the deployment uses.
  • FR-004: Wiring MUST read the capabilities that components provide and need, not node names.
  • FR-005: The setups MUST read the apex domain once, from the deployment coordinates, and MUST NOT fall back to a default apex. A fallback apex conflicts with the goal of portable infrastructure.
  • FR-006: The setups MUST NOT include a machine without an address in a zone.
  • FR-007: The groups of core MUST NOT contain machines whose components only a front end provides. A front end adds such machines with the fold helpers.
  • FR-008: The setups MUST give the overview page the browser UIs of only the nodes that the deployment deploys. A node that an external service replaces (such as netbox with an external NetBox) MUST NOT get a tile.
  • FR-009: By default, the setups MUST also serve the overview page at the apex domain. Both zone sources (the zone made at evaluation and the zone that NetBox renders) MUST give the apex a copy of the address records of the overview node, because a CNAME cannot be at the apex. At most one node MUST be served at the apex.
  • FR-010: The central group (the one group whose roster provides varsBackend) MUST find the other groups from their group data or components, not from their names. The apex and the deploy runners come from the central group. The operator groups are the groups that state how their nodes are named. The central-login groups are the groups that opt in to central login. The central group selects these groups from the groups of the caller, and evaluates the model of each selected group from the same groups, so a group that only the caller defines has a model. The node that runs the containers is the one published node that provides instances. The other groups read the central group.
  • FR-011: A property of a component (what it provides and needs, its store, the audience of its certificate, its PKI peers) MUST be a static declaration in the wiring vocabulary of the component. It is true on each node that runs the component, so a consumer, the panel included, MUST be able to read it without an evaluation of core/setups.
  • FR-012: A value of a deployment (a served name, a domain, a mail sender, an address) MUST be read from the resolved config when the consumer runs in the setup eval of the group that has the value. Examples are nodeServedNames, nodeUiDomains, servedAliases and deploymentSenders in core/setups/hosts-common.nix. These values include the overrides of the operator.
  • FR-013: A consumer outside the setup eval of the group (a read from another group, a group file, the panel, the plan of the operator) MUST read a value of a deployment as a static value. The static value MUST come from one file that the option default or the resource request of the component also imports, such as the ui-subdomain.nix and admin-ui-subdomain.nix files of the components. A unit test MUST make sure that each static value is also in the resolved values of the group. Thus core/setups/served-aliases-test.nix compares the table of core/setups/wiring/served-domains.nix with the aliases of the zone, and core/setups/apex-cohesion-test.nix compares it with the names that the nginx of each node serves.
  • FR-014: When an operator disables an application, the setups MUST keep its data: its database, its buckets, its generated secrets and its valkey cache. A new enable MUST use that data again. Only deleteData with enable false MUST delete the data, at the next deploy. The backup buckets MUST stay for the grace period of the hosting provider.

Key Entities

  • Group: a named set of published and internal machines.
  • Machine: a node with hardware, addresses and a set of components.
  • Apex: the domain that all names of a deployment are under.
  • Central group: the group that holds the secrets store, and that the other groups read for the apex, the deploy runners and the central services.
  • Static declaration: a value that a consumer reads without an evaluation of the components of the group: a wiring key of a component, a single-value file such as ui-subdomain.nix, a flag or the servedSubdomains of a machine in the group data, or a table such as core/setups/wiring/served-domains.nix. It does not see the overrides of the operator.
  • Resolved value: a value that the setup eval of a group reads from the evaluated config of its components. It is exact and has one source, but only the setup eval of that group has it.
  • Wiring decision: a pure function from the model to one part of the configuration (DNS, TLS, SMTP, routing, served domains, node selection, workload identity, database roster, NetBox pinning, public uplink, S3 keys).

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: All unit tests of the setups pass.
  • SC-002: The integration tests of the bridge DNS, SMTP, SPIRE, the OpenBao PKI and Incus pass.

Assumptions

  • The groups of the hosting provider and of the operator are in core today. core can split after the split of the monorepo (issue #527).

[NEEDS CLARIFICATION: the setups read the central OpenBao and OpenTelemetry hosts from the central group. Must a deployment of one provider work without the group of another?]

Known Deviations

None.