Hosting resources

This page documents the two sides of each resource module:

  • the request options: the options a component sets to request a resource from a hosting provider;
  • the policy options: the options a hosting provider sets in an environment to supply that resource.

It also gives an example of each side for each resource. For the generic request/policy pattern these build on, see the data model page. For where a provider writes a policy, see how a hosting provider configures these below, which ties these options to the data model's environments.

The data model declares the request and the policy of each resource as a deferred module, so the concrete sub-options live in the resource configuration rather than in the option type. This page surfaces them by building a submodule from each module, giving the <name>.request.* and <name>.policy.* options below.

The page builds a section for each resource that the data model loads from core/resources, so the list below always agrees with the code.

Each policy also has the options that the data model declares for all policies, such as apply, process and routing. The data model page documents those one time, as resources.<name>.policy, so the <name>.policy.* options below do not repeat them.

How a hosting provider configures these

The <name>.request.* options below are the component-facing half of a resource. A hosting provider does not set them directly; they configure the environment a component is deployed to, with the <name>.policy.* options. An environment is one entry of the data model's environments option, and the environments section of the example at examples/hosting-config.nix shows the shape: an attrset keyed by environment name.

A provider points the HOSTING_CONFIG environment variable at such a file. The file has two sections: environments (the provider resources described here) and effect-defaults (operator-form leaf defaults, a map keyed by effect tag). The environments attrset is lib.recursiveUpdate-merged on top of the defaults (the hosting configuration wins). The file may be a .nix, a .toml or a .json file -- the loader dispatches on the extension. examples/hosting-config.toml shows the TOML form of the same override. JSON is there for a generator rather than a hand: it is what services.fediversity-api.hostingConfig renders the provider's curation to.

TOML is sufficient for the common case because the override surface is plain data, but it cannot represent function values, derivations/packages, or null; those only appear in resource policies (defaults), not the override. Reach for the .nix form when you need any of them (or .json, for null alone). Two TOML hand-authoring caveats:

  • Override values are type-checked against the resource policy's option types after the merge, so encode each as the plain-data equivalent the option coerces from; mismatches surface as normal module eval errors.
  • Use dotted-table headers ([mastodon.resources.garage.external]), not array-of-tables ([[...]]) -- the latter parses to a list and fails the merge. The example file demonstrates the correct form.

