about summary refs log tree commit diff
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
parentbf0f5bd42439d08d356b9cf9abb2d2cebfc5a46b (diff)
parent3f11bdb2e7128e5dc0622b405f5095ba588d18de (diff)
Merge pull request #182360 from Yarny0/cups-pdf
cups-pdf(-to-pdf): init
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2305.section.xml9
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/printing/cups-pdf.nix185
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/cups-pdf.nix40
-rw-r--r--pkgs/misc/cups/drivers/cups-pdf-to-pdf/default.nix62
-rw-r--r--pkgs/top-level/all-packages.nix2
8 files changed, 302 insertions, 0 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
index 4837b29c585a1..b1c4745a3f590 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
@@ -39,6 +39,15 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://github.com/alexivkin/CUPS-PDF-to-PDF">cups-pdf-to-pdf</link>,
+          a pdf-generating cups backend based on
+          <link xlink:href="https://www.cups-pdf.de/">cups-pdf</link>.
+          Available as
+          <link linkend="opt-services.printing.cups-pdf.enable">services.printing.cups-pdf</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://github.com/junegunn/fzf">fzf</link>,
           a command line fuzzyfinder. Available as
           <link linkend="opt-programs.fzf.fuzzyCompletion">programs.fzf</link>.
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index b3354eec65fb5..6301579016742 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -18,6 +18,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [blesh](https://github.com/akinomyoga/ble.sh), a line editor written in pure bash. Available as [programs.bash.blesh](#opt-programs.bash.blesh.enable).
 
+- [cups-pdf-to-pdf](https://github.com/alexivkin/CUPS-PDF-to-PDF), a pdf-generating cups backend based on [cups-pdf](https://www.cups-pdf.de/). Available as [services.printing.cups-pdf](#opt-services.printing.cups-pdf.enable).
+
 - [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
 
 - [atuin](https://github.com/ellie/atuin), a sync server for shell history. Available as [services.atuin](#opt-services.atuin.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index a1e7cf01882e3..a41cae9f87756 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1028,6 +1028,7 @@
   ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
   ./services/printing/ipp-usb.nix
+  ./services/printing/cups-pdf.nix
   ./services/scheduling/atd.nix
   ./services/scheduling/cron.nix
   ./services/scheduling/fcron.nix
diff --git a/nixos/modules/services/printing/cups-pdf.nix b/nixos/modules/services/printing/cups-pdf.nix
new file mode 100644
index 0000000000000..07f24367132f5
--- /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 ];
+
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e385dfebebf3d..0a5144072e04a 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -155,6 +155,7 @@ in {
   coturn = handleTest ./coturn.nix {};
   couchdb = handleTest ./couchdb.nix {};
   cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {};
+  cups-pdf = handleTest ./cups-pdf.nix {};
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
   deluge = handleTest ./deluge.nix {};
diff --git a/nixos/tests/cups-pdf.nix b/nixos/tests/cups-pdf.nix
new file mode 100644
index 0000000000000..70d14f29e2e5d
--- /dev/null
+++ b/nixos/tests/cups-pdf.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "cups-pdf";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    environment.systemPackages = [ pkgs.poppler_utils ];
+    fonts.fonts = [ pkgs.dejavu_fonts ];  # yields more OCR-able pdf
+    services.printing.cups-pdf.enable = true;
+    services.printing.cups-pdf.instances = {
+      opt = {};
+      noopt.installPrinter = false;
+    };
+    hardware.printers.ensurePrinters = [{
+      name = "noopt";
+      model = "CUPS-PDF_noopt.ppd";
+      deviceUri = "cups-pdf:/noopt";
+    }];
+  };
+
+  # we cannot check the files with pdftotext, due to
+  # https://github.com/alexivkin/CUPS-PDF-to-PDF/issues/7
+  # we need `imagemagickBig` as it has ghostscript support
+
+  testScript = ''
+    from subprocess import run
+    machine.wait_for_unit("cups.service")
+    for name in ("opt", "noopt"):
+        text = f"test text {name}".upper()
+        machine.wait_until_succeeds(f"lpstat -v {name}")
+        machine.succeed(f"su - alice -c 'echo -e \"\n  {text}\" | lp -d {name}'")
+        # wait until the pdf files are completely produced and readable by alice
+        machine.wait_until_succeeds(f"su - alice -c 'pdfinfo /var/spool/cups-pdf-{name}/users/alice/*.pdf'")
+        machine.succeed(f"cp /var/spool/cups-pdf-{name}/users/alice/*.pdf /tmp/{name}.pdf")
+        machine.copy_from_vm(f"/tmp/{name}.pdf", "")
+        run(f"${pkgs.imagemagickBig}/bin/convert -density 300 $out/{name}.pdf $out/{name}.jpeg", shell=True, check=True)
+        assert text.encode() in run(f"${lib.getExe pkgs.tesseract} $out/{name}.jpeg stdout", shell=True, check=True, capture_output=True).stdout
+  '';
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+})
diff --git a/pkgs/misc/cups/drivers/cups-pdf-to-pdf/default.nix b/pkgs/misc/cups/drivers/cups-pdf-to-pdf/default.nix
new file mode 100644
index 0000000000000..a26216cbc7273
--- /dev/null
+++ b/pkgs/misc/cups/drivers/cups-pdf-to-pdf/default.nix
@@ -0,0 +1,62 @@
+{ lib
+, stdenv
+, fetchFromGitHub
+, cups
+, coreutils
+, nixosTests
+}:
+
+stdenv.mkDerivation rec {
+  pname = "cups-pdf-to-pdf";
+  version = "unstable-2021-12-22";
+
+  src = fetchFromGitHub {
+    owner = "alexivkin";
+    repo = "CUPS-PDF-to-PDF";
+    rev = "c14428c2ca8e95371daad7db6d11c84046b1a2d4";
+    hash = "sha256-pa4PFf8OAFSra0hSazmKUfbMYL/cVWvYA1lBf7c7jmY=";
+  };
+
+  buildInputs = [ cups ];
+
+  postPatch = ''
+    sed -r 's|(gscall, size, ")cp |\1${coreutils}/bin/cp |' cups-pdf.c -i
+  '';
+
+  # gcc command line is taken from original cups-pdf's README file
+  # https://fossies.org/linux/cups-pdf/README
+  # however, we replace gcc with $CC following
+  # https://nixos.org/manual/nixpkgs/stable/#sec-darwin
+  buildPhase = ''
+    runHook preBuild
+    $CC -O9 -s cups-pdf.c -o cups-pdf -lcups
+    runHook postBuild
+  '';
+
+  installPhase = ''
+    runHook preInstall
+    install -Dt $out/lib/cups/backend cups-pdf
+    install -Dm 0644 -t $out/etc/cups cups-pdf.conf
+    install -Dm 0644 -t $out/share/cups/model *.ppd
+    runHook postInstall
+  '';
+
+  passthru.tests.vmtest = nixosTests.cups-pdf;
+
+  meta = with lib; {
+    description = "A CUPS backend that turns print jobs into searchable PDF files";
+    homepage = "https://github.com/alexivkin/CUPS-PDF-to-PDF";
+    license = licenses.gpl2Only;
+    maintainers = [ maintainers.yarny ];
+    longDescription = ''
+      cups-pdf is a CUPS backend that generates a PDF file for each print job and puts this file
+      into a folder on the local machine such that the print job's owner can access the file.
+
+      https://www.cups-pdf.de/
+
+      cups-pdf-to-pdf is a fork of cups-pdf which tries hard to preserve the original text of the print job by avoiding rasterization.
+
+      Note that in order to use this package, you have to make sure that the cups-pdf program is called with root privileges.
+    '';
+  };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 0ed5d0aa98557..ecc617a9e0288 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -36718,6 +36718,8 @@ with pkgs;
 
   cups-dymo = callPackage ../misc/cups/drivers/dymo {};
 
+  cups-pdf-to-pdf = callPackage ../misc/cups/drivers/cups-pdf-to-pdf {};
+
   cups-toshiba-estudio = callPackage ../misc/cups/drivers/estudio {};
 
   cups-zj-58 =  callPackage ../misc/cups/drivers/zj-58 { };