Merge pull request #203454 from rnhmjoj/pr-cups-socket

nixos/hardware/printers: stop cupsd when unneeded

Changed files
+108 -106
nixos
modules
hardware
services
printing
tests
pkgs
misc
+12 -7
nixos/modules/hardware/printers.nix
···
};
config = mkIf (cfg.ensurePrinters != [] && config.services.printing.enable) {
-
systemd.services.ensure-printers = let
-
cupsUnit = if config.services.printing.startWhenNeeded then "cups.socket" else "cups.service";
-
in {
+
systemd.services.ensure-printers = {
description = "Ensure NixOS-configured CUPS printers";
wantedBy = [ "multi-user.target" ];
-
requires = [ cupsUnit ];
-
after = [ cupsUnit ];
+
wants = [ "cups.service" ];
+
after = [ "cups.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
-
script = concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters
-
+ optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter);
+
script = concatStringsSep "\n" [
+
(concatMapStrings ensurePrinter cfg.ensurePrinters)
+
(optionalString (cfg.ensureDefaultPrinter != null)
+
(ensureDefaultPrinter cfg.ensureDefaultPrinter))
+
# Note: if cupsd is "stateless" the service can't be stopped,
+
# otherwise the configuration will be wiped on the next start.
+
(optionalString (with config.services.printing; startWhenNeeded && !stateless)
+
"systemctl stop cups.service")
+
];
};
};
}
+2 -5
nixos/modules/services/printing/cupsd.nix
···
systemd.sockets.cups = mkIf cfg.startWhenNeeded {
wantedBy = [ "sockets.target" ];
-
listenStreams = [ "/run/cups/cups.sock" ]
+
listenStreams = [ "" "/run/cups/cups.sock" ]
++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
};
···
''}
'';
-
serviceConfig = {
-
PrivateTmp = true;
-
RuntimeDirectory = [ "cups" ];
-
};
+
serviceConfig.PrivateTmp = true;
};
systemd.services.cups-browsed = mkIf avahiEnabled
+2 -1
nixos/release-combined.nix
···
(onFullSupported "nixos.tests.predictable-interface-names.predictable")
(onFullSupported "nixos.tests.predictable-interface-names.unpredictableNetworkd")
(onFullSupported "nixos.tests.predictable-interface-names.unpredictable")
-
(onFullSupported "nixos.tests.printing")
+
(onFullSupported "nixos.tests.printing-service")
+
(onFullSupported "nixos.tests.printing-socket")
(onFullSupported "nixos.tests.proxy")
(onFullSupported "nixos.tests.sddm.default")
(onFullSupported "nixos.tests.shadow")
+2 -1
nixos/tests/all-tests.nix
···
power-profiles-daemon = handleTest ./power-profiles-daemon.nix {};
pppd = handleTest ./pppd.nix {};
predictable-interface-names = handleTest ./predictable-interface-names.nix {};
-
printing = handleTest ./printing.nix {};
+
printing-socket = handleTest ./printing.nix { socket = true; };
+
printing-service = handleTest ./printing.nix { socket = false; };
privacyidea = handleTest ./privacyidea.nix {};
privoxy = handleTest ./privoxy.nix {};
prometheus = handleTest ./prometheus.nix {};
+85 -92
nixos/tests/printing.nix
···
# Test printing via CUPS.
-
import ./make-test-python.nix ({pkgs, ... }:
-
let
-
printingServer = startWhenNeeded: {
-
services.printing.enable = true;
-
services.printing.stateless = true;
-
services.printing.startWhenNeeded = startWhenNeeded;
-
services.printing.listenAddresses = [ "*:631" ];
-
services.printing.defaultShared = true;
-
services.printing.extraConf = ''
-
<Location />
-
Order allow,deny
-
Allow from all
-
</Location>
-
'';
+
import ./make-test-python.nix (
+
{ pkgs
+
, socket ? true # whether to use socket activation
+
, ...
+
}:
+
+
{
+
name = "printing";
+
meta = with pkgs.lib.maintainers; {
+
maintainers = [ domenkozar eelco matthewbauer ];
+
};
+
+
nodes.server = { ... }: {
+
services.printing = {
+
enable = true;
+
stateless = true;
+
startWhenNeeded = socket;
+
listenAddresses = [ "*:631" ];
+
defaultShared = true;
+
extraConf = ''
+
<Location />
+
Order allow,deny
+
Allow from all
+
</Location>
+
'';
+
};
networking.firewall.allowedTCPPorts = [ 631 ];
# Add a HP Deskjet printer connected via USB to the server.
hardware.printers.ensurePrinters = [{
···
model = "drv:///sample.drv/deskjet.ppd";
}];
};
-
printingClient = startWhenNeeded: {
+
+
nodes.client = { ... }: {
services.printing.enable = true;
-
services.printing.startWhenNeeded = startWhenNeeded;
+
services.printing.startWhenNeeded = socket;
# Add printer to the client as well, via IPP.
hardware.printers.ensurePrinters = [{
name = "DeskjetRemote";
-
deviceUri = "ipp://${if startWhenNeeded then "socketActivatedServer" else "serviceServer"}/printers/DeskjetLocal";
+
deviceUri = "ipp://server/printers/DeskjetLocal";
model = "drv:///sample.drv/deskjet.ppd";
}];
hardware.printers.ensureDefaultPrinter = "DeskjetRemote";
};
-
in {
-
name = "printing";
-
meta = with pkgs.lib.maintainers; {
-
maintainers = [ domenkozar eelco matthewbauer ];
-
};
-
-
nodes = {
-
socketActivatedServer = { ... }: (printingServer true);
-
serviceServer = { ... }: (printingServer false);
-
-
socketActivatedClient = { ... }: (printingClient true);
-
serviceClient = { ... }: (printingClient false);
-
};
-
testScript = ''
import os
import re
···
start_all()
with subtest("Make sure that cups is up on both sides and printers are set up"):
-
serviceServer.wait_for_unit("cups.service")
-
serviceClient.wait_for_unit("cups.service")
-
socketActivatedClient.wait_for_unit("ensure-printers.service")
+
server.wait_for_unit("cups.${if socket then "socket" else "service"}")
+
client.wait_for_unit("cups.${if socket then "socket" else "service"}")
+
assert "scheduler is running" in client.succeed("lpstat -r")
-
def test_printing(client, server):
-
assert "scheduler is running" in client.succeed("lpstat -r")
+
with subtest("UNIX socket is used for connections"):
+
assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
+
+
with subtest("HTTP server is available too"):
+
client.succeed("curl --fail http://localhost:631/")
+
client.succeed(f"curl --fail http://{server.name}:631/")
+
server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
-
with subtest("UNIX socket is used for connections"):
-
assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
-
with subtest("HTTP server is available too"):
-
client.succeed("curl --fail http://localhost:631/")
-
client.succeed(f"curl --fail http://{server.name}:631/")
-
server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
+
with subtest("LP status checks"):
+
assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
+
assert "DeskjetLocal accepting requests" in client.succeed(
+
f"lpstat -h {server.name}:631 -a"
+
)
+
client.succeed("cupsdisable DeskjetRemote")
+
out = client.succeed("lpq")
+
print(out)
+
assert re.search(
+
"DeskjetRemote is not ready.*no entries",
+
client.succeed("lpq"),
+
flags=re.DOTALL,
+
)
+
client.succeed("cupsenable DeskjetRemote")
+
assert re.match(
+
"DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
+
)
-
with subtest("LP status checks"):
-
assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
-
assert "DeskjetLocal accepting requests" in client.succeed(
-
f"lpstat -h {server.name}:631 -a"
-
)
-
client.succeed("cupsdisable DeskjetRemote")
-
out = client.succeed("lpq")
-
print(out)
-
assert re.search(
-
"DeskjetRemote is not ready.*no entries",
-
client.succeed("lpq"),
-
flags=re.DOTALL,
-
)
-
client.succeed("cupsenable DeskjetRemote")
-
assert re.match(
-
"DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
+
# Test printing various file types.
+
for file in [
+
"${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
+
"${pkgs.groff.doc}/share/doc/*/meref.ps",
+
"${pkgs.cups.out}/share/doc/cups/images/cups.png",
+
"${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
+
]:
+
file_name = os.path.basename(file)
+
with subtest(f"print {file_name}"):
+
# Print the file on the client.
+
print(client.succeed("lpq"))
+
client.succeed(f"lp {file}")
+
client.wait_until_succeeds(
+
f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
)
-
# Test printing various file types.
-
for file in [
-
"${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
-
"${pkgs.groff.doc}/share/doc/*/meref.ps",
-
"${pkgs.cups.out}/share/doc/cups/images/cups.png",
-
"${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
-
]:
-
file_name = os.path.basename(file)
-
with subtest(f"print {file_name}"):
-
# Print the file on the client.
-
print(client.succeed("lpq"))
-
client.succeed(f"lp {file}")
-
client.wait_until_succeeds(
-
f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
-
)
-
-
# Ensure that a raw PCL file appeared in the server's queue
-
# (showing that the right filters have been applied). Of
-
# course, since there is no actual USB printer attached, the
-
# file will stay in the queue forever.
-
server.wait_for_file("/var/spool/cups/d*-001")
-
server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
-
-
# Delete the job on the client. It should disappear on the
-
# server as well.
-
client.succeed("lprm")
-
client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
+
# Ensure that a raw PCL file appeared in the server's queue
+
# (showing that the right filters have been applied). Of
+
# course, since there is no actual USB printer attached, the
+
# file will stay in the queue forever.
+
server.wait_for_file("/var/spool/cups/d*-001")
+
server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
-
retry(lambda _: "no entries" in server.succeed("lpq -a"))
-
-
# The queue is empty already, so this should be safe.
-
# Otherwise, pairs of "c*"-"d*-001" files might persist.
-
server.execute("rm /var/spool/cups/*")
+
# Delete the job on the client. It should disappear on the
+
# server as well.
+
client.succeed("lprm")
+
client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
+
retry(lambda _: "no entries" in server.succeed("lpq -a"))
-
test_printing(serviceClient, serviceServer)
-
test_printing(socketActivatedClient, socketActivatedServer)
+
# The queue is empty already, so this should be safe.
+
# Otherwise, pairs of "c*"-"d*-001" files might persist.
+
server.execute("rm /var/spool/cups/*")
'';
})
+5
pkgs/misc/cups/default.nix
···
postPatch = ''
substituteInPlace cups/testfile.c \
--replace 'cupsFileFind("cat", "/bin' 'cupsFileFind("cat", "${coreutils}/bin'
+
+
# The cups.socket unit shouldn't be part of cups.service: stopping the
+
# service would stop the socket and break subsequent socket activations.
+
# See https://github.com/apple/cups/issues/6005
+
sed -i '/PartOf=cups.service/d' scheduler/cups.socket.in
'';
nativeBuildInputs = [ pkg-config removeReferencesTo ];