Each environment is more than its resources. Besides the resources sub-option (where the policies matching the requests below are declared, each under a name of the provider's choice and tagged with its resource), an environment also carries the functions that turn a configuration into a deployment:

  • resources -- the per-resource policies the provider offers; this is the sub-option that takes the policy options below.
  • implementation -- maps the components' required resources to this environment's resources.
  • resource-mapping -- the function type of implementation.
  • config-mapping -- maps an operator configuration to a deployment.
  • deployment -- the end-to-end entry point applying the environment's policies to the components' resource mappings.

Those sibling options are config-dependent functions rather than concrete values, so they are documented informally on the data model page rather than shown with concrete types here. The options on this page are the request shape a component declares, and the policy shape that an environment's resources supply for it.

Examples

A component states a request in its resources function, as <key>.<resource>, where the key is a name of its choice. An environment states a policy as resources.<name>.<resource>, where the name is of the provider's choice. The setups in core/setups write the policies of the in-tree groups.

The build takes each request below from a component in core/components, and each policy from the example values of the policy options that have no default.

dns

A request of the windmill component, without the options that have their default value:

dns.dns = {
  consumerName = "windmill";
  enable = true;
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.dns = {
  enable = true;
};

firewall

A request of the authelia component, without the options that have their default value:

web.firewall = {
  publicTCPPorts = [
    80
    443
  ];
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.firewall = { };

garage

A request of the bind component, without the options that have their default value:

bind-backups.garage = {
  ensureBuckets = {
    bind-backups = {
      corsRules = { };
      retainKvStems = [
        "bind-backups-restic"
      ];
    };
  };
  ensureKeys = {
    bind-backups = {
      ensureAccess = {
        bind-backups = {
          owner = true;
          read = true;
          write = true;
        };
      };
      kvName = "bind-backups";
    };
  };
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.garage = {
  enable = true;
  domain = "garage.example.org";
};

generateFiles

A request of the netbox component, without the options that have their default value:

secrets.generateFiles = {
  writtenInstances = [
    "netbox"
  ];
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.generateFiles = { };

ldap

A request of the pixelfed component, without the options that have their default value:

pixelfed-roles.ldap = {
  roleGroups = {
    application-admins = {
      seedAdmin = true;
    };
    pixelfed-admins = { };
  };
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.ldap = {
  enable = true;
  adminUser = "operator";
  domain = "ldap.example.org";
  email = "admin@example.org";
};

netbox

A request of the windmill component, without the options that have their default value:

ipam.netbox = {
  consumerName = "windmill";
  enable = true;
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.netbox = {
  enable = true;
};

network

No component of the in-tree groups makes a request to this resource.

The smallest policy, in an environment:

environments.<environment>.resources.<name>.network = {
  domain = "example.org";
};

nixos-module

A request of the lldap component, without the options that have their default value:

ui-reset-limit.nixos-module = {
  module = {
    services = {
      nginx = {
        appendHttpConfig = ''
          limit_req_zone $binary_remote_addr zone=lldap-reset:1m rate=6r/m;
        '';
      };
    };
  };
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.nixos-module = { };

oidc

A request of the netbox component, without the options that have their default value:

netbox-oidc.oidc = {
  clientID = "netbox";
  enable = true;
  redirectURI = "https://netbox.example.org/oauth/complete/oidc/";
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.oidc = {
  enable = true;
  domain = "auth.example.org";
};

otel

A request of the netbox component, without the options that have their default value:

otel.otel = {
  consumerName = "netbox";
  enable = true;
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.otel = { };

pod-roles

A request of the mastodon component, without the options that have their default value:

pods.pod-roles = {
  roles = {
    sidekiq = { };
  };
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.pod-roles = { };

postgresql

A request of the lldap component, without the options that have their default value:

lldap-db.postgresql = {
  consumerName = "lldap";
  database = "lldap";
  enable = true;
  urlEnvironment = {
    units = [
      "lldap"
    ];
    variable = "LLDAP_DATABASE_URL";
  };
  username = "lldap";
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.postgresql = {
  enable = true;
  domain = "postgresql.example.org";
};

redis

A request of the valkey component, without the options that have their default value:

valkey-cache.redis = {
  consumerName = "valkey";
  namespace = "valkey";
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.redis = {
  enable = true;
  host = "valkey.example.org";
};

secrets

No component of the in-tree groups makes a request to this resource.

The smallest policy, in an environment:

environments.<environment>.resources.<name>.secrets = { };

smtp

A request of the netbox component, without the options that have their default value:

netbox-smtp.smtp = {
  enable = true;
  sender = "noreply@netbox.example.org";
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.smtp = {
  enable = true;
  domain = "mail.example.org";
};

spiffe

A request of the authelia component, without the options that have their default value:

spiffe.spiffe = {
  enable = true;
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.spiffe = { };

ssl

A request of the incus component, without the options that have their default value:

tls.ssl = {
  domains = [
    "incus.example.org"
  ];
};

The smallest policy, in an environment:

environments.<environment>.resources.<name>.ssl = { };

Options

dns

Authoritative-DNS coordinates for an application, fulfilled by either a self-hosted bind node or an external service, through the local dns contract.

Type: submodule

dns.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

dns.policy.enable

Whether to enable authoritative-DNS provisioning for this deployment.

Type: boolean

Default:

false

Example:

true

Declared by:

dns.policy.domain

The domain the deployment publishes its records under. Must already be a zone at whichever provider is routed – the effect writes records and creates no zones.

Type: string

Default:

""

Declared by:

dns.request

Options a component sets to request this resource.

Type: submodule

dns.request.enable

Whether to enable authoritative-DNS coordinates on this application’s host.

Type: boolean

Default:

false

Example:

true

Declared by:

dns.request.consumerName

Unique consumer name for this app (analog of ldap’s/redis’s consumerName), used as the contracts.dns.want.<consumerName> key. The node gets the contract result ({ provider; host; port; scheme; apiTokenFile; domain; }) at contracts.dns.results.<consumerName>.default.

Type: string

Declared by:

firewall

Which sources reach a node, and which of the ports it serves are published beyond them.

Type: submodule

firewall.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

firewall.policy.adminSources

The operator’s own sources: allowed to reach every port a node serves, exactly like internalSources. Same dual-stack inference.

Every port, not only adminTCPPorts – a source reaching nothing but :22 would be a second SSH allow-list rather than a trusted source. This is what an operator’s VPN comes in on, and what makes a management node reachable at its own public name: those nodes publish none of their ports, so being an admitted source is the only way in (notes/private-access.md has the recipe).

Deployment data, not repository data: it arrives with the rest of the deployment’s coordinates (ADMIN_SOURCES, core/effects/env-config.nix).

Leaving it empty publishes the admin surface (adminTCPPorts) instead, so forgetting to state it does not lock anyone out.

Type: list of string

Default:

[ ]

Example:

[
  "185.206.235.36/32"
  "2a00:51c0:13:1100::4/128"
]

Declared by:

firewall.policy.adminTCPPorts

What counts as the administrative surface in this environment: the ways IN to a node, :22 by default.

These are always served, a node’s own way in being a property of the node rather than of who arrives on it; who may reach them is the gate’s decision, which adminSources turns on. Empty, they are published to everyone; stated, they are reachable from adminSources alone – which reach every port the node serves regardless, so this list narrows nothing.

An environment-level answer, since the ways in to a node belong to whoever runs it. A host with one of its own – the hypervisor’s incus API on :8443 – gets it from the environment too.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[
  22
]

Example:

[
  22
  8443
]

Declared by:

firewall.policy.bridgeInterfaces

Interfaces whose traffic reaches only bridgeTCPPorts, bridgeUDPPorts, the public ports of the node, and ICMP, e.g. a hypervisor’s container bridge. The limit also holds for a source in internalSources or adminSources, and the traffic of operatorSources stays limited to the operator ports.

The bridge ports are also fed into networking.firewall.interfaces.<name>, since nixos-fw’s input is an independent base chain that has to be told that the node serves them there.

Type: list of string

Default:

[ ]

Example:

[
  "incusbr0"
]

Declared by:

firewall.policy.bridgeTCPPorts

TCP ports that the traffic on bridgeInterfaces reaches, in addition to the public ports.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  5000
]

Declared by:

firewall.policy.bridgeUDPPorts

UDP counterpart of bridgeTCPPorts.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  67
]

Declared by:

firewall.policy.internalSources

Sources allowed to reach every port a node serves – the deployment’s own peers. Dual-stack: the family of each entry is inferred from whether it contains a :.

This carries the hosting-provider mesh (spire’s trust-bundle fetch, openbao, the TF state backend, postgres, valkey), none of it published. Which addresses those are is a fact about where the deployment runs, not about any application.

Type: list of string

Default:

[ ]

Example:

[
  "10.0.100.0/24"
]

Declared by:

firewall.policy.operatorSources

Sources that reach only operatorTCPPorts, operatorUDPPorts and the public ports of the node, and ICMP. Same dual-stack inference.

The gate reads these before bridgeInterfaces, trustedInterfaces, internalSources and adminSources, thus a source here stays limited when it is also in one of them. This is the case for the operator deployments on the hosting provider’s bridge: their range is in the bridge subnet, and on the hypervisor they come in on the bridge interface. The addresses of a cluster (CLUSTER_SOURCES) are also here.

Type: list of string

Default:

[ ]

Example:

[
  "10.0.104.0/21"
]

Declared by:

firewall.policy.operatorTCPPorts

TCP ports that operatorSources reach, in addition to the public ports.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  5432
]

Declared by:

firewall.policy.operatorUDPPorts

UDP counterpart of operatorTCPPorts.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  67
]

Declared by:

firewall.policy.publicTCPPorts

Ports this environment publishes on the node regardless of any application request, because the reason to publish them is a fact about the environment rather than about what the node serves.

The ACME challenge port is the case: whether a node’s names take a publicly trusted leaf follows from the deployment’s uplink and its published zone (core/setups/wiring/tls-routing.nix), and where they do, the node must answer an HTTP-01 challenge on :80. The same application behind no public uplink publishes nothing.

Concatenated with the applications’ requests of the same name, and subject to the same conjunction: it says who may reach the port, not that anything serves it.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  80
]

Declared by:

firewall.policy.publicUDPPorts

UDP counterpart of the policy’s publicTCPPorts.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  443
]

Declared by:

firewall.policy.trustedInterfaces

Interfaces whose traffic bypasses the gate. The traffic of operatorSources stays limited on them. Also fed into networking.firewall.trustedInterfaces, since nixos-fw’s input is an independent base chain that has to be told the same thing.

Type: list of string

Default:

[ ]

Example:

[
  "wg0"
]

Declared by:

firewall.request

Options a component sets to request this resource.

Type: submodule

firewall.request.publicTCPPorts

TCP ports this application needs reachable from the whole internet rather than from the deployment’s own peers only.

Request this only for a port whose users are outside the deployment by nature – an ingest, a federation endpoint, a browser-facing listener. Everything else is reachable from internalSources without asking.

The application still has to serve the port the ordinary way (networking.firewall.allowedTCPPorts, or an upstream module’s openFirewall); this says who may reach it, not that it exists.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  1935
]

Declared by:

firewall.request.publicUDPPorts

UDP counterpart of publicTCPPorts.

Type: list of 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

[ ]

Example:

[
  53
]

Declared by:

garage

Garage storage configuration for an application.

Type: submodule

garage.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

garage.policy.enable

Whether to enable S3-compatible storage provider Garage.

Type: boolean

Default:

false

Example:

true

Declared by:

garage.policy.api.domain

The domain of the S3 API.

Type: string

Default:

"s3.${config.domain}"

Declared by:

garage.policy.api.domainForBucket

Gives the domain of a bucket on the S3 API (virtual-host style) from the name of the bucket.

Type: function that evaluates to a(n) string

Declared by:

garage.policy.api.port

The port on which Garage serves the S3 API.

Type: signed integer

Default:

3900

Declared by:

garage.policy.api.publicUrl

The URL of the S3 API for clients outside the internal network, for example the target of a migration: HTTPS through the nginx virtual host of bucketEndpointVhosts.

Type: string

Default:

"https://${config.api.domain}"

Declared by:

garage.policy.api.url

The URL of the S3 API for clients on the internal network: plain HTTP to api.port, which the firewall opens only to internal sources.

Type: string

Default:

"http://${config.api.domain}:${toString config.api.port}"

Declared by:

garage.policy.bucketEndpointVhosts

The NixOS modules with the nginx virtual hosts in front of the bucket endpoints. There is one virtual host for each family, not one for each bucket, because no evaluation knows all the buckets.

Type: list of unspecified value

Default: One module with three nginx virtual hosts: *.<web.rootDomain> for the website buckets, and <api.domain> and *.<api.domain> for the S3 API.

Declared by:

garage.policy.cacheBackupRequestFor

Gives, from a list of { namespace, bucket ? ..., s3AccessKeyFile, s3SecretKeyFile }, the requests for the offsite cache-backup bucket and key of each namespace. The requests have final names, thus the caller adds them after apply and does not send them through it.

Type: function that evaluates to a(n) list of unspecified value

Declared by:

garage.policy.domain

The domain of the Garage node. The domains of the S3 API (api.domain) and of the websites (web.rootDomain) are below it by default.

Type: string

Example:

"garage.example.org"

Declared by:

garage.policy.externalCopy

The S3 target outside this group for a daily copy of the *-backups buckets (./external-copy.nix). With null, the node makes no copy.

Type: null or (submodule)

Default:

null

Declared by:

garage.policy.externalCopy.bucket

The bucket at the target. A person makes it: the node does not make a bucket, thus the key of the target needs no right to do so.

Type: string

Example:

"fediversity-offsite"

Declared by:

garage.policy.externalCopy.credentialsSeed

The label of the S3 key of the target. The node reads the fields accessKeyId and secretAccessKey of the KV entry vars/secret/backup-external-copy/<label>, which a person seeds (notes/bootstrap-seeds.md).

Type: string

Declared by:

garage.policy.externalCopy.endpoint

The S3 endpoint of the target, as <scheme>://<host>[:<port>]. The target must not depend on the hosting provider of the group: a loss of that site must not also lose the target.

Type: string

Example:

"https://s3.offsite.example.net"

Declared by:

garage.policy.externalCopy.historyDays

The days that an object stays at the target below history/ after it changes or goes away in the source. This adds to the retention of the source: see the retention section of the hosting provider guide.

Type: unsigned integer, meaning >=0

Default:

7

Declared by:

garage.policy.externalCopy.prefix

The path in bucket below which this garage writes. It must be unique for each garage that shares bucket.

Type: string

Example:

"example.org"

Declared by:

garage.policy.externalCopy.region

The S3 region of the target.

Type: string

Example:

"eu-central-1"

Declared by:

garage.policy.provisionFor

Gives, from a list of requests with final names, the NixOS module that makes their buckets, keys and grants on the Garage node (./provision.nix).

Type: function that evaluates to a(n) unspecified value

Declared by:

garage.policy.provisioning

Who makes the buckets and the keys of this cluster.

  • in-nix: a oneshot on the Garage node makes them, and each consumer generates its own S3 key pair. Use it when the Garage node is in the same group as its consumers.
  • tf: the operator TF graph makes them and publishes the key pairs at the same KV paths. Use it when the Garage node is in a different group, where a provisioner on the node cannot reach.

The setup sets one of them with mkDefault, thus a test can set the other.

Type: one of “in-nix”, “tf”

Default:

"tf"

Declared by:

garage.policy.rpc.port

The port for the RPC traffic between the Garage nodes.

Type: signed integer

Default:

3901

Declared by:

garage.policy.tenant

A prefix for the names in a cluster that more than one group uses. When it is not empty, each bucket name, key name and ensureAccess bucket key gets the physical name <tenant>-<name>, thus the objects of one group stay apart from those of a different group. When it is empty, the names do not change.

The setup supplies the deployment where there is one, and the group otherwise (core/setups/hosts-common.nix).

Type: string

Default:

""

Example:

"operator"

Declared by:

garage.policy.web.domainForBucket

Gives the domain of the website of a bucket from the name of the bucket.

Type: function that evaluates to a(n) string

Declared by:

garage.policy.web.internalPort

The local port on which Garage serves the website buckets. The nginx virtual host of bucketEndpointVhosts sends requests to it.

Type: signed integer

Default:

3902

Declared by:

garage.policy.web.rootDomain

The domain below which Garage serves each website bucket (s3_web.root_domain).

Type: string

Default:

"web.${config.domain}"

Declared by:

garage.policy.web.urlForBucket

Gives the URL of the website of a bucket from the name of the bucket.

Type: function that evaluates to a(n) string

Declared by:

garage.request

Options a component sets to request this resource.

Type: submodule

garage.request.enable

Whether to enable Enable a Garage server on the machine.

Type: boolean

Default:

false

Example:

true

Declared by:

garage.request.ensureBuckets

S3 buckets to create on Garage for this application, keyed by bucket name. Each entry is created with its website access when the deployment runs. CORS rules apply under each provisioning mode: on the co-located test path (ensure-garage), in the operator TF graph and on the garage node’s own reconciler.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

garage.request.ensureBuckets.<name>.corsRules.enable

Whether to enable CORS rules on this bucket, allowing other origins (e.g. a frontend) to fetch its objects from the browser.

Type: boolean

Default:

false

Example:

true

Declared by:

garage.request.ensureBuckets.<name>.corsRules.allowedHeaders

Request headers permitted by the bucket’s CORS policy (the AllowedHeaders of the S3 CORS rule).

Type: list of string

Default:

[ ]

Declared by:

garage.request.ensureBuckets.<name>.corsRules.allowedMethods

HTTP methods (e.g. GET, PUT) permitted by the bucket’s CORS policy.

Type: list of string

Default:

[ ]

Declared by:

garage.request.ensureBuckets.<name>.corsRules.allowedOrigins

Origins (e.g. https://app.example.org) allowed to make cross-origin requests to the bucket.

Type: list of string

Default:

[ ]

Declared by:

garage.request.ensureBuckets.<name>.retainKvStems

The vars instances of the deployment that the data in this bucket cannot be used without, such as the password of a restic repository or the keys of an offsite backup. When the operator TF graph retires a *-backups bucket, it keeps these instances (their secret/ and public/ halves) for the grace period of the retained bucket, and the garage node deletes them together with the bucket.

Type: list of string

Default:

[ ]

Declared by:

garage.request.ensureBuckets.<name>.serveAsWebsite

Whether to serve this bucket’s contents as a static website over HTTP, so its objects are publicly reachable by URL.

Type: boolean

Default:

false

Declared by:

garage.request.ensureKeys

S3 access keys to provision on Garage for this application, keyed by key name. Each key is imported from the given credential files and granted the configured per-bucket access.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

garage.request.ensureKeys.<name>.ensureAccess

Per-bucket access this key is granted, keyed by bucket name. Each entry must set at least one of read/write/owner.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

garage.request.ensureKeys.<name>.ensureAccess.<name>.owner

Grant this key owner permission on the bucket (manage the bucket itself, not just its objects).

Type: boolean

Default:

false

Declared by:

garage.request.ensureKeys.<name>.ensureAccess.<name>.read

Grant this key permission to read objects from the bucket.

Type: boolean

Default:

false

Declared by:

garage.request.ensureKeys.<name>.ensureAccess.<name>.write

Grant this key permission to write objects to the bucket.

Type: boolean

Default:

false

Declared by:

garage.request.ensureKeys.<name>.kvName

OpenBao KV instance stem the key’s credentials publish under (<kvName>-s3). Defaults to the key name; not tenant-prefixed.

Type: string

Default:

"‹name›"

Declared by:

garage.request.ensureKeys.<name>.s3AccessKeyFile

Path to a file holding the S3 access key id to import for this key. The in-Nix (garageSide) provisioner and the garage node’s own reconciler (provisionFor) import a pre-generated key from it; the operator TF graph has garage create the key and captures the secret from state, so it leaves this null. The path is the rendered read-back location, not a generation source.

Type: null or absolute path

Default:

null

Declared by:

garage.request.ensureKeys.<name>.s3SecretKeyFile

Path to a file containing the S3 secret access key paired with s3AccessKeyFile. Null for the tf provisioner (see s3AccessKeyFile).

Type: null or absolute path

Default:

null

Declared by:

generateFiles

Declarative generation of an application’s secret files, via the generateFiles contract.

Type: submodule

generateFiles.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

generateFiles.policy.address

Address of the OpenBao server backing vars and the shared agent.

Type: string

Default:

"http://127.0.0.1:8200"

Example:

"http://openbao:8200"

Declared by:

generateFiles.policy.caCertFile

CA certificate the vars-openbao upload and the shared openbao agent verify the server’s TLS against. Set in production, where the openbao node’s PKI leaf chains to it; empty in tests, which use plain http://.

Type: string

Default:

""

Declared by:

generateFiles.policy.consumerModule

The bare OpenBao consumer plumbing – the vars/vars-openbao modules, shared agent, production extraModules and routing – with no generateFiles request. A node that only reads a shared vars-openbao secret back through the agent needs this body but has no generation of its own to trigger apply. This resource exposes the body; the consuming resource owns whether to emit it (the setup feeds it into the oidc resource’s consumerAgent).

Type: unspecified value (read only)

Declared by:

generateFiles.policy.extraModules

Extra NixOS modules emitted alongside the consumer – e.g. ./spire-login.nix, which in production publishes the SPIRE SVID pair the agent logs in with, and populates tokenFile for the units that use a token.

Type: list of unspecified value

Default:

[ ]

Declared by:

generateFiles.policy.tokenFile

File holding the OpenBao token for vars-openbao and the agent’s token_file auto-auth.

Type: absolute path

Default:

"/run/keys/vars-openbao-token"

Declared by:

generateFiles.request

Options a component sets to request this resource.

Type: submodule

generateFiles.request.instances

Generation instances this application needs run on its host, keyed by name. Each mirrors a contracts.generateFiles request: a script producing the named files under $out/. The vars provider bridges each generated file into contracts.fileSecrets.want.vars.<name>_<file>, read back via config.contracts.fileSecrets.results.vars.<name>_<file>.path.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

generateFiles.request.instances.<name>.dependencies

Names of other instances whose outputs are made available under $in/<name>/.

Type: list of string

Default:

[ ]

Declared by:

generateFiles.request.instances.<name>.files

Files to generate; keys are file names the script must write under $out/.

Type: attribute set of (submodule)

Declared by:

generateFiles.request.instances.<name>.files.<name>.group

Unix group that must own the generated file.

Type: string

Default:

"root"

Declared by:

generateFiles.request.instances.<name>.files.<name>.mode

File permissions as an octal string.

Type: string

Default:

"0400"

Declared by:

generateFiles.request.instances.<name>.files.<name>.owner

Unix user that must own the generated file.

Type: string

Default:

"root"

Declared by:

generateFiles.request.instances.<name>.files.<name>.secret

Whether the file contains sensitive data.

Type: boolean

Default:

true

Declared by:

generateFiles.request.instances.<name>.independent

Whether the instance’s files stand on their own. An atomic instance (the default) refuses to run in a partial state; an independent one generates only what is missing, so it can grow a file without rotating its siblings.

Type: boolean

Default:

false

Declared by:

generateFiles.request.instances.<name>.runtimeInputs

Attribute paths in pkgs of the packages available in PATH during script execution, such as "openssl". They are names because the plan of a node must hold no derivation.

Type: list of string

Default:

[ ]

Declared by:

generateFiles.request.instances.<name>.script

Shell script writing each declared file to $out/<name>. Dependency outputs are available under $in/<dep>/.

Type: string

Declared by:

generateFiles.request.readInstances

Names of instances that another application writes and this application reads, such as a token that the two share. This resource generates nothing for them; the setup only grants the application’s host read access under vars/secret/<name>/.

Type: list of string

Default:

[ ]

Declared by:

generateFiles.request.readKeys

Single keys, as <instance>/<key>, of instances that another application writes and this application reads, where the instance also holds keys that this application must not read. This resource generates nothing for them; the setup only grants the application’s host read access on vars/secret/<instance>/<key>.

Type: list of string

Default:

[ ]

Declared by:

generateFiles.request.writtenInstances

Names of instances this application writes to the vars backend by its own means, such as a contracts.generateFiles.want in its module or a oneshot that publishes a runtime value. This resource generates nothing for them; the setup only grants the application’s host write access under vars/secret/<name>/, as it does for instances.

Type: list of string

Default:

[ ]

Declared by:

ldap

LDAP directory access for an application, fulfilled by an lldap-backed provider of the nixpkgs ldap contract.

Type: submodule

ldap.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

ldap.policy.enable

Whether to enable LDAP directory provider.

Type: boolean

Default:

false

Example:

true

Declared by:

ldap.policy.adminUser

Name of the seeded directory administrator. The setup reads it from ldap.adminUser in the group data. The in-tree groups use the group name, so the account says which group it administers and two groups’ directories never read as one.

An lldap user id is its immutable primary key, so a changed name arrives as a second account and the first one needs a manual removal.

Pixelfed refuses an account named on its own RestrictedNames list, admin and administrator among them, so a group that runs pixelfed needs a name pixelfed permits.

Type: string

Example:

"operator"

Declared by:

ldap.policy.baseDN

Base distinguished name for the directory. Defaults to the domain with its leading subdomain label dropped and each remaining label turned into a dc= component (e.g. ldap.fediversity.net -> dc=fediversity,dc=net).

Type: string

Default:

concatMapStringsSep "," (label: "dc=${label}") (drop 1 (splitString "." config.domain))

Declared by:

ldap.policy.bindInstanceName

Name of the generateFiles/vars instance carrying the directory’s admin (management-bind) password, and so the stem of both its central-OpenBao KV path and the rendered file path. The setup group-qualifies it to ldap-bind-<group> so two groups’ lldap nodes never overwrite each other’s admin password on the one shared central OpenBao KV.

Type: string

Default:

"ldap-bind"

Declared by:

ldap.policy.crossNode

Cross-node factory: { consumer, group } -> { result; bridge; ... }, the crossNodeWant answer. result holds url, host, port, caFile, baseDN, userBaseDN, groupBaseDN, bindDN and bindPasswordFile; bridge gives the result to the ldap contract of the consumer node.

Type: unspecified value (read only)

Declared by:

ldap.policy.domain

Service domain the LDAP directory is named under; baseDN derives from it.

Type: string

Example:

"ldap.example.org"

Declared by:

ldap.policy.email

Email address of the seeded directory administrator (adminUser).

The directory is seeded with a single administrator at deploy time, added to authelia-users so it can authenticate through authelia OIDC.

The account is created password-less, and the administrator establishes its password with “Forgot password?” on the authelia portal, which mails a single-use link here, so this address has to be deliverable.

lldap-provision reconciles the account on each deploy with OpenTofu. When this option becomes empty, the unit deletes the account. Directory users that the deployment did not seed stay.

Type: string

Example:

"admin@example.org"

Declared by:

ldap.policy.host

Reachable address consumers connect to the lldap daemon at – the lldap node’s deployment FQDN, supplied by hosts-common.nix. Defaults to the domain for the single-node behaviorTest.

Type: string

Default:

config.domain

Declared by:

ldap.policy.httpPort

HTTP/GraphQL port the lldap web interface and CLI use.

Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

17170

Declared by:

ldap.policy.jwtSecretInstanceName

Name of the generateFiles/vars instance carrying the secret with which lldap signs the tokens of its sessions, and so the stem of both its central-OpenBao KV path and the rendered file path. All pods and nodes of a group that serve the same directory must have the same secret. The setup group-qualifies it to ldap-jwt-<group>, for the same reason as bindInstanceName.

Type: string

Default:

"ldap-jwt"

Declared by:

ldap.policy.keySeedInstanceName

Name of the generateFiles/vars instance carrying the seed of the OPAQUE server key of lldap, and so the stem of both its central-OpenBao KV path and the rendered file path. The key encrypts the password of each user, thus a key that is not the same after a rebuild or a restore makes all passwords unusable. The setup group-qualifies it to ldap-key-<group>, for the same reason as bindInstanceName.

Type: string

Default:

"ldap-key"

Declared by:

ldap.policy.port

Plaintext LDAP port the lldap daemon listens on.

Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

3890

Declared by:

ldap.policy.rolesInstanceName

Name of the vars instance that holds the members of the roleGroups of the applications, as the file members, and so the stem of its central-OpenBao KV path. The lldap node writes it, and each application node with roleGroups reads it. The setup group-qualifies it to ldap-roles-<group>, for the same reason as bindInstanceName.

Type: string

Default:

"ldap-roles"

Declared by:

ldap.policy.tls.enable

Whether the lldap daemon also serves LDAPS. Consumers then connect with ldaps:// on tls.port, and verify the listener against tls.caFile. The setup supplies the certificate on the lldap node, with fediversity.lldap.tls.certFile and keyFile.

Type: boolean

Default:

false

Declared by:

ldap.policy.tls.caFile

Path, on a consumer node, to the PEM CA certificate that signs the LDAPS certificate. Given to consumers as the contract result.caFile.

Type: null or string

Default:

null

Declared by:

ldap.policy.tls.port

LDAPS port the lldap daemon listens on.

Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

6360

Declared by:

ldap.request

Options a component sets to request this resource.

Type: submodule

ldap.request.enable

Whether to enable LDAP directory access for the application.

Type: boolean

Default:

false

Example:

true

Declared by:

ldap.request.consumerName

Unique consumer name for this app (analog of oidc’s clientID), used as the contracts.ldap.want.<consumerName> key. Necessary when group is not null.

Type: null or string

Default:

null

Declared by:

ldap.request.group

LDAP group the application requires. The provider creates this group. Carried verbatim by the nixpkgs ldap contract request. If null, the request asks for no directory access, and only roleGroups applies.

Type: null or string

Default:

null

Declared by:

ldap.request.roleGroups

The directory groups that give a role in the application, by group name. The provider creates each group, and publishes the user ids of its members to the KV stem rolesInstanceName. The application node gets a read grant for that stem, and the application syncs the roles of its accounts from it. The application does not get a password of the directory.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

ldap.request.roleGroups.<name>.seedAdmin

Whether the seeded directory administrator (adminUser) is a member of this group.

Type: boolean

Default:

false

Declared by:

netbox

NetBox IPAM coordinates for an application, fulfilled by either a self-hosted NetBox node or an external instance, through the local netbox contract.

Type: submodule

netbox.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

netbox.policy.enable

Whether to enable NetBox IPAM provisioning for this deployment.

Type: boolean

Default:

false

Example:

true

Declared by:

netbox.policy.allocateIpv4

Whether a public node is allocated a v4 address from the pool. Off (the default), the deployment is v6-only, apart from a v4 address that a node’s machines entry pins.

Type: boolean

Default:

false

Declared by:

netbox.policy.clusterName

Name of the NetBox cluster the deployment’s virtual machines are recorded under. Must already exist at whichever provider is routed – the deploy reads it and creates no NetBox infrastructure. At the external provider this today means naming one of their clusters; a dedicated Fediversity cluster there is a follow-up (see notes/bootstrap-full-ladder.md).

A deployment’s node names are its own – every deployment has an openbao – so either this names a cluster per deployment, or the deployments sharing one are told apart by tenant.

Type: string

Default:

"incus"

Declared by:

netbox.policy.prefixSelector

How the deployment discovers the prefix(es) to allocate from, without naming a subnet.

Type: submodule

Default:

{ }

Declared by:

netbox.policy.prefixSelector.kind

How to select the prefix pool: cidr, id, or the name of a NetBox prefix filter (tag, status, site_id, …).

Type: string

Default:

"tag"

Declared by:

netbox.policy.prefixSelector.value

The value of the selector: a CIDR, a comma-separated list of prefix IDs, or the value of the filter.

Type: string

Default:

"fediversity"

Declared by:

netbox.policy.rangeSelector

How the deployment discovers an IP range to allocate from instead of the whole prefix, for the family that range covers. Where the NetBox instance is shared with its owner, the prefix is theirs and only a range within it is ours. An empty kind (the default) selects no range and allocates from the prefix.

Type: submodule

Default:

{ }

Declared by:

netbox.policy.rangeSelector.kind

How to select the range: id, or the name of a NetBox IP range filter such as tag. When it is empty, the deployment selects no range.

Type: string

Default:

""

Example:

"tag"

Declared by:

netbox.policy.rangeSelector.value

The value of the selector: a comma-separated list of range IDs, or the value of the filter.

Type: string

Default:

""

Example:

"fediversity"

Declared by:

netbox.policy.tenant

Name of the NetBox tenant the deployment’s virtual machines are recorded under, which is what makes two deployments able to share one cluster: NetBox keys a virtual machine’s uniqueness on cluster, tenant and name together, so without one the second deployment’s openbao collides with the first’s. Must already exist at whichever provider is routed, on the same read-never-create footing as clusterName.

Empty (the default) records the virtual machines untenanted, which is correct wherever the deployment has a cluster to itself.

Type: string

Default:

""

Declared by:

netbox.request

Options a component sets to request this resource.

Type: submodule

netbox.request.enable

Whether to enable NetBox IPAM coordinates on this application’s host.

Type: boolean

Default:

false

Example:

true

Declared by:

netbox.request.consumerName

Unique consumer name for this app (analog of ldap’s/redis’s consumerName), used as the contracts.netbox.want.<consumerName> key. The node gets the contract result ({ serverUrl; apiTokenFile; clusterName; tenant; prefixSelector; rangeSelector; allocateIpv4; }) at contracts.netbox.results.<consumerName>.default.

Type: string

Declared by:

network

Networking info.

Type: submodule

network.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

network.policy.domain

The apex this machine’s FQDN suffix and resolv.conf search domain hang off, i.e. networking.domain.

Deliberately undefaulted: it is the deployment’s apex, which only the setup knows, and a module default here is silent when wrong – a short peer name still resolves, just possibly to another deployment’s node. core/setups/hosts-common.nix supplies the group’s own domain for every node it deploys.

Type: unspecified value

Example:

"example.org"

Declared by:

network.policy.ipv4

The IPv4 settings of the machine.

Type: submodule

Default:

{ }

Declared by:

network.policy.ipv4.address

The IPv4 address of the machine, with or without a prefix length (default 24). The machine gets it on interface, and the gates of the group admit it (core/setups/hosts-common.nix). An incus machine reads the address that NetBox allocates at run time (./run-time.nix), and uses this address when it finds none.

Type: unspecified value

Default:

null

Declared by:

network.policy.ipv4.gateway

The IP address of the default gateway. With null, the machine gets no static default route, and takes it from somewhere else, for example a DHCP lease.

Type: unspecified value

Default:

null

Declared by:

network.policy.ipv4.interface

The interface that carries the machine’s IPv4 network.

Type: unspecified value

Default:

"eth0"

Declared by:

network.policy.ipv6

The IPv6 settings of the machine.

Type: submodule

Default:

{ }

Declared by:

network.policy.ipv6.address

The IPv6 address of the machine, with or without a prefix length (default 64). The machine gets it on interface, and the gates of the group admit it (core/setups/hosts-common.nix). An incus machine reads the address that NetBox allocates at run time (./run-time.nix), and uses this address when it finds none.

Type: unspecified value

Default:

null

Declared by:

network.policy.ipv6.gateway

The IP address of the default gateway. With null, the machine gets no static default route, and takes it from somewhere else, for example a DHCP lease.

Type: unspecified value

Default:

null

Declared by:

network.policy.ipv6.interface

The interface that carries the machine’s IPv6 network.

Type: unspecified value

Default:

"eth0"

Declared by:

network.policy.name

The name of the machine. Most of the time, this will look like vm02XXX or fediYYY.

Type: unspecified value

Default:

"nixos"

Declared by:

network.policy.nameservers

Resolvers this machine asks before the upstream ones (fediversity.resolvers, core/setups/nixos/networking.nix), i.e. the head of networking.nameservers.

“Before” only in the rendered list: it goes to systemd-resolved’s global DNS=, where position carries no preference, and every server there is eligible for every name. So a resolver here gets every name it is willing to answer, and the list must hold only resolvers that agree with the group zone. What keeps the apex off the upstream resolvers is the routing domain on the bridge link, not this order.

core/setups/hosts-common.nix sets it to the group’s own DNS nodes where they hold the apex.

Type: list of string

Default:

[ ]

Declared by:

network.policy.needsPublicIp

Whether this node exposes public services and therefore needs a routed/public IP allocated from NetBox. Internal-only nodes (which settle for internal/PKI TLS and are reached over the private network) set this false, so hosts-common.nix skips NetBox allocation for them. Defaults to true.

Type: unspecified value

Default:

true

Declared by:

How the machine’s public address is DELIVERED to it, and hence whether the gateway its machines entry states is reachable from the machine at all.

  • bridged (the default): the address is delivered on-link, on the interface the machine sees – a physical segment, or a VLAN tagged onto it. The stated gateway is a neighbour, so it is rendered as the default route. This is what a machine on a hosting provider’s own network has always had.
  • nat: the address is only an ALIAS on a NAT bridge that has no uplink to the public segment. The stated gateway is NOT reachable, and rendering it installs a metric-0 default route that shadows the bridge’s own DHCP default – taking the machine’s egress away entirely. So it is suppressed, and the address stays what it is: a label the outside world resolves, which nothing yet delivers.

This is a property of the hosting provider’s topology rather than of the machine, so it arrives with the rest of the deployment’s coordinates (PUBLIC_UPLINK, core/effects/env-config.nix) rather than being stated per machine.

Type: one of “bridged”, “nat”

Default:

"bridged"

Declared by:

network.request

Options a component sets to request this resource.

Type: submodule

nixos-module

A NixOS configuration module to include as-is.

Type: submodule

nixos-module.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

nixos-module.policy.module

The NixOS configuration module to include.

Type: unspecified value

Default:

{ }

Declared by:

nixos-module.request

Options a component sets to request this resource.

Type: submodule

nixos-module.request.module

The NixOS configuration module to include.

Type: unspecified value

Declared by:

oidc

Single sign-on (OIDC) for an application, fulfilled by an authelia-backed provider of the nixpkgs oidc contract.

Type: submodule

oidc.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

oidc.policy.enable

Whether to enable single sign-on (OIDC) provider.

Type: boolean

Default:

false

Example:

true

Declared by:

oidc.policy.cookieDomain

Domain the authelia session cookie is scoped to. Defaults to the provider domain (e.g. auth.example.org), which is correct for pure OIDC. Forward-auth needs the cookie to reach the protected UI subdomains, so the setup widens this to the registrable parent (example.org) when forward-auth consumers exist – authelia_url stays the portal subdomain, which authelia allows as long as it is a subdomain of the cookie domain. Kept default (== domain) otherwise so an OIDC-only deployment is byte-unchanged.

Type: string

Default:

config.domain

Declared by:

oidc.policy.deployAdminUIs

Whether to deploy the hosting provider’s forward-auth admin UIs (garage-webui, pgweb, redis-commander) – plain services fronted by authelia forward-auth for backends with no native OIDC. A single policy switch for the whole admin-UI category rather than a per-app option: the UIs are prod conveniences with no test value, so a focused deployment test drops them all at once (keeping the UI packages + their nginx forward-auth vhosts out of the node closures) by routing this policy to false. Defaults true so a real deployment is unchanged.

It gates that CATEGORY alone: a forwardAuth request that states adminUI = false is part of the service it fronts and is deployed whatever this says. core/setups/hosts-common.nix reads the same rule, so a UI dropped here is not a name it counts as served.

Type: boolean

Default:

true

Declared by:

oidc.policy.deploymentDomain

The domain this deployment is configured for, which a forwardAuth request’s subdomain is hung off.

Distinct from the static apex (core/apex.nix): a tenant deployment names its own domain in its deployment configuration, long after the hosting provider whose apex the nodes sit under was deployed. Set by core/setups/hosts-common.nix, the one reader of the resolved deployment configuration.

Type: string

Default:

""

Declared by:

oidc.policy.domain

Domain the OIDC provider (authelia) is served under.

Type: string

Example:

"auth.example.org"

Declared by:

oidc.policy.instanceName

Name of the generateFiles/vars instance holding the authelia daemon’s secrets and the shared OIDC client secret, and so the stem of their central OpenBao KV paths. Reaches the authelia node and every consumer of it alike, the consumer reading the client secret back from that path.

The setup qualifies it by the group running the authelia, which is what keeps two groups’ client registrations apart on the one shared KV; see the provider module’s own instanceName for what a collision costs.

Type: string

Default:

"authelia-oidc"

Declared by:

oidc.policy.issuer

OIDC issuer URL (the provider’s discovery endpoint base).

Type: string

Default:

"https://${config.domain}"

Declared by:

oidc.policy.port

Local port the authelia daemon listens on.

Type: signed integer

Default:

9091

Declared by:

oidc.request

Options a component sets to request this resource.

Type: submodule

oidc.request.enable

Whether to enable an OIDC single-sign-on client for the application.

Type: boolean

Default:

false

Example:

true

Declared by:

oidc.request.accessTokenClaims

Claims that the provider also puts in the JWT access token of this client, such as groups. Each claim needs a scope in scopes that grants it. For a consumer that reads the claims of the access token, e.g. the authorization scriptlet of the incus daemon. Needs jwtAccessTokens.

Type: list of string

Default:

[ ]

Declared by:

oidc.request.allowedGroups

Directory groups whose members can log in to this client. Membership of one group is sufficient. An empty list sets no group limit, and each user of the directory can log in.

Set this for a client that gives access to all users that complete the login and has no group check of its own. authorizationPolicy must be one_factor or two_factor when this list is not empty.

Type: list of string

Default:

[ ]

Declared by:

oidc.request.authorizationPolicy

Authelia authorization policy applied to the client (one_factor, two_factor, or a named policy).

one_factor is the default, matching authelia’s access_control.default_policy and the forward-auth default: a directory account alone opens the client. A client that demands MFA sets two_factor itself, and then every user of that client has to enroll a second factor before the authorize request succeeds – authelia refuses it with “The resource you’re attempting to access requires two-factor authentication”.

Type: string

Default:

"one_factor"

Declared by:

oidc.request.clientID

Client ID to register with the OIDC provider. Null for a forwardAuth request (which registers no OIDC client – authelia protects the vhost through nginx auth_request, not a redirect flow).

Type: null or string

Default:

null

Declared by:

oidc.request.forwardAuth

Forward-auth protection for a plain (non-OIDC) admin UI vhost, instead of an OIDC client. A request is EITHER an OIDC client (clientID/redirectURI set) OR a forward-auth protection (forwardAuth.enable). When set, nginx fronts upstream with an auth_request to authelia’s /api/authz/auth-request endpoint, so only authenticated sessions reach the UI – used for services with no native OIDC (garage-webui, pgweb, redis-commander, windmill).

Type: null or (submodule)

Default:

null

Declared by:

oidc.request.forwardAuth.enable

Whether to enable forward-auth protection for this UI vhost.

Type: boolean

Default:

false

Example:

true

Declared by:

oidc.request.forwardAuth.adminUI

Whether this vhost belongs to the hosting provider’s admin-UI category, which deployAdminUIs gates as a whole.

True for a prod convenience fronting a backend with no native OIDC (garage-webui, pgweb, redis-commander). Set false for a UI that is part of the service it fronts and that every group running that service needs – lldap’s own web UI, which is the directory’s self-service surface.

Type: boolean

Default:

true

Declared by:

oidc.request.forwardAuth.allowedGroups

Restrict access to members of these authelia groups. Empty (the default) allows any authenticated user (subject to authorizationPolicy).

Type: list of string

Default:

[ ]

Declared by:

oidc.request.forwardAuth.authorizationPolicy

Authelia authorization policy for this domain (one_factor suffices for any authenticated user; two_factor for MFA).

Type: string

Default:

"one_factor"

Declared by:

oidc.request.forwardAuth.domain

Public domain the protected UI is served under, stated in full (e.g. garage-webui.example.org). For a UI published in a zone this deployment does not own; subdomain below is what names one under the deployment’s own domain.

Exactly one of the two is set – see ./forward-auth-domain.nix, which resolves them.

Type: null or string

Default:

null

Declared by:

oidc.request.forwardAuth.proxyWebsockets

Pass websocket upgrades through to the protected UI. Needed by a UI that streams over a websocket – the windmill UI’s job logs – and off by default, since the plain admin UIs (garage-webui, pgweb, redis-commander, lldap) do not.

Type: boolean

Default:

false

Declared by:

oidc.request.forwardAuth.subdomain

The label the protected UI is served under, hung off the deployment’s own deploymentDomain.

What a component SHARED between groups states: a tenant deployment’s domain is the tenant’s own name, and a shared component that instead reaches for the static apex (core/apex.nix) names the hosting provider’s node from inside the tenant’s deployment – a name the tenant may neither publish nor hold a certificate for.

Type: null or string

Default:

null

Declared by:

oidc.request.forwardAuth.unauthenticatedLocations

nginx location keys that the gate proxies to upstream without auth_request, each with extra nginx config for that location (usually empty). For a flow of the protected UI that a user must reach without a session, such as the password reset of lldap. On these locations the gate sends empty Remote-* headers, so a client cannot set them.

Keep each key as narrow as the flow. An exempt prefix that also covers a login of the UI lets a user log in without the portal.

Type: attribute set of strings concatenated with “\n”

Default:

{ }

Example:

{
  "^~ /reset-password/" = "";
}

Declared by:

oidc.request.forwardAuth.upstream

Proxy target nginx forwards to once authenticated (e.g. 127.0.0.1:3909).

Type: string

Declared by:

oidc.request.forwardAuth.upstreamAuthorizationInclude

nginx include pattern pulled into location /, for a protected UI that cannot log a user in itself: one who has just cleared the portal would otherwise be met by a second, unrelated login form. A unit on the node drops a proxy_set_header Authorization ...; line matching this pattern once it holds a token to present, and the gate then presents it on that user’s behalf.

A glob rather than a fixed path, because nginx has to start before that unit has ever run: an include of a missing file refuses to load, while one whose mask matches nothing is not an error.

Null (the default) leaves the gate a plain proxy, which is what a UI that authenticates the forwarded Remote-User itself wants.

Type: null or string

Default:

null

Declared by:

oidc.request.jwtAccessTokens

Issue RFC 9068 JWT-profile access tokens to this client (access_token_signed_response_alg: RS256) instead of authelia’s default opaque tokens. For consumers that verify the bearer access token locally as a JWT against the issuer’s JWKS, e.g. the incus daemon, which parses every bearer token and rejects an opaque one with “token contains an invalid number of segments”.

Type: boolean

Default:

false

Declared by:

oidc.request.public

Register the client as a public OIDC client (no client secret; token_endpoint_auth_method: none, PKCE enforced). For consumers that cannot hold a secret, e.g. the incus UI, whose token exchange sends no client authentication.

Type: boolean

Default:

false

Declared by:

oidc.request.redirectURI

Primary callback URI the provider should redirect to after authentication. This is the value carried by the nixpkgs oidc contract request; additional URIs go in redirectURIs. Null for a forwardAuth request (no redirect flow).

Type: null or string

Default:

null

Declared by:

oidc.request.redirectURIs

Extra callback URIs to register on the OIDC client, beyond redirectURI. Authelia registers the union of redirectURI and these. The contract request carries them, so the client reads them from contracts.oidc.results.<clientID>.default.

Type: list of string

Default:

[ ]

Declared by:

oidc.request.scopes

OIDC scopes the client may request. Defaults to the standard OpenID Connect profile scopes. The contract request carries them, so the client reads them from contracts.oidc.results.<clientID>.default and requests the scopes that authelia lets it request.

Type: list of string

Default:

[
  "openid"
  "profile"
  "email"
]

Declared by:

otel

OpenTelemetry collector endpoint for emitting telemetry signals from an application. The policy carries the collector’s OTLP endpoint. When an application on a node requests the resource, the resource sets fediversity.otel.endpoints.<consumerName> on that node, and the OTel module of the application renders its per-service settings from it.

Type: submodule

otel.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

otel.policy.endpoint

OTLP HTTP endpoint of the fediversity collector (e.g. http://otel-collector.vpn:4318). When null, per-app OTel wiring stays disabled even if applications request the resource.

Type: null or string

Default:

null

Example:

"http://otel-collector.vpn:4318"

Declared by:

otel.request

Options a component sets to request this resource.

Type: submodule

otel.request.enable

Whether to enable emitting OpenTelemetry signals from this application to the configured collector.

Type: boolean

Default:

false

Example:

true

Declared by:

otel.request.consumerName

The name of the application under fediversity.otel.endpoints, where the OTel module of the application reads the endpoint.

Type: string

Example:

"windmill"

Declared by:

pod-roles

Roles of a node, each with pods of its own when the node runs as a pod.

Type: submodule

pod-roles.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

pod-roles.request

Options a component sets to request this resource.

Type: submodule

pod-roles.request.roles

The roles of the node. The name of a role is a part of the name of its Deployment.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

pod-roles.request.roles.<name>.companion

Whether each pod of the role has a second container, from the image of the service, for work that connects to the service on the loopback address of the pod. The node module of the application gives the program in fediversity.podRoles.services.

Type: boolean

Default:

false

Declared by:

pod-roles.request.roles.<name>.metrics

The port at which each pod of a role serves its Prometheus metrics, on the address of the pod. The deploy makes a headless Service <node>-<role>-metrics for the port, and lets only the main pod of the node reach it. The metrics shipper of the main pod scrapes each pod of the role and pushes the series with the series of the node. null when the pods of the role have no metrics to scrape.

Type: null or (submodule)

Default:

null

Declared by:

pod-roles.request.roles.<name>.metrics.path

The HTTP path of the metrics.

Type: string

Default:

"/metrics"

Declared by:

pod-roles.request.roles.<name>.metrics.port

The TCP port of the metrics.

Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Declared by:

pod-roles.request.roles.<name>.ports

The ports at which the pods of a role receive connections. The deploy makes a Service <node>-<role> in the cluster for them, and a NetworkPolicy that lets in the sources that reach the ports that the node serves. With no ports, the pods of the role receive no connections.

Type: list of (submodule)

Default:

[ ]

Declared by:

pod-roles.request.roles.<name>.ports.*.port

The number of the port.

Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Declared by:

pod-roles.request.roles.<name>.ports.*.protocol

The protocol of the port.

Type: one of “TCP”, “UDP”

Default:

"TCP"

Declared by:

pod-roles.request.roles.<name>.replicas

The number of pods of the role.

Type: positive integer, meaning >0

Default:

1

Declared by:

pod-roles.request.roles.<name>.secrets

The secrets that the pods of a role with a service read, by the name of the file in /run/secrets. A sidecar of the pod gets them from OpenBao, and writes each secret again when its value changes. The service restarts only for a secret with restart.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.encoding

The encoding of the field in OpenBao. The file holds the decoded value. A file of generateFiles of the vars-openbao contract has the field content with the encoding base64.

Type: one of “none”, “base64”

Default:

"none"

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.field

The field of the secret that the file holds.

Type: string

Default:

"value"

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.parameters

The parameters of the request to OpenBao, for an engine that issues a value at each read, such as PKI. The response of such an engine has no KV fields, thus a secret with parameters needs a template.

Type: attribute set of string

Default:

{ }

Example:

{
  common_name = "web-lldap.example.svc.cluster.local";
}

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.path

The full API path of the secret in OpenBao. A path in the KV v2 engine has /data/. A path in a different engine needs a template.

Type: string

Example:

"secret/data/vars/secret/web/hello"

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.restart

Restart the container of the service when the sidecar writes a new value of this file. Use it when the service reads the file only when it starts. The pod then shares its process namespace, and its sidecar has the capability KILL.

Type: boolean

Default:

false

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.template

If not null, the file holds this text, which is a template of the agent of OpenBao. The template reads the response of OpenBao as .Data. Use it for a secret that is not in the KV v2 engine. field, encoding and variable do not apply to a secret with a template.

Type: null or string

Default:

null

Example:

"{{ .Data.password }}"

Declared by:

pod-roles.request.roles.<name>.secrets.<name>.variable

If not null, the field is a line <variable>=<value> of an environment file, and the file holds only <value>, with no line end. This lets a service that reads the bare value use a secret that is stored for an EnvironmentFile, thus OpenBao has one copy of the value.

Type: null or string matching the pattern [A-Za-z_][A-Za-z0-9_]*

Default:

null

Declared by:

pod-roles.request.roles.<name>.service

The name of the modular service whose image the pods of the role run. null when they run the image of the node.

Type: null or string

Default:

null

Declared by:

postgresql

PostgreSQL database access for an application, fulfilled by a postgres-backed provider of the postgresql contract on a dedicated database node.

Type: submodule

postgresql.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

postgresql.policy.enable

Whether to enable PostgreSQL database provider.

Type: boolean

Default:

false

Example:

true

Declared by:

postgresql.policy.crossNode

Cross-node factory: { consumer, database, username } -> { result; bridge; ... }, the crossNodeWant answer. result holds host, port, database, username, passwordFile, urlFile and sslMode; bridge gives the result to the postgresql contract of the consumer node.

Type: unspecified value (read only)

Declared by:

postgresql.policy.domain

Service domain the database node is named under (drives its TLS cert).

Type: string

Example:

"postgresql.example.org"

Declared by:

postgresql.policy.host

Reachable address consumers connect to the postgres daemon at: the postgres node’s deployment FQDN, supplied by the setup. Defaults to the domain for the single-node behaviorTest.

Type: string

Default:

config.domain

Declared by:

postgresql.policy.namespacePrefix

Operator id prefixed onto every request’s database and username, so the created database, the OpenBao static/dynamic role, and the credential the consumer reads are all keyed on <namespacePrefix>-<app>. Empty is identity.

Type: string

Default:

""

Declared by:

postgresql.policy.namespaceSuffix

Deployment namespace appended to every request’s database and username, so one group deployed per tenant gets a database, a role and a rotated credential of its own per deployment rather than one shared set keyed on the group alone.

NOT appended to consumerName: that stays the app node name the per-host grant keys on, which the deployment namespace already scopes. The on-disk /run/vars/secret/<username>-db paths key on the namespaced username, so they carry the suffix.

Empty is identity, which is what a group deployed once (the hosting provider) wants.

Type: string

Default:

""

Declared by:

postgresql.policy.port

TCP port the postgres daemon listens on.

Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

5432

Declared by:

postgresql.policy.provisioning

Who creates the database and the login role behind every request:

  • in-nix (default): the postgres node itself, with its OpenTofu reconciler postgresql-provision, which also drops what leaves its config. Right for a group deployed once, whose set of databases is fixed by its own configuration.

  • tf: the consuming group’s OpenTofu graph, which creates the database, the role and the engine’s static role on apply and DROPs all three on destroy. Required wherever namespaceSuffix is set: the postgres node reconciles only when its own group deploys, so a per-deployment database created that way outlives the deployment that asked for it, and the next deployment of the same group inherits its rows.

Under tf this resource emits no providerSide module, so the postgres node’s declarative convergence leaves those names alone, and the roster entry is marked so the OpenBao engine states the SHAPE of the role name (<stem>-*) rather than the name.

Type: one of “in-nix”, “tf”

Default:

"in-nix"

Declared by:

postgresql.policy.sslMode

libpq sslmode consumers use (the contract result.sslMode).

Type: string

Default:

"require"

Declared by:

postgresql.request

Options a component sets to request this resource.

Type: submodule

postgresql.request.enable

Whether to enable PostgreSQL database access for the application.

Type: boolean

Default:

false

Example:

true

Declared by:

postgresql.request.bootstrapSQL

Application-specific SQL run as the postgres superuser against this app’s database, after it is created and before any consumer connects. For a one-time statement that groupRoles cannot declare, for example a REASSIGN OWNED. A grant or a setting must not go here: when the statement leaves the code, nothing reverses it. Has to be idempotent, being re-applied every deploy. @username@ and @database@ are replaced by the request’s username and database as created, namespacePrefix included; quote them as identifiers, since a prefixed name carries a hyphen.

Type: strings concatenated with “\n”

Default:

""

Declared by:

postgresql.request.consumerName

Unique consumer name for this app (analog of oidc’s clientID), used as the contracts.postgresql.want.<consumerName> key.

Type: string

Declared by:

postgresql.request.credentialMode

How the central OpenBao database engine issues this consumer’s credential:

  • rotating (default): a fixed-name database/static-roles/<role> whose password the engine rotates on rotation_period; the consumer reads database/static-creds/<role> and the login name never changes. Required wherever a consumer pins a fixed database user.

  • dynamic: a database/roles/<role> minting a short-lived role with a random per-lease username on each read of database/creds/<role>. Usable by a consumer that takes the full DSN (result.urlFile) and pins no user, buying per-lease revocation at the cost of a stable username.

A consumer that puts a fixed user in its configuration uses rotating: a dynamic lease changes the username, and dynamic renders no passwordFile.

Type: one of “rotating”, “dynamic”

Default:

"rotating"

Declared by:

postgresql.request.database

PostgreSQL database the application requires; the provider creates it. Carried verbatim by the postgresql contract request.

Type: string

Declared by:

postgresql.request.dynamicOwnerRole

Persistent NOLOGIN role owning every object this app creates under dynamic credentials. Each lease role is granted membership in it and gets ALTER ROLE ... SET role = '<owner>', so objects land on the persistent role rather than the random per-lease role, whose objects become unreachable once revoked. The provider node creates the role with postgresql-provision and re-owns stranded objects to it. Empty derives <username>_owner. Must not name a role of groupRoles. Ignored for rotating consumers.

Type: string

Default:

""

Declared by:

postgresql.request.dynamicRoleGrants

Existing roles each freshly-minted dynamic lease role is granted membership in, folded into the engine’s dynamic creation_statements as GRANT <role> TO "{{name}}". A dynamic consumer connects as a random per-lease role, so any group role it expects to SET ROLE to has to be granted per lease rather than once to a fixed user. The roles have to exist when a lease is minted, or role creation fails. The groups of groupRoles with grantToUser are added. Ignored for rotating consumers.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.gateUnits

Units that must not start until the openbao agent has rendered this consumer’s credential. Each named unit gets an ExecStartPre probe (./wait-password-file.sh), which spends a short budget inside that unit’s own start timeout.

Ordering after openbao-agent-default.service does not give this. The agent goes active carrying a template it could not render, and a unit that cats the credential then fails on a file that does not exist. Where that unit is a Type = "oneshot" upstream gives no Restart=, systemd drops the start job of every unit that Requires= it and re-queues none of them when the render lands, which leaves the application behind an nginx 502 for the life of the deployment.

The gate reads whether the file has contents, so a render from an earlier generation satisfies it. It covers the FIRST render; reloadUnits is what carries a consumer over a rotation.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.groupRoles

NOLOGIN group roles that the postgres node creates with postgresql-provision, keyed by role name. Some upstream service modules ship migrations that assume group roles which their NixOS module creates only under a createLocally = true path, and a remote consumer disables that path. The name gets no operator id, so the role is shared by all requests that declare it, and these requests must declare the same attributes for it. A role that leaves all requests is dropped.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

postgresql.request.groupRoles.<name>.bypassRowLevelSecurity

Whether the role bypasses row-level security (BYPASSRLS).

Type: boolean

Default:

false

Declared by:

postgresql.request.groupRoles.<name>.databasePrivileges

Privileges of this role on this request’s database, for example CONNECT, CREATE and TEMPORARY. The list is complete: other privileges of the role on the database are revoked.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.groupRoles.<name>.grantToUser

Whether the request’s login role is a member of this role. For a dynamic consumer, each lease role is a member, as with dynamicRoleGrants.

Type: boolean

Default:

false

Declared by:

postgresql.request.groupRoles.<name>.memberOf

Roles that this role is a member of.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.groupRoles.<name>.sequencePrivileges

Privileges of this role on the sequences in schema public of this request’s database, as tablePrivileges states them for tables.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.groupRoles.<name>.tablePrivileges

Privileges of this role on the tables in schema public of this request’s database, for example SELECT or ALL. They cover all tables that exist when postgresql-provision runs, and the tables that the login role of this request makes later. The list is complete: other privileges of the role on the tables are revoked.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.passwordFileMode

Mode of the rendered passwordFile/urlFile. Defaults to 0640, root-owned and group-readable, which suits consumers reading it via systemd LoadCredential. Set 0400 with a non-root passwordFileOwner for an app reading the file as its own service user.

Type: string

Default:

"0640"

Declared by:

postgresql.request.passwordFileOwner

Owner of the rendered passwordFile/urlFile. Defaults to root; set to the app’s service user when that user cats the file directly. The agent gives the file to this owner on every render, and again before it starts, so a change to this value reaches a file the render leaves alone.

Type: string

Default:

"root"

Declared by:

postgresql.request.reloadUnits

Systemd units the openbao agent restarts after it re-renders this consumer’s credential. Every consumer reads the credential once at process start, so one holding the previous credential starts failing with password authentication failed.

List the app service plus any unit baking the credential into a derived file the app reads; they are restarted in one systemd transaction, which honors their own After=. Empty means the consumer does not converge on rotation, correct only where it reads the credential afresh on each use.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.urlEnvironment

Give the DSN to units as the environment variable variable, for an application that reads its database from the environment and not from a file. A oneshot <app>-db-env writes <variable>=<DSN> to an EnvironmentFile of each unit in units, and each unit starts after it. On each re-render of the DSN, the openbao agent restarts the oneshot and units, so do not put them in reloadUnits.

The oneshot retries until the DSN is rendered. On a fleet that is new all at once, the agent cannot render the DSN until the central database engine is configured, which can be many minutes after the first start, and the restart on a re-render cannot help before the first render.

systemd reads an EnvironmentFile after Environment=, so the variable overrides a static value from the application’s module.

Type: null or (submodule)

Default:

null

Declared by:

postgresql.request.urlEnvironment.oneshotUnits

Systemd services, without the .service suffix, that also get the variable, but that a re-render of the DSN does not restart: oneshots that a timer starts, which read the file at each start. A restart would run them out of their schedule, and its reset-failed would clear a failure that they report. Each one requires the oneshot <app>-db-env, so it does not run without the variable.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.urlEnvironment.units

Systemd services, without the .service suffix, that get the variable.

Type: list of string

Default:

[ ]

Declared by:

postgresql.request.urlEnvironment.variable

Name of the environment variable that holds the DSN.

Type: string

Example:

"DATABASE_URL"

Declared by:

postgresql.request.username

PostgreSQL role the application connects as. Defaults to the consumerName. Carried by the postgresql contract request.

Type: string

Default:

config.consumerName

Declared by:

redis

Redis-compatible cache access for an application, fulfilled by an external provider (shared valkey node, OpenBao-brokered per-namespace credentials) of the nixpkgs redis contract.

Type: submodule

redis.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

redis.policy.enable

Whether to enable shared redis (valkey) cache provider.

Type: boolean

Default:

false

Example:

true

Declared by:

redis.policy.crossNode

Cross-node factory: { consumer, namespace } -> { result; bridge; ... }, the crossNodeWant answer. result holds host, namespace, port, username, passwordFile, tls and caFile; bridge gives the result to the redis contract of the consumer node.

Type: unspecified value (read only)

Declared by:

redis.policy.host

Address consumers connect to the group’s valkey node at: its deployment FQDN, supplied by core/setups/hosts-common.nix. Each namespace’s valkey@<namespace> instance lives on this node at its own namespace-derived port (./port.nix).

Type: string

Example:

"valkey.example.org"

Declared by:

redis.policy.namespacePrefix

Operator id prefixed onto every request’s namespace, so the physical instance – valkey@<namespace>, its namespace-derived port and its OpenBao static role – is keyed on <namespacePrefix>-<app> and two operators’ same-named apps stay distinct. Empty is identity. Supplied by the deployment (core/setups/hosts-common.nix), and must match the prefix the valkey node and OpenBao engine use, or a consumer’s static-role name matches no rotated instance.

Type: string

Default:

""

Declared by:

redis.policy.passwordDir

Directory the openbao-agent renders per-namespace rotated passwords into on each consumer node (one file per namespace). Off /nix/store (rotates; peertube asserts off-store).

Type: string

Default:

"/run/redis-external"

Declared by:

redis.policy.roleSuffix

Deployment namespace put after every request’s namespace in the name of its OpenBao static role, as <namespace>-<roleSuffix>. The instance, its port and its KV entries keep <namespace>. Empty is identity. Supplied by the deployment (core/setups/hosts-common.nix), and must match the role name the OpenBao engine uses for the deployment, or the consumer reads a role that does not exist.

Type: string

Default:

""

Declared by:

redis.policy.tls.enable

Whether the group’s valkey node serves each namespace with TLS only, and each consumer connects with TLS (rediss://). The deployment sets it to whether its PKI plan holds the valkey host (core/setups/hosts-common.nix). The server gate and every consumer read this one value, so they agree. False means plain text (redis://) and no CA.

Type: boolean

Default:

false

Declared by:

redis.policy.tls.caFile

CA bundle on each consumer node that signs the certificate of the valkey node. Used only when enable is true.

Type: null or string

Default:

null

Declared by:

redis.request

Options a component sets to request this resource.

Type: submodule

redis.request.consumerName

Unique consumer name for this app (analog of ldap’s consumerName), used as the contracts.redis.want.<consumerName> key.

Type: string

Declared by:

redis.request.gateUnits

Units that must not start until this namespace’s valkey listener answers. When the result has tls, the listener must also complete a TLS handshake the consumer node can verify. Each named unit gets an ExecStartPre probe (./wait-reachable.sh), which spends a short budget inside that unit’s own start timeout.

For a database migration runner. A Laravel migration can change the schema and then dispatch a queued job, and MySQL does not roll back DDL, so a dispatch that fails on a TLS error leaves the schema changed and the ledger row unwritten. Each later run then stops on a duplicate column, a state no configuration repairs. The gate makes the unit fail before its first statement instead.

Type: list of string

Default:

[ ]

Declared by:

redis.request.namespace

The application’s logical cache identity. Selects the application’s own valkey@<namespace> instance – its own process and port on the group’s valkey node – so namespaces are isolated by process, not by an ACL key-scope. Also the name of the OpenBao static role that rotates the instance’s password, with roleSuffix after it. The multi-operator case folds the operator id in via namespacePrefix.

Type: string

Declared by:

secrets

Secrets provisioning for an application, via the fileSecrets contract.

Type: submodule

secrets.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

secrets.request

Options a component sets to request this resource.

Type: submodule

secrets.request.secrets

Secrets this application needs provisioned on its host, keyed by name. Each becomes a contracts.fileSecrets.want.<app>.<name> request, read back from config.contracts.fileSecrets.results.<app>.<name>.path.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

secrets.request.secrets.<name>.file

Encrypted .age source for the agenix provider. The consumer states it, because this resource holds the secrets of no deployment.

Type: absolute path

Declared by:

secrets.request.secrets.<name>.group

Linux group that must own the secret file. Defaults to the owner’s primary group, as agenix does.

Type: string

Default:

owner

Declared by:

secrets.request.secrets.<name>.mode

Mode the secret file must have.

Type: string

Default:

"0400"

Declared by:

secrets.request.secrets.<name>.owner

Linux user that must own the secret file.

Type: string

Declared by:

smtp

Outbound SMTP mail submission for an application, fulfilled by a maddy-backed (or external) provider of the nixpkgs smtp contract.

Type: submodule

smtp.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

smtp.policy.enable

Whether to enable outbound SMTP mail submission provider.

Type: boolean

Default:

false

Example:

true

Declared by:

smtp.policy.domain

Mail domain the relay serves (maddy hostname/primaryDomain and the TLS domain).

Type: string

Example:

"mail.example.org"

Declared by:

smtp.policy.instanceName

Stem the relay’s generateFiles/vars instances – and so their central OpenBao KV paths – are named from. The setup group-qualifies it outside the hosting provider, so two groups’ relays neither overwrite each other’s per-consumer submission passwords nor share a DKIM signing key on the one shared KV.

Provider-agnostic in name only: the external-relay provider generates nothing and ignores it.

Type: string

Default:

"smtp"

Declared by:

smtp.policy.port

Submission port the relay listens on.

Type: signed integer

Default:

587

Declared by:

smtp.request

Options a component sets to request this resource.

Type: submodule

smtp.request.enable

Whether to enable outbound SMTP mail submission for the application.

Type: boolean

Default:

false

Example:

true

Declared by:

smtp.request.sender

Email address used as the sender (From header), carried verbatim by the nixpkgs smtp contract request.

Type: string

Example:

"noreply@example.com"

Declared by:

spiffe

SPIFFE workload identity (spire-agent) provisioning for an application’s host.

Type: submodule

spiffe.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

spiffe.request

Options a component sets to request this resource.

Type: submodule

spiffe.request.enable

Whether to enable a spire-agent on this host for SPIFFE workload identity.

Type: boolean

Default:

false

Example:

true

Declared by:

ssl

TLS certificate provisioning for an application’s vhosts.

Type: submodule

ssl.policy

Options a hosting provider sets in an environment to supply this resource. The options that all policies share are on the data model page.

Type: submodule

ssl.policy.crossNode

Cross-node trust factory: { consumer, domains } -> { results; bakeableDomains; pkiCaCertFiles; } from one eval.

Type: unspecified value (read only)

Declared by:

ssl.request

Options a component sets to request this resource.

Type: submodule

ssl.request.domains

Domains this application terminates TLS for (its own vhosts).

Type: list of string

Default:

[ ]

Declared by:

ssl.request.trustDomains

Domains served by another node this node must trust as a client (e.g. the garage bucket endpoints). Drives cross-node trust baking.

Type: list of string

Default:

[ ]

Declared by: