From 387997f7b6f45b2163ab9505768851013e99793c Mon Sep 17 00:00:00 2001 From: Daniel Fainberg Date: Mon, 27 Jul 2026 10:40:36 +0200 Subject: [PATCH] feat(nixos): add opt-in printing and scanning module New `modules/printing-scanning.nix` exposes `local.printing.*` and `local.scanning.*` (CUPS drivers, SANE with a Canon BJNP `bjnpHosts` escape hatch, shared Avahi mDNS), all off by default so a consumer opts in and a server gets none of it. Imported from `modules/nixos.nix`. Co-Authored-By: Claude Opus 4.8 --- modules/nixos.nix | 2 +- modules/printing-scanning.nix | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 modules/printing-scanning.nix diff --git a/modules/nixos.nix b/modules/nixos.nix index 864d2a3..87fab4e 100644 --- a/modules/nixos.nix +++ b/modules/nixos.nix @@ -5,7 +5,7 @@ { config, lib, pkgs, ... }: { - imports = [ ./shutdown-debug.nix ]; + imports = [ ./shutdown-debug.nix ./printing-scanning.nix ]; nix.settings.experimental-features = [ "nix-command" "flakes" ]; # Deduplicate identical store paths via hardlinks. diff --git a/modules/printing-scanning.nix b/modules/printing-scanning.nix new file mode 100644 index 0000000..1858783 --- /dev/null +++ b/modules/printing-scanning.nix @@ -0,0 +1,80 @@ +# Printing and scanning, opt-in and off by default. Both are network-facing +# (CUPS, and mDNS discovery for WiFi devices), so a consumer that doesn't want +# them — a server, a hardened host — simply leaves the flags off and gets none +# of it: no avahi, no open firewall, no cups. Nothing to override. +# +# Printers take vendor drivers (the `drivers` option, never hard-coded). +# Scanners are driverless by default via sane-airscan (eSCL/WSD); the one +# exception is Canon's BJNP protocol, whose hosts are listed in `bjnpHosts`. +# Avahi (mDNS) is shared by both and comes up when either is enabled, so WiFi +# devices are discoverable. + +{ config, lib, pkgs, ... }: + +let + cfg = config.local; + netDiscovery = cfg.printing.enable || cfg.scanning.enable; +in +{ + options.local = { + printing = { + enable = lib.mkEnableOption "CUPS printing (WiFi/driverless-friendly)"; + drivers = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + example = lib.literalExpression "[ pkgs.gutenprint ]"; + description = "Extra CUPS driver packages (vendor/model specific)."; + }; + }; + scanning = { + enable = + lib.mkEnableOption "SANE scanning with driverless network scanners (sane-airscan)"; + bjnpHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = lib.literalExpression ''[ "bjnp://192.168.1.50" ]''; + description = '' + Network scanners reachable over Canon's BJNP protocol, registered + with SANE's `pixma` backend (written to `/etc/sane.d/pixma.conf`). + BJNP is not driverless eSCL/WSD, so `sane-airscan` can't see these; + and BJNP broadcast auto-discovery is usually dropped by the firewall, + so list each device's `bjnp://` explicitly. + ''; + }; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.printing.enable { + services.printing = { + enable = true; + drivers = cfg.printing.drivers; + }; + }) + + # sane-airscan = driverless eSCL/WSD; hardware.sane also creates the + # `scanner` group + udev rules. Group membership is per-user → inventory. + # A pixma.conf built the same way the module builds net.conf (a text file + # dropped at /etc/sane.d/, merged by mkSaneConfig) registers BJNP hosts. + (lib.mkIf cfg.scanning.enable { + hardware.sane = { + enable = true; + extraBackends = [ pkgs.sane-airscan ] + ++ lib.optional (cfg.scanning.bjnpHosts != [ ]) (pkgs.writeTextFile { + name = "sane-pixma-net-conf"; + destination = "/etc/sane.d/pixma.conf"; + text = lib.concatMapStrings (h: h + "\n") cfg.scanning.bjnpHosts; + }); + }; + }) + + # mDNS so CUPS/SANE can discover WiFi printers and scanners on the LAN. + (lib.mkIf netDiscovery { + services.avahi = { + enable = true; + nssmdns4 = true; + openFirewall = true; + }; + }) + ]; +}