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 LogLevel ${cfg.logLevel}
87
88 ${cfg.extraConf}
89 '';
90
91 browsedFile = writeConf "cups-browsed.conf" cfg.browsedConf;
92
93 rootdir = pkgs.buildEnv {
94 name = "cups-progs";
95 paths = [
96 cupsFilesFile
97 cupsdFile
98 (writeConf "client.conf" cfg.clientConf)
99 (writeConf "snmp.conf" cfg.snmpConf)
100 ] ++ optional avahiEnabled browsedFile
101 ++ cfg.drivers;
102 pathsToLink = [ "/etc/cups" ];
103 ignoreCollisions = true;
104 };
105
106 filterGutenprint = pkgs: filter (pkg: pkg.meta.isGutenprint or false == true) pkgs;
107 containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
108 getGutenprint = pkgs: head (filterGutenprint pkgs);
109
110in
111
112{
113
114 ###### interface
115
116 options = {
117 services.printing = {
118
119 enable = mkOption {
120 type = types.bool;
121 default = false;
122 description = ''
123 Whether to enable printing support through the CUPS daemon.
124 '';
125 };
126
127 startWhenNeeded = mkOption {
128 type = types.bool;
129 default = false;
130 description = ''
131 If set, CUPS is socket-activated; that is,
132 instead of having it permanently running as a daemon,
133 systemd will start it on the first incoming connection.
134 '';
135 };
136
137 listenAddresses = mkOption {
138 type = types.listOf types.str;
139 default = [ "localhost:631" ];
140 example = [ "*:631" ];
141 description = ''
142 A list of addresses and ports on which to listen.
143 '';
144 };
145
146 bindirCmds = mkOption {
147 type = types.lines;
148 internal = true;
149 default = "";
150 description = ''
151 Additional commands executed while creating the directory
152 containing the CUPS server binaries.
153 '';
154 };
155
156 defaultShared = mkOption {
157 type = types.bool;
158 default = false;
159 description = ''
160 Specifies whether local printers are shared by default.
161 '';
162 };
163
164 browsing = mkOption {
165 type = types.bool;
166 default = false;
167 description = ''
168 Specifies whether shared printers are advertised.
169 '';
170 };
171
172 webInterface = mkOption {
173 type = types.bool;
174 default = true;
175 description = ''
176 Specifies whether the web interface is enabled.
177 '';
178 };
179
180 logLevel = mkOption {
181 type = types.str;
182 default = "info";
183 example = "debug";
184 description = ''
185 Specifies the cupsd logging verbosity.
186 '';
187 };
188
189 extraFilesConf = mkOption {
190 type = types.lines;
191 default = "";
192 description = ''
193 Extra contents of the configuration file of the CUPS daemon
194 (<filename>cups-files.conf</filename>).
195 '';
196 };
197
198 extraConf = mkOption {
199 type = types.lines;
200 default = "";
201 example =
202 ''
203 BrowsePoll cups.example.com
204 MaxCopies 42
205 '';
206 description = ''
207 Extra contents of the configuration file of the CUPS daemon
208 (<filename>cupsd.conf</filename>).
209 '';
210 };
211
212 clientConf = mkOption {
213 type = types.lines;
214 default = "";
215 example =
216 ''
217 ServerName server.example.com
218 Encryption Never
219 '';
220 description = ''
221 The contents of the client configuration.
222 (<filename>client.conf</filename>)
223 '';
224 };
225
226 browsedConf = mkOption {
227 type = types.lines;
228 default = "";
229 example =
230 ''
231 BrowsePoll cups.example.com
232 '';
233 description = ''
234 The contents of the configuration. file of the CUPS Browsed daemon
235 (<filename>cups-browsed.conf</filename>)
236 '';
237 };
238
239 snmpConf = mkOption {
240 type = types.lines;
241 default = ''
242 Address @LOCAL
243 '';
244 description = ''
245 The contents of <filename>/etc/cups/snmp.conf</filename>. See "man
246 cups-snmp.conf" for a complete description.
247 '';
248 };
249
250 drivers = mkOption {
251 type = types.listOf types.path;
252 default = [];
253 example = literalExample "[ pkgs.gutenprint pkgs.hplip pkgs.splix ]";
254 description = ''
255 CUPS drivers to use. Drivers provided by CUPS, cups-filters,
256 Ghostscript and Samba are added unconditionally. If this list contains
257 Gutenprint (i.e. a derivation with
258 <literal>meta.isGutenprint = true</literal>) the PPD files in
259 <filename>/var/lib/cups/ppd</filename> will be updated automatically
260 to avoid errors due to incompatible versions.
261 '';
262 };
263
264 tempDir = mkOption {
265 type = types.path;
266 default = "/tmp";
267 example = "/tmp/cups";
268 description = ''
269 CUPSd temporary directory.
270 '';
271 };
272 };
273
274 };
275
276
277 ###### implementation
278
279 config = mkIf config.services.printing.enable {
280
281 users.users = singleton
282 { name = "cups";
283 uid = config.ids.uids.cups;
284 group = "lp";
285 description = "CUPS printing services";
286 };
287
288 environment.systemPackages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
289 environment.etc."cups".source = "/var/lib/cups";
290
291 services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
292
293 # Cups uses libusb to talk to printers, and does not use the
294 # linux kernel driver. If the driver is not in a black list, it
295 # gets loaded, and then cups cannot access the printers.
296 boot.blacklistedKernelModules = [ "usblp" ];
297
298 systemd.packages = [ cups.out ];
299
300 systemd.sockets.cups = mkIf cfg.startWhenNeeded {
301 wantedBy = [ "sockets.target" ];
302 listenStreams = map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
303 };
304
305 systemd.services.cups =
306 { wantedBy = optionals (!cfg.startWhenNeeded) [ "multi-user.target" ];
307 wants = [ "network.target" ];
308 after = [ "network.target" ];
309
310 path = [ cups.out ];
311
312 preStart =
313 ''
314 mkdir -m 0700 -p /var/cache/cups
315 mkdir -m 0700 -p /var/spool/cups
316 mkdir -m 0755 -p ${cfg.tempDir}
317
318 mkdir -m 0755 -p /var/lib/cups
319 # Backwards compatibility
320 if [ ! -L /etc/cups ]; then
321 mv /etc/cups/* /var/lib/cups
322 rmdir /etc/cups
323 ln -s /var/lib/cups /etc/cups
324 fi
325 # First, clean existing symlinks
326 if [ -n "$(ls /var/lib/cups)" ]; then
327 for i in /var/lib/cups/*; do
328 [ -L "$i" ] && rm "$i"
329 done
330 fi
331 # Then, populate it with static files
332 cd ${rootdir}/etc/cups
333 for i in *; do
334 [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i"
335 done
336
337 #update path reference
338 [ -L /var/lib/cups/path ] && \
339 rm /var/lib/cups/path
340 [ ! -e /var/lib/cups/path ] && \
341 ln -s ${bindir} /var/lib/cups/path
342
343 ${optionalString (containsGutenprint cfg.drivers) ''
344 if [ -d /var/lib/cups/ppd ]; then
345 ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd
346 fi
347 ''}
348 '';
349
350 serviceConfig = {
351 PrivateTmp = true;
352 RuntimeDirectory = [ "cups" ];
353 };
354 };
355
356 systemd.services.cups-browsed = mkIf avahiEnabled
357 { description = "CUPS Remote Printer Discovery";
358
359 wantedBy = [ "multi-user.target" ];
360 wants = [ "cups.service" "avahi-daemon.service" ];
361 bindsTo = [ "cups.service" "avahi-daemon.service" ];
362 partOf = [ "cups.service" "avahi-daemon.service" ];
363 after = [ "cups.service" "avahi-daemon.service" ];
364
365 path = [ cups ];
366
367 serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed";
368
369 restartTriggers = [ browsedFile ];
370 };
371
372 services.printing.extraConf =
373 ''
374 DefaultAuthType Basic
375
376 <Location />
377 Order allow,deny
378 Allow localhost
379 </Location>
380
381 <Location /admin>
382 Order allow,deny
383 Allow localhost
384 </Location>
385
386 <Location /admin/conf>
387 AuthType Basic
388 Require user @SYSTEM
389 Order allow,deny
390 Allow localhost
391 </Location>
392
393 <Policy default>
394 <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>
395 Require user @OWNER @SYSTEM
396 Order deny,allow
397 </Limit>
398
399 <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>
400 AuthType Basic
401 Require user @SYSTEM
402 Order deny,allow
403 </Limit>
404
405 <Limit Cancel-Job CUPS-Authenticate-Job>
406 Require user @OWNER @SYSTEM
407 Order deny,allow
408 </Limit>
409
410 <Limit All>
411 Order deny,allow
412 </Limit>
413 </Policy>
414 '';
415
416 security.pam.services.cups = {};
417
418 };
419}