{ config, lib, pkgs, ... }:
with lib;
let
inherit (pkgs) cups cups-pk-helper cups_filters;
cfg = config.services.printing;
avahiEnabled = config.services.avahi.enable;
polkitEnabled = config.security.polkit.enable;
additionalBackends = pkgs.runCommand "additional-cups-backends" { }
''
mkdir -p $out
if [ ! -e ${cups}/lib/cups/backend/smb ]; then
mkdir -p $out/lib/cups/backend
ln -sv ${pkgs.samba}/bin/smbspool $out/lib/cups/backend/smb
fi
# Provide support for printing via HTTPS.
if [ ! -e ${cups}/lib/cups/backend/https ]; then
mkdir -p $out/lib/cups/backend
ln -sv ${cups}/lib/cups/backend/ipp $out/lib/cups/backend/https
fi
'';
# Here we can enable additional backends, filters, etc. that are not
# part of CUPS itself, e.g. the SMB backend is part of Samba. Since
# we can't update ${cups}/lib/cups itself, we create a symlink tree
# here and add the additional programs. The ServerBin directive in
# cupsd.conf tells cupsd to use this tree.
bindir = pkgs.buildEnv {
name = "cups-progs";
paths = cfg.drivers;
pathsToLink = [ "/lib/cups" "/share/cups" "/bin" "/etc/cups" ];
postBuild = cfg.bindirCmds;
ignoreCollisions = true;
};
in
{
###### interface
options = {
services.printing = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable printing support through the CUPS daemon.
'';
};
listenAddresses = mkOption {
type = types.listOf types.str;
default = [ "127.0.0.1:631" ];
example = [ "*:631" ];
description = ''
A list of addresses and ports on which to listen.
'';
};
bindirCmds = mkOption {
type = types.lines;
internal = true;
default = "";
description = ''
Additional commands executed while creating the directory
containing the CUPS server binaries.
'';
};
defaultShared = mkOption {
type = types.bool;
default = false;
description = ''
Specifies whether local printers are shared by default.
'';
};
browsing = mkOption {
type = types.bool;
default = false;
description = ''
Specifies whether shared printers are advertised.
'';
};
webInterface = mkOption {
type = types.bool;
default = true;
description = ''
Specifies whether the web interface is enabled.
'';
};
cupsdConf = mkOption {
type = types.lines;
default = "";
example =
''
BrowsePoll cups.example.com
LogLevel debug
'';
description = ''
The contents of the configuration file of the CUPS daemon
(cupsd.conf).
'';
};
cupsFilesConf = mkOption {
type = types.lines;
default = "";
description = ''
The contents of the configuration file of the CUPS daemon
(cups-files.conf).
'';
};
extraConf = mkOption {
type = types.lines;
default = "";
example =
''
BrowsePoll cups.example.com
LogLevel debug
'';
description = ''
Extra contents of the configuration file of the CUPS daemon
(cupsd.conf).
'';
};
clientConf = mkOption {
type = types.lines;
default = "";
example =
''
ServerName server.example.com
Encryption Never
'';
description = ''
The contents of the client configuration.
(client.conf)
'';
};
browsedConf = mkOption {
type = types.lines;
default = "";
example =
''
BrowsePoll cups.example.com
'';
description = ''
The contents of the configuration. file of the CUPS Browsed daemon
(cups-browsed.conf)
'';
};
snmpConf = mkOption {
type = types.lines;
default = ''
Address @LOCAL
'';
description = ''
The contents of /etc/cups/snmp.conf. See "man
cups-snmp.conf" for a complete description.
'';
};
drivers = mkOption {
type = types.listOf types.path;
example = literalExample "[ pkgs.splix ]";
description = ''
CUPS drivers to use. Drivers provided by CUPS, cups-filters, Ghostscript
and Samba are added unconditionally.
'';
};
tempDir = mkOption {
type = types.path;
default = "/tmp";
example = "/tmp/cups";
description = ''
CUPSd temporary directory.
'';
};
};
};
###### implementation
config = mkIf config.services.printing.enable {
users.extraUsers = singleton
{ name = "cups";
uid = config.ids.uids.cups;
group = "lp";
description = "CUPS printing services";
};
environment.systemPackages = [ cups ] ++ optional polkitEnabled cups-pk-helper;
environment.etc."cups/client.conf".text = cfg.clientConf;
environment.etc."cups/cups-files.conf".text = cfg.cupsFilesConf;
environment.etc."cups/cupsd.conf".text = cfg.cupsdConf;
environment.etc."cups/cups-browsed.conf".text = cfg.browsedConf;
environment.etc."cups/snmp.conf".text = cfg.snmpConf;
services.dbus.packages = [ cups ] ++ optional polkitEnabled cups-pk-helper;
# Cups uses libusb to talk to printers, and does not use the
# linux kernel driver. If the driver is not in a black list, it
# gets loaded, and then cups cannot access the printers.
boot.blacklistedKernelModules = [ "usblp" ];
systemd.packages = [ cups ];
systemd.services.cups =
{ wantedBy = [ "multi-user.target" ];
wants = [ "network.target" ];
after = [ "network.target" ];
path = [ cups ];
preStart =
''
mkdir -m 0755 -p /etc/cups
mkdir -m 0700 -p /var/cache/cups
mkdir -m 0700 -p /var/spool/cups
mkdir -m 0755 -p ${cfg.tempDir}
'';
restartTriggers =
[ config.environment.etc."cups/cups-files.conf".source
config.environment.etc."cups/cupsd.conf".source
];
};
systemd.services.cups-browsed = mkIf avahiEnabled
{ description = "CUPS Remote Printer Discovery";
wantedBy = [ "multi-user.target" ];
wants = [ "cups.service" "avahi-daemon.service" ];
bindsTo = [ "cups.service" "avahi-daemon.service" ];
partOf = [ "cups.service" "avahi-daemon.service" ];
after = [ "cups.service" "avahi-daemon.service" ];
path = [ cups ];
serviceConfig.ExecStart = "${cups_filters}/bin/cups-browsed";
restartTriggers =
[ config.environment.etc."cups/cups-browsed.conf".source
];
};
services.printing.drivers =
[ cups pkgs.ghostscript pkgs.cups_filters additionalBackends
pkgs.perl pkgs.coreutils pkgs.gnused pkgs.bc pkgs.gawk pkgs.gnugrep
];
services.printing.cupsFilesConf =
''
SystemGroup root wheel
ServerBin ${bindir}/lib/cups
DataDir ${bindir}/share/cups
AccessLog syslog
ErrorLog syslog
PageLog syslog
TempDir ${cfg.tempDir}
# User and group used to run external programs, including
# those that actually send the job to the printer. Note that
# Udev sets the group of printer devices to `lp', so we want
# these programs to run as `lp' as well.
User cups
Group lp
'';
services.printing.cupsdConf =
''
LogLevel info
${concatMapStrings (addr: ''
Listen ${addr}
'') cfg.listenAddresses}
Listen /var/run/cups/cups.sock
SetEnv PATH ${bindir}/lib/cups/filter:${bindir}/bin:${bindir}/sbin
DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
Browsing ${if cfg.browsing then "Yes" else "No"}
WebInterface ${if cfg.webInterface then "Yes" else "No"}
DefaultAuthType Basic
Order allow,deny
Allow localhost
Order allow,deny
Allow localhost
AuthType Basic
Require user @SYSTEM
Order allow,deny
Allow localhost
Require user @OWNER @SYSTEM
Order deny,allow
AuthType Basic
Require user @SYSTEM
Order deny,allow
Require user @OWNER @SYSTEM
Order deny,allow
Order deny,allow
${cfg.extraConf}
'';
security.pam.services.cups = {};
};
}