Adding rules to udev: easy or a consenus best practice

So I have a FiiO FA15, and I would like to be able to program it from a NixOS box.

Bus 003 Device 016: ID 2972:0104 FiiO Electronics Technology FIIO KA15

I have some custom rules on my Debian box, and it works perfectly. But on NixOS, you cannot just drop a ruleset into /etc/udev/rules.d and I can understand the rational for that.

HIDEvent[15:09:48] Failed to open '/dev/hidraw3': FILE_ERROR_ACCESS_DENIED
HIDEvent[15:09:48] Access denied opening device read-write, trying read-only.
HIDUser[15:09:40] HID device added: vendorId=10610, productId=260, name='FIIO KA15', serial='', deviceIds=['/sys/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2:1.0/0003:2972:0104.0033/hidraw/hidraw3'], reportDescriptor='BQwJAaEBhQMVACUDdQGVAgnpCeqBApUECc0Jzwm1CbaBApUCgQEGAf+FS3UIlT8JAYEDlT8JApEChQd1CJU/CQOBA5U/CQSRAsA='
USBUser[15:09:40] USB device added: path=/dev/bus/usb/003/013 vendor=10610 "FIIO", product=260 "FIIO KA15", serial="", guid=e9ddc125-8df6-459a-ba6c-145b320a26aaa
# hid_listen
KERNEL=="hidraw*", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

I understand that the plugdev group is considered deprecated on NixOS, and there is maybe a trezord group instead but that does not exist on my system. I have also seen ${cfg.group} or simply dropping GROUP altogether.

There is services.udev.extraRules which will apparently drop it into 99-local.rules, but I am not sure that is ideal.

There is services.udev.packages which lets you specify the contents and target of your rule.

But I would like to see a more modular approach and ask to see what if any the consensus is.

There is a flake approach and a module approach, but it seems like the flake approach would be better.

I will for now probably just stick in a services.udev.packages to try to get it working. But what would be nice would be a guide to doing this in a nice way that is scalable. I had to do this with a keychrome on Debian and Void and it was fairly simple. It ought to be just as simple, well as simple as possible, for a NixOS newbie to do it on NixOS. I think NixOS would benefit from more easy to follow guides and documentation. Haskell often suffers from the same issue, just throw the type sigs out there and declare that is all you need to understand.

Thank you for your thoughts

You can add the rule to services.udev.extraRules, although you should consider scoping the rule to the vendor’/product you are targeting.

  services.udev.extraRules = ''
    # hid_listen
    KERNEL=="hidraw*", ATTR{idVendor}=="2972", ATTR{idProduct}=="0104", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
  '';

Yes I get that, I mentioned that approach, but sticking a bunch of stuff into 99-local.rules seems less than ideal. And again, the plugdev group does not exist on NixOS, and as far I can tell, is considered a legacy Debian approach

This is a config file for configuring a keyboard for web driver access; it’s an example of how to create a package derivation that writes to a different rules.d/ file and then add it to the udev service so it gets pulled into the system image.

{
  config,
  lib,
  pkgs,
  ...
}:

{
  # Provides support for the DAREU Cool 60 keyboard
  # in particular, enables udev uaccess so that the web driver can have access to the keyboard
  # TODO: there may be security implications to enabling this

  # see https://github.com/NixOS/nixpkgs/issues/308681 for reasons why
  # services.udev.extraRules will not work for uaccess

  services.udev.packages = [
    (pkgs.writeTextFile {
      name = "udev-rules-keyboard-dareu-cool60";
      text = ''
        SUBSYSTEM=="hidraw", ATTRS{idVendor}=="260d", ATTRS{idProduct}=="0117",
TAG+="uaccess"
        SUBSYSTEM=="usb", ATTRS{idVendor}=="260d", ATTRS{idProduct}=="0117", TAG+="uaccess"
      '';
      destination = "/etc/udev/rules.d/70-keyboard-dareu-cool60-webdriver-access.rules";
    })
  ];

}

You hadn’t yet edited in that mention when I first replied.

You can create any new groups you like, or scope it to wheel instead, or create a plugdev group. and add yourself to it.

Thank you. I did something like this:

  system.udev.packages = [ 
      ( pkgs.writeTextFile {
        name = "fiio-udev-rules";
        text = ''
          KERNEL=="hidraw*", ATTR{idVendor}=="2972", ATTR{idProduct}=="0104", MODE="0660", TAG+="uaccess", TAG+="udev-acl"
        '';
        destination = "/etc/udev/rules.d/50-fiio.rules";
      })
    ];

and it indeed produced

petrockette@faline:~$ cat /etc/udev/rules.d/50-fiio.rules 
KERNEL=="hidraw*", ATTR{idVendor}=="2972", ATTR{idProduct}=="0104", MODE="0660", TAG+="uaccess", TAG+="udev-acl"

but even a reboot or fussing with udevadm did not help. Manually doing a chmod on /dev/hidraw* did work, so now we have identified the issue.

