185 lines
6.9 KiB
Nix
185 lines
6.9 KiB
Nix
{ 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 = ''
|
|
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 = "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 = "spool directory";
|
|
};
|
|
options.Anonuser = lib.mkOption {
|
|
type = lib.types.singleLineStr;
|
|
default = "root";
|
|
description = ''
|
|
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 = "location of GhostScript binary";
|
|
};
|
|
};
|
|
|
|
instanceConfig = { name, config, ... }: {
|
|
options = {
|
|
enable = (lib.mkEnableOption "this cups-pdf instance") // { default = true; };
|
|
installPrinter = (lib.mkEnableOption ''
|
|
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 = ''
|
|
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 = ''
|
|
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 ''
|
|
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 = ''
|
|
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 ];
|
|
|
|
}
|