diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix index 5a348665ae10..059bf3024ddf 100644 --- a/nixos/modules/hardware/printers.nix +++ b/nixos/modules/hardware/printers.nix @@ -21,98 +21,12 @@ let submodule ; - ensurePrinter = - p: - let - args = lib.cli.toCommandLineShellGNU { } ( - { - p = p.name; - v = p.deviceUri; - m = p.model; - } - // lib.optionalAttrs (p.location != null) { - L = p.location; - } - // lib.optionalAttrs (p.description != null) { - D = p.description; - } - // lib.optionalAttrs (p.ppdOptions != { }) { - o = lib.mapAttrsToList (name: value: "${name}=${value}") p.ppdOptions; - } - ); - in - '' - # shellcheck disable=SC2016 - ${pkgs.cups}/bin/lpadmin ${args} -E - ''; - - ensureDefaultPrinter = name: '' - ${pkgs.cups}/bin/lpadmin -d '${name}' - ''; - # "graph but not # or /" can't be implemented as regex alone due to missing lookahead support noInvalidChars = str: lib.all (c: c != "#" && c != "/") (lib.stringToCharacters str); printerName = (lib.types.addCheck (lib.types.strMatching "[[:graph:]]+") noInvalidChars) // { description = "printable string without spaces, # and /"; }; - getPrinters = - class: - lib.uniqueStrings ( - lib.concatLists ( - lib.textClosureList (lib.mapAttrs ( - _: - { - classes ? [ ], - printers ? [ ], - ... - }: - { - deps = classes; - text = printers; - } - ) cfg.ensureClasses) [ class ] - ) - ); - - mkClass = name: '' - lpadmin -p _tmp -v file:/dev/null - lpadmin -p _tmp -c ${escapeShellArg name} - lpadmin -x _tmp - ''; - - populateClass = - name: - { - description ? null, - location ? null, - ... - }: - lib.concatStringsSep "\n" [ - (lib.optionalString (location != null) '' - lpadmin -p ${escapeShellArg name} -L ${escapeShellArg location} - '') - - (lib.optionalString (description != null) '' - lpadmin -p ${escapeShellArg name} -D ${escapeShellArg description} - '') - ]; - - addPrintersToClass = - className: class: - let - printers = getPrinters className; - addToClass = printer: '' - lpadmin -p ${escapeShellArg printer} -c ${escapeShellArg className} - ''; - in - map addToClass printers; - - enableClass = className: '' - cupsenable ${escapeShellArg className} - cupsaccept ${escapeShellArg className} - ''; - # if CUPS is configured only to start when it's needed, we can kill the service # once the printer setup is done. However, if stateless is configured then we # can't shut down the servive without losing the printers (and classes) we @@ -272,163 +186,248 @@ in }; }; - config.assertions = lib.mkIf config.services.printing.enable [ - ( - let - referencedClasses = lib.unique ( - lib.flatten ( - lib.mapAttrsToList ( - _: - { - classes ? [ ], - ... - }: - classes - ) cfg.ensureClasses - ) - ); - definedClasses = lib.attrNames cfg.ensureClasses; - missingClasses = lib.subtractLists definedClasses referencedClasses; - getReferencingClasses = - missingClass: - (lib.mapAttrsToList (name: _: "`${name}`") ( - lib.filterAttrs ( - _: - { - classes ? [ ], - ... - }: - builtins.elem missingClass classes - ) cfg.ensureClasses - )); - in - { - assertion = missingClasses == [ ]; - message = '' - One or more values of `config.hardware.printers.ensureClasses..classes` - contained values not present in `config.hardware.printers.ensureClasses` + config = lib.mkIf config.services.printing.enable { + assertions = [ + ( + let + referencedClasses = lib.unique ( + lib.flatten ( + lib.mapAttrsToList ( + _: + { + classes ? [ ], + ... + }: + classes + ) cfg.ensureClasses + ) + ); + definedClasses = lib.attrNames cfg.ensureClasses; + missingClasses = lib.subtractLists definedClasses referencedClasses; + getReferencingClasses = + missingClass: + (lib.mapAttrsToList (name: _: "`${name}`") ( + lib.filterAttrs ( + _: + { + classes ? [ ], + ... + }: + builtins.elem missingClass classes + ) cfg.ensureClasses + )); + in + { + assertion = missingClasses == [ ]; + message = '' + One or more values of `config.hardware.printers.ensureClasses..classes` + contained values not present in `config.hardware.printers.ensureClasses` - Missing classes: - ${lib.concatMapStringsSep "\n" ( - p: " - `${p}` (included in class(es): ${lib.concatStringsSep ", " (getReferencingClasses p)})" - ) missingClasses} - ''; - } - ) - - ( - let - referencedPrinters = lib.unique ( - lib.flatten ( - lib.mapAttrsToList ( - _: - { - printers ? [ ], - ... - }: - printers - ) cfg.ensureClasses - ) - ); - definedPrinters = lib.catAttrs "name" cfg.ensurePrinters; - missingPrinters = lib.subtractLists definedPrinters referencedPrinters; - getReferencingClasses = - missingPrinter: - (lib.mapAttrsToList (name: _: "`${name}`") ( - lib.filterAttrs ( - _: - { - printers ? [ ], - ... - }: - builtins.elem missingPrinter printers - ) cfg.ensureClasses - )); - in - { - assertion = missingPrinters == [ ]; - message = '' - One or more values of `config.hardware.printers.ensureClasses..printers` - contained values not present in `config.hardware.printers.ensurePrinters` - - Missing printers: - ${lib.concatMapStringsSep "\n" ( - p: " - `${p}` (included in class(es): ${lib.concatStringsSep ", " (getReferencingClasses p)})" - ) missingPrinters} - ''; - } - ) - ]; - - config.systemd.services.ensure-printer-classes = - lib.mkIf (cfg.ensureClasses != { } && config.services.printing.enable) - { - description = "Ensure NixOS-configured CUPS classes"; - wantedBy = [ "multi-user.target" ]; - wants = [ - "cups.service" - ]; - after = [ - "cups.service" - "ensure-printers.service" - ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - path = with pkgs; [ cups ]; - script = - let - attrMap = fn: l: map fn (builtins.attrNames l); - classDefs = lib.pipe cfg.ensureClasses [ - (attrMap mkClass) - concatLines - ]; - classAdds = lib.pipe cfg.ensureClasses [ - (lib.mapAttrsToList addPrintersToClass) - lib.flatten - concatLines - ]; - populatedClasses = lib.pipe cfg.ensureClasses [ - (lib.mapAttrsToList populateClass) - concatLines - ]; - enables = lib.pipe cfg.ensureClasses [ - (attrMap enableClass) - concatLines - ]; - in - '' - #### CLASS DEFINITIONS #### - ${classDefs} - - #### ADDING PRINTERS TO CLASSES #### - ${classAdds} - - #### POPULATING CLASSES #### - ${populatedClasses} - - #### ENABLING CLASSES #### - ${enables} - - ${lib.optionalString ultimatelyStopCups "systemctl stop cups.service"} + Missing classes: + ${lib.concatMapStringsSep "\n" ( + p: " - `${p}` (included in class(es): ${lib.concatStringsSep ", " (getReferencingClasses p)})" + ) missingClasses} ''; + } + ) + + ( + let + referencedPrinters = lib.unique ( + lib.flatten ( + lib.mapAttrsToList ( + _: + { + printers ? [ ], + ... + }: + printers + ) cfg.ensureClasses + ) + ); + definedPrinters = lib.catAttrs "name" cfg.ensurePrinters; + missingPrinters = lib.subtractLists definedPrinters referencedPrinters; + getReferencingClasses = + missingPrinter: + (lib.mapAttrsToList (name: _: "`${name}`") ( + lib.filterAttrs ( + _: + { + printers ? [ ], + ... + }: + builtins.elem missingPrinter printers + ) cfg.ensureClasses + )); + in + { + assertion = missingPrinters == [ ]; + message = '' + One or more values of `config.hardware.printers.ensureClasses..printers` + contained values not present in `config.hardware.printers.ensurePrinters` + + Missing printers: + ${lib.concatMapStringsSep "\n" ( + p: " - `${p}` (included in class(es): ${lib.concatStringsSep ", " (getReferencingClasses p)})" + ) missingPrinters} + ''; + } + ) + ]; + + systemd.services.ensure-printer-classes = lib.mkIf (cfg.ensureClasses != { }) { + description = "Ensure NixOS-configured CUPS classes"; + wantedBy = [ "multi-user.target" ]; + wants = [ + "cups.service" + ]; + after = [ + "cups.service" + "ensure-printers.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + path = with pkgs; [ cups ]; + script = + let + mkClass = name: '' + lpadmin -p _tmp -v file:/dev/null + lpadmin -p _tmp -c ${escapeShellArg name} + lpadmin -x _tmp + ''; + + populateClass = + name: + { + description ? null, + location ? null, + ... + }: + lib.concatStringsSep "\n" [ + (lib.optionalString (location != null) '' + lpadmin -p ${escapeShellArg name} -L ${escapeShellArg location} + '') + + (lib.optionalString (description != null) '' + lpadmin -p ${escapeShellArg name} -D ${escapeShellArg description} + '') + ]; + + getPrinters = + class: + lib.uniqueStrings ( + lib.concatLists ( + lib.textClosureList (lib.mapAttrs ( + _: + { + classes ? [ ], + printers ? [ ], + ... + }: + { + deps = classes; + text = printers; + } + ) cfg.ensureClasses) [ class ] + ) + ); + + addPrintersToClass = + className: class: + let + printers = getPrinters className; + addToClass = printer: '' + lpadmin -p ${escapeShellArg printer} -c ${escapeShellArg className} + ''; + in + map addToClass printers; + + enableClass = className: '' + cupsenable ${escapeShellArg className} + cupsaccept ${escapeShellArg className} + ''; + + attrMap = fn: l: map fn (builtins.attrNames l); + classDefs = lib.pipe cfg.ensureClasses [ + (attrMap mkClass) + concatLines + ]; + classAdds = lib.pipe cfg.ensureClasses [ + (lib.mapAttrsToList addPrintersToClass) + lib.flatten + concatLines + ]; + populatedClasses = lib.pipe cfg.ensureClasses [ + (lib.mapAttrsToList populateClass) + concatLines + ]; + enables = lib.pipe cfg.ensureClasses [ + (attrMap enableClass) + concatLines + ]; + in + '' + #### CLASS DEFINITIONS #### + ${classDefs} + + #### ADDING PRINTERS TO CLASSES #### + ${classAdds} + + #### POPULATING CLASSES #### + ${populatedClasses} + + #### ENABLING CLASSES #### + ${enables} + + ${lib.optionalString ultimatelyStopCups "systemctl stop cups.service"} + ''; + }; + + systemd.services.ensure-printers = lib.mkIf (cfg.ensurePrinters != [ ]) { + description = "Ensure NixOS-configured CUPS printers"; + wantedBy = [ "multi-user.target" ]; + wants = [ "cups.service" ]; + after = [ "cups.service" ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; }; - config.systemd.services.ensure-printers = - lib.mkIf (cfg.ensurePrinters != [ ] && config.services.printing.enable) - { - description = "Ensure NixOS-configured CUPS printers"; - wantedBy = [ "multi-user.target" ]; - wants = [ "cups.service" ]; - after = [ "cups.service" ]; + script = + let + ensurePrinter = + p: + let + args = lib.cli.toCommandLineShellGNU { } ( + { + p = p.name; + v = p.deviceUri; + m = p.model; + } + // lib.optionalAttrs (p.location != null) { + L = p.location; + } + // lib.optionalAttrs (p.description != null) { + D = p.description; + } + // lib.optionalAttrs (p.ppdOptions != { }) { + o = lib.mapAttrsToList (name: value: "${name}=${value}") p.ppdOptions; + } + ); + in + '' + # shellcheck disable=SC2016 + ${pkgs.cups}/bin/lpadmin ${args} -E + ''; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - - script = lib.concatStringsSep "\n" [ + ensureDefaultPrinter = name: '' + ${pkgs.cups}/bin/lpadmin -d '${name}' + ''; + in + lib.concatStringsSep "\n" [ (lib.concatMapStrings ensurePrinter cfg.ensurePrinters) (lib.optionalString (cfg.ensureDefaultPrinter != null) ( ensureDefaultPrinter cfg.ensureDefaultPrinter @@ -439,5 +438,6 @@ in ultimatelyStopCups && !(config.systemd.services ? ensure-printer-classes) ) "systemctl stop cups.service") ]; - }; + }; + }; }