I am clearly doing something wrong here, but I don’t know what. The vendor and product id are correct.

I could create a plugdev group and add myself to it. I understand that is not in the direction the NixOS community desires to go, but one small step at time.

aaaaahhh

omg. it is ATTRS not ATTR

3 Likes

ok. now just to understand the nice way to do this at scale, you know with many different rule sets and also make it easy for people to do this themselves. Because I know there must be an easy and elegant way to add this to NixOS

Thank you all!

There are options like hardware.keyboard.qmk.enable that enable the qmk module.

You could contribute a similar module for FiiO hardware.

That might just do it already.

It worked on the Debian box because I had already dropped in the udev rules for a qmk keychron which I was just playing around with, which also relied on access to /dev/hidraw*

But of course that is not exactly discoverable.

I think people, even me, should collaborate to put together a guide for something like this. We should not have to keep reinventing the wheel

Why? Given the way nixos works, it really doesn’t seem important which file the rules are in. The modularity is provided at the nixos option layer.

1 Like

In principle, I agree. However in the specific case of udev rules needing uaccess, my code snippet above has a link to

where I learned that “TAG uaccess” specifically needs to be set before e.g. /nix/store/a8avqfxd649rfgfpqldja6v38ljb8fj5-systemd-260.1/lib/udev/rules.d/73-seat-late.rules, since that is what converts the TAG into actual FACLs with “RUN builtin uaccess”. In other words, “TAG uaccess” needs to be set in a file lexically before 73-seat-late.rules, so 99-local.rules doesn’t cut it for this use case.

EDIT: see also Udev regression / recommended way for udev rules? for more discussion on uaccess.

1 Like

Maybe udev should have options similar to systemd.tmpfiles.settings that allow you to choose the filename (and thus the order) in which the rules go?

That’s above my paygrade; I definitely don’t know enough about udev or the way it’s maintained in NixOS specifically to advocate for exactly what the interface should be.

However, the linked github issue had a user who 2 weeks ago showed code for module that apparently lets you do something like

{
  services.udev.orderedRules."70-kiisu" = {
    text = ''
      #Flipper Zero serial port
      SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{manufacturer}=="Flipper Devices Inc.", TAG+="uaccess"
      #Flipper Zero DFU
      SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", ATTRS{manufacturer}=="STMicroelectronics", TAG+="uaccess"
      #Flipper ESP32s2 BlackMagic
      SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="40??", ATTRS{manufacturer}=="Flipper Devices Inc.", TAG+="uaccess"
    '';
  };
}

Specifically this comment has the module definition.

1 Like

That is very similar to the flake implemention I mentioned above

Yep, agreed that it’s not too different.

So I suppose to bring it back your original question about consensus best practice; it doesn’t seem like there’s a clear community preference between udev.packages, a flake approach, or a module approach. extraRules won’t work for this case because of rules ordering issues.

udev.packages is already in NixOS, flakes are not yet stabilized and thus not suitable for inclusion into the actual NixOS tree for implementation of options, tejing has suggested that perhaps a module could be provided by NixOS to handle this more ergonomically. My read is that a PR to bring the module would mostly need to agree on an interface; the actual implementation is clearly reasonably easy and similar across users.

Hello, author of the comment module here.

I was unaware of how the flake implements the functionality or that it actually exists :smiley: they’re very similar indeed and the fact that the original module is in a flake is no hindrance at all, as it’d just be the block inside nixosModule that’d be ported into nixpkgs.

I may be biased and not properly informed, but personally I prefer my approach for the following reasons:

  1. Attribute sets are easier to discover than lists; exploring a list of attribute sets in the repl just yields a [ { ... } { ... } ] with the literal dots while attributes can be easily explored.
  2. Merging rules for attributes makes more sense and should from my understanding add safeguards. E.g. if you add multiple entries with the same name via the module from the flake, you’ll get a file conflict warning during evaluation and will get only one of the rulesets in rules.d, depending on list ordering, while the attribute solution will either fail during evaluation or create one file with all rules merged.

All in all both solutions are good enough I think, I prefer mine and actually use it from within a flake, but I stripped everything but the module itself so people can just copy it easily into their configuration instead of adding yet another input to their system flakes.

Here is a related thread.

One neat bit from that thread is TLATER’s solution includes a check to verify the custom rule:

I’d like some built-in module options to define custom udev rules too. I quite like the interface that rnhmjoj suggested so setting the priority isn’t essential (assuming there would be a sensible default) but can be easily adjusted.

Personally, I’m not 100% sure the priority attribute makes sense, but that’d be bikeshedding and @rnhmjoj is much more experienced. Maybe there’s something where you want to access a rule’s priority from somewhere else in the config and then it’d be nice to have without doing string magic. The check is a very nice addition though and you could argue that destination is useless for udev rules as they all go into rules.d anyways, so that parameter is probably useless

Fair! I can see advantages both ways.

I’ve just been having a look at the udev module and it looks to me like all the rules from services.udev.packages get checked already (see here), so that extra check would be superfluous.