Contract reference

This page is the reference of each contract type: what the contract is for, how to use it, the providers that this repository has for it, and the options of its request and its result. The Contracts (RFC 189) page tells what a contract is and how contracts relate to hosting resources.

How to read a section

Each section has the same parts:

  • The description of the contract type, and the file that defines it.
  • The providers in this repository, with the name that selects each provider.
  • An example of a consumer.
  • The options. A consumer sets the request options. A provider sets the result options, and the consumer reads them.

The common pattern

All contract types have the same option tree, config.contracts.<type>.

A consumer declares a request below want, and reads the answer below results. The path below want is free: most consumers use want.<consumer>.<instance>. The value at the end of the path has the request. The results tree has the same paths, and each value has the request options and the result options together.

{ config, ... }:
{
  contracts.smtp.want.myapp.default.request.sender = "myapp@example.org";

  services.myapp.mail = {
    inherit (config.contracts.smtp.results.myapp.default) host port username;
  };
}

A provider declares an option of the type mkProviderType, and registers the option below providers.<name>. The fulfill function calculates the result from the request. The default of the option is providerRequests.<name>, the requests that the provider must answer, so a provider that answers no request does nothing.

{
  config,
  lib,
  options,
  ...
}:
{
  options.services.myrelay.smtp = lib.mkOption {
    default = config.contracts.smtp.providerRequests.myrelay;
    type = config.contracts.smtp.mkProviderType {
      fulfill = request: {
        host = "relay.example.org";
        port = 587;
        username = request.sender;
        passwordFile = "/run/secrets/relay-password";
      };
    };
  };

  config.contracts.smtp.providers.myrelay.module = options.services.myrelay.smtp;
}

One provider answers each request. Select the provider with one of these options:

  • contracts.<type>.defaultProviderName = "<name>"; selects the provider for all requests of the type, by its name.
  • contracts.<type>.defaultProvider = config.contracts.<type>.providers.<name>; does the same with a reference to the provider.
  • contracts.<type>.instances.<consumer>.<instance> = config.contracts.<type>.providers.<name>; selects the provider for one request.
  • contracts.<type>.instanceProviderNames.<consumer>.<instance> = "<name>"; selects the provider for one request, by its name.

If a consumer reads a result and no provider is selected, the evaluation fails.

A result that is a path, for example passwordFile, is the path of a file on the target machine. The file does not exist at evaluation time, and its contents do not go into the Nix store.

In a Fediversity deployment, a component usually requests a hosting resource, and the resource declares the contract requests and selects the providers. Use a contract directly when you write a resource, a provider, or a NixOS module of a component.

credentials

Contract for delivering a secret to a systemd unit as a systemd credential. A consumer names the unit, the credential id that unit loads it under, and where the value comes from; a provider makes that id resolvable and returns the LoadCredential= entry to attach.

Unlike fileSecrets (a file at a path) and envSecrets (an environment file bound to a unit), nothing is rendered onto the node: the value exists only inside the unit's credentials directory, for as long as the unit runs. That also rules out EnvironmentFile=, which systemd loads before it materializes %d -- a consumer that needs the value in its environment has to read it out of the credentials directory itself.

A request the provider cannot resolve is NOT a failed start. systemd reads the source until EOF and takes whatever arrived as the value, so a provider that refuses or errors hands the unit an EMPTY credential and the unit starts normally. Restart= therefore buys a consumer nothing on its own: it has to reject an empty value itself, and can then rely on the restart to converge over the window in which the value exists nowhere yet -- a fresh deployment whose generating node has not published it.

Defined in nix/contracts/lib/contracts/definitions/credentials.nix.

Use credentials when a systemd unit reads a secret as a systemd credential. The value is only in the credentials directory of the unit, and is not in a file that other units can read.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • openbao: the option services.systemd-creds-openbao.contracts.credentials in nix/contracts/nixos/modules/services/security/creds-openbao.nix.

    credentials provider instances. Each request becomes one services.systemd-creds-openbao.rules entry, letting the requesting unit alone load the secret at path under the credential id it asked for. Requesting anything at all switches the daemon on.

  • hardcoded-credential (only for tests): the option testing.hardcoded-credential in nix/contracts/nixos/modules/testing/hardcoded-credential.nix.

    Hardcoded systemd credentials. These should only be used in tests.

    They let a test exercise a unit's LoadCredential= wiring without standing up the secret store a real provider reads from. The value is installed at runtime as a 0400 root file the credential is loaded from, rather than handed over as a store path, so what the unit sees matches what a real provider delivers.

Give the name of the unit with its suffix. Add the loadCredential result to the unit:

{ config, ... }:
{
  contracts.credentials.want.audit.ingest.request = {
    unit = "fediversity-audit-shipper.service";
    id = "auth-header";
    path = "vars/secret/audit-ingest/authHeader";
  };

  systemd.services.fediversity-audit-shipper.serviceConfig.LoadCredential = [
    config.contracts.credentials.results.audit.ingest.loadCredential
  ];
}

The unit reads the value from $CREDENTIALS_DIRECTORY/auth-header.

  • A request is for one unit. Declare a second request for a second unit.
  • When the provider cannot find the value, the credential is empty and the unit starts. The service must refuse an empty value.
  • A credential cannot be an EnvironmentFile. Use envSecrets for environment variables.

Options of credentials

credentials.request.base64Decode

Whether the field value is base64-encoded and must be decoded before being served.

Type: boolean

Default:

true

Declared by:

credentials.request.field

Field within the secret at path holding the value.

Type: string

Default:

"content"

Declared by:

credentials.request.id

Credential id the unit loads the value under, i.e. the <id> of LoadCredential=<id>:<source> and the name of the file the unit finds under $CREDENTIALS_DIRECTORY.

Type: string

Declared by:

credentials.request.kv

Whether path addresses a versioned key-value entry rather than a leased or engine-issued secret. Set false for an endpoint that mints or rotates its own value – e.g. a database secrets engine’s database/static-creds/<role>, whose current password is at field password and is not base64-wrapped.

Type: boolean

Default:

true

Declared by:

credentials.request.path

Provider-specific read path of the secret holding the value (e.g. a KV v2 read path for an OpenBao-backed provider). A provider that carries the value itself (a test stub) ignores it, as it does the other addressing options below.

Type: string

Default:

""

Declared by:

credentials.request.transient

Whether unit is created at run time by systemd-run --unit= rather than declared, as restic-run-<name>.service is (nix/contracts/nixos/modules/services/backup/restic.nix).

