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}/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}/lib/cups/backend/https ]; then 24 mkdir -p $out/lib/cups/backend 25 ln -sv ${cups}/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}/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 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. 242 ''; 243 }; 244 245 tempDir = mkOption { 246 type = types.path; 247 default = "/tmp"; 248 example = "/tmp/cups"; 249 description = '' 250 CUPSd temporary directory. 251 ''; 252 }; 253 }; 254 255 }; 256 257 258 ###### implementation 259 260 config = mkIf config.services.printing.enable { 261 262 users.extraUsers = singleton 263 { name = "cups"; 264 uid = config.ids.uids.cups; 265 group = "lp"; 266 description = "CUPS printing services"; 267 }; 268 269 environment.systemPackages = [ cups ] ++ optional polkitEnabled cups-pk-helper; 270 environment.etc."cups".source = "/var/lib/cups"; 271 272 services.dbus.packages = [ cups ] ++ optional polkitEnabled cups-pk-helper; 273 274 # Cups uses libusb to talk to printers, and does not use the 275 # linux kernel driver. If the driver is not in a black list, it 276 # gets loaded, and then cups cannot access the printers. 277 boot.blacklistedKernelModules = [ "usblp" ]; 278 279 systemd.packages = [ cups ]; 280 281 systemd.services.cups = 282 { wantedBy = [ "multi-user.target" ]; 283 wants = [ "network.target" ]; 284 after = [ "network.target" ]; 285 286 path = [ cups ]; 287 288 preStart = 289 '' 290 mkdir -m 0700 -p /var/cache/cups 291 mkdir -m 0700 -p /var/spool/cups 292 mkdir -m 0755 -p ${cfg.tempDir} 293 294 mkdir -m 0755 -p /var/lib/cups 295 # Backwards compatibility 296 if [ ! -L /etc/cups ]; then 297 mv /etc/cups/* /var/lib/cups 298 rmdir /etc/cups 299 ln -s /var/lib/cups /etc/cups 300 fi 301 # First, clean existing symlinks 302 if [ -n "$(ls /var/lib/cups)" ]; then 303 for i in /var/lib/cups/*; do 304 [ -L "$i" ] && rm "$i" 305 done 306 fi 307 # Then, populate it with static files 308 cd ${rootdir}/etc/cups 309 for i in *; do 310 [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i" 311 done 312 ${optionalString cfg.gutenprint '' 313 ${gutenprint}/bin/cups-genppdupdate -p /etc/cups/ppd 314 ''} 315 ''; 316 }; 317 318 systemd.services.cups-browsed = mkIf avahiEnabled 319 { description = "CUPS Remote Printer Discovery"; 320 321 wantedBy = [ "multi-user.target" ]; 322 wants = [ "cups.service" "avahi-daemon.service" ]; 323 bindsTo = [ "cups.service" "avahi-daemon.service" ]; 324 partOf = [ "cups.service" "avahi-daemon.service" ]; 325 after = [ "cups.service" "avahi-daemon.service" ]; 326 327 path = [ cups ]; 328 329 serviceConfig.ExecStart = "${cups_filters}/bin/cups-browsed"; 330 331 restartTriggers = [ browsedFile ]; 332 }; 333 334 services.printing.extraConf = 335 '' 336 LogLevel info 337 338 DefaultAuthType Basic 339 340 <Location /> 341 Order allow,deny 342 Allow localhost 343 </Location> 344 345 <Location /admin> 346 Order allow,deny 347 Allow localhost 348 </Location> 349 350 <Location /admin/conf> 351 AuthType Basic 352 Require user @SYSTEM 353 Order allow,deny 354 Allow localhost 355 </Location> 356 357 <Policy default> 358 <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> 359 Require user @OWNER @SYSTEM 360 Order deny,allow 361 </Limit> 362 363 <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> 364 AuthType Basic 365 Require user @SYSTEM 366 Order deny,allow 367 </Limit> 368 369 <Limit Cancel-Job CUPS-Authenticate-Job> 370 Require user @OWNER @SYSTEM 371 Order deny,allow 372 </Limit> 373 374 <Limit All> 375 Order deny,allow 376 </Limit> 377 </Policy> 378 ''; 379 380 security.pam.services.cups = {}; 381 382 }; 383}