Feature Specification: Resource dns

Created: 2026-10-07

Status: Implemented

Input: Reconstructed from the code at 093e68b6f (core/resources/dns/default.nix, core/resources/dns/contract.nix, core/resources/dns/self-hosted.nix, core/resources/dns/external.nix, core/lib/coordinate-resource.nix, core/lib/coordinate-provider.nix, core/setups/wiring/dns-routing.nix, core/setups/wiring/resource-routing.nix, core/setups/hosts-common.nix, core/components/hosting-provider/windmill/default.nix), pull request #1235.

User Scenarios & Testing (mandatory)

The dns resource gives a node the coordinates of the authoritative DNS service of its deployment. It does not write records. The deployer writes records with the octodns-zone effect (see 010 Effects). The shared resource rules are in the resource family spec. The resource has the same shape as netbox: both use core/lib/coordinate-resource.nix.

User Story 1 - Find a self-hosted DNS server (Priority: P1)

A node needs to know where the deployment's own named node listens. The deployment has no external DNS service. The routing row selects the bind provider and sets fediversity.dns-self-hosted.host to the address of the DNS primary. The node gets the result at contracts.dns.results.<consumerName>.default.

Why this priority: a deployment without an external DNS service must publish its records to its own server.

Independent Test: nix-unit-setups-dns-routing, test test-self-hosted-result.

Acceptance Scenarios:

  1. Given a dns setup argument of null, When a node requests dns, Then the result is { provider = "bind"; port = 53; scheme = "http"; apiTokenFile = ""; } plus the primary host and the requested domain.
  2. Given a self-hosted route, When the result renders, Then apiTokenFile is "", because the TSIG key reaches octoDNS through the secretFiles.key_secret of the octodns-zone effect.

User Story 2 - Use an external DNS service (Priority: P1)

A hosting provider already holds its zone at an external service (PowerDNS by default). The setup gets a dns argument with a non-empty serverUrl. core/setups/wiring/dns-routing.nix parses the URL into scheme, host and port. The routing row selects the external provider. The roster drops the self-hosted named nodes.

Why this priority: the deployment must not stand up a second authority for a zone that a third party holds.

Independent Test: nix-unit-setups-dns-routing, tests test-external-result, test-external-overrides-domain, test-external-coordinates-* and test-roster-drops-node-when-external.

Acceptance Scenarios:

  1. Given serverUrl = "https://pdns.example.org", When the coordinates resolve, Then scheme is https and port is 443.
  2. Given a serverUrl with no scheme and no port, When the coordinates resolve, Then scheme is http and port is 8081.
  3. Given an external route with its own domain, When the result renders, Then that domain replaces the requested one.
  4. Given an external route, When the roster is built, Then it does not contain the self-hosted named nodes, except a node that answers ACME challenges.

User Story 3 - Hand the coordinates to the operator deployer (Priority: P2)

The windmill component deploys operator groups. It requests "dns".dns = { enable = true; consumerName = "windmill"; }. A client module imports the contract module and, gated on the result, reads contracts.dns.results.windmill.default into the operator curation. The curation feeds HOSTING_CONFIG of the worker.

Why this priority: operator deploys publish their app names to the same DNS service as the hosting provider.

Independent Test: nix-unit-components-operator-curation.

Acceptance Scenarios:

  1. Given a windmill node, When its configuration evaluates, Then the curation contains the dns contract result of that node.

Edge Cases

  • external.host = "" makes the external provider inert.
  • apiTokenFile is a path. The result never holds the token.
  • The policy option domain must already be a zone at the routed provider. The effect writes records and creates no zones.
  • A request with enable = false, or a policy with enable = false, emits no module.

Requirements (mandatory)

Functional Requirements

  • FR-001: The dns contract request MUST be { domain }. The result MUST be { provider; host; port; scheme; apiTokenFile; domain; }.
  • FR-002: The policy MUST copy its domain option into the request.
  • FR-003: The bind provider MUST fulfill provider = "bind", host from fediversity.dns-self-hosted.host, port from fediversity.dns-self-hosted.port (default 53), scheme = "http", apiTokenFile = "" and the requested domain.
  • FR-004: The external provider MUST pass provider (default powerdns), host, port (default 8081), scheme (default http) and apiTokenFile through, and MUST replace the requested domain when its own domain is not empty.
  • FR-005: dns-routing.nix MUST treat the DNS as external only when the dns argument is not null and has a non-empty serverUrl.
  • FR-006: externalCoordinates MUST derive scheme from the URL prefix and port from an explicit port, else 443 for https, else 8081.
  • FR-007: core/setups/hosts-common.nix MUST set "dns".dns with enable = true, the group domain, and routing = mkDefault providerModules.dns, and MUST add the consumerSide of its process to each node.
  • FR-008: The dns request MUST hold only data: enable and consumerName. The consumerSide MUST declare the want of the request, so the node gets the contract result at contracts.dns.results.<consumerName>.default. A client module MUST import core/resources/dns/contract.nix to read it.

Key Entities

  • dns contract: local contract in core/resources/dns/contract.nix.
  • fediversity.dns-self-hosted: options host and port of the bind provider.
  • fediversity.dns-external: options provider, host, port, scheme, apiTokenFile and domain of the external provider.
  • dns setup argument: the coordinates from the DNS_* environment or from effect-defaults.octodns-zone in HOSTING_CONFIG. null keeps the self-hosted route.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: nix-unit-setups-dns-routing passes.
  • SC-002: nix-unit-components-operator-curation passes.
  • SC-003: nix-unit-resources-coordinate-consumer and nix-unit-resources-plain-requests pass.

Assumptions

  • The zone exists at the routed provider before a deploy.
  • The external service speaks the PowerDNS API unless provider says otherwise.

Known Deviations

None.