Such a unit can be given nothing on disk, and a provider must not try: systemd-run refuses a name that is already loaded or carries a fragment file. A fragment fails the first invocation; a <unit>.wants/ symlink is worse, pinning the unit object past its own exit so that --collect never reaps it and only the SECOND invocation fails. Either way the name is lost, which is the name the request exists to fix – the runner names its unit precisely so the serving rule can name one unit rather than every run-*.service.

A provider may therefore serve a transient unit but may hang no dependency on it, so whatever a provider does for an ordinary consumer to be reachable when it starts, a transient one goes without.

Type: boolean

Default:

false

Declared by:

credentials.request.unit

Systemd unit, with its suffix (authelia-main.service), that loads the credential. A provider may serve the value to that unit alone, so a second unit reading the same secret is a second request.

Type: string

Declared by:

credentials.result.loadCredential

The whole LoadCredential= entry, <id>:<source>, to add to the requesting unit’s serviceConfig.LoadCredential.

Type: string

Declared by:

credentials.result.source

The <source> of LoadCredential=<id>:<source>: the file or socket systemd reads the value from. Useful on its own where a consumer builds the entry itself, or maps several ids to one source.

Type: string

Declared by:

dns

Contract for authoritative-DNS coordinates, where a consumer requests publication under a domain and a provider returns the coordinates needed to reach the service holding that zone: the octoDNS provider name, its host/port (and scheme), and an optional path to an API credential. The credential itself is never carried -- only a path, so it stays out of the Nix store.

Defined in core/resources/dns/contract.nix.

Use dns to find the authoritative DNS service that publishes the records of a domain. The result has only coordinates, so the consumer evaluates the contract on its own node, and no shared evaluation is necessary.

These modules register a provider. The name of a provider is the value for defaultProviderName.

lib.contracts does not have this contract type. A module that uses it must import the definition:

{ config, ... }:
let
  dns = config.contracts.dns.results.windmill.default;
in
{
  imports = [ ../../../resources/dns/contract.nix ];

  contracts.dns.want.windmill.default.request.domain = "example.org";

  environment.etc."octodns/provider.json".text = builtins.toJSON {
    inherit (dns)
      provider
      host
      port
      scheme
      ;
  };
}

In a deployment, the dns resource declares the request, and core/setups/wiring/resource-routing.nix selects the provider with defaultProviderName.

apiTokenFile is the path of a file with the credential, and not the credential. The empty string means that there is no file: the deployer gets the credential by a different method.

Options of dns

dns.request.domain

The domain (zone) the consumer needs records published under.

Type: string

Example:

"example.tld"

Declared by:

dns.result.apiTokenFile

Path to a file containing the credential authenticating against the service, read on the deployer at apply time. Never the credential value: contents must not reach the Nix store. Empty means no file is supplied and the credential is resolved another way (for the self-hosted bind node, the out-of-band TSIG key file).

Type: string

Default:

""

Example:

"/run/secrets/powerdns-api-key"

Declared by:

dns.result.domain

The domain the provider settled on (the request’s, unless the provider overrides it).

Type: string

Declared by:

dns.result.host

Host the service’s management API (or, for bind, its RFC2136/AXFR listener) answers on.

Type: string

Example:

"pdns.example.org"

Declared by:

dns.result.port

Port that management endpoint listens on.

Type: signed integer

Example:

8081

Declared by:

dns.result.provider

Name of the octoDNS provider that speaks to this service, as keyed in the octodns-zone effect’s provider catalog (bind, powerdns, …).

Type: string

Example:

"powerdns"

Declared by:

dns.result.scheme

URL scheme the management API is reached over (http or https). Meaningless for providers whose transport is not HTTP (bind), which leave it at the default.

Type: string

Default:

"http"

Example:

"https"

Declared by:

envSecrets

Contract for delivering a set of secret values to a service as an environment file. A consumer requests one or more environment variables, each sourced from a secret at a given path, and a provider renders them into an EnvironmentFile attached to the consumer's systemd unit at runtime, reloading the unit when a value rotates.

Unlike fileSecrets (a single file at a path), envSecrets expresses a whole set of variables bound to one unit, which is what services taking an EnvironmentFile (Mastodon, PeerTube, ...) need.

Defined in nix/contracts/lib/contracts/definitions/env-secrets.nix.

Use envSecrets when a systemd unit reads secrets from environment variables. The provider writes an environment file and adds it to the unit as EnvironmentFile, so the consumer usually does not read the result.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • openbao-agent: the option services.secrets-openbao.envSecrets in nix/contracts/nixos/modules/services/security/secrets-openbao.nix.

    envSecrets provider instances. Each request delivers a set of environment variables to its unit via an agent-rendered EnvironmentFile, reloading the unit on rotation.

    Each optional variable is rendered into an EnvironmentFile of its own, attached to the same unit but outside its startup barrier, so a secret nobody ever publishes cannot withhold the required variables beside it, nor the other optional ones. result.environmentFile names the required file: it is the one whose arrival the unit is ordered behind.

Give the name of the unit without the .service suffix. Each variable names the location of its value at the provider:

{
  contracts.envSecrets.want.myapp.default.request = {
    unit = "myapp";
    variables.API_TOKEN.path = "kv/data/myapp/secret/api/token";
  };
}

If the consumer writes the unit itself, it can read the path of the file from config.contracts.envSecrets.results.myapp.default.environmentFile. The file exists only at run time.

Options of envSecrets

envSecrets.request.unit

Name of the systemd unit (without the .service suffix) the rendered environment file is attached to. The provider wires the unit to wait for the file and reload when any value rotates.

Type: string

Declared by:

envSecrets.request.variables

Environment variables to deliver, keyed by variable name. Each maps to a secret at the given path, reading the named field, optionally base64-decoding it.

A variable is required unless it says optional; see that option for what a provider owes the two kinds.

Type: attribute set of (submodule)

Declared by:

envSecrets.request.variables.<name>.base64Decode

Whether the field value is base64-encoded and must be decoded before being written into the environment file.

Type: boolean

Default:

true

Declared by:

envSecrets.request.variables.<name>.field

Field within the secret at path holding the value.

Type: string

Default:

"content"

Declared by:

envSecrets.request.variables.<name>.optional

Whether this variable may be absent: the secret at path need never be published, and the unit starts and runs without it.

A provider must deliver an optional variable such that its absence can neither delay nor block the required ones. Delivery mechanisms that resolve a whole set at once – an agent rendering one file from several reads, say – have to carry the optional variables separately, or one unpublished secret withholds every variable beside it.

Only the requester knows which of its variables it can run without, so this is stated here rather than inferred from whether a value happens to exist.

Type: boolean

Default:

false

Declared by:

envSecrets.request.variables.<name>.path

