1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 inherit (pkgs) cups cups-pk-helper cups-filters;
8
9 cfg = config.services.printing;
10
11 avahiEnabled = config.services.avahi.enable;
12 polkitEnabled = config.security.polkit.enable;
13
14 additionalBackends = pkgs.runCommand "additional-cups-backends" { }
15 ''
16 mkdir -p $out
17 if [ ! -e ${cups.out}/lib/cups/backend/smb ]; then
18 mkdir -p $out/lib/cups/backend
19 ln -sv ${pkgs.samba}/bin/smbspool $out/lib/cups/backend/smb
20 fi
21
22 # Provide support for printing via HTTPS.
23 if [ ! -e ${cups.out}/lib/cups/backend/https ]; then
24 mkdir -p $out/lib/cups/backend
25 ln -sv ${cups.out}/lib/cups/backend/ipp $out/lib/cups/backend/https
26 fi
27 '';
28
29 # Here we can enable additional backends, filters, etc. that are not
30 # part of CUPS itself, e.g. the SMB backend is part of Samba. Since
31 # we can't update ${cups.out}/lib/cups itself, we create a symlink tree
32 # here and add the additional programs. The ServerBin directive in
33 # cupsd.conf tells cupsd to use this tree.
34 bindir = pkgs.buildEnv {
35 name = "cups-progs";
36 paths =
37 [ cups.out additionalBackends cups-filters pkgs.ghostscript ]
38 ++ cfg.drivers;
39 pathsToLink = [ "/lib" "/share/cups" "/bin" ];
40 postBuild = cfg.bindirCmds;
41 ignoreCollisions = true;
42 };
43
44 writeConf = name: text: pkgs.writeTextFile {
45 inherit name text;
46 destination = "/etc/cups/${name}";
47 };
48
49 cupsFilesFile = writeConf "cups-files.conf" ''
50 SystemGroup root wheel
51
52 ServerBin ${bindir}/lib/cups
53 DataDir ${bindir}/share/cups
54 DocumentRoot ${cups.out}/share/doc/cups
55
56 AccessLog syslog
57 ErrorLog syslog
58 PageLog syslog
59
60 TempDir ${cfg.tempDir}
61
62 # User and group used to run external programs, including
63 # those that actually send the job to the printer. Note that
64 # Udev sets the group of printer devices to `lp', so we want
65 # these programs to run as `lp' as well.
66 User cups
67 Group lp
68
69 ${cfg.extraFilesConf}
70 '';
71
72 cupsdFile = writeConf "cupsd.conf" ''
73 ${concatMapStrings (addr: ''
74 Listen ${addr}
75 '') cfg.listenAddresses}
76 Listen /var/run/cups/cups.sock
77
78 SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
79
80 DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
81
82 Browsing ${if cfg.browsing then "Yes" else "No"}
83
84 WebInterface ${if cfg.webInterface then "Yes" else "No"}
85
86 ${cfg.extraConf}
87 '';
88
89 browsedFile = writeConf "cups-browsed.conf" cfg.browsedConf;
90
91 rootdir = pkgs.buildEnv {
92 name = "cups-progs";
93 paths = [
94 cupsFilesFile
95 cupsdFile
96 (writeConf "client.conf" cfg.clientConf)
97 (writeConf "snmp.conf" cfg.snmpConf)
98 ] ++ optional avahiEnabled browsedFile
99 ++ cfg.drivers;
100 pathsToLink = [ "/etc/cups" ];
101 ignoreCollisions = true;
102 };
103
104 filterGutenprint = pkgs: filter (pkg: pkg.meta.isGutenprint or false == true) pkgs;
105 containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
106 getGutenprint = pkgs: head (filterGutenprint pkgs);
107
108in
109
110{
111
112 ###### interface
113
114 options = {
115 services.printing = {
116
117 enable = mkOption {
118 type = types.bool;
119 default = false;
120 description = ''
121 Whether to enable printing support through the CUPS daemon.
122 '';
123 };
124
125 listenAddresses = mkOption {
126 type = types.listOf types.str;
127 default = [ "127.0.0.1:631" ];
128 example = [ "*:631" ];
129 description = ''
130 A list of addresses and ports on which to listen.
131 '';
132 };
133
134 bindirCmds = mkOption {
135 type = types.lines;
136 internal = true;
137 default = "";
138 description = ''
139 Additional commands executed while creating the directory
140 containing the CUPS server binaries.
141 '';
142 };
143
144 defaultShared = mkOption {
145 type = types.bool;
146 default = false;
147 description = ''
148 Specifies whether local printers are shared by default.
149 '';
150 };
151
152 browsing = mkOption {
153 type = types.bool;
154 default = false;
155 description = ''
156 Specifies whether shared printers are advertised.
157 '';
158 };
159
160 webInterface = mkOption {
161 type = types.bool;
162 default = true;
163 description = ''
164 Specifies whether the web interface is enabled.
165 '';
166 };
167
168 extraFilesConf = mkOption {
169 type = types.lines;
170 default = "";
171 description = ''
172 Extra contents of the configuration file of the CUPS daemon
173 (<filename>cups-files.conf</filename>).
174 '';
175 };
176
177 extraConf = mkOption {
178 type = types.lines;
179 default = "";
180 example =
181 ''
182 BrowsePoll cups.example.com
183 LogLevel debug
184 '';
185 description = ''
186 Extra contents of the configuration file of the CUPS daemon
187 (<filename>cupsd.conf</filename>).
188 '';
189 };
190
191 clientConf = mkOption {
192 type = types.lines;
193 default = "";
194 example =
195 ''
196 ServerName server.example.com
197 Encryption Never
198 '';
199 description = ''
200 The contents of the client configuration.
201 (<filename>client.conf</filename>)
202 '';
203 };
204
205 browsedConf = mkOption {
206 type = types.lines;
207 default = "";
208 example =
209 ''
210 BrowsePoll cups.example.com
211 '';
212 description = ''
213 The contents of the configuration. file of the CUPS Browsed daemon
214 (<filename>cups-browsed.conf</filename>)
215 '';
216 };
217
218 snmpConf = mkOption {
219 type = types.lines;
220 default = ''
221 Address @LOCAL
222 '';
223 description = ''
224 The contents of <filename>/etc/cups/snmp.conf</filename>. See "man
225 cups-snmp.conf" for a complete description.
226 '';
227 };
228
229 drivers = mkOption {
230 type = types.listOf types.path;
231 default = [];
232 example = literalExample "[ pkgs.gutenprint pkgs.hplip pkgs.splix ]";
233 description = ''
234 CUPS drivers to use. Drivers provided by CUPS, cups-filters,
235 Ghostscript and Samba are added unconditionally. If this list contains
236 Gutenprint (i.e. a derivation with
237 <literal>meta.isGutenprint = true</literal>) the PPD files in
238 <filename>/var/lib/cups/ppd</filename> will be updated automatically
239 to avoid errors due to incompatible versions.
240 '';
241 };
242
243 tempDir = mkOption {
244 type = types.path;
245 default = "/tmp";
246 example = "/tmp/cups";
247 description = ''
248 CUPSd temporary directory.
249 '';
250 };
251 };
252
253 };
254
255
256 ###### implementation
257
258 config = mkIf config.services.printing.enable {
259
260 users.extraUsers = singleton
261 { name = "cups";
262 uid = config.ids.uids.cups;
263 group = "lp";
264 description = "CUPS printing services";
265 };
266
267 environment.systemPackages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
268 environment.etc."cups".source = "/var/lib/cups";
269
270 services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
271
272 # Cups uses libusb to talk to printers, and does not use the
273 # linux kernel driver. If the driver is not in a black list, it
274 # gets loaded, and then cups cannot access the printers.
275 boot.blacklistedKernelModules = [ "usblp" ];
276
277 systemd.packages = [ cups.out ];
278
279 systemd.services.cups =
280 { wantedBy = [ "multi-user.target" ];
281 wants = [ "network.target" ];
282 after = [ "network.target" ];
283
284 path = [ cups.out ];
285
286 preStart =
287 ''
288 mkdir -m 0700 -p /var/cache/cups
289 mkdir -m 0700 -p /var/spool/cups
290 mkdir -m 0755 -p ${cfg.tempDir}
291
292 mkdir -m 0755 -p /var/lib/cups
293 # Backwards compatibility
294 if [ ! -L /etc/cups ]; then
295 mv /etc/cups/* /var/lib/cups
296 rmdir /etc/cups
297 ln -s /var/lib/cups /etc/cups
298 fi
299 # First, clean existing symlinks
300 if [ -n "$(ls /var/lib/cups)" ]; then
301 for i in /var/lib/cups/*; do
302 [ -L "$i" ] && rm "$i"
303 done
304 fi
305 # Then, populate it with static files
306 cd ${rootdir}/etc/cups
307 for i in *; do
308 [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i"
309 done
310
311 #update path reference
312 [ -L /var/lib/cups/path ] && \
313 rm /var/lib/cups/path
314 [ ! -e /var/lib/cups/path ] && \
315 ln -s ${bindir} /var/lib/cups/path
316
317 ${optionalString (containsGutenprint cfg.drivers) ''
318 if [ -d /var/lib/cups/ppd ]; then
319 ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd
320 fi
321 ''}
322 '';
323
324 serviceConfig.PrivateTmp = true;
325 };
326
327 systemd.services.cups-browsed = mkIf avahiEnabled
328 { description = "CUPS Remote Printer Discovery";
329
330 wantedBy = [ "multi-user.target" ];
331 wants = [ "cups.service" "avahi-daemon.service" ];
332 bindsTo = [ "cups.service" "avahi-daemon.service" ];
333 partOf = [ "cups.service" "avahi-daemon.service" ];
334 after = [ "cups.service" "avahi-daemon.service" ];
335
336 path = [ cups ];
337
338 serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed";
339
340 restartTriggers = [ browsedFile ];
341 };
342
343 services.printing.extraConf =
344 ''
345 LogLevel info
346
347 DefaultAuthType Basic
348
349 <Location />
350 Order allow,deny
351 Allow localhost
352 </Location>
353
354 <Location /admin>
355 Order allow,deny
356 Allow localhost
357 </Location>
358
359 <Location /admin/conf>
360 AuthType Basic
361 Require user @SYSTEM
362 Order allow,deny
363 Allow localhost
364 </Location>
365
366 <Policy default>
367 <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job CUPS-Move-Job>
368 Require user @OWNER @SYSTEM
369 Order deny,allow
370 </Limit>
371
372 <Limit Pause-Printer Resume-Printer Set-Printer-Attributes Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After CUPS-Add-Printer CUPS-Delete-Printer CUPS-Add-Class CUPS-Delete-Class CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default>
373 AuthType Basic
374 Require user @SYSTEM
375 Order deny,allow
376 </Limit>
377
378 <Limit Cancel-Job CUPS-Authenticate-Job>
379 Require user @OWNER @SYSTEM
380 Order deny,allow
381 </Limit>
382
383 <Limit All>
384 Order deny,allow
385 </Limit>
386 </Policy>
387 '';
388
389 security.pam.services.cups = {};
390
391 };
392}