summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/printing
diff options
context:
space:
mode:
authorRyan Lahfa <masterancpp@gmail.com>2022-12-28 09:08:49 +0100
committerGitHub <noreply@github.com>2022-12-28 09:08:49 +0100
commit861c7b189cc8e2a3760be50730192379d9370bc6 (patch)
treed5c2bfbf9110b09def4dd3735b9bec1965ac5c22 /nixos/modules/services/printing
parentbf0f5bd42439d08d356b9cf9abb2d2cebfc5a46b (diff)
parent3f11bdb2e7128e5dc0622b405f5095ba588d18de (diff)
Merge pull request #182360 from Yarny0/cups-pdf
cups-pdf(-to-pdf): init
Diffstat (limited to 'nixos/modules/services/printing')
-rw-r--r--nixos/modules/services/printing/cups-pdf.nix185
1 files changed, 185 insertions, 0 deletions
diff --git a/nixos/modules/services/printing/cups-pdf.nix b/nixos/modules/services/printing/cups-pdf.nix
new file mode 100644
index 000000000000..07f24367132f
--- /dev/null
+++ b/nixos/modules/services/printing/cups-pdf.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+ # cups calls its backends as user `lp` (which is good!),
+ # but cups-pdf wants to be called as `root`, so it can change ownership of files.
+ # We add a suid wrapper and a wrapper script to trick cups into calling the suid wrapper.
+ # Note that a symlink to the suid wrapper alone wouldn't suffice, cups would complain
+ # > File "/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-cups-progs/lib/cups/backend/cups-pdf" has insecure permissions (0104554/uid=0/gid=20)
+
+ # wrapper script that redirects calls to the suid wrapper
+ cups-pdf-wrapper = pkgs.writeTextFile {
+ name = "${pkgs.cups-pdf-to-pdf.name}-wrapper.sh";
+ executable = true;
+ destination = "/lib/cups/backend/cups-pdf";
+ checkPhase = ''
+ ${pkgs.stdenv.shellDryRun} "$target"
+ ${lib.getExe pkgs.shellcheck} "$target"
+ '';
+ text = ''
+ #! ${pkgs.runtimeShell}
+ exec "${config.security.wrapperDir}/cups-pdf" "$@"
+ '';
+ };
+
+ # wrapped cups-pdf package that uses the suid wrapper
+ cups-pdf-wrapped = pkgs.buildEnv {
+ name = "${pkgs.cups-pdf-to-pdf.name}-wrapped";
+ # using the wrapper as first path ensures it is used
+ paths = [ cups-pdf-wrapper pkgs.cups-pdf-to-pdf ];
+ ignoreCollisions = true;
+ };
+
+ instanceSettings = name: {
+ freeformType = with lib.types; nullOr (oneOf [ int str path package ]);
+ # override defaults:
+ # inject instance name into paths,
+ # also avoid conflicts between user names and special dirs
+ options.Out = lib.mkOption {
+ type = with lib.types; nullOr singleLineStr;
+ default = "/var/spool/cups-pdf-${name}/users/\${USER}";
+ defaultText = "/var/spool/cups-pdf-{instance-name}/users/\${USER}";
+ example = "\${HOME}/cups-pdf";
+ description = lib.mdDoc ''
+ output directory;
+ `''${HOME}` will be expanded to the user's home directory,
+ `''${USER}` will be expanded to the user name.
+ '';
+ };
+ options.AnonDirName = lib.mkOption {
+ type = with lib.types; nullOr singleLineStr;
+ default = "/var/spool/cups-pdf-${name}/anonymous";
+ defaultText = "/var/spool/cups-pdf-{instance-name}/anonymous";
+ example = "/var/lib/cups-pdf";
+ description = lib.mdDoc "path for anonymously created PDF files";
+ };
+ options.Spool = lib.mkOption {
+ type = with lib.types; nullOr singleLineStr;
+ default = "/var/spool/cups-pdf-${name}/spool";
+ defaultText = "/var/spool/cups-pdf-{instance-name}/spool";
+ example = "/var/lib/cups-pdf";
+ description = lib.mdDoc "spool directory";
+ };
+ options.Anonuser = lib.mkOption {
+ type = lib.types.singleLineStr;
+ default = "root";
+ description = lib.mdDoc ''
+ User for anonymous PDF creation.
+ An empty string disables this feature.
+ '';
+ };
+ options.GhostScript = lib.mkOption {
+ type = with lib.types; nullOr path;
+ default = lib.getExe pkgs.ghostscript;
+ defaultText = lib.literalExpression "lib.getExe pkgs.ghostscript";
+ example = lib.literalExpression ''''${pkgs.ghostscript}/bin/ps2pdf'';
+ description = lib.mdDoc "location of GhostScript binary";
+ };
+ };
+
+ instanceConfig = { name, config, ... }: {
+ options = {
+ enable = (lib.mkEnableOption (lib.mdDoc "this cups-pdf instance")) // { default = true; };
+ installPrinter = (lib.mkEnableOption (lib.mdDoc ''
+ a CUPS printer queue for this instance.
+ The queue will be named after the instance and will use the {file}`CUPS-PDF_opt.ppd` ppd file.
+ If this is disabled, you need to add the queue yourself to use the instance
+ '')) // { default = true; };
+ confFileText = lib.mkOption {
+ type = lib.types.lines;
+ description = lib.mdDoc ''
+ This will contain the contents of {file}`cups-pdf.conf` for this instance, derived from {option}`settings`.
+ You can use this option to append text to the file.
+ '';
+ };
+ settings = lib.mkOption {
+ type = lib.types.submodule (instanceSettings name);
+ default = {};
+ example = {
+ Out = "\${HOME}/cups-pdf";
+ UserUMask = "0033";
+ };
+ description = lib.mdDoc ''
+ Settings for a cups-pdf instance, see the descriptions in the template config file in the cups-pdf package.
+ The key value pairs declared here will be translated into proper key value pairs for {file}`cups-pdf.conf`.
+ Setting a value to `null` disables the option and removes it from the file.
+ '';
+ };
+ };
+ config.confFileText = lib.pipe config.settings [
+ (lib.filterAttrs (key: value: value != null))
+ (lib.mapAttrs (key: builtins.toString))
+ (lib.mapAttrsToList (key: value: "${key} ${value}\n"))
+ lib.concatStrings
+ ];
+ };
+
+ cupsPdfCfg = config.services.printing.cups-pdf;
+
+ copyConfigFileCmds = lib.pipe cupsPdfCfg.instances [
+ (lib.filterAttrs (name: lib.getAttr "enable"))
+ (lib.mapAttrs (name: lib.getAttr "confFileText"))
+ (lib.mapAttrs (name: pkgs.writeText "cups-pdf-${name}.conf"))
+ (lib.mapAttrsToList (name: confFile: "ln --symbolic --no-target-directory ${confFile} /var/lib/cups/cups-pdf-${name}.conf\n"))
+ lib.concatStrings
+ ];
+
+ printerSettings = lib.pipe cupsPdfCfg.instances [
+ (lib.filterAttrs (name: lib.getAttr "enable"))
+ (lib.filterAttrs (name: lib.getAttr "installPrinter"))
+ (lib.mapAttrsToList (name: instance: (lib.mapAttrs (key: lib.mkDefault) {
+ inherit name;
+ model = "CUPS-PDF_opt.ppd";
+ deviceUri = "cups-pdf:/${name}";
+ description = "virtual printer for cups-pdf instance ${name}";
+ location = instance.settings.Out;
+ })))
+ ];
+
+in
+
+{
+
+ options.services.printing.cups-pdf = {
+ enable = lib.mkEnableOption (lib.mdDoc ''
+ the cups-pdf virtual pdf printer backend.
+ By default, this will install a single printer `pdf`.
+ but this can be changed/extended with {option}`services.printing.cups-pdf.instances`
+ '');
+ instances = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.submodule instanceConfig);
+ default.pdf = {};
+ example.pdf.settings = {
+ Out = "\${HOME}/cups-pdf";
+ UserUMask = "0033";
+ };
+ description = lib.mdDoc ''
+ Permits to raise one or more cups-pdf instances.
+ Each instance is named by an attribute name, and the attribute's values control the instance' configuration.
+ '';
+ };
+ };
+
+ config = lib.mkIf cupsPdfCfg.enable {
+ services.printing.enable = true;
+ services.printing.drivers = [ cups-pdf-wrapped ];
+ hardware.printers.ensurePrinters = printerSettings;
+ # the cups module will install the default config file,
+ # but we don't need it and it would confuse cups-pdf
+ systemd.services.cups.preStart = lib.mkAfter ''
+ rm -f /var/lib/cups/cups-pdf.conf
+ ${copyConfigFileCmds}
+ '';
+ security.wrappers.cups-pdf = {
+ group = "lp";
+ owner = "root";
+ permissions = "+r,ug+x";
+ setuid = true;
+ source = "${pkgs.cups-pdf-to-pdf}/lib/cups/backend/cups-pdf";
+ };
+ };
+
+ meta.maintainers = [ lib.maintainers.yarny ];
+
+}