Provider-specific path to the secret holding this variable’s value (e.g. a KV v2 read path for an OpenBao-backed provider).

Type: string

Declared by:

envSecrets.result.environmentFile

Path to the rendered environment file. This path will exist after deploying to a target host; it is not available through the nix store.

Type: string

Declared by:

fileBackup

Contract for file backup where a directory containing regular files is to be backed up.

Defined in nix/contracts/lib/contracts/definitions/file-backup.nix.

Use fileBackup to make a backup of directories.

These modules register a provider. The name of a provider is the value for defaultProviderName.

The request names the directories and the user that reads them. The settings of the repository are options of the provider, at the same path as the request:

{ config, ... }:
{
  contracts.fileBackup = {
    want.netbox.uploads.request = {
      user = "netbox";
      sourceDirectories = [ "/var/lib/netbox/media" ];
    };
    defaultProvider = config.contracts.fileBackup.providers.restic;
  };

  services.restic.contracts.fileBackup.netbox.uploads = {
    repository = "s3:https://s3.example.org/netbox-backups";
    passwordFile = "/run/secrets/restic-password";
  };
}

The results are two scripts:

  • script has the commands backup, snapshots, restore <snapshot> and exec.
  • upgradeScript has the commands backup <version> and restore <version>, for a backup before an upgrade of the application. It runs as root. The restore command refuses a snapshot of a newer version unless RESTORE_IGNORE_VERSION=1 is set.

sourceDirectories must have one directory or more.

Options of fileBackup

fileBackup.request.excludePatterns

File patterns to exclude.

Type: list of string

Default:

[ ]

Declared by:

fileBackup.request.hooks

Hooks to run around the backup.

Type: submodule

Default:

{ }

Declared by:

fileBackup.request.hooks.afterBackup

Hooks to run after backup.

Type: list of absolute path

Default:

[ ]

Declared by:

fileBackup.request.hooks.beforeBackup

Hooks to run before backup.

Type: list of absolute path

Default:

[ ]

Declared by:

fileBackup.request.sourceDirectories

Directories to back up.

Type: non-empty (list of string)

Example:

[
  "/var/lib/vaultwarden"
]

Declared by:

fileBackup.request.user

Unix user doing the backups.

Type: string

Example:

"vaultwarden"

Declared by:

fileBackup.request.version

The version of the format of the files, for example the version of the application that writes them. The provider records it with each snapshot, and script restore <snap> refuses a snapshot with a newer version. null records no version.

Type: null or string

Default:

null

Example:

"9.20.0"

Declared by:

fileBackup.result.script

Script package exposing verbs:

<script> backup - take a backup

<script> snapshots - list snapshots, one id per line

<script> restore <snap> - restore named snapshot. It refuses a snapshot whose version is newer than request.version, except when RESTORE_IGNORE_VERSION=1 is set.

<script> exec <args...> - passthrough to underlying tool

Type: package

Declared by:

fileBackup.result.upgradeScript

Path to a script, to run as root, for the backup before an upgrade:

  • backup <version> takes a snapshot of sourceDirectories as an upgrade backup of the files of version <version>. The provider keeps the 2 newest upgrade backups, and the scheduled backups do not remove them.
  • restore <version> puts the newest upgrade backup of version <version> back. It deletes the files that the snapshot does not have, but it keeps the files that match excludePatterns.

Type: absolute path

Declared by:

fileSecrets

Contract for secrets handling where a consumer requests a secret and a provider provides it at runtime at a given file path.

Defined in nix/contracts/lib/contracts/definitions/file-secrets.nix.

Use fileSecrets when a service reads a secret from a file.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • agenix: the option fediversity.agenix.fileSecrets in core/resources/secrets/agenix.nix.

    Age-backed fileSecrets provider: decrypts .age files at activation via agenix.

  • openbao-agent: the option services.secrets-openbao.fileSecrets in nix/contracts/nixos/modules/services/security/secrets-openbao.nix.

    fileSecrets provider instances. Each request composes the configured lines into a single environment-style secret file rendered by the agent, returning its path. The file is delivered to unit as an EnvironmentFile so this module handles ordering and reload; consumers that read the file directly point at result.path.

  • vars: the option services.vars in nix/contracts/nixos/modules/services/security/vars.nix.

    Declarative file generation provider (vars).

    Reads generateFiles contract requests and generates files at boot using the configured varsBackend for storage.

  • hardcoded-secret (only for tests): the option testing.hardcoded-secret in nix/contracts/nixos/modules/testing/hardcoded-secret.nix.

    Hardcoded file secrets. These should only be used in tests.

    They aim to replace the usage of pkgs.writeText in NixOS VM tests as those make the file world readable while this module set runtime permissions on the file. This makes the tests more accurate, ensuring the permissions set by the contract consumer are correct.

The request tells who can read the file. The result is the path of the file:

{ config, ... }:
{
  contracts.fileSecrets.want.forgejo.database-password.request = {
    owner = "forgejo";
    group = "forgejo";
    mode = "0400";
  };

  services.forgejo.database.passwordFile =
    config.contracts.fileSecrets.results.forgejo.database-password.path;
}

The secrets resource selects a provider for each of its requests with instances, and sets no defaultProvider. Thus other consumers on the node can select their own provider.

Options of fileSecrets

fileSecrets.request.group

Linux group that must own the secret file.

Type: string

Declared by:

fileSecrets.request.mode

Mode the secret file must have.

Type: string

Default:

"0400"

Declared by:

fileSecrets.request.owner

Linux user that must own the secret file.

Type: string

Declared by:

fileSecrets.result.path

Path to the file containing the secret generated out of band.

This path will exist after deploying to a target host, it is not available through the nix store.

Type: string

Declared by:

generateFiles

Contract for declarative file generation where a consumer provides a script that produces named files and a provider handles generation, storage, and provisioning with the requested permissions.

Defined in nix/contracts/lib/contracts/definitions/generate-files.nix.

Use generateFiles when a machine must generate files one time and keep them, for example a random key.

These modules register a provider. The name of a provider is the value for defaultProviderName.

The path of a request has one level, want.<instance>. The script writes each declared file to $out/<file>:

{ config, pkgs, ... }:
{
  contracts.generateFiles.want.netbox.request = {
    files.secretKeyFile = {
      owner = "netbox";
      group = "netbox";
    };
    runtimeInputs = [ pkgs.openssl ];
    script = ''
      openssl rand -hex 64 > "$out"/secretKeyFile
    '';
  };

  services.netbox.secretKeyFile =
    config.contracts.generateFiles.results.netbox.files.secretKeyFile.path;
}
  • A request can name other requests in dependencies. The script reads their files from $in/<dependency>/<file>.
  • The provider does not run the script again when all files exist. When only some of the files exist, the provider refuses to run. Set independent = true when each file is independent of the others: the provider then runs the script, and keeps only the files that did not exist.
  • An item of runtimeInputs can be a string, which is an attribute path in the package set of the provider. Use this when the request must be plain data.

