diff --git a/modules/features/printing/default.nix b/modules/features/printing/default.nix new file mode 100644 index 0000000..0ab68bb --- /dev/null +++ b/modules/features/printing/default.nix @@ -0,0 +1,104 @@ +{...}: { + # CUPS printing + eSCL scanning for the networked EPSON ET-8550. + # + # Two queues, deliberately: + # EPSON_ET_8550 driverless / IPP Everywhere. The default. PDF is + # passed straight through and the printer's own RIP + # does the work. 51 page sizes, 26 borderless, and the + # only queue that can print CD/DVDs (MediaType Disc). + # EPSON_ET_8550_Photo Epson ESC/P-R. Host rasterises to ESC/P-R at 360dpi. + # 34 sizes, 12 borderless, no disc support - but it is + # the only way to reach Epson's named media/quality + # presets ("Epson Ultra Glossy-Max Quality" etc) that + # IPP Everywhere cannot express. Kept for photo A/B. + # + # Note both PPDs are 360x360 HWResolution - escpr2 is not higher resolution. + # + # Both queues use the mDNS name, not 10.10.20.62, so a DHCP lease change + # doesn't break them. services.avahi.nssmdns4 is what resolves it. + # Port 9100 is closed on this printer, so socket:// is not an option. + # + # cups-browsed is off: it would auto-create a third duplicate queue for the + # same printer. avahi stays on for mDNS resolution and scanner discovery. + flake.nixosModules.printing = { + config, + lib, + pkgs, + ... + }: { + programs.system-config-printer.enable = true; + + services.avahi = { + enable = true; + nssmdns4 = true; + }; + + services.printing = { + enable = true; + browsed.enable = false; + drivers = with pkgs; [ + epson-escpr2 + ]; + }; + + hardware.printers = { + ensureDefaultPrinter = "EPSON_ET_8550"; + ensurePrinters = [ + { + name = "EPSON_ET_8550"; + description = "EPSON ET-8550 (driverless)"; + location = "Network"; + deviceUri = "ipp://EPSONA70973.local:631/ipp/print"; + model = "everywhere"; + } + { + name = "EPSON_ET_8550_Photo"; + description = "EPSON ET-8550 (ESC/P-R photo presets)"; + location = "Network"; + deviceUri = "ipp://EPSONA70973.local:631/ipp/print"; + model = "epson-inkjet-printer-escpr2/Epson-ET-8550_Series-epson-escpr2-en.ppd"; + } + ]; + }; + + # hardware/printers.nix orders ensure-printers only after cups.service, but + # our deviceUri is an mDNS name and the driverless queue queries the printer + # while it is created. Run the generated script asynchronously so an offline + # printer cannot fail boot or nixos-rebuild, then retry until it is reachable. + systemd.services.ensure-printers = lib.mkIf (config.hardware.printers.ensurePrinters != [] && config.services.printing.enable) { + after = [ + "avahi-daemon.service" + "network-online.target" + "nss-lookup.target" + ]; + wants = [ + "avahi-daemon.service" + "network-online.target" + ]; + serviceConfig = { + Type = lib.mkForce "simple"; + Restart = "on-failure"; + RestartSec = "1min"; + }; + }; + + # eSCL/AirScan. The printer advertises _uscan._tcp with rs=eSCL and + # mopria-certified-scan=1.3, so sane-airscan drives it with no vendor + # backend. No scanner/lp group needed - there's no local udev device. + # + # sane-backends ships its own `escl` backend which also claims eSCL + # devices. Left enabled it double-lists the scanner and, worse, GETs the + # printer's web root and dumps the HTML response onto scanimage's stdout. + # Disabling it leaves sane-airscan as the only eSCL backend, which reports + # a far better device string (airscan:e0:EPSON ET-8550 Series). + hardware.sane = { + enable = true; + extraBackends = [pkgs.sane-airscan]; + disabledDefaultBackends = ["escl"]; + }; + + environment.systemPackages = with pkgs; [ + simple-scan + ]; + }; +}