about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.xml10
-rw-r--r--nixos/modules/hardware/openrazer.nix133
-rw-r--r--nixos/modules/hardware/printers.nix135
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/services/desktops/blueman.nix25
-rw-r--r--nixos/modules/services/desktops/gnome3/glib-networking.nix6
-rw-r--r--nixos/modules/services/networking/syncplay.nix80
-rw-r--r--nixos/tests/containers-tmpfs.nix2
-rw-r--r--nixos/tests/printing.nix177
9 files changed, 487 insertions, 85 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml
index 11c13b8d942b..c0b7cc48a461 100644
--- a/nixos/doc/manual/release-notes/rl-1909.xml
+++ b/nixos/doc/manual/release-notes/rl-1909.xml
@@ -135,7 +135,17 @@
      <literal>./programs/dwm-status.nix</literal>
     </para>
    </listitem>
+   <listitem>
+    <para>
+     The new <varname>hardware.printers</varname> module allows to declaratively configure CUPS printers
+     via the <varname>ensurePrinters</varname> and
+     <varname>ensureDefaultPrinter</varname> options.
+     <varname>ensurePrinters</varname> will never delete existing printers,
+     but will make sure that the given printers are configured as declared.
+    </para>
+   </listitem>
   </itemizedlist>
+
  </section>
 
  <section xmlns="http://docbook.org/ns/docbook"
diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix
new file mode 100644
index 000000000000..883db7f2f4f1
--- /dev/null
+++ b/nixos/modules/hardware/openrazer.nix
@@ -0,0 +1,133 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.openrazer;
+  kernelPackages = config.boot.kernelPackages;
+
+  toPyBoolStr = b: if b then "True" else "False";
+
+  daemonExe = "${pkgs.openrazer-daemon}/bin/openrazer-daemon --config ${daemonConfFile}";
+
+  daemonConfFile = pkgs.writeTextFile {
+    name = "razer.conf";
+    text = ''
+      [General]
+      verbose_logging = ${toPyBoolStr cfg.verboseLogging}
+
+      [Startup]
+      sync_effects_enabled = ${toPyBoolStr cfg.syncEffectsEnabled}
+      devices_off_on_screensaver = ${toPyBoolStr cfg.devicesOffOnScreensaver}
+      mouse_battery_notifier = ${toPyBoolStr cfg.mouseBatteryNotifier}
+
+      [Statistics]
+      key_statistics = ${toPyBoolStr cfg.keyStatistics}
+    '';
+  };
+
+  dbusServiceFile = pkgs.writeTextFile rec {
+    name = "org.razer.service";
+    destination = "/share/dbus-1/services/${name}";
+    text = ''
+      [D-BUS Service]
+      Name=org.razer
+      Exec=${daemonExe}
+      SystemdService=openrazer-daemon.service
+    '';
+  };
+
+  drivers = [
+    "razerkbd"
+    "razermouse"
+    "razerfirefly"
+    "razerkraken"
+    "razermug"
+    "razercore"
+  ];
+in
+{
+  options = {
+    hardware.openrazer = {
+      enable = mkEnableOption "OpenRazer drivers and userspace daemon.";
+
+      verboseLogging = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable verbose logging. Logs debug messages.
+        '';
+      };
+
+      syncEffectsEnabled = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Set the sync effects flag to true so any assignment of
+          effects will work across devices.
+        '';
+      };
+
+      devicesOffOnScreensaver = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Turn off the devices when the systems screensaver kicks in.
+        '';
+      };
+
+      mouseBatteryNotifier = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Mouse battery notifier.
+        '';
+      };
+
+      keyStatistics = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Collects number of keypresses per hour per key used to
+          generate a heatmap.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.extraModulePackages = [ kernelPackages.openrazer ];
+    boot.kernelModules = drivers;
+
+    # Makes the man pages available so you can succesfully run
+    # > systemctl --user help openrazer-daemon
+    environment.systemPackages = [ pkgs.python3Packages.openrazer-daemon.man ];
+
+    services.udev.packages = [ kernelPackages.openrazer ];
+    services.dbus.packages = [ dbusServiceFile ];
+
+    # A user must be a member of the plugdev group in order to start
+    # the openrazer-daemon. Therefore we make sure that the plugdev
+    # group exists.
+    users.groups.plugdev = {};
+
+    systemd.user.services.openrazer-daemon = {
+      description = "Daemon to manage razer devices in userspace";
+      unitConfig.Documentation = "man:openrazer-daemon(8)";
+        # Requires a graphical session so the daemon knows when the screensaver
+        # starts. See the 'devicesOffOnScreensaver' option.
+        wantedBy = [ "graphical-session.target" ];
+        partOf = [ "graphical-session.target" ];
+        serviceConfig = {
+          Type = "dbus";
+          BusName = "org.razer";
+          ExecStart = "${daemonExe} --foreground";
+          Restart = "always";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ roelvandijk ];
+  };
+}
diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix
new file mode 100644
index 000000000000..12ee5516d4ed
--- /dev/null
+++ b/nixos/modules/hardware/printers.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.hardware.printers;
+  ppdOptionsString = options: optionalString (options != {})
+    (concatStringsSep " "
+      (mapAttrsToList (name: value: "-o '${name}'='${value}'") options)
+    );
+  ensurePrinter = p: ''
+    ${pkgs.cups}/bin/lpadmin -p '${p.name}' -E \
+      ${optionalString (p.location != null) "-L '${p.location}'"} \
+      ${optionalString (p.description != null) "-D '${p.description}'"} \
+      -v '${p.deviceUri}' \
+      -m '${p.model}' \
+      ${ppdOptionsString p.ppdOptions}
+  '';
+  ensureDefaultPrinter = name: ''
+    ${pkgs.cups}/bin/lpoptions -d '${name}'
+  '';
+
+  # "graph but not # or /" can't be implemented as regex alone due to missing lookahead support
+  noInvalidChars = str: all (c: c != "#" && c != "/") (stringToCharacters str);
+  printerName = (types.addCheck (types.strMatching "[[:graph:]]+") noInvalidChars)
+    // { description = "printable string without spaces, # and /"; };
+
+
+in {
+  options = {
+    hardware.printers = {
+      ensureDefaultPrinter = mkOption {
+        type = types.nullOr printerName;
+        default = null;
+        description = ''
+          Ensures the named printer is the default CUPS printer / printer queue.
+        '';
+      };
+      ensurePrinters = mkOption {
+        description = ''
+          Will regularly ensure that the given CUPS printers are configured as declared here.
+          If a printer's options are manually changed afterwards, they will be overwritten eventually.
+          This option will never delete any printer, even if removed from this list.
+          You can check existing printers with <command>lpstat -s</command>
+          and remove printers with <command>lpadmin -x &lt;printer-name&gt;</command>.
+          Printers not listed here can still be manually configured.
+        '';
+        default = [];
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = printerName;
+              example = "BrotherHL_Workroom";
+              description = ''
+                Name of the printer / printer queue.
+                May contain any printable characters except "/", "#", and space.
+              '';
+            };
+            location = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "Workroom";
+              description = ''
+                Optional human-readable location.
+              '';
+            };
+            description = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "Brother HL-5140";
+              description = ''
+                Optional human-readable description.
+              '';
+            };
+            deviceUri = mkOption {
+              type = types.str;
+              example = [
+                "ipp://printserver.local/printers/BrotherHL_Workroom"
+                "usb://HP/DESKJET%20940C?serial=CN16E6C364BH"
+              ];
+              description = ''
+                How to reach the printer.
+                <command>lpinfo -v</command> shows a list of supported device URIs and schemes.
+              '';
+            };
+            model = mkOption {
+              type = types.str;
+              example = literalExample ''
+                gutenprint.''${lib.version.majorMinor (lib.getVersion pkgs.cups)}://brother-hl-5140/expert
+              '';
+              description = ''
+                Location of the ppd driver file for the printer.
+                <command>lpinfo -m</command> shows a list of supported models.
+              '';
+            };
+            ppdOptions = mkOption {
+              type = types.attrsOf types.str;
+              example = {
+                "PageSize" = "A4";
+                "Duplex" = "DuplexNoTumble";
+              };
+              default = {};
+              description = ''
+                Sets PPD options for the printer.
+                <command>lpoptions [-p printername] -l</command> shows suported PPD options for the given printer.
+              '';
+            };
+          };
+        });
+      };
+    };
+  };
+
+  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 {
+      description = "Ensure NixOS-configured CUPS printers";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ cupsUnit ];
+      # in contrast to cups.socket, for cups.service, this is actually not enough,
+      # as the cups service reports its activation before clients can actually interact with it.
+      # Because of this, commands like `lpinfo -v` will report a bad file descriptor
+      # due to the missing UNIX socket without sufficient sleep time.
+      after = [ cupsUnit ];
+
+      serviceConfig = {
+        Type = "oneshot";
+      };
+
+       # sleep 10 is required to wait until cups.service is actually initialized and has created its UNIX socket file
+      script = (optionalString (!config.services.printing.startWhenNeeded) "sleep 10\n")
+        + (concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters)
+        + optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter);
+    };
+  };
+}
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 4bc37ed3f171..290c29993b58 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -58,7 +58,9 @@
   ./hardware/network/intel-2200bg.nix
   ./hardware/nitrokey.nix
   ./hardware/opengl.nix