Options of generateFiles

generateFiles.request.dependencies

Names of other generateFiles instances whose outputs are made available under $in/<name>/ when the script runs.

Type: list of string

Default:

[ ]

Declared by:

generateFiles.request.files

Files to generate. Keys are file names the script must produce under $out/.

Type: attribute set of (submodule)

Declared by:

generateFiles.request.files.<name>.group

Unix group that must own the generated file.

Type: string

Default:

"root"

Declared by:

generateFiles.request.files.<name>.mode

File permissions as an octal string.

Type: string

Default:

"0400"

Declared by:

generateFiles.request.files.<name>.owner

Unix user that must own the generated file.

Type: string

Default:

"root"

Declared by:

generateFiles.request.files.<name>.secret

Whether the file contains sensitive data.

Type: boolean

Default:

true

Declared by:

generateFiles.request.independent

Whether the requested files are independent of one another.

By default an instance is ATOMIC: the provider refuses to run when some of its files exist and others do not, because a generator whose outputs are correlated (the halves of a keypair, a secret and its digest) cannot regenerate one without invalidating the rest. That refusal is also what makes adding a file to an already-deployed instance a hard failure with no recovery short of hand-placing the missing file on every node.

Set this when each file stands on its own – one independent random password per consuming app, say. The provider then still runs the whole script, but keeps only the files that were missing and discards regenerated copies of the ones already present, so an instance can grow a file without rotating its siblings. Generators need no change: they always produce their full set.

Type: boolean

Default:

false

Declared by:

generateFiles.request.runtimeInputs

Packages available in PATH during script execution. A string is the attribute path of a package in the pkgs of the provider, such as "openssl" or "python3Packages.pip". A request that is data, such as one in the plan of a node, uses strings.

Type: list of (string or package)

Default:

[ ]

Declared by:

generateFiles.request.script

Shell script that generates the requested files. The script must write each declared file to $out/<name>. Dependency outputs (from dependencies) are available under $in/<dep>/.

Type: string

Declared by:

generateFiles.result.files

Generated files with their provisioned paths.

Type: attribute set of (submodule)

Declared by:

generateFiles.result.files.<name>.path

Path to the generated file on the target system.

Type: string

Declared by:

ldap

Contract for LDAP integration where a consumer requests access to a directory group and a provider supplies connection details.

Defined in nix/contracts/lib/contracts/definitions/ldap.nix.

Use ldap when an application reads its users from a directory.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • lldap: the option fediversity.lldap in core/resources/ldap/lldap-provider.nix.

    The lldap-backed provider of the nixpkgs ldap contract on this node: its contract instances (ldap) plus the deployment parameters shared across all requests.

  • openldap (only for tests): the option testing.openldap in nix/contracts/nixos/modules/testing/openldap.nix.

    openldap reference provider for the ldap contract, for testing. Runs openldap with a minimal directory containing requested groups.

The request names the group whose members can use the application. The provider makes the group:

{ config, ... }:
let
  ldap = config.contracts.ldap.results.forgejo.default;
in
{
  contracts.ldap.want.forgejo.default.request.group = "forgejo-users";

  services.myapp.ldap = {
    inherit (ldap) url bindDN userBaseDN;
    bindPasswordFile = ldap.bindPasswordFile;
    userFilter = "(memberOf=cn=${ldap.group},${ldap.groupBaseDN})";
    caFile = ldap.caFile;
  };
}

caFile is null when the server has no TLS, or when its certificate is from a public certificate authority.

The directory is usually on a different node. The ldap resource then evaluates the contract one time with crossNodeWant, and gives the result to the node of the consumer. See contracts across nodes.

Options of ldap

ldap.request.group

LDAP group that the service requires. The provider is expected to create this group.

Type: string

Example:

"myapp-users"

Declared by:

ldap.result.baseDN

Base distinguished name for LDAP queries.

Type: string

Example:

"dc=example,dc=com"

Declared by:

ldap.result.bindDN

DN used to bind (authenticate) to the LDAP server.

Type: string

Declared by:

ldap.result.bindPasswordFile

Path to a file containing the bind password.

Type: string

Declared by:

ldap.result.caFile

Path to a PEM file with the CA certificate that signs the server certificate. null when url is a plaintext ldap:// URL, or when the system trust store already trusts the server certificate.

Type: null or string

Default:

null

Declared by:

ldap.result.groupBaseDN

Base DN under which group entries reside.

Type: string

Example:

"ou=groups,dc=example,dc=com"

Declared by:

ldap.result.host

Hostname of the LDAP server.

Type: string

Declared by:

ldap.result.port

Port of the LDAP server.

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

Declared by:

ldap.result.url

URL of the LDAP server, with the scheme. A consumer connects to this URL. With ldaps://, the consumer verifies the server certificate against caFile.

Type: string

Example:

"ldaps://ldap.example.com:6360"

Declared by:

ldap.result.userBaseDN

Base DN under which user entries reside.

Type: string

Example:

"ou=people,dc=example,dc=com"

Declared by:

netbox

Contract for NetBox IPAM coordinates, where a consumer requests allocation context (the cluster its virtual machines are recorded under and the prefix pool its addresses come from) and a provider returns the coordinates needed to reach a NetBox instance: its URL and an optional path to an API token. The token itself is never carried -- only a path, so credentials stay out of the Nix store.

Defined in core/resources/netbox/contract.nix.

Use netbox to find the NetBox instance that allocates the addresses of the nodes, and to tell where the allocation comes from. The result has only coordinates, so the consumer evaluates the contract on its own node.

These modules register a provider. The name of a provider is the value for defaultProviderName.

lib.contracts does not have this contract type. A module that uses it must import the definition:

{ config, ... }:
let
  netbox = config.contracts.netbox.results.windmill.default;
in
{
  imports = [ ../../../resources/netbox/contract.nix ];

  contracts.netbox.want.windmill.default.request = {
    clusterName = "incus";
    prefixSelector = {
      kind = "tag";
      value = "fediversity";
    };
    allocateIpv4 = true;
  };

  environment.etc."netbox/coordinates.json".text = builtins.toJSON {
    inherit (netbox) serverUrl clusterName tenant;
  };
}
  • The result repeats the options of the request, so a reader of the result has the full context of the allocation.
  • An empty serverUrl means that the deployer finds the URL at apply time.
  • apiTokenFile is the path of a file with the token, and not the token. The empty string means that there is no file.

