-
-
Notifications
You must be signed in to change notification settings - Fork 161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC 0042] NixOS settings options #42
Conversation
Write (not-so-)detailed design and drawbacks Adjust formatting Expand Additional config options Add section for configuration types Add format writers and documentation sections Adjust additional options example Add some more links and change name Move file
I think I like this overall. Documentation is important here. Also I'd like to keep the One (admittedly minor) drawback is that it can feel a little bit uncanny-valley like when the upstream configuration options don't adhere to nix conventions like starting with a lowercase letter (NixOS/nixpkgs#57554 (comment)). |
I'd rather not if possible, because then we have problem of not being able to merge values set in {
services.foo.config = builtins.fromJSON ''
{
"logDir": "/var/log/foo"
# ...
}
'';
} If the user really doesn't want to use the modular |
The advantage of extraConfig is that the user can keep a standard format config file around (which can also be used outside of nixos) and just import that with That mostly makes sense for rc-type configs, less for json I guess. |
On the other hand, JSON can actually be imported with Ideally, declarative config formats could be parsed and merged, but for imperative configs indeed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for bringing this up! After reading through the RFC I'm confident that the overall idea is a good thing to do, both from a collaborator's and user's perspective 👍
Below are some thoughts and comments from my side.
rfcs/0042-config-option.md
Outdated
# Set minimal config to get service working by default | ||
services.foo.config = { | ||
dataPath = "/var/lib/foo"; | ||
logLevel = mkDefault "WARN"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just thought a little bit about this and I personally think that one would like to see some kind of mechanism that prints all default values into the summary of config
. But IIRC only config options are evaluated when generating the manual, so I'm not even sure if that's possible without too much effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would certainly be nice in the future, I feel like it should be possible, by just determining the default by actually evaluating the options value with an empty configuration.nix
, instead of just looking at the options default
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea and would be something I'd probably help to implement. How about adding this to "Future work" for now? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is pretty important that the defaults in use should show up in the documentation. Perhaps the mkOption
function could be adjusted to allow setting a priority of the default option? Then you could have something like
mkOption {
default = {
logLevel = "WARN";
port = 2546;
};
defaultModifier = def: mapAttrs (n: mkOptionDefault) def;
# …
}
To have user config merged into the option default value unless the user applies mkForce
.
I haven't thought this through very carefully, though, so I might have missed some point causing this not to work.
But this may be an easier way to solve this documentation issue instead of attempting to evaluate an "empty" configuration. A difficulty, for example, is that we have to figure out how to actually get the config option to be populated. In the foo module, for example, you would have to know that the enable option should be set to true
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about something like
mkOption {
default = lib.noPriority (mapAttrs (n: mkOptionDefault) {
logLevel = "WARN";
port = 2546;
});
}
The manual could generate
default = {
logLevel = <default> "WARN";
port = <default> 2546;
}
But then there's also the problem with defaults that depend on other options, these can't be in the manual. And then the default declarations would have to be split up in the module itself.
And there's also the problem with serializing nix values from nix itself. We can't properly escape stuff, and multi-line strings won't work, and interpolated values, derivations.. I feel like we almost need some Nix language change to make this work properly.
And then there's also the discussion on which defaults should be seen by the user at all. Does it make sense that logType = "syslog"
should be in the manual for the user to see? Probably not. See the Defaults section of this RFC as well, the first type of default specifically shouldn't be seen by the user.
I guess the simplest solution is to just say "A reasonable default base configuration is set in order for the module to work, see for details". We already link to the module files already anyways.
Not entirely sure what to do about this. But then again, having this problem is nothing inherent to this RFC, lots of modules already set defaults in this way invisible to users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any advantage in using mkDefault
to override some configuration keys over using apply = recursiveUpdate default
in mkOption
like here?
config = mkOption rec {
type = types.attrs;
apply = recursiveUpdate default;
default = {
ircService = {
databaseUri = "nedb://${dataDir}/data";
passwordEncryptionKeyPath = "${dataDir}/passkey.pem";
};
};
This has the advantage of already showing the default values in the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While this apply
way doesn't seem too bad, I don't think it's a good idea because it completely sidesteps the module system, which leads to some unexpected behavior. E.g. in your example if a user sets config.ircService = mkForce {}
, this doesn't do anything, because recursiveUpdate
doesn't know not to recurse over that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add addDefaultAnnotation = annotation: content: { _type = "annotation:default"; inherit content; };
and let the documentation builder use that for describing the value in the manual?
This pull request has been mentioned on Nix community. There might be relevant details there: https://discourse.nixos.org/t/open-for-nomination-rfc-0042-nixos-config-options/2538/1 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
Co-authored-by: Domen Kožar <[email protected]> Co-authored-by: Jörg Thalheim <[email protected]> Co-authored-by: Linus Heckemann <[email protected]>
Summary
Part 1: Structural
settings
instead of stringlyextraConfig
NixOS modules often use stringly-typed options like
extraConfig
to allow specifying extra settings in addition to the default ones. This has multiple disadvantages: The defaults can't be changed, multiple values might not get merged properly, inspection of it is almost impossible because it's an opaque string and more. The first part of this RFC aims to discourage such options and proposes genericsettings
options instead, which can encode the modules configuration file as a structural Nix value. Here is an example showcasing some advantages:Jump to the detailed design of part 1
Part 2: Balancing module option count
Since with this approach there will be no more hardcoded defaults and composability is not a problem anymore, there is not a big need to have NixOS options for every setting anymore. Traditionally this has lead to huge modules with dozens of options, each of them only for a single field in the configuration. Such modules are problematic because they're hard to write, review and maintain, are generally of lower quality, fill the option listing with noise and more. Additional options aren't without advantages however: They are presented in the NixOS manual and can have better type checking than the equivalent with
settings
.The second part of this RFC aims to encourage module authors to strike a balance for the number of additional options such as to not make the module too big, but still provide the most commonly used settings as separate options. Quality is encouraged over quantity: Authors should spend more time on writing documentation, NixOS tests or useful high-level abstractions. This is in contrast to the fiddly labor of copying dozens of options from upstream to NixOS. With a
settings
option, it's also very easy to add additional options over time if the need arises. In contrast, removing options has always been nigh impossible.Jump to the detailed design of part 2
Rendered