How to create files in the `/etc/udev/rules.d/` directory?

There is a software that needs to check whether the specified file in this directory exists. I know that you can use services.udev.extraRules to add new rule content, but it can’t generate the file I need, so it can’t achieve the goal.

Try to use environment.etc."udev/rules.d/99-steelseries-rival.rules".text = ... to generate this file, but it still fails. This method will cause permission problems: ln: failed to create symbolic link'/nix/store/10snfh4rgdifz7jzda7j33q24jri743s-etc/etc/udev/rules.d/99-steelseries-rival.rules': Permission denied.

Note: I used sudo to run the nixos-rebuild switch command.

1 Like

okay, I had a play with this, and your right, it seems that udev files cannot be added in this way. :-(. This looks like it’s by design

However i see in nixpkgs/blob/nixos-20.09/nixos/modules/services/hardware/udev.nix

a bit of bash

      # Add the udev rules from other packages.
      for i in $packages; do
        echo "Adding rules for package $i"
        for j in $i/{etc,lib}/udev/rules.d/*; do
          echo "Copying $j to $out/$(basename $j)"
          cat $j > $out/$(basename $j)

which i think is wired to

    services.udev = {

      packages = mkOption {
        type = types.listOf types.path;
        default = [];
        description = ''
          List of packages containing <command>udev</command> rules.
          All files found in
          <filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
          will be included.
        apply = map getBin;

this seens to copy udev rules from nix/OS packages.

This has a down side that you will need to create a package, which contains your udev file then

sevices.udev.package = [ mypackage ];

this examples may help

  services.udev.packages = with pkgs; [

if you check out


you can see

which you may be able to adapt to your own package.

If you get stuck i can probably hacksome thing together for you if i have time… !

There maybe an easier way to do this…


You can create single file packages using trivial builders. For example:

writeTextFile {
  name = "my-udev-package";
  text = ''
  destination = "/lib/udev/rules.d/20-ledger.rules";

very nice!!!

Hey @jtojnar,

thank you for this hint. I still have problems seeing the whole picture, for adding the writeTextFile package to the services.udev.packages option.

I tried the following:

services.udev.packages = [
    pkgs.writeTextFile {
      name = "wally_udev";
      text = ''

      destination = "/etc/udev/rules.d/50-wally.rules";

However I get the following error:

building Nix...
building the system configuration...
error: A definition for option `services.udev.packages.[definition 2-entry 1]' is not of type `path'. Definition values:
- In `/home/tim/.dotfiles/system/configuration.nix': <function, args: {checkPhase?, destination?, executable?, name, text}>
(use '--show-trace' to show detailed location information)

Can you tell whats still wrong with how I apply this?

You are missing parentheses around the list item so you are adding two separate items: pkgs.writeTextFile function and the attribute set.

Indeed! I am obviously new to nix - works like a charm now! Thank you very much!

1 Like

Hi @elbart can you please post your working solution? I have the following and Nixos doesn’t complain but my Moonlander Keyboard can’t be flashed by wally-cli. Wally instructs me to push the reset button but it never proceeds…

That’s what i have right now:

  services.udev.packages = [
    (pkgs.writeTextFile {
      name = "wally_udev";
      text = ''
        # Rules for Oryx web flashing and live training
        # KERNEL=="hidraw*", ATTRS{idVendor}=="16c0", MODE="0664", GROUP="plugdev"
        # KERNEL=="hidraw*", ATTRS{idVendor}=="3297", MODE="0664", GROUP="plugdev"

        # Wally Flashing rules for the Moonlander and Planck EZ
        SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", \
            MODE:="0666", \
      destination = "/etc/udev/rules.d/50-zsa.rules";

I have hardware.keyboard.zsa.enable and it just works.

It creates the following /etc/udev/rules.d/50-wally.rules:

# Teensy rules for the Ergodox EZ Original / Shine / Glow
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", TAG+="uaccess"
KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", TAG+="uaccess"

# STM32 rules for the Moonlander and Planck EZ Standard / Glow
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", TAG+="uaccess", SYMLINK+="stm32_dfu"

Oh, and before I forget, changes to udev rules require a restart!

1 Like

Thanks a lot! i didn’t know about this option.

1 Like

hardware.keyboard.zsa.enable does not just work for me with a Moonlander keyboard. I had to look up the idVendor and idProduct using lsusb and udevadm (as described here). Then put this in my config:

services.udev.packages = [
      (pkgs.writeTextFile {
        name = "moonlander_udev";
        text = ''
          SUBSYSTEMS=="usb", ATTRS{idVendor}=="3297", ATTRS{idProduct}=="1969", MODE="0666", TAG+="uaccess", SYMLINK+="stm32_dfu", GROUP="plugdev"
        destination = "/etc/udev/rules.d/50-zsa.rules";

I also had to create the plugdev group and add myself to it:

  users.groups.plugdev = {};
  users.users.felix = {
    extraGroups = [ "wheel" "foo" "bar" "plugdev"];

After that, everything works like a charm. I’m not a Linux power user, much less a NixOS wizard. Just putting this here in case anyone else lands on this page while trying to get Oryx Live Training working on NixOS. (I don’t care about flashing for now, and am not sure whether this is sufficient.)

I’m not sure if the zsa-udev-rules package should be updated. I assume there might be a difference between different Moonlander generations? If more knowledgeable people chime in here, maybe we can make the ZSA keyboard experience on NixOS better! Seems to me there might be a relevant overlap between Moonlander users and NixOS users :slight_smile:

When using tags, like in the config that NixOS generates by default (see above), you don’t need to create additional groups and add your user to them.

What exactly does not work for you?

Wally or Oryx?

I have no problems with wally. And Oryx, well, I can pair, though I have no clue how to actually start the training then.

Ah, good to know. I proceeded by trial and error, and I first fixed the group which got my one step further in the right direction (no permission error in the web console). Will remove it now!

Oryx doesn’t work for me. Looking now in the wally repo I notice there is a legacy rules file which has the same idVendor and idProduct as my config, but the nix package refers to the non-legacy file. Would it make sense to add a hardware.keyboard.zsa.legacy attribute to zsa-udev-rules? (I could make a PR.)

Sorry to TA that its somehow getting overtaken by moonlander discussion…

i dont know about that, but as i was basically the person updating from the legacy to the other udev rule i just wanted to give reference to the discussion about changing the udev rules.

And the change intention was at least for jankaifer and me and Oryx was not running via the legacy rules. So thats kinda stange thats the opposite for you. Possibly we could also just try to ship both rulesets?

Do you possibly have a pre v21 firmware? the upstream commit mentions that the rules are for v21+ firmwares, which could explain the difference.

Hosted by Flying Circus.