Options of netbox

netbox.request.allocateIpv4

Whether the consumer allocates a v4 address for a public node. Off, the consumer allocates only v6, and registers only the v4 addresses its nodes pin.

Type: boolean

Default:

false

Declared by:

netbox.request.clusterName

Name of the NetBox cluster the per-node virtual machines are recorded under. Must already exist at the provider (the deploy reads it, it does not create it). One cluster per deployment, unless a tenant tells two deployments apart within a shared one.

Type: string

Example:

"incus"

Declared by:

netbox.request.prefixSelector

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

Type: submodule

Declared by:

netbox.request.prefixSelector.kind

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

Type: string

Example:

"tag"

Declared by:

netbox.request.prefixSelector.value

The selector’s value: a CIDR, a comma-separated list of prefix ids, or a filter value.

Type: string

Example:

"fediversity"

Declared by:

netbox.request.rangeSelector

How the consumer discovers an IP range to allocate from instead of the whole prefix, for a family the range covers. An empty kind selects no range.

Type: submodule

Default:

{
  kind = "";
  value = "";
}

Declared by:

netbox.request.rangeSelector.kind

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

Type: string

Example:

"tag"

Declared by:

netbox.request.rangeSelector.value

The selector’s value: a CIDR, a comma-separated list of prefix ids, or a filter value.

Type: string

Example:

"fediversity"

Declared by:

netbox.request.tenant

Name of the NetBox tenant the per-node virtual machines are recorded under. NetBox keys a virtual machine’s uniqueness on cluster, tenant and name together, so this is what lets two deployments share one cluster and still name a node openbao. Empty (the default) records them untenanted, which needs a cluster of this deployment’s own. Must already exist at the provider (the deploy reads it, it does not create it).

Type: string

Default:

""

Example:

"fediversity-dev"

Declared by:

netbox.result.allocateIpv4

Whether v4 addresses are allocated (the request’s).

Type: boolean

Default:

false

Declared by:

netbox.result.apiTokenFile

Path to a file containing the NetBox API token, read on the deployer at apply time. Never the token value: contents must not reach the Nix store. Empty means no file is supplied and the token is resolved another way (for the self-hosted node, from OpenBao KV).

Type: string

Default:

""

Example:

"/run/secrets/netbox-api-token"

Declared by:

netbox.result.clusterName

The cluster name the provider settled on (the request’s, unless the provider overrides it).

Type: string

Declared by:

netbox.result.prefixSelector

The prefix selector the provider settled on (the request’s, unless the provider overrides it).

Type: submodule

Declared by:

netbox.result.prefixSelector.kind

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

Type: string

Example:

"tag"

Declared by:

netbox.result.prefixSelector.value

The selector’s value: a CIDR, a comma-separated list of prefix ids, or a filter value.

Type: string

Example:

"fediversity"

Declared by:

netbox.result.rangeSelector

The range selector the provider settled on (the request’s, unless the provider overrides it). An empty kind means the whole prefix is the pool.

Type: submodule

Default:

{
  kind = "";
  value = "";
}

Declared by:

netbox.result.rangeSelector.kind

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

Type: string

Example:

"tag"

Declared by:

netbox.result.rangeSelector.value

The selector’s value: a CIDR, a comma-separated list of prefix ids, or a filter value.

Type: string

Example:

"fediversity"

Declared by:

netbox.result.serverUrl

Base URL of the NetBox instance to allocate against. Empty when the provider expects the URL to be discovered at apply time (the self-hosted node’s own published TF state).

Type: string

Example:

"https://netbox.example.org"

Declared by:

netbox.result.tenant

The tenant the provider settled on (the request’s, unless the provider overrides it). Empty means the virtual machines are recorded untenanted.

Type: string

Default:

""

Declared by:

oidc

Contract for OIDC where a consumer requests OpenID Connect integration and a provider supplies the OpenID Connect endpoint and client credentials.

Defined in nix/contracts/lib/contracts/definitions/oidc.nix.

Use oidc when an application lets its users log in through an OpenID Connect provider.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • authelia-oidc: the option fediversity.authelia in core/resources/oidc/authelia-oidc-provider.nix.

    The authelia-backed provider of the nixpkgs oidc (OIDC) contract on this node: its contract instances (oidc) plus the deployment parameters shared across all OIDC clients.

  • dex (only for tests): the option testing.dex in nix/contracts/nixos/modules/testing/dex.nix.

    Dex reference provider for the oidc contract, for testing. Runs Dex with static client configuration.

By convention, the path of a request starts with the client ID:

{ config, ... }:
let
  oidc = config.contracts.oidc.results.grafana.default;
in
{
  contracts.oidc.want.grafana.default.request = {
    clientID = "grafana";
    redirectURI = "https://grafana.example.org/login/generic_oauth";
    scopes = [
      "openid"
      "profile"
      "email"
      "groups"
    ];
  };

  services.grafana.settings."auth.generic_oauth" = {
    enabled = true;
    client_id = oidc.clientID;
    client_secret = "$__file{${oidc.clientSecretFile}}";
    auth_url = oidc.authorizationEndpoint;
    token_url = oidc.tokenEndpoint;
    api_url = oidc.userinfoEndpoint;
    scopes = oidc.scopes;
  };
}

Settings of a client that the request does not have, for example the groups that can log in, are options of the provider at the same path as the request.

Options of oidc

oidc.request.clientID

Client ID to register with the OIDC provider.

Type: string

Declared by:

oidc.request.redirectURI

Callback URI the provider should redirect to after authentication.

Type: string

Example:

"https://myapp.example.com/oauth2/callback"

Declared by:

oidc.request.redirectURIs

More callback URIs the provider accepts, in addition to redirectURI. Use them when the client is served under more than one name, or when it also has a local callback. The provider registers redirectURI and these URIs.

Type: list of string

Default:

[ ]

Example:

[
  "http://localhost:8250/oidc/callback"
]

Declared by:

oidc.request.scopes

OIDC scopes that the provider lets the client request. The client requests the same scopes, so that the provider and the client agree.

Type: list of string

Default:

[
  "openid"
  "profile"
  "email"
]

Example:

[
  "openid"
  "profile"
  "email"
  "groups"
]

Declared by:

oidc.result.authorizationEndpoint

OIDC authorization endpoint URL. Provided for consumers that cannot perform discovery from the issuer and need the endpoint spelled out.

Type: string

Example:

"https://auth.example.com/api/oidc/authorization"

