1 { config, lib, pkgs, ... }:
4 udev = config.systemd.package;
6 cfg = config.services.udev;
8 initrdUdevRules = pkgs.runCommand "initrd-udev-rules" {} ''
9 mkdir -p $out/etc/udev/rules.d
10 for f in 60-cdrom_id 60-persistent-storage 75-net-description 80-drivers 80-net-setup-link; do
11 ln -s ${config.boot.initrd.systemd.package}/lib/udev/rules.d/$f.rules $out/etc/udev/rules.d
16 extraUdevRules = pkgs.writeTextFile {
17 name = "extra-udev-rules";
18 text = cfg.extraRules;
19 destination = "/etc/udev/rules.d/99-local.rules";
22 extraHwdbFile = pkgs.writeTextFile {
23 name = "extra-hwdb-file";
25 destination = "/etc/udev/hwdb.d/99-local.hwdb";
30 SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
34 # Mark dm devices as db_persist so that they are kept active after switching root
35 SUBSYSTEM=="block", KERNEL=="dm-[0-9]*", ACTION=="add|change", OPTIONS+="db_persist"
38 # Perform substitutions in all udev rules files.
39 udevRulesFor = { name, udevPackages, udevPath, udev, systemd, binPackages, initrdBin ? null }: pkgs.runCommand name
40 { preferLocalBuild = true;
41 allowSubstitutes = false;
42 packages = lib.unique (map toString udevPackages);
49 # Set a reasonable $PATH for programs called by udev rules.
50 echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules
52 # Add the udev rules from other packages.
53 for i in $packages; do
54 echo "Adding rules for package $i"
55 for j in $i/{etc,lib}/udev/rules.d/*; do
56 echo "Copying $j to $out/$(basename $j)"
57 cat $j > $out/$(basename $j)
61 # Fix some paths in the standard udev rules. Hacky.
62 for i in $out/*.rules; do
63 substituteInPlace $i \
64 --replace \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
65 --replace \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
66 --replace \"/sbin/blkid \"${pkgs.util-linux}/sbin/blkid \
67 --replace \"/bin/mount \"${pkgs.util-linux}/bin/mount \
68 --replace /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
69 --replace /usr/bin/basename ${pkgs.coreutils}/bin/basename 2>/dev/null
70 ${lib.optionalString (initrdBin != null) ''
71 substituteInPlace $i --replace '/run/current-system/systemd' "${lib.removeSuffix "/bin" initrdBin}"
75 echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
76 import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* |
77 sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
78 run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
79 sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
80 for i in $import_progs $run_progs; do
81 if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
83 echo "$i is called in udev rules but not installed by udev"
89 echo -n "Checking that all programs called by absolute paths in udev rules exist... "
90 import_progs=$(grep 'IMPORT{program}="/' $out/* |
91 sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
92 run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
93 sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
94 for i in $import_progs $run_progs; do
95 # if the path refers to /run/current-system/systemd, replace with config.systemd.package
96 if [[ $i == /run/current-system/systemd* ]]; then
97 i="${systemd}/''${i#/run/current-system/systemd/}"
100 if [[ ! -x $i ]]; then
102 echo "$i is called in udev rules but is not executable or does not exist"
108 filesToFixup="$(for i in "$out"/*; do
109 # list all files referring to (/usr)/bin paths, but allow references to /bin/sh.
110 grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || :
113 if [ -n "$filesToFixup" ]; then
114 echo "Consider fixing the following udev rules:"
115 echo "$filesToFixup" | while read localFile; do
116 remoteFile="origin unknown"
117 for i in ${toString binPackages}; do
118 for j in "$i"/*/udev/rules.d/*; do
119 [ -e "$out/$(basename "$j")" ] || continue
120 [ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
121 remoteFile="originally from $j"
126 grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \
127 | sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br'
129 echo "$localFile ($remoteFile) contains references to $refs."
134 # If auto-configuration is disabled, then remove
135 # udev's 80-drivers.rules file, which contains rules for
136 # automatically calling modprobe.
137 ${lib.optionalString (!config.boot.hardwareScan) ''
138 ln -s /dev/null $out/80-drivers.rules
142 hwdbBin = pkgs.runCommand "hwdb.bin"
143 { preferLocalBuild = true;
144 allowSubstitutes = false;
145 packages = lib.unique (map toString ([udev] ++ cfg.packages));
148 mkdir -p etc/udev/hwdb.d
149 for i in $packages; do
150 echo "Adding hwdb files for package $i"
151 for j in $i/{etc,lib}/udev/hwdb.d/*; do
152 ln -s $j etc/udev/hwdb.d/$(basename $j)
156 echo "Generating hwdb database..."
157 # hwdb --update doesn't return error code even on errors!
158 res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)"
160 [ -z "$(echo "$res" | egrep '^Error')" ]
161 mv etc/udev/hwdb.bin $out
164 compressFirmware = firmware:
166 inherit (config.boot.kernelPackages) kernelAtLeast;
168 if ! (firmware.compressFirmware or true) then
171 if kernelAtLeast "5.19" then pkgs.compressFirmwareZstd firmware
172 else if kernelAtLeast "5.3" then pkgs.compressFirmwareXz firmware
175 # Udev has a 512-character limit for ENV{PATH}, so create a symlink
176 # tree to work around this.
177 udevPath = pkgs.buildEnv {
180 pathsToLink = [ "/bin" "/sbin" ];
181 ignoreCollisions = true;
191 boot.hardwareScan = lib.mkOption {
192 type = lib.types.bool;
195 Whether to try to load kernel modules for all detected hardware.
196 Usually this does a good job of providing you with the modules
197 you need, but sometimes it can crash the system or cause other
203 enable = lib.mkEnableOption "udev, a device manager for the Linux kernel" // {
207 packages = lib.mkOption {
208 type = lib.types.listOf lib.types.path;
211 List of packages containing {command}`udev` rules.
213 {file}`«pkg»/etc/udev/rules.d` and
214 {file}`«pkg»/lib/udev/rules.d`
217 apply = map lib.getBin;
220 path = lib.mkOption {
221 type = lib.types.listOf lib.types.path;
224 Packages added to the {env}`PATH` environment variable when
225 executing programs from Udev rules.
227 coreutils, gnu{sed,grep}, util-linux and config.systemd.package are
228 automatically included.
232 extraRules = lib.mkOption {
235 ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1"
237 type = lib.types.lines;
239 Additional {command}`udev` rules. They'll be written
240 into file {file}`99-local.rules`. Thus they are
241 read and applied after all other rules.
245 extraHwdb = lib.mkOption {
248 evdev:input:b0003v05AFp8277*
249 KEYBOARD_KEY_70039=leftalt
250 KEYBOARD_KEY_700e2=leftctrl
252 type = lib.types.lines;
254 Additional {command}`hwdb` files. They'll be written
255 into file {file}`99-local.hwdb`. Thus they are
256 read after all other files.
262 hardware.firmware = lib.mkOption {
263 type = lib.types.listOf lib.types.package;
266 List of packages containing firmware files. Such files
267 will be loaded automatically if the kernel asks for them
268 (i.e., when it has detected specific hardware that requires
269 firmware to function). If multiple packages contain firmware
270 files with the same name, the first package in the list takes
271 precedence. Note that you must rebuild your system if you add
272 files to any of these directories.
274 apply = list: pkgs.buildEnv {
276 paths = map compressFirmware list;
277 pathsToLink = [ "/lib/firmware" ];
278 ignoreCollisions = true;
282 networking.usePredictableInterfaceNames = lib.mkOption {
284 type = lib.types.bool;
286 Whether to assign [predictable names to network interfaces](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/).
287 If enabled, interfaces
288 are assigned names that contain topology information
289 (e.g. `wlp3s0`) and thus should be stable
290 across reboots. If disabled, names depend on the order in
291 which interfaces are discovered by the kernel, which may
292 change randomly across reboots; for instance, you may find
293 `eth0` and `eth1` flipping
298 boot.initrd.services.udev = {
300 packages = lib.mkOption {
301 type = lib.types.listOf lib.types.path;
304 *This will only be used when systemd is used in stage 1.*
306 List of packages containing {command}`udev` rules that will be copied to stage 1.
308 {file}`«pkg»/etc/udev/rules.d` and
309 {file}`«pkg»/lib/udev/rules.d`
314 binPackages = lib.mkOption {
315 type = lib.types.listOf lib.types.path;
318 *This will only be used when systemd is used in stage 1.*
320 Packages to search for binaries that are referenced by the udev rules in stage 1.
321 This list always contains /bin of the initrd.
323 apply = map lib.getBin;
326 rules = lib.mkOption {
329 SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
331 type = lib.types.lines;
333 {command}`udev` rules to include in the initrd
334 *only*. They'll be written into file
335 {file}`99-local.rules`. Thus they are read and applied
336 after the essential initrd rules.
345 ###### implementation
347 config = lib.mkIf cfg.enable {
349 services.udev.extraRules = nixosRules;
351 services.udev.packages = [ extraUdevRules extraHwdbFile ];
353 services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.util-linux udev ];
355 boot.kernelParams = lib.mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
357 boot.initrd.extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
359 cat <<'EOF' > $out/99-local.rules
360 ${config.boot.initrd.services.udev.rules}
364 boot.initrd.services.udev.rules = nixosInitrdRules;
366 boot.initrd.systemd.additionalUpstreamUnits = [
367 "initrd-udevadm-cleanup-db.service"
368 "systemd-udevd-control.socket"
369 "systemd-udevd-kernel.socket"
370 "systemd-udevd.service"
371 "systemd-udev-settle.service"
372 "systemd-udev-trigger.service"
374 boot.initrd.systemd.storePaths = [
375 "${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd"
376 "${config.boot.initrd.systemd.package}/lib/udev/ata_id"
377 "${config.boot.initrd.systemd.package}/lib/udev/cdrom_id"
378 "${config.boot.initrd.systemd.package}/lib/udev/scsi_id"
379 "${config.boot.initrd.systemd.package}/lib/udev/rules.d"
380 ] ++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages;
382 # Generate the udev rules for the initrd
383 boot.initrd.systemd.contents = {
384 "/etc/udev/rules.d".source = udevRulesFor {
385 name = "initrd-udev-rules";
386 initrdBin = config.boot.initrd.systemd.contents."/bin".source;
387 udevPackages = config.boot.initrd.services.udev.packages;
388 udevPath = config.boot.initrd.systemd.contents."/bin".source;
389 udev = config.boot.initrd.systemd.package;
390 systemd = config.boot.initrd.systemd.package;
391 binPackages = config.boot.initrd.services.udev.binPackages ++ [ config.boot.initrd.systemd.contents."/bin".source ];
394 # Insert initrd rules
395 boot.initrd.services.udev.packages = [
397 (lib.mkIf (config.boot.initrd.services.udev.rules != "") (pkgs.writeTextFile {
398 name = "initrd-udev-rules";
399 destination = "/etc/udev/rules.d/99-local.rules";
400 text = config.boot.initrd.services.udev.rules;
405 "udev/rules.d".source = udevRulesFor {
407 udevPackages = cfg.packages;
408 systemd = config.systemd.package;
409 binPackages = cfg.packages;
410 inherit udevPath udev;
412 "udev/hwdb.bin".source = hwdbBin;
413 } // lib.optionalAttrs config.boot.modprobeConfig.enable {
414 # We don't place this into `extraModprobeConfig` so that stage-1 ramdisk doesn't bloat.
415 "modprobe.d/firmware.conf".text = "options firmware_class path=${config.hardware.firmware}/lib/firmware";
418 system.requiredKernelConfig = with config.lib.kernelConfig; [
420 (isYes "INOTIFY_USER")
424 system.activationScripts.udevd = lib.mkIf config.boot.kernel.enable ''
425 # The deprecated hotplug uevent helper is not used anymore
426 if [ -e /proc/sys/kernel/hotplug ]; then
427 echo "" > /proc/sys/kernel/hotplug
430 # Allow the kernel to find our firmware.
431 if [ -e /sys/module/firmware_class/parameters/path ]; then
432 echo -n "${config.hardware.firmware}/lib/firmware" > /sys/module/firmware_class/parameters/path
436 systemd.services.systemd-udevd =
437 { restartTriggers = [ config.environment.etc."udev/rules.d".source ];
443 (lib.mkRenamedOptionModule [ "services" "udev" "initrdRules" ] [ "boot" "initrd" "services" "udev" "rules" ])