diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix
index 85e3215127fd..846ff6f3fb4f 100644
--- a/nixos/modules/hardware/printers.nix
+++ b/nixos/modules/hardware/printers.nix
@@ -110,21 +110,26 @@ in {
};
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")
+ ];
};
};
}
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index ae59dcc226de..9ac89e057620 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -341,7 +341,7 @@ in
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;
};
@@ -395,10 +395,7 @@ in
''}
'';
- serviceConfig = {
- PrivateTmp = true;
- RuntimeDirectory = [ "cups" ];
- };
+ serviceConfig.PrivateTmp = true;
};
systemd.services.cups-browsed = mkIf avahiEnabled
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 337b5192776f..ed698b63ee63 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -146,7 +146,8 @@ in rec {
(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")
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 1956d3c9e8c7..7a005ff3d9ea 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -531,7 +531,8 @@ in {
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 {};
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index cfebe232d92a..7df042e72e90 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -1,19 +1,31 @@
# 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 = ''
-
- Order allow,deny
- Allow from all
-
- '';
+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 = ''
+
+ Order allow,deny
+ Allow from all
+
+ '';
+ };
networking.firewall.allowedTCPPorts = [ 631 ];
# Add a HP Deskjet printer connected via USB to the server.
hardware.printers.ensurePrinters = [{
@@ -22,32 +34,19 @@ let
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
@@ -55,75 +54,69 @@ in {
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("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("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}'")
- # 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'")
- # 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"))
- 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/*")
-
-
- 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/*")
'';
})
diff --git a/pkgs/misc/cups/default.nix b/pkgs/misc/cups/default.nix
index 30e0ce44ca4a..b707647377ca 100644
--- a/pkgs/misc/cups/default.nix
+++ b/pkgs/misc/cups/default.nix
@@ -38,6 +38,11 @@ stdenv.mkDerivation rec {
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 ];