Declared by:

oidc.result.clientSecretFile

Path to a file containing the OIDC client secret.

Type: string

Declared by:

oidc.result.issuer

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

Type: string

Example:

"https://auth.example.com"

Declared by:

oidc.result.tokenEndpoint

OIDC token endpoint URL. Provided for consumers that cannot perform discovery from the issuer and need the endpoint spelled out.

Type: string

Example:

"https://auth.example.com/api/oidc/token"

Declared by:

oidc.result.userinfoEndpoint

OIDC userinfo endpoint URL. Provided for consumers that cannot perform discovery from the issuer and need the endpoint spelled out.

Type: string

Example:

"https://auth.example.com/api/oidc/userinfo"

Declared by:

postgresql

Contract for PostgreSQL database access where a consumer requests a database (and optionally a role name) and a provider supplies the connection coordinates plus a credential.

Defined in nix/contracts/lib/contracts/definitions/postgresql.nix.

Use postgresql when an application needs a PostgreSQL database.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • postgresql: the option fediversity.postgresql in core/resources/postgresql/postgresql-provider.nix.

    The postgres-backed provider of the postgresql contract on this node: its contract instances (postgresql) plus the deployment parameters shared across all requests.

{ config, ... }:
let
  db = config.contracts.postgresql.results.forgejo.default;
in
{
  contracts.postgresql.want.forgejo.default.request = {
    database = "forgejo";
    username = "forgejo";
  };

  services.forgejo.database = {
    type = "postgres";
    inherit (db) host port;
    name = db.database;
    user = db.username;
    passwordFile = db.passwordFile;
  };
}

urlFile is the path of a file with the full connection URL, with the password. Use it for an application that reads one URL, for example with LoadCredential.

The fulfill function of the provider does not read the state of the server. Thus a consumer on a different node can get the result from a shared evaluation. See contracts across nodes.

Options of postgresql

postgresql.request.database

PostgreSQL database the service requires. The provider is expected to create this database.

Type: string matching the pattern [A-Za-z0-9._@-]+

Example:

"myapp"

Declared by:

postgresql.request.username

PostgreSQL role the service connects as. Defaults to the consumer name. The provider owns whether this role is a static, generate-once account or a short-lived one minted on demand; either way it resolves to the credential delivered through passwordFile/urlFile.

Type: string matching the pattern [A-Za-z0-9._@-]+

Example:

"myapp"

Declared by:

postgresql.result.host

Hostname of the PostgreSQL server.

Type: string matching the pattern [A-Za-z0-9._:-]+

Declared by:

postgresql.result.passwordFile

Path to a file containing the password for username. Consumers that configure discrete connection fields (mastodon, peertube) read this; consumers that want a single DSN read urlFile instead.

Type: string

Declared by:

postgresql.result.port

Port of the PostgreSQL server.

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

Declared by:

postgresql.result.sslMode

libpq sslmode consumers should use when connecting. PostgreSQL is internal-only and reached over TLS, so the provider serves a cert from the group’s ssl provider and consumers verify against it.

Type: string

Default:

"require"

Example:

"require"

Declared by:

postgresql.result.urlFile