+  ./hardware/openrazer.nix
   ./hardware/pcmcia.nix
+  ./hardware/printers.nix
   ./hardware/raid/hpsa.nix
   ./hardware/steam-hardware.nix
   ./hardware/usb-wwan.nix
@@ -281,6 +283,7 @@
   ./services/databases/virtuoso.nix
   ./services/desktops/accountsservice.nix
   ./services/desktops/bamf.nix
+  ./services/desktops/blueman.nix
   ./services/desktops/deepin/deepin.nix
   ./services/desktops/dleyna-renderer.nix
   ./services/desktops/dleyna-server.nix
@@ -698,6 +701,7 @@
   ./services/networking/supybot.nix
   ./services/networking/syncthing.nix
   ./services/networking/syncthing-relay.nix
+  ./services/networking/syncplay.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
   ./services/networking/tedicross.nix
diff --git a/nixos/modules/services/desktops/blueman.nix b/nixos/modules/services/desktops/blueman.nix
new file mode 100644
index 000000000000..18ad610247ed
--- /dev/null
+++ b/nixos/modules/services/desktops/blueman.nix
@@ -0,0 +1,25 @@
+# blueman service
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.blueman;
+in {
+  ###### interface
+  options = {
+    services.blueman = {
+      enable = mkEnableOption "blueman";
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.blueman ];
+
+    services.dbus.packages = [ pkgs.blueman ];
+
+    systemd.packages = [ pkgs.blueman ];
+  };
+}
diff --git a/nixos/modules/services/desktops/gnome3/glib-networking.nix b/nixos/modules/services/desktops/gnome3/glib-networking.nix
index 186668d7d385..fcd58509d6fc 100644
--- a/nixos/modules/services/desktops/gnome3/glib-networking.nix
+++ b/nixos/modules/services/desktops/gnome3/glib-networking.nix
@@ -22,11 +22,11 @@ with lib;
 
   config = mkIf config.services.gnome3.glib-networking.enable {
 
-    services.dbus.packages = [ pkgs.gnome3.glib-networking ];
+    services.dbus.packages = [ pkgs.glib-networking ];
 
-    systemd.packages = [ pkgs.gnome3.glib-networking ];
+    systemd.packages = [ pkgs.glib-networking ];
 
-    environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.gnome3.glib-networking.out}/lib/gio/modules" ];
+    environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.glib-networking.out}/lib/gio/modules" ];
 
   };
 
diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
new file mode 100644
index 000000000000..e3147c10502c
--- /dev/null
+++ b/nixos/modules/services/networking/syncplay.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncplay;
+
+  cmdArgs =
+    [ "--port" cfg.port ]
+    ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ]
+    ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ];
+
+in
+{
+  options = {
+    services.syncplay = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If enabled, start the Syncplay server.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8999;
+        description = ''
+          TCP port to bind to.
+        '';
+      };
+
+      salt = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Salt to allow room operator passwords generated by this server
+          instance to still work when the server is restarted.
+        '';
+      };
+
+      certDir = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          TLS certificates directory to use for encryption. See
+          <link xlink:href="https://github.com/Syncplay/syncplay/wiki/TLS-support"/>.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nobody";
+        description = ''
+          User to use when running Syncplay.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nogroup";
+        description = ''
+          Group to use when running Syncplay.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.syncplay = {
+      description = "Syncplay Service";
+      wantedBy    = [ "multi-user.target" ];
+      after       = [ "network-online.target "];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+  };
+}
diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix
index 05c21f4907bf..e29fe6bbf032 100644
--- a/nixos/tests/containers-tmpfs.nix
+++ b/nixos/tests/containers-tmpfs.nix
@@ -3,7 +3,7 @@
 import ./make-test.nix ({ pkgs, ...} : {
   name = "containers-tmpfs";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ ckampka ];
+    maintainers = [ kampka ];
   };
 
   machine =
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index 74583ae55623..4d0df289cf75 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -1,99 +1,114 @@
 # Test printing via CUPS.
 
