diff --git a/crates/fbuild-cli/src/cli/port_scan.rs b/crates/fbuild-cli/src/cli/port_scan.rs index 4ea3de22d..2c273d40a 100644 --- a/crates/fbuild-cli/src/cli/port_scan.rs +++ b/crates/fbuild-cli/src/cli/port_scan.rs @@ -82,7 +82,7 @@ pub enum PortAction { group: Option, /// Install the rules (asks for root with the desktop's polkit dialog, /// or sudo on a terminal). On NixOS they go to /run/udev/rules.d and - /// the permanent `services.udev.extraRules` form is printed. + /// the permanent `services.udev.packages` form is printed. #[arg(long, conflicts_with = "group")] install: bool, }, diff --git a/crates/fbuild-cli/src/cli/udev.rs b/crates/fbuild-cli/src/cli/udev.rs index 943afa80f..e4ba0005c 100644 --- a/crates/fbuild-cli/src/cli/udev.rs +++ b/crates/fbuild-cli/src/cli/udev.rs @@ -18,7 +18,7 @@ //! This module only *renders* text. Installing is deliberately left to the //! caller: on NixOS `/etc` is generated from declarative config, so a written //! file there is both out-of-band and liable to be clobbered. What those users -//! need is the content for `services.udev.extraRules`, not a mutation. +//! need is the content for a `services.udev.packages` entry, not a mutation. /// Group granted access by generated rules. `plugdev` is the conventional /// choice for pluggable non-storage hardware and, unlike `dialout`, does not @@ -110,8 +110,12 @@ pub fn render_udev_rules(vids: &[u16], group: Option<&str>) -> Option { )); } out.push_str("#\n"); - out.push_str("# On NixOS, paste the rules below into services.udev.extraRules instead;\n"); - out.push_str("# /etc there is generated and a hand-written file will not persist.\n"); + out.push_str("# On NixOS, ship the rules below as a services.udev.packages entry\n"); + out.push_str(&format!( + "# (pkgs.writeTextDir \"lib/udev/rules.d/{UDEV_RULES_FILENAME}\" ...) instead;\n" + )); + out.push_str("# /etc there is generated, and services.udev.extraRules lands in\n"); + out.push_str("# 99-local.rules, too late for uaccess.\n"); out.push('\n'); for vid in sorted { diff --git a/crates/fbuild-cli/src/cli/udev_install.rs b/crates/fbuild-cli/src/cli/udev_install.rs index 9db6c3e2f..25a6393ed 100644 --- a/crates/fbuild-cli/src/cli/udev_install.rs +++ b/crates/fbuild-cli/src/cli/udev_install.rs @@ -359,13 +359,18 @@ pub fn run_install() -> Result<()> { } /// NixOS: the `/run` install is lost at reboot; this is the permanent form. +/// It is a udev package rather than `services.udev.extraRules` because NixOS +/// writes `extraRules` to `99-local.rules`, after `73-seat-late.rules`, so +/// `uaccess` would never be applied (FastLED/fbuild#1654). pub fn nixos_snippet(rules: &str) -> String { let body: String = rules .lines() .filter(|l| l.starts_with("SUBSYSTEM==")) - .map(|l| format!(" {l}\n")) + .map(|l| format!(" {l}\n")) .collect(); - format!("services.udev.extraRules = ''\n{body}'';\n") + format!( + "services.udev.packages = [\n (pkgs.writeTextDir \"lib/udev/rules.d/{UDEV_RULES_FILENAME}\" ''\n{body} '')\n];\n" + ) } /// `/dev/bus/usb/BBB/DDD` nodes of attached devices from registry vendors diff --git a/crates/fbuild-cli/src/cli/udev_install_tests.rs b/crates/fbuild-cli/src/cli/udev_install_tests.rs index f986d2215..476b6301d 100644 --- a/crates/fbuild-cli/src/cli/udev_install_tests.rs +++ b/crates/fbuild-cli/src/cli/udev_install_tests.rs @@ -184,6 +184,6 @@ fn nixos_snippet_wraps_only_the_rules() { let s = nixos_snippet("# header\nSUBSYSTEM==\"usb\", X\n"); assert_eq!( s, - "services.udev.extraRules = ''\n SUBSYSTEM==\"usb\", X\n'';\n" + "services.udev.packages = [\n (pkgs.writeTextDir \"lib/udev/rules.d/70-fbuild.rules\" ''\n SUBSYSTEM==\"usb\", X\n '')\n];\n" ); }