Path to a file containing the full connection DSN (postgres://user:pass@host:port/db). Consumers that take a single connection string (windmill, the panel’s DATABASE_URL) load this via LoadCredential; it carries the same credential as passwordFile, just in URL form.

Type: string

Declared by:

redis

Contract for a Redis-compatible cache/key-value store where a consumer requests an isolated namespace and a provider supplies the connection coordinates plus the credential for that namespace's store.

Mirrors s3 (the capability is named after the abstract service, not the implementation): the in-repo provider gives each namespace its OWN valkey instance (valkey@<namespace>, its own process + port on the group's valkey node), whose default-user password is rotated by OpenBao's valkey-database-secrets engine. Isolation is physical (one process per namespace) rather than an ACL key-scope, so the consumer authenticates with a password only -- there is no per-namespace ACL username. The contract names only the capability (redis), so any compatible backend could fulfil it.

Defined in nix/contracts/lib/contracts/definitions/redis.nix.

Use redis when an application needs a key-value store with the Redis protocol.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • external: the option fediversity.redis in core/resources/redis/external-provider.nix.

    The external provider of the nixpkgs redis contract on this node: its contract instances (redis) plus the deployment parameters shared across all requests -- the shared valkey node's coordinates and the openbao-agent password render directory.

  • valkey (only for tests): the option testing.valkey in nix/contracts/nixos/modules/testing/valkey.nix.

    Reference valkey provider of the redis contract for testing. Runs one valkey instance per requested namespace on localhost (each on its own namespace-derived port, password on the instance's default user) and materializes a static password file per namespace -- standing in for the per-namespace valkey@<ns> instances + OpenBao-rotated password that the real external provider uses. Isolation is physical (one instance per namespace), so there is no ACL key-scope.

Each namespace is a separate Valkey process with its own port and password, so a consumer cannot read the data of a different namespace:

{ config, ... }:
let
  redis = config.contracts.redis.results.mastodon.default;
in
{
  contracts.redis.want.mastodon.default.request.namespace = "mastodon";

  services.mastodon.redis = {
    createLocally = false;
    inherit (redis) host port;
    passwordFile = redis.passwordFile;
    enableUnixSocket = false;
  };
}

When tls is true, connect with TLS, and trust caFile if it is not null.

The store is usually on a different node. The redis resource then evaluates the contract one time with crossNodeWant. See contracts across nodes.

Options of redis

redis.request.namespace

The consumer’s logical cache identity. The provider gives this namespace its own isolated store (the in-repo provider: its own valkey@<namespace> instance). The in-repo provider also uses it as the name of the OpenBao static role that rotates the password of that instance. In a tenant deployment, the deployment namespace follows it.

Type: string

Example:

"mastodon"

Declared by:

redis.result.caFile

Path to the CA bundle that signs the server certificate, when tls is true. Null means the system trust store.

Type: null or string

Default:

null

Declared by:

redis.result.host

Hostname of the Redis-compatible server. A consumer writes this value with no quotes into its configuration, so it cannot hold a newline or a quote.

Type: string matching the pattern [A-Za-z0-9._:-]+

Declared by:

redis.result.passwordFile

Path to a file containing the password. Rendered out of band by a secret provider (the openbao-agent renders the OpenBao-rotated password here). Must not live under /nix/store – the peertube module asserts this, and the value rotates.

Type: string

Declared by:

redis.result.port

Port of the Redis-compatible server. The in-repo provider gives each namespace its own instance on its own port, so this is per-namespace.

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

Declared by:

redis.result.tls

Whether the server accepts only TLS on port. When true, the consumer connects with TLS (a rediss:// URL) and verifies the server certificate against caFile. When false, the consumer connects in plain text (a redis:// URL).

Type: boolean

Default:

false

Declared by:

redis.result.username

ACL username the consumer authenticates as, if the provider scopes access by ACL user. Empty (the default) means password-only auth on the server’s built-in default user – the in-repo provider isolates by per-namespace instance, not by ACL, so it leaves this empty. A consumer writes this value with no quotes into its configuration, so it cannot hold a newline or a quote.

Type: string matching the pattern [A-Za-z0-9._@-]*

Default:

""

Example:

"mastodon"

Declared by:

s3

Contract for a bucket on S3-compatible object storage. The consumer requests a bucket by its logical name. The provider gives the physical bucket name, the address of the S3 API, the region and, if it serves them, the domains of the bucket on the virtual-host API and on the website endpoint. The access keys are not part of this contract: a secrets contract, for example credentials or envSecrets, gives them.

Defined in nix/contracts/lib/contracts/definitions/s3.nix.

Use s3 when an application needs a bucket in an object store with the S3 API.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • garage (only for tests): the option testing.garage in nix/contracts/nixos/modules/testing/garage.nix.

    Garage reference provider for the s3 contract, for testing. Runs a single-node Garage on localhost and provisions requested buckets.

In a deployment, the garage resource (core/resources/garage/default.nix) registers no provider. It calculates the result of each bucket and sets contracts.s3.instances directly.

{ config, ... }:
let
  s3 = config.contracts.s3.results.myapp-media.default;
in
{
  contracts.s3.want.myapp-media.default.request.bucket = "myapp-media";

  services.myapp.storage = {
    inherit (s3) endpoint region bucket;
  };
}
  • The bucket of the request is a logical name. The bucket of the result is the name in the object store, which can have a prefix. Read the name from the result.
  • The access keys are not part of this contract. Get them from a contract for secrets, for example credentials or envSecrets.

Options of s3

s3.request.bucket

The logical name of the bucket.

Type: string

Example:

"mastodon"

Declared by:

s3.result.apiDomain

The virtual-host domain of the bucket on the S3 API, or null if the provider does not serve virtual-host requests.

Type: null or string

Default:

null

Example:

"group-mastodon.s3.example.org"

Declared by:

s3.result.bucket

The physical name of the bucket. It can differ from the requested name, for example by a tenant prefix.

Type: string

Example:

"group-mastodon"

Declared by:

s3.result.endpoint

The URL of the S3 API, with the scheme and the port.

Type: string

Example:

"http://s3.example.org:3900"

Declared by:

s3.result.port

The port of the S3 API, the same port as in endpoint.

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

Example:

3900

Declared by:

s3.result.publicEndpoint

The URL of the S3 API that a client outside the network of the provider can reach, with the scheme, or null if the provider does not serve the S3 API publicly. The API takes path-style requests at this URL.

Type: null or string

Default:

null

Example:

"https://s3.example.org"

Declared by:

s3.result.region

The region of the bucket.

Type: string

Default:

"us-east-1"

Declared by:

s3.result.webDomain

The domain of the bucket on the website endpoint, or null if the provider has no website endpoint.

Type: null or string

Default:

null

Example:

"group-mastodon.web.example.org"

Declared by:

s3.result.webUrl

The URL of the bucket on the website endpoint, or null if the provider has no website endpoint.

Type: null or string

Default:

null

Example:

"https://group-mastodon.web.example.org"

Declared by:

smtp

Contract for SMTP email sending where a consumer requests mail relay configuration and a provider supplies the connection details.

Defined in nix/contracts/lib/contracts/definitions/smtp.nix.

Use smtp when an application sends mail through a relay.

These modules register a provider. The name of a provider is the value for defaultProviderName.

  • external-relay: the option fediversity.smtp-relay.smtp in core/resources/smtp/external-relay.nix.

    External-relay-backed provider of the nixpkgs smtp contract.

  • maddy: the option fediversity.smtp in core/resources/smtp/maddy-provider.nix.

    The maddy-backed provider of the nixpkgs smtp contract on this node: its contract instances (smtp) plus the deployment parameters shared across all consumers.

  • opensmtpd (only for tests): the option testing.opensmtpd in nix/contracts/nixos/modules/testing/opensmtpd.nix.

    opensmtpd reference provider for the smtp contract, for testing. Runs a local opensmtpd instance that accepts mail on localhost.

The path of the request starts with the name of the application. The maddy provider makes one account for each name, and the user name of the account is the sender address:

{ config, ... }:
let
  smtp = config.contracts.smtp.results.authelia.default;
in
{
  contracts.smtp.want.authelia.default.request.sender = "auth@example.org";

  services.authelia.instances.main.settings.notifier.smtp = {
    address = "submission://${smtp.host}:${toString smtp.port}";
    inherit (smtp) username;
    sender = smtp.sender;
  };
}

passwordFile is the path of a file on the target machine. Give it to the service as a file or as a credentials request.

Options of smtp

smtp.request.sender

Email address used as the sender (From header).

Type: string matching the pattern [A-Za-z0-9._%+@-]+

Example:

"noreply@example.com"

Declared by:

smtp.result.host

Hostname of the SMTP server.

Type: string matching the pattern [A-Za-z0-9._:-]+

Declared by:

smtp.result.passwordFile

Path to a file containing the SMTP password.

Type: string

Declared by:

smtp.result.port

Port of the SMTP server.

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

Declared by:

smtp.result.username

Username for SMTP authentication.

Type: string matching the pattern [A-Za-z0-9._%+=/@-]*

Declared by:

ssl

Contract for SSL certificate provisioning where a consumer requests a certificate and a provider generates it.

Defined in nix/contracts/lib/contracts/definitions/ssl.nix.

Use ssl to get a TLS certificate and its key for a domain.

These modules register a provider. The name of a provider is the value for defaultProviderName.

By convention, the path of a request ends with the domain:

{ config, ... }:
let
  domain = "ldap.example.org";
  tls = config.contracts.ssl.results.fediversity.${domain};
in
{
  contracts.ssl.want.fediversity.${domain}.request = {
    inherit domain;
    keyMode = "0600";
    restartServices = [ "lldap.service" ];
  };

  systemd.services.lldap = {
    after = [ tls.systemdService ];
    wants = [ tls.systemdService ];
  };
  services.lldap.settings = {
    ldaps_options.cert_file = tls.cert;
    ldaps_options.key_file = tls.key;
  };
}

The provider restarts the units in restartServices when it renews the certificate. When keyOwner, keyGroup or keyMode is the empty string, the provider uses its default.

Options of ssl

ssl.request.domain

Domain name for which the certificate is requested.

Type: string

Example:

"example.com"

Declared by:

ssl.request.keyGroup

Group the provider should give the private key file. Empty leaves the provider default. See keyOwner.

Type: string

Default:

""

Example:

"postgres"

Declared by:

ssl.request.keyMode

Octal mode the provider should give the private key file. Empty leaves the provider default (a private key must never be world-readable, so a sane provider default is at most 0640). Set 0600 for a service that owns the key exclusively (e.g. PostgreSQL, which rejects a key with any group or world access).

Type: string

Default:

""

Example:

"0600"

Declared by:

ssl.request.keyOwner

Owner (user) the provider should give the private key file. Empty leaves the provider default (root). A consumer whose terminating service reads the key as a non-root user must set this (e.g. PostgreSQL, which rejects a key it cannot own): the provider chowns the key to this owner. Providers that cannot honour ownership (e.g. a store-baked key) ignore it.

Type: string

Default:

""

Example:

"postgres"

Declared by:

ssl.request.restartServices

Systemd units the provider should converge after (re)issuing the certificate. A runtime-cert provider (e.g. openbao-pki) uses this to heal consumers that raced the first issue at boot (a service that burned its start limit while the cert file was still missing) and to reload long-running consumers onto a renewed leaf. Providers whose certs are eval-time store paths (self-signed) or that manage reloads themselves (acme) ignore it.

Type: list of string

Default:

[ ]

Example:

[
  "nginx.service"
]

Declared by:

ssl.result.cert

Path to the certificate file.

Type: string

Declared by:

ssl.result.key

Path to the private key file.

Type: string

Declared by:

ssl.result.systemdService

Systemd oneshot service that generates the certificate. Ends with the .service suffix.

Type: string

Example:

"cert-generator.service"

Declared by:

streamingBackup

Contract for streaming backup where what to back up comes from a stream. Well suited for backing up databases or tar archives.

Defined in nix/contracts/lib/contracts/definitions/streaming-backup.nix.

Use streamingBackup to make a backup of data that a command writes as a stream, for example a database dump.

These modules register a provider. The name of a provider is the value for defaultProviderName.

backupCmd writes the data to its standard output. restoreCmd reads the data from its standard input:

{ config, pkgs, ... }:
{
  contracts.streamingBackup = {
    want.myapp.postgres.request = {
      user = "postgres";
      backupName = "myapp.sql";
      backupCmd = "${pkgs.postgresql}/bin/pg_dump myapp";
      restoreCmd = "${pkgs.postgresql}/bin/psql myapp";
    };
    defaultProvider = config.contracts.streamingBackup.providers.restic;
  };
}

As for fileBackup, the settings of the repository are options of the provider, at services.restic.contracts.streamingBackup.myapp.postgres.

The results are:

  • backupService, the name of the systemd unit that makes the backup.
  • restoreScript, with the commands restore latest, snapshots and drill latest. The drill command needs a drillCmd in the request.
  • upgradeScript, with the commands backup <version> and restore <version>.

Options of streamingBackup

streamingBackup.request.backupCmd

A bash command that produces the backup data on stdout.

Type: string

Declared by:

streamingBackup.request.backupName

Name of the backup in the repository.

Type: string

Example:

"postgresql.sql"

Declared by:

streamingBackup.request.drillCmd

A bash command that reads the backup data on stdin and checks that it restores, into a scratch target. It does not change the data of the service. null gives no drill.

Type: null or string

Default:

null

Declared by:

streamingBackup.request.restoreCmd

A bash command that reads the backup data on stdin and restores it.

Type: string

Declared by:

streamingBackup.request.user

Unix user doing the backups.

Type: string

Example:

"vaultwarden"

Declared by:

streamingBackup.request.version

The version of the format of the backup data, for example the version of the application that writes it. The provider records it with each snapshot, and restoreScript refuses a snapshot with a newer version. null records no version.

Type: null or string

Default:

null

Example:

"4.3.0"

Declared by:

streamingBackup.result.backupService

Name of the systemd service that performs the backup.

Type: string

Declared by:

streamingBackup.result.restoreScript

Path to a script that can restore from backup. Supports restore latest and snapshots subcommands, and drill latest when request.drillCmd is set. restore latest gives the newest snapshot to restoreCmd, and drill latest gives it to drillCmd, both as request.user. Both refuse a snapshot whose version is newer than request.version, except when RESTORE_IGNORE_VERSION=1 is set.

Type: absolute path

Declared by:

streamingBackup.result.upgradeScript

Path to a script, to run as root, for the backup before an upgrade:

  • backup <version> runs backupCmd and keeps its output as an upgrade backup of the data of version <version>. The provider keeps the 2 newest upgrade backups, and the scheduled backups do not remove them.
  • restore <version> gives the newest upgrade backup of version <version> to restoreCmd.

Type: absolute path

Declared by:

varsBackend

Contract for vars storage backends. A backend determines where and how generated files are stored and retrieved, returning the final file paths.

Defined in nix/contracts/lib/contracts/definitions/vars-backend.nix.

varsBackend is the interface between the vars provider of generateFiles and the storage of the generated files. Application code does not declare a request: the vars module is the only consumer, and declares want.vars.<instance> for each generateFiles request.

These modules register a provider. The name of a provider is the value for defaultProviderName.

To use this contract, select the backend:

{
  contracts.varsBackend.defaultProviderName = "on-machine";
}

To write a backend, give the final path of each file in the request:

fulfill' =
  { name, request, ... }:
  {
    files = lib.mapAttrs (fileName: file: {
      path = "/var/lib/mybackend/${name}/${fileName}";
    }) request.files;
  };

Options of varsBackend

varsBackend.request.files

Files to be stored by the backend, with their metadata. Keys are file names matching those declared in the generateFiles request.

Type: attribute set of (submodule)

Declared by:

varsBackend.request.files.<name>.group

Unix group that must own the file.

Type: string

Default:

"root"

Declared by:

varsBackend.request.files.<name>.mode

File permissions as an octal string.

Type: string

Default:

"0400"

Declared by:

varsBackend.request.files.<name>.owner

Unix user that must own the file.

Type: string

Default:

"root"

Declared by:

varsBackend.request.files.<name>.secret

Whether the file contains sensitive data.

Type: boolean

Default:

true

Declared by:

varsBackend.result.files

Final provisioned paths per file.

Type: attribute set of (submodule)

Declared by:

varsBackend.result.files.<name>.path

Path to the provisioned file on the target system.

Type: string

Declared by: