diff options
-rw-r--r-- | nixos/doc/manual/from_md/release-notes/rl-2305.section.xml | 9 | ||||
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2305.section.md | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/printing/cups-pdf.nix | 185 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/cups-pdf.nix | 40 | ||||
-rw-r--r-- | pkgs/misc/cups/drivers/cups-pdf-to-pdf/default.nix | 62 | ||||
-rw-r--r-- | pkgs/top-level/all-packages.nix | 2 |
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 { }; |