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.
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
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.
generatefilename jsonValue
A function that can render a value of the format to a file.
Returns a file path.
This function puts the value contents in the Nix store. So
this should be avoided for secrets.
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.
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.
'';
};