nixpkgs/nixos/doc/manual/development/settings-options.section.md
benaryorg 8b2d86b982
pkgs.formats: toINIWithGlobalSection wrapper
The new format is based on the existing wrapper and generates an INI file with an unnamed global section at the top as is used by *stunnel* for instance.
Technically the INI format is a subset of this however testing, type checking, and API guarantees profit from two separate generators.

Co-authored-by: tim-tx <tim-tx@users.noreply.github.com>
Signed-off-by: benaryorg <binary@benary.org>
2024-02-12 17:58:48 +00:00

9.2 KiB

Options for Program Settings

Many programs have configuration files where program-specific settings can be declared. File formats can be separated into two categories:

  • Nix-representable ones: These can trivially be mapped to a subset of Nix syntax. E.g. JSON is an example, since its values like {"foo":{"bar":10}} can be mapped directly to Nix: { foo = { bar = 10; }; }. Other examples are INI, YAML and TOML. The following section explains the convention for these settings.

  • Non-nix-representable ones: These can't be trivially mapped to a subset of Nix syntax. Most generic programming languages are in this group, e.g. bash, since the statement if true; then echo hi; fi doesn't have a trivial representation in Nix.

    Currently there are no fixed conventions for these, but it is common to have a configFile option for setting the configuration file path directly. The default value of configFile can be an auto-generated file, with convenient options for controlling the contents. For example an option of type attrsOf str can be used for representing environment variables which generates a section like export FOO="foo". Often it can also be useful to also include an extraConfig option of type lines to allow arbitrary text after the autogenerated part of the file.

Nix-representable Formats (JSON, YAML, TOML, INI, ...)

By convention, formats like this are handled with a generic settings option, representing the full program configuration as a Nix value. The type of this option should represent the format. The most common formats have a predefined type and string generator already declared under pkgs.formats:

pkgs.formats.javaProperties { comment ? "Generated with Nix" }

A function taking an attribute set with values

comment

A string to put at the start of the file in a comment. It can have multiple lines.

It returns the type: attrsOf str and a function generate to build a Java .properties file, taking care of the correct escaping, etc.

pkgs.formats.json { }

A function taking an empty attribute set (for future extensibility) and returning a set with JSON-specific attributes type and generate as specified below.

pkgs.formats.yaml { }

A function taking an empty attribute set (for future extensibility) and returning a set with YAML-specific attributes type and generate as specified below.

pkgs.formats.ini { listsAsDuplicateKeys ? false, listToValue ? null, ... }

A function taking an attribute set with values

listsAsDuplicateKeys

A boolean for controlling whether list values can be used to represent duplicate INI keys

listToValue

A function for turning a list of values into a single value.

It returns a set with INI-specific attributes type and generate as specified below. The type of the input is an attrset of sections; key-value pairs where the key is the section name and the value is the corresponding content which is also an attrset of key-value pairs for the actual key-value mappings of the INI format. The values of the INI atoms are subject to the above parameters (e.g. lists may be transformed into multiple key-value pairs depending on listToValue).

pkgs.formats.iniWithGlobalSection { listsAsDuplicateKeys ? false, listToValue ? null, ... }

A function taking an attribute set with values

listsAsDuplicateKeys

A boolean for controlling whether list values can be used to represent duplicate INI keys

listToValue

A function for turning a list of values into a single value.

It returns a set with INI-specific attributes type and generate as specified below. The type of the input is an attrset of the structure { sections = {}; globalSection = {}; } where sections are several sections as with pkgs.formats.ini and globalSection being just a single attrset of key-value pairs for a single section, the global section which preceedes the section definitions.

pkgs.formats.toml { }

A function taking an empty attribute set (for future extensibility) and returning a set with TOML-specific attributes type and generate as specified below.

pkgs.formats.elixirConf { elixir ? pkgs.elixir }

A function taking an attribute set with values

elixir

The Elixir package which will be used to format the generated output

It returns a set with Elixir-Config-specific attributes type, lib, and generate as specified below.

The lib attribute contains functions to be used in settings, for generating special Elixir values:

mkRaw elixirCode

Outputs the given string as raw Elixir code

mkGetEnv { envVariable, fallback ? null }

Makes the configuration fetch an environment variable at runtime

mkAtom atom

Outputs the given string as an Elixir atom, instead of the default Elixir binary string. Note: lowercase atoms still needs to be prefixed with :

mkTuple array

Outputs the given array as an Elixir tuple, instead of the default Elixir list

mkMap attrset

Outputs the given attribute set as an Elixir map, instead of the default Elixir keyword list

[]{#pkgs-formats-result} These functions all return an attribute set with these values:

type

A module system type representing a value of the format

lib

Utility functions for convenience, or special interactions with the format. This attribute is optional. It may contain inside a types attribute containing types specific to this format.

generate filename jsonValue

A function that can render a value of the format to a file. Returns a file path.

::: {.note} This function puts the value contents in the Nix store. So this should be avoided for secrets. :::

::: {#ex-settings-nix-representable .example}

Module with conventional settings option

The following shows a module for an example program that uses a JSON configuration file. It demonstrates how above values can be used, along with some other related best practices. See the comments for explanations.

{ options, config, lib, pkgs, ... }:
let
  cfg = config.services.foo;
  # Define the settings format used for this program
  settingsFormat = pkgs.formats.json {};
in {

  options.services.foo = {
    enable = lib.mkEnableOption "foo service";

    settings = lib.mkOption {
      # Setting this type allows for correct merging behavior
      type = settingsFormat.type;
      default = {};
      description = ''
        Configuration for foo, see
        <link xlink:href="https://example.com/docs/foo"/>
        for supported settings.
      '';
    };
  };

  config = lib.mkIf cfg.enable {
    # We can assign some default settings here to make the service work by just
    # enabling it. We use `mkDefault` for values that can be changed without
    # problems
    services.foo.settings = {
      # Fails at runtime without any value set
      log_level = lib.mkDefault "WARN";

      # We assume systemd's `StateDirectory` is used, so we require this value,
      # therefore no mkDefault
      data_path = "/var/lib/foo";

      # Since we use this to create a user we need to know the default value at
      # eval time
      user = lib.mkDefault "foo";
    };

    environment.etc."foo.json".source =
      # The formats generator function takes a filename and the Nix value
      # representing the format value and produces a filepath with that value
      # rendered in the format
      settingsFormat.generate "foo-config.json" cfg.settings;

    # We know that the `user` attribute exists because we set a default value
    # for it above, allowing us to use it without worries here
    users.users.${cfg.settings.user} = { isSystemUser = true; };

    # ...
  };
}

:::

Option declarations for attributes

Some settings attributes may deserve some extra care. They may need a different type, default or merging behavior, or they are essential options that should show their documentation in the manual. This can be done using .

We extend above example using freeform modules to declare an option for the port, which will enforce it to be a valid integer and make it show up in the manual.

::: {#ex-settings-typed-attrs .example}

Declaring a type-checked settings attribute

settings = lib.mkOption {
  type = lib.types.submodule {

    freeformType = settingsFormat.type;

    # Declare an option for the port such that the type is checked and this option
    # is shown in the manual.
    options.port = lib.mkOption {
      type = lib.types.port;
      default = 8080;
      description = ''
        Which port this service should listen on.
      '';
    };

  };
  default = {};
  description = ''
    Configuration for Foo, see
    <link xlink:href="https://example.com/docs/foo"/>
    for supported values.
  '';
};

:::