at 23.05-pre 14 kB view raw
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 preferLocalBuild = true; 16 } '' 17 mkdir -p $out 18 if [ ! -e ${cups.out}/lib/cups/backend/smb ]; then 19 mkdir -p $out/lib/cups/backend 20 ln -sv ${pkgs.samba}/bin/smbspool $out/lib/cups/backend/smb 21 fi 22 23 # Provide support for printing via HTTPS. 24 if [ ! -e ${cups.out}/lib/cups/backend/https ]; then 25 mkdir -p $out/lib/cups/backend 26 ln -sv ${cups.out}/lib/cups/backend/ipp $out/lib/cups/backend/https 27 fi 28 ''; 29 30 # Here we can enable additional backends, filters, etc. that are not 31 # part of CUPS itself, e.g. the SMB backend is part of Samba. Since 32 # we can't update ${cups.out}/lib/cups itself, we create a symlink tree 33 # here and add the additional programs. The ServerBin directive in 34 # cups-files.conf tells cupsd to use this tree. 35 bindir = pkgs.buildEnv { 36 name = "cups-progs"; 37 paths = 38 [ cups.out additionalBackends cups-filters pkgs.ghostscript ] 39 ++ cfg.drivers; 40 pathsToLink = [ "/lib" "/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 DocumentRoot ${cups.out}/share/doc/cups 56 57 AccessLog syslog 58 ErrorLog syslog 59 PageLog syslog 60 61 TempDir ${cfg.tempDir} 62 63 SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin 64 65 # User and group used to run external programs, including 66 # those that actually send the job to the printer. Note that 67 # Udev sets the group of printer devices to `lp', so we want 68 # these programs to run as `lp' as well. 69 User cups 70 Group lp 71 72 ${cfg.extraFilesConf} 73 ''; 74 75 cupsdFile = writeConf "cupsd.conf" '' 76 ${concatMapStrings (addr: '' 77 Listen ${addr} 78 '') cfg.listenAddresses} 79 Listen /run/cups/cups.sock 80 81 DefaultShared ${if cfg.defaultShared then "Yes" else "No"} 82 83 Browsing ${if cfg.browsing then "Yes" else "No"} 84 85 WebInterface ${if cfg.webInterface then "Yes" else "No"} 86 87 LogLevel ${cfg.logLevel} 88 89 ${cfg.extraConf} 90 ''; 91 92 browsedFile = writeConf "cups-browsed.conf" cfg.browsedConf; 93 94 rootdir = pkgs.buildEnv { 95 name = "cups-progs"; 96 paths = [ 97 cupsFilesFile 98 cupsdFile 99 (writeConf "client.conf" cfg.clientConf) 100 (writeConf "snmp.conf" cfg.snmpConf) 101 ] ++ optional avahiEnabled browsedFile 102 ++ cfg.drivers; 103 pathsToLink = [ "/etc/cups" ]; 104 ignoreCollisions = true; 105 }; 106 107 filterGutenprint = filter (pkg: pkg.meta.isGutenprint or false == true); 108 containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0; 109 getGutenprint = pkgs: head (filterGutenprint pkgs); 110 111in 112 113{ 114 115 imports = [ 116 (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ] 117 (config: 118 let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config; 119 in if enabled then [ pkgs.gutenprint ] else [ ])) 120 (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "") 121 (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "") 122 ]; 123 124 ###### interface 125 126 options = { 127 services.printing = { 128 129 enable = mkOption { 130 type = types.bool; 131 default = false; 132 description = lib.mdDoc '' 133 Whether to enable printing support through the CUPS daemon. 134 ''; 135 }; 136 137 stateless = mkOption { 138 type = types.bool; 139 default = false; 140 description = lib.mdDoc '' 141 If set, all state directories relating to CUPS will be removed on 142 startup of the service. 143 ''; 144 }; 145 146 startWhenNeeded = mkOption { 147 type = types.bool; 148 default = true; 149 description = lib.mdDoc '' 150 If set, CUPS is socket-activated; that is, 151 instead of having it permanently running as a daemon, 152 systemd will start it on the first incoming connection. 153 ''; 154 }; 155 156 listenAddresses = mkOption { 157 type = types.listOf types.str; 158 default = [ "localhost:631" ]; 159 example = [ "*:631" ]; 160 description = lib.mdDoc '' 161 A list of addresses and ports on which to listen. 162 ''; 163 }; 164 165 allowFrom = mkOption { 166 type = types.listOf types.str; 167 default = [ "localhost" ]; 168 example = [ "all" ]; 169 apply = concatMapStringsSep "\n" (x: "Allow ${x}"); 170 description = lib.mdDoc '' 171 From which hosts to allow unconditional access. 172 ''; 173 }; 174 175 bindirCmds = mkOption { 176 type = types.lines; 177 internal = true; 178 default = ""; 179 description = lib.mdDoc '' 180 Additional commands executed while creating the directory 181 containing the CUPS server binaries. 182 ''; 183 }; 184 185 defaultShared = mkOption { 186 type = types.bool; 187 default = false; 188 description = lib.mdDoc '' 189 Specifies whether local printers are shared by default. 190 ''; 191 }; 192 193 browsing = mkOption { 194 type = types.bool; 195 default = false; 196 description = lib.mdDoc '' 197 Specifies whether shared printers are advertised. 198 ''; 199 }; 200 201 webInterface = mkOption { 202 type = types.bool; 203 default = true; 204 description = lib.mdDoc '' 205 Specifies whether the web interface is enabled. 206 ''; 207 }; 208 209 logLevel = mkOption { 210 type = types.str; 211 default = "info"; 212 example = "debug"; 213 description = lib.mdDoc '' 214 Specifies the cupsd logging verbosity. 215 ''; 216 }; 217 218 extraFilesConf = mkOption { 219 type = types.lines; 220 default = ""; 221 description = lib.mdDoc '' 222 Extra contents of the configuration file of the CUPS daemon 223 ({file}`cups-files.conf`). 224 ''; 225 }; 226 227 extraConf = mkOption { 228 type = types.lines; 229 default = ""; 230 example = 231 '' 232 BrowsePoll cups.example.com 233 MaxCopies 42 234 ''; 235 description = lib.mdDoc '' 236 Extra contents of the configuration file of the CUPS daemon 237 ({file}`cupsd.conf`). 238 ''; 239 }; 240 241 clientConf = mkOption { 242 type = types.lines; 243 default = ""; 244 example = 245 '' 246 ServerName server.example.com 247 Encryption Never 248 ''; 249 description = lib.mdDoc '' 250 The contents of the client configuration. 251 ({file}`client.conf`) 252 ''; 253 }; 254 255 browsedConf = mkOption { 256 type = types.lines; 257 default = ""; 258 example = 259 '' 260 BrowsePoll cups.example.com 261 ''; 262 description = lib.mdDoc '' 263 The contents of the configuration. file of the CUPS Browsed daemon 264 ({file}`cups-browsed.conf`) 265 ''; 266 }; 267 268 snmpConf = mkOption { 269 type = types.lines; 270 default = '' 271 Address @LOCAL 272 ''; 273 description = lib.mdDoc '' 274 The contents of {file}`/etc/cups/snmp.conf`. See "man 275 cups-snmp.conf" for a complete description. 276 ''; 277 }; 278 279 drivers = mkOption { 280 type = types.listOf types.path; 281 default = []; 282 example = literalExpression "with pkgs; [ gutenprint hplip splix ]"; 283 description = lib.mdDoc '' 284 CUPS drivers to use. Drivers provided by CUPS, cups-filters, 285 Ghostscript and Samba are added unconditionally. If this list contains 286 Gutenprint (i.e. a derivation with 287 `meta.isGutenprint = true`) the PPD files in 288 {file}`/var/lib/cups/ppd` will be updated automatically 289 to avoid errors due to incompatible versions. 290 ''; 291 }; 292 293 tempDir = mkOption { 294 type = types.path; 295 default = "/tmp"; 296 example = "/tmp/cups"; 297 description = lib.mdDoc '' 298 CUPSd temporary directory. 299 ''; 300 }; 301 }; 302 303 }; 304 305 306 ###### implementation 307 308 config = mkIf config.services.printing.enable { 309 310 users.users.cups = 311 { uid = config.ids.uids.cups; 312 group = "lp"; 313 description = "CUPS printing services"; 314 }; 315 316 environment.systemPackages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper; 317 environment.etc.cups.source = "/var/lib/cups"; 318 319 services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper; 320 321 # Allow asswordless printer admin for members of wheel group 322 security.polkit.extraConfig = mkIf polkitEnabled '' 323 polkit.addRule(function(action, subject) { 324 if (action.id == "org.opensuse.cupspkhelper.mechanism.all-edit" && 325 subject.isInGroup("wheel")){ 326 return polkit.Result.YES; 327 } 328 }); 329 ''; 330 331 # Cups uses libusb to talk to printers, and does not use the 332 # linux kernel driver. If the driver is not in a black list, it 333 # gets loaded, and then cups cannot access the printers. 334 boot.blacklistedKernelModules = [ "usblp" ]; 335 336 # Some programs like print-manager rely on this value to get 337 # printer test pages. 338 environment.sessionVariables.CUPS_DATADIR = "${bindir}/share/cups"; 339 340 systemd.packages = [ cups.out ]; 341 342 systemd.sockets.cups = mkIf cfg.startWhenNeeded { 343 wantedBy = [ "sockets.target" ]; 344 listenStreams = [ "/run/cups/cups.sock" ] 345 ++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses; 346 }; 347 348 systemd.services.cups = 349 { wantedBy = optionals (!cfg.startWhenNeeded) [ "multi-user.target" ]; 350 wants = [ "network.target" ]; 351 after = [ "network.target" ]; 352 353 path = [ cups.out ]; 354 355 preStart = lib.optionalString cfg.stateless '' 356 rm -rf /var/cache/cups /var/lib/cups /var/spool/cups 357 '' + '' 358 mkdir -m 0700 -p /var/cache/cups 359 mkdir -m 0700 -p /var/spool/cups 360 mkdir -m 0755 -p ${cfg.tempDir} 361 362 mkdir -m 0755 -p /var/lib/cups 363 # While cups will automatically create self-signed certificates if accessed via TLS, 364 # this directory to store the certificates needs to be created manually. 365 mkdir -m 0700 -p /var/lib/cups/ssl 366 367 # Backwards compatibility 368 if [ ! -L /etc/cups ]; then 369 mv /etc/cups/* /var/lib/cups 370 rmdir /etc/cups 371 ln -s /var/lib/cups /etc/cups 372 fi 373 # First, clean existing symlinks 374 if [ -n "$(ls /var/lib/cups)" ]; then 375 for i in /var/lib/cups/*; do 376 [ -L "$i" ] && rm "$i" 377 done 378 fi 379 # Then, populate it with static files 380 cd ${rootdir}/etc/cups 381 for i in *; do 382 [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i" 383 done 384 385 #update path reference 386 [ -L /var/lib/cups/path ] && \ 387 rm /var/lib/cups/path 388 [ ! -e /var/lib/cups/path ] && \ 389 ln -s ${bindir} /var/lib/cups/path 390 391 ${optionalString (containsGutenprint cfg.drivers) '' 392 if [ -d /var/lib/cups/ppd ]; then 393 ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd 394 fi 395 ''} 396 ''; 397 398 serviceConfig = { 399 PrivateTmp = true; 400 RuntimeDirectory = [ "cups" ]; 401 }; 402 }; 403 404 systemd.services.cups-browsed = mkIf avahiEnabled 405 { description = "CUPS Remote Printer Discovery"; 406 407 wantedBy = [ "multi-user.target" ]; 408 wants = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service"; 409 bindsTo = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service"; 410 partOf = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service"; 411 after = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service"; 412 413 path = [ cups ]; 414 415 serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed"; 416 417 restartTriggers = [ browsedFile ]; 418 }; 419 420 services.printing.extraConf = 421 '' 422 DefaultAuthType Basic 423 424 <Location /> 425 Order allow,deny 426 ${cfg.allowFrom} 427 </Location> 428 429 <Location /admin> 430 Order allow,deny 431 ${cfg.allowFrom} 432 </Location> 433 434 <Location /admin/conf> 435 AuthType Basic 436 Require user @SYSTEM 437 Order allow,deny 438 ${cfg.allowFrom} 439 </Location> 440 441 <Policy default> 442 <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> 443 Require user @OWNER @SYSTEM 444 Order deny,allow 445 </Limit> 446 447 <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> 448 AuthType Basic 449 Require user @SYSTEM 450 Order deny,allow 451 </Limit> 452 453 <Limit Cancel-Job CUPS-Authenticate-Job> 454 Require user @OWNER @SYSTEM 455 Order deny,allow 456 </Limit> 457 458 <Limit All> 459 Order deny,allow 460 </Limit> 461 </Policy> 462 ''; 463 464 security.pam.services.cups = {}; 465 466 }; 467 468 meta.maintainers = with lib.maintainers; [ matthewbauer ]; 469 470}