1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 inherit (pkgs) cups cups-pk-helper cups-filters gutenprint;
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 ++ optional cfg.gutenprint gutenprint
39 ++ cfg.drivers;
40 pathsToLink = [ "/lib/cups" "/share/cups" "/bin" ];
41 postBuild = cfg.bindirCmds;
42 ignoreCollisions = true;
43 };
44
45 writeConf = name: text: pkgs.writeTextFile {
46 inherit name text;
47 destination = "/etc/cups/${name}";
48 };
49
50 cupsFilesFile = writeConf "cups-files.conf" ''
51 SystemGroup root wheel
52
53 ServerBin ${bindir}/lib/cups
54 DataDir ${bindir}/share/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 ${bindir}/lib/cups/filter:${bindir}/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 ++ optional cfg.gutenprint gutenprint
100 ++ cfg.drivers;
101 pathsToLink = [ "/etc/cups" ];
102 ignoreCollisions = true;
103 };
104
105in
106
107{
108
109 ###### interface
110
111 options = {
112 services.printing = {
113
114 enable = mkOption {
115 type = types.bool;
116 default = false;
117 description = ''
118 Whether to enable printing support through the CUPS daemon.
119 '';
120 };
121
122 listenAddresses = mkOption {
123 type = types.listOf types.str;
124 default = [ "127.0.0.1:631" ];
125 example = [ "*:631" ];
126 description = ''
127 A list of addresses and ports on which to listen.
128 '';
129 };
130
131 bindirCmds = mkOption {
132 type = types.lines;
133 internal = true;
134 default = "";
135 description = ''
136 Additional commands executed while creating the directory
137 containing the CUPS server binaries.
138 '';
139 };
140
141 defaultShared = mkOption {
142 type = types.bool;
143 default = false;
144 description = ''
145 Specifies whether local printers are shared by default.
146 '';
147 };
148
149 browsing = mkOption {
150 type = types.bool;
151 default = false;
152 description = ''
153 Specifies whether shared printers are advertised.
154 '';
155 };
156
157 webInterface = mkOption {
158 type = types.bool;
159 default = true;
160 description = ''
161 Specifies whether the web interface is enabled.
162 '';
163 };
164
165 extraFilesConf = mkOption {
166 type = types.lines;
167 default = "";
168 description = ''
169 Extra contents of the configuration file of the CUPS daemon
170 (<filename>cups-files.conf</filename>).
171 '';
172 };
173
174 extraConf = mkOption {
175 type = types.lines;
176 default = "";
177 example =
178 ''
179 BrowsePoll cups.example.com
180 LogLevel debug
181 '';
182 description = ''
183 Extra contents of the configuration file of the CUPS daemon
184 (<filename>cupsd.conf</filename>).
185 '';
186 };
187
188 clientConf = mkOption {
189 type = types.lines;
190 default = "";
191 example =
192 ''
193 ServerName server.example.com
194 Encryption Never
195 '';
196 description = ''
197 The contents of the client configuration.
198 (<filename>client.conf</filename>)
199 '';
200 };
201
202 browsedConf = mkOption {
203 type = types.lines;
204 default = "";
205 example =
206 ''
207 BrowsePoll cups.example.com
208 '';
209 description = ''
210 The contents of the configuration. file of the CUPS Browsed daemon
211 (<filename>cups-browsed.conf</filename>)
212 '';
213 };
214
215 snmpConf = mkOption {
216 type = types.lines;
217 default = ''
218 Address @LOCAL
219 '';
220 description = ''
221 The contents of <filename>/etc/cups/snmp.conf</filename>. See "man
222 cups-snmp.conf" for a complete description.
223 '';
224 };
225
226 gutenprint = mkOption {
227 type = types.bool;
228 default = false;
229 description = ''
230 Whether to enable Gutenprint drivers for CUPS. This includes auto-updating
231 Gutenprint PPD files.
232 '';
233 };
234
235 drivers = mkOption {
236 type = types.listOf types.path;
237 default = [];
238 example = literalExample "[ pkgs.splix ]";
239 description = ''
240 CUPS drivers to use. Drivers provided by CUPS, cups-filters, Ghostscript
241 and Samba are added unconditionally. For adding Gutenprint, see
242 <literal>gutenprint</literal>.
243 '';
244 };
245
246 tempDir = mkOption {
247 type = types.path;
248 default = "/tmp";
249 example = "/tmp/cups";
250 description = ''
251 CUPSd temporary directory.
252 '';
253 };
254 };
255
256 };
257
258
259 ###### implementation
260
261 config = mkIf config.services.printing.enable {
262
263 users.extraUsers = singleton
264 { name = "cups";
265 uid = config.ids.uids.cups;
266 group = "lp";
267 description = "CUPS printing services";
268 };
269
270 environment.systemPackages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
271 environment.etc."cups".source = "/var/lib/cups";
272
273 services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
274
275 # Cups uses libusb to talk to printers, and does not use the
276 # linux kernel driver. If the driver is not in a black list, it
277 # gets loaded, and then cups cannot access the printers.
278 boot.blacklistedKernelModules = [ "usblp" ];
279
280 systemd.packages = [ cups.out ];
281
282 systemd.services.cups =
283 { wantedBy = [ "multi-user.target" ];
284 wants = [ "network.target" ];
285 after = [ "network.target" ];
286
287 path = [ cups.out ];
288
289 preStart =
290 ''
291 mkdir -m 0700 -p /var/cache/cups
292 mkdir -m 0700 -p /var/spool/cups
293 mkdir -m 0755 -p ${cfg.tempDir}
294
295 mkdir -m 0755 -p /var/lib/cups
296 # Backwards compatibility
297 if [ ! -L /etc/cups ]; then
298 mv /etc/cups/* /var/lib/cups
299 rmdir /etc/cups
300 ln -s /var/lib/cups /etc/cups
301 fi
302 # First, clean existing symlinks
303 if [ -n "$(ls /var/lib/cups)" ]; then
304 for i in /var/lib/cups/*; do
305 [ -L "$i" ] && rm "$i"
306 done
307 fi
308 # Then, populate it with static files
309 cd ${rootdir}/etc/cups
310 for i in *; do
311 [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i"
312 done
313 ${optionalString cfg.gutenprint ''
314 if [ -d /var/lib/cups/ppd ]; then
315 ${gutenprint}/bin/cups-genppdupdate -p /var/lib/cups/ppd
316 fi
317 ''}
318 '';
319 };
320
321 systemd.services.cups-browsed = mkIf avahiEnabled
322 { description = "CUPS Remote Printer Discovery";
323
324 wantedBy = [ "multi-user.target" ];
325 wants = [ "cups.service" "avahi-daemon.service" ];
326 bindsTo = [ "cups.service" "avahi-daemon.service" ];
327 partOf = [ "cups.service" "avahi-daemon.service" ];
328 after = [ "cups.service" "avahi-daemon.service" ];
329
330 path = [ cups ];
331
332 serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed";
333
334 restartTriggers = [ browsedFile ];
335 };
336
337 services.printing.extraConf =
338 ''
339 LogLevel info
340
341 DefaultAuthType Basic
342
343 <Location />
344 Order allow,deny
345 Allow localhost
346 </Location>
347
348 <Location /admin>
349 Order allow,deny
350 Allow localhost
351 </Location>
352
353 <Location /admin/conf>
354 AuthType Basic
355 Require user @SYSTEM
356 Order allow,deny
357 Allow localhost
358 </Location>
359
360 <Policy default>
361 <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>
362 Require user @OWNER @SYSTEM
363 Order deny,allow
364 </Limit>
365
366 <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>
367 AuthType Basic
368 Require user @SYSTEM
369 Order deny,allow
370 </Limit>
371
372 <Limit Cancel-Job CUPS-Authenticate-Job>
373 Require user @OWNER @SYSTEM
374 Order deny,allow
375 </Limit>
376
377 <Limit All>
378 Order deny,allow
379 </Limit>
380 </Policy>
381 '';
382
383 security.pam.services.cups = {};
384
385 };
386}