-import ./make-test.nix ({pkgs, ... }: {
+import ./make-test.nix ({pkgs, ... }:
+let
+  printingServer = startWhenNeeded: {
+    services.printing.enable = true;
+    services.printing.startWhenNeeded = startWhenNeeded;
+    services.printing.listenAddresses = [ "*:631" ];
+    services.printing.defaultShared = true;
+    services.printing.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 = [{
+      name = "DeskjetLocal";
+      deviceUri = "usb://foobar/printers/foobar";
+      model = "drv:///sample.drv/deskjet.ppd";
+    }];
+  };
+  printingClient = startWhenNeeded: {
+    services.printing.enable = true;
+    services.printing.startWhenNeeded = startWhenNeeded;
+    # Add printer to the client as well, via IPP.
+    hardware.printers.ensurePrinters = [{
+      name = "DeskjetRemote";
+      deviceUri = "ipp://${if startWhenNeeded then "socketActivatedServer" else "serviceServer"}/printers/DeskjetLocal";
+      model = "drv:///sample.drv/deskjet.ppd";
+    }];
+    hardware.printers.ensureDefaultPrinter = "DeskjetRemote";
+  };
+
+in
+
+{
   name = "printing";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ domenkozar eelco matthewbauer ];
   };
 
   nodes = {
+    socketActivatedServer = { ... }: (printingServer true);
+    serviceServer = { ... }: (printingServer false);
 
-    server =
-      { ... }:
-      { services.printing.enable = true;
-        services.printing.listenAddresses = [ "*:631" ];
-        services.printing.defaultShared = true;
-        services.printing.extraConf =
-          ''
-            <Location />
-              Order allow,deny
-              Allow from all
-            </Location>
-          '';
-        networking.firewall.allowedTCPPorts = [ 631 ];
-      };
-
-    client =
-      { ... }:
-      { services.printing.enable = true;
-      };
-
+    socketActivatedClient = { ... }: (printingClient true);
+    serviceClient = { ... }: (printingClient false);
   };
 
   testScript =
     ''
       startAll;
 
-      $client->succeed("lpstat -r") =~ /scheduler is running/ or die;
-      # check local encrypted connections work without error
-      $client->succeed("lpstat -E -r") =~ /scheduler is running/ or die;
-      # Test that UNIX socket is used for connections.
-      $client->succeed("lpstat -H") =~ "/run/cups/cups.sock" or die;
-      # Test that HTTP server is available too.
-      $client->succeed("curl --fail http://localhost:631/");
-      $client->succeed("curl --fail http://server:631/");
-      $server->fail("curl --fail --connect-timeout 2  http://client:631/");
-
-      # Add a HP Deskjet printer connected via USB to the server.
-      $server->succeed("lpadmin -p DeskjetLocal -E -v usb://foobar/printers/foobar");
-
-      # Add it to the client as well via IPP.
-      $client->succeed("lpadmin -p DeskjetRemote -E -v ipp://server/printers/DeskjetLocal");
-      $client->succeed("lpadmin -d DeskjetRemote");
-
-      # Do some status checks.
-      $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die;
-      $client->succeed("lpstat -h server:631 -a") =~ /DeskjetLocal accepting requests/ or die;
-      $client->succeed("cupsdisable DeskjetRemote");
-      $client->succeed("lpq") =~ /DeskjetRemote is not ready.*no entries/s or die;
-      $client->succeed("cupsenable DeskjetRemote");
-      $client->succeed("lpq") =~ /DeskjetRemote is ready.*no entries/s or die;
-
-      # Test printing various file types.
-      foreach my $file ("${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 =~ /([^\/]*)$/; my $fn = $1;
-
-          subtest "print $fn", sub {
-
-              # Print the file on the client.
-              $client->succeed("lp $file");
-              $client->sleep(10);
-              $client->succeed("lpq") =~ /active.*root.*$fn/ or die;
-
-              # 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->waitForFile("/var/spool/cups/d*-001");
-              $server->sleep(10);
-              $server->succeed("lpq -a") =~ /$fn/ or die;
-
-              # Delete the job on the client.  It should disappear on the
-              # server as well.
-              $client->succeed("lprm");
-              $client->sleep(10);
-              $client->succeed("lpq -a") =~ /no entries/;
-              Machine::retry sub {
-                return 1 if $server->succeed("lpq -a") =~ /no entries/;
+      # Make sure that cups is up on both sides.
+      $serviceServer->waitForUnit("cups.service");
+      $serviceClient->waitForUnit("cups.service");
+      # wait until cups is fully initialized and ensure-printers has executed with 10s delay
+      $serviceClient->sleep(20);
+      $socketActivatedClient->waitUntilSucceeds("systemctl status ensure-printers | grep -q -E 'code=exited, status=0/SUCCESS'");
+      sub testPrinting {
+          my ($client, $server) = (@_);
+          my $clientHostname = $client->name();
+          my $serverHostname = $server->name();
+          $client->succeed("lpstat -r") =~ /scheduler is running/ or die;
+          # Test that UNIX socket is used for connections.
+          $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die;
+          # Test that HTTP server is available too.
+          $client->succeed("curl --fail http://localhost:631/");
+          $client->succeed("curl --fail http://$serverHostname:631/");
+          $server->fail("curl --fail --connect-timeout 2  http://$clientHostname:631/");
+          # Do some status checks.
+          $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die;
+          $client->succeed("lpstat -h $serverHostname:631 -a") =~ /DeskjetLocal accepting requests/ or die;
+          $client->succeed("cupsdisable DeskjetRemote");
+          $client->succeed("lpq") =~ /DeskjetRemote is not ready.*no entries/s or die;
+          $client->succeed("cupsenable DeskjetRemote");
+          $client->succeed("lpq") =~ /DeskjetRemote is ready.*no entries/s or die;
+          # Test printing various file types.
+          foreach my $file ("${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 =~ /([^\/]*)$/; my $fn = $1;
+              subtest "print $fn", sub {
+                  # Print the file on the client.
+                  $client->succeed("lp $file");
+                  $client->waitUntilSucceeds("lpq | grep -q -E 'active.*root.*$fn'");
+                  # 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->waitForFile("/var/spool/cups/d*-001");
+                  $server->waitUntilSucceeds("lpq -a | grep -q -E '$fn'");
+                  # Delete the job on the client.  It should disappear on the
+                  # server as well.
+                  $client->succeed("lprm");
+                  $client->waitUntilSucceeds("lpq -a | grep -q -E 'no entries'");
+                  Machine::retry sub {
+                    return 1 if $server->succeed("lpq -a") =~ /no entries/;
+                  };
+                  # 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/*");
               };
-              # 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/*");
-          };
+          }
       }
-    '';
+      testPrinting($serviceClient, $serviceServer);
+      testPrinting($socketActivatedClient, $socketActivatedServer);
+  '';
 })