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
lib/modules: Add mkPostMerge #157070
base: master
Are you sure you want to change the base?
lib/modules: Add mkPostMerge #157070
Conversation
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.
not sure it's a good idea to have this without also having postmerge functions be explicitly orderable (as if by mkOrder
). also very much not a fan of the proliferation of '
in mergeDefinitions
, that's a footgun waiting to go off at someone.
I considered making it strictly ordered but I'm not sure if the extra complexity is worth it |
One solution might be to just make it an error to have more than one postMerge, and worry about ordering if it starts causing issues. |
Looks like a possibly nicer solution that works well with mkOverride but it doesn't provide a way to provide a normal definition along with with function. I think I'll try to rewrite mine to work in a similar way. |
I think enforcing strictly defined ordering would just end up creating a worse problem than this solves. If multiple modules make use of this feature on the same option then what will happen is that the config will refuse to be evaluated with no way for the user to solve it when it would most likely be perfectly fine to apply the multiple functions in any order. The only case I can see where multiple uses of this feature could be a problem is if one module used it to add to an option but that would just be an unnecessary misuse of the feature. I think there is no good solution to the problem and it's best to just allow the functions to be applied in the order they appear. |
I disagree. Without a strictly defined ordering, it's not safe to change the order of imports and won't allow us to fix the weird order in which definitions are currently processed. Some things are getting reversed, as of now.
This is what we shouldn't do.
This would indicate that we don't need the power of functions, but something weaker instead, such as the merging we have or any other commutative operation. Composition of arbitrary functions is not commutative.
No, it breaks the goals of the module system. The solution is no good when it just allows the functions to be applied in any order they appear. When I wrote my version of it, what I had in mind is that end users would use it in cases where they want to reuse the default, as the main use case, then generalize it to other valid priority combinations. Although it is a semantically valid solution, it steepens the learning curve when users start writing their own modules, or modules for Nixpkgs, where this feature must not be used, as it makes such modules harder to reuse and combine with other modules that may or may not use it. Instead, we should improve the modules that would invite the use of this feature, because by adding this feature, we allow those hard to use options to remain unfixed, moving both NixOS and the module system in a direction that's more fragile and complex, and less composable/reusable. |
I invite anyone to come up with a real-world use case for this function that isn't better solved by improving the options. Unless we find one, this is a bad feature. (I wish it was different, or that it was obvious for that matter) |
I think the primary use case most people run into is removing a thing from a list without redefining the entire list. |
Removals are also bad, but for another reason. It's only commutative if you consider the option to consist of two values, a positive and a negative list, that only combine at merge time. When implemented this way, it avoids some of the composability argument, but, it still creates a bad coupling between modules (where a configuration is also a module). It's better to teach the original module to insert less. That way, the coupling is expressed in high-level terms, making clear why things are the way they are in the original module. |
Opening a pull request and waiting for it to get merged is a very inconvenient short term solution. It should be possible to get my configuration to how I need it within the module/config system without copy/pasting an entire module with one small change or using a weird hack. The solution doesn't need to compose well and I think it would even be fine if it was limited to just the main configuration. |
I've argued for this before, but I think this problem is best solved by deprecating
|
@roberth I find a need to modify configuration values produced by a module whenever I am patching a NixOS issue in my system configuration, but either a PR hasn't been merged yet or a general solution is not yet known. I think these examples meet your criteria because "improving the options" requires that a PR is merged to do that. Leaving our systems in a broken state while we wait for a PR to be merged or authored is not a good option. Example 1: Removing a kernel moduleI have an ARM system that could not # Make TPM2 modules optional.
# See: https://github.com/NixOS/nixpkgs/pull/207969
{ nixpkgs, lib, config, pkgs, utils, ... }:
{
# Mask the original module.
disabledModules = [
"system/boot/systemd/initrd.nix"
];
# Reimport the module so that we can modify its config.
imports = let
module = (import "${nixpkgs}/nixos/modules/system/boot/systemd/initrd.nix" { inherit config lib pkgs utils; });
in [
# Remove tpm-tis and tpm-crb from availableKernelModules.
(lib.recursiveUpdate module {
# The 'content' in the path refers to a mkIf.
config.content.boot.initrd.availableKernelModules = lib.remove "tpm-tis" (lib.remove "tpm-crb" module.config.content.boot.initrd.availableKernelModules);
})
];
# Add them back conditionally.
boot.initrd.availableKernelModules =
lib.optional (config.boot.kernelPackages.kernel.config.isEnabled "TCG_TIS") "tpm-tis" ++
lib.optional (config.boot.kernelPackages.kernel.config.isEnabled "TCG_CRB") "tpm-crb";
} Some things that feel hacky:
This is a little better than maintaining a fork, but it's brittle. Example 2: Fixing a boot scriptI found that # Fixes application of device tree overlays.
# TODO: See: https://github.com/NixOS/nixpkgs/pull/209964
{ config, lib, ... }:
{
boot.initrd.systemd.services.initrd-nixos-activation.script = let
cfg = config.boot.initrd.systemd;
in lib.mkForce ''
set -uo pipefail
export PATH="/bin:${cfg.package.util-linux}/bin"
# Figure out what closure to boot
closure=
for o in $(< /proc/cmdline); do
case $o in
init=*)
IFS== read -r -a initParam <<< "$o"
closure="$(dirname "$(readlink -m "/sysroot''${initParam[1]}")")"
;;
esac
done
# Sanity check
if [ -z "''${closure:-}" ]; then
echo 'No init= parameter on the kernel command line' >&2
exit 1
fi
# If we are not booting a NixOS closure (e.g. init=/bin/sh),
# we don't know what root to prepare so we don't do anything
if ! [ -x "/sysroot$closure/prepare-root" ]; then
echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf
echo "$closure does not look like a NixOS installation - not activating"
exit 0
fi
echo 'NEW_INIT=' > /etc/switch-root.conf
# We need to propagate /run for things like /run/booted-system
# and /run/current-system.
mkdir -p /sysroot/run
mount --bind /run /sysroot/run
# Initialize the system
export IN_NIXOS_SYSTEMD_STAGE1=true
exec chroot /sysroot $closure/prepare-root
'';
} This has a few problems:
Option modifiersTo sidestep the ordering issue, we can give up the full power of functions, as mentioned earlier on this PR. Example 1 could be addressed by introducing a function to remove list elements. Example 2 could be improved by introducing a function to apply a patch file to an option value. This would strip the patch module down to only the operative change, and it would solve the problem of Module patchesHowever, patching an option value would break in situations where part of the diff contains an expression, like a patch to delete a line that contains Nix store path. In that situation, we are more interested in applying a patch to the module source itself. That capability would solve for both examples. Would such a patch approach partially solve the ordering issue? |
It looks like NixOS/nix#3920 could also address these use cases (in a similar way as "module patches"). |
this could be solved much more elegantly with a proposal from @infinisil a while back: just don't string lists, use attrsets. list elements become keys, true/false become "present" vs "not present". you'd write
adding some sort of patch functionality that is in any way resilient against unrelated modifications requires IFD and is thus not viable. what we could do though is to move all scripts that are currently defined inline in modules into files. once that's done you can redefine the script as being a derivation built from the stock script and a real patch. doing that would also give us the opportunity to clean up many (lack of) shell-escaping crimes. |
Since I don't see it here, I'll mention that I've been using boot.supportedFilesystems = mkOption {
apply = subtractLists [ "reiserfs" "xfs" "cifs" "f2fs" ];
}; for ages to work around this issue. Of course, this is nothing more than a work-around, and I don't think it's reasonable to expect to be able to use the module system to apply arbitrary patches (in the sense of git) to modules, and I'm inclined to put "changes to scripts" in that category. Note that the |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/natural-deduction-rules-for-the-nixos-module-merge/26513/6 |
This does keep popping up and I'm still ambivalent about lib.mkMerge [
(mkPostMerge (mkAfter (x: x + 1)))
(mkPostMerge (x: x * 100))
3
]
# => 301 As with anything that supports |
For what it's worth, I found a much nicer solution for my use cases: patching the let
patches = [
./fix-something.patch
# can also use fetchpatch
];
patchNixpkgs = system: input: patches:
let
source = flake-utils-plus.lib.patchChannel system input patches;
flake = import "${source}/flake.nix";
outputs = flake.outputs { self = patched; };
patched = outputs // {
inherit outputs;
outPath = "${source}";
};
in patched;
patchedNixpkgs = patchNixpkgs "x86_64-linux" nixpkgs patches;
in
patchedNixpkgs.lib.nixosSystem {
modules = [
# ordinary system modules here
];
} It works great! Before 23.05, I had something like 30 patches pulled from unstable on top of 22.11. I still have a few. |
Yikes. We should backport patches! Feel free to use the backport label or ask maintainers to do it if you don't have permission.
For future readers, proof of concept code (NixOS/nix#6530) exists to add patch support to flake inputs natively. |
Motivation for this change
NixOS provides the ability to forcibly set the value of an option and add to the value value of option but what is missing is a way to remove from an option's value. Currently the only way to achieve the same thing is with mkOverride but that is impractical if the module that is being overriden is non trivial. This change adds a new property that allows you to to provide an arbitrary function that gets applied to the merged value.
From my config the use of it looks like this:
Things done
sandbox = true
set innix.conf
? (See Nix manual)nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD"
. Note: all changes have to be committed, also see nixpkgs-review usage./result/bin/
)nixos/doc/manual/md-to-db.sh
to update generated release notes