Using changes from a nixpkgs PR in your flake

I’d like to use a nixpkgs PR (in this case my PR that adds tls mode to security.acme) in my flake. Very common situation I’d think. There are multiple ways to do this:

  • maintaining an own fork (meh)
  • patching nixpkgs with applyPatches
  • blacklisting-and-reimporting the relevant modules with disabledModules

Here’s a flake (also available in this repo) that demonstrates this:

{
  description = "Patching nixpkgs in a flake";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-24.11";
    # My fork from where I submitted PR#340136
    # https://github.com/NixOS/nixpkgs/pull/340136
    nixpkgs-yann.url = "github:nobodyinperson/nixpkgs?ref=acme-tls-mode";
    # The corresponding patch file for PR#340136
    nixpkgs-acme-tls-pr-patch = {
      url =
        "https://patch-diff.githubusercontent.com/raw/NixOS/nixpkgs/pull/340136.patch";
      flake = false;
    };
  };
  # 📟 Run VMs with:
  # > nix build .#nixosConfigurations.<NAME>.config.system.build.vm && find -maxdepth 0 -name '*.qcow*' -delete && result/bin/run-*-vm
  outputs = { self, ... }@inputs:
    let
      osConfig = {
        security.acme.acceptTerms = true;
        security.acme.defaults = {
          tlsMode = true;
          email = "me@example.org";
          server = "https://acme-staging-v02.api.letsencrypt.org/directory";
        };
        services.nginx.enable = true;
        services.nginx.virtualHosts."example.org".enableACME = true;
        # 🍳 boilerplate to get VM usable
        users.users.me = {
          isNormalUser = true;
          initialPassword = "asdf";
          extraGroups = [ "wheel" ];
        };
        security.sudo.wheelNeedsPassword = false;
        services.getty.autologinUser = "me";
        console.keyMap = "de";
        system.stateVersion = "nixos-24.11";
      };
    in {
      nixosConfigurations = {
        # 👉 Just using my fork to build the system.
        # ✅ This works. But then I always have to rebase manually, which is tiring. 😪
        viaFork = inputs.nixpkgs-yann.lib.nixosSystem {
          system = "x86_64-linux";
          modules = [ osConfig ];
        };
        # 👉 Patching nixpkgs with GitHub's auto-generated PR patch
        # ✅ This below stunt also works, the reimporting of the nixpkgs flake seems to be the important thing, 
        # which many random posts about this topic seem to not mention.
        # https://discourse.nixos.org/t/proper-way-of-applying-patch-to-system-managed-via-flake/21073/26?u=nobodyinperson
        withApplyPatches = let
          nixpkgs' = import inputs.nixpkgs { system = "x86_64-linux"; };
          nixpkgsPatched' = nixpkgs'.applyPatches {
            name = "nixpkgs-wth-acme-tls-mode";
            src = inputs.nixpkgs;
            patches = [ inputs.nixpkgs-acme-tls-pr-patch ];
          };
          nixpkgsPatched = (import "${nixpkgsPatched'}/flake.nix").outputs {
            self = inputs.self;
          };
        in nixpkgsPatched.lib.nixosSystem {
          system = "x86_64-linux";
          modules = [ osConfig ];
        };
        # 👉 Via Blacklisting modules, as explained here:
        # https://github.com/NixOS/nixpkgs/blob/cceb51f2d43b57023cdd7e8c168b7eeab0779e53/nixos/doc/manual/development/replace-modules.section.md
        # 💥 Doesn't even build
        withDisabledModules = inputs.nixpkgs.lib.nixosSystem {
          system = "x86_64-linux";
          modules = [
            {
              disabledModules = [
                # disable acme and nginx modules (the ones touched in PR#340136)
                "security/acme/default.nix" # 👈ℹ️ UPDATE: solution is to remove the `/default.nix` here, see error message
                "services/web-servers/nginx/default.nix"
              ];
              imports = [
                # reimport acme and nginx modules (the ones touched in PR#340136)
                "${inputs.nixpkgs-yann}/nixos/modules/services/web-servers/nginx/default.nix"
                # `nix build .#nixosConfigurations.withDisabledModules.config.system.build.vm` says:
                # 💥 error: The option `virtualisation.vmVariant.security.acme.certs' in `/nix/store/lb6ypkpf38qsd0p4gc5nqvb97s2brh5h-source/nixos/modules/security/acme' is already declared in `/nix/store/j5qxlcx2zr0bs6nvw45f8j3yf2ijfa48-source/nixos/modules/security/acme/default.nix'
                "${inputs.nixpkgs-yann}/nixos/modules/security/acme/default.nix"
              ];
            }
            osConfig
          ];
        };
      };
    };
}

While writing this MVE, I got the applyPatches-approach to work, of which there is frustratingly few examples out there (and the ones existing often result in missing builtins.currentSystem or lib.nixosSystem errors etc.), so I’ll just leave this here.

However, I’d like to understand why the disabledModules-approach doesn’t work. I blacklist the relevant modules, then reimport them. Why does it somehow import the acme module twice and thus throw an already declared error?

❯ nix build .#nixosConfigurations.withDisabledModules.config.system.build.vm
error:
       … while evaluating 'strict' to select 'drvPath' on it
         at /builtin/derivation.nix:1:552:
       … while calling the 'derivationStrict' builtin
         at /builtin/derivation.nix:1:208:
       (stack trace truncated; use '--show-trace' to show the full trace)

       error: The option `virtualisation.vmVariant.security.acme.certs' in `/nix/store/lb6ypkpf38qsd0p4gc5nqvb97s2brh5h-source/nixos/modules/security/acme' is already declared in `/nix/store/j5qxlcx2zr0bs6nvw45f8j3yf2ijfa48-source/nixos/modules/security/acme/default.nix'.

Try explicitly prefixing with modulesPath, e.g. "${modulesPath}/security/acme/default.nix". (And since modulesPath is a module arg, it has to be expressed as such.)

Thanks. Same error though. (this is what I did)

Ah, the error doesn’t actually mention default.nix wrt acme, so the first entry should be "${modulesPath}/security/acme" instead of "${modulesPath}/security/acme/default.nix"

That also corresponds with:

1 Like

Thanks, that was it. I did try security/acme before and it failed, so very weird that it works now. Even without modulesPath in it. :person_shrugging:

1 Like