"cups" interface merged into snapd - Additional steps to complete

We want that developers can simply upload applications which are able to print to the Snap Store, without them to be able to upload applications which can do administrative operations (create/remove queues, modify printing permissions, remove someone else’s jobs, …).

The already existing cups-control interface allows any access to CUPS, including administrative operation, therefore there is no general permission for this interface being auto-connected. The permission has to be given by the Snap Store team for each Snap individually.

Therefore we have added a new interface, named cups which only allows non-administrative operations, like listing print queues, printer options, jobs, and, naturally, printing to be executed. To make this safely working it required a much more complex design than the cups-control interface with its implementation being mainly in snapd but partially also in the upstream source code of CUPS. The first (not merged) PR was near a year ago and there, shortly after in a long thread here in the forum, and after that in the finally merged PR a lot of discussion about the design has happened. Today the PR got merged and so the interface is there.

Now, to make everything working as smoothly and transparently as possible for both application developers an users we need to define the additional steps which are still needed and to not having this discussion hidden in long threads I am starting this new thread.

Thanks again, especially to @ijohnson and @jamesh, but also to @pedronis, @alexmurray, @mardy, and Michael Sweet, for their contributions to get to the point where we are now. We are now really close to finally launch the new interface.

1 Like

What We Have Now

For everyone who did not watch the past seasons of this series, here a summary of what we currently have:

Today, the new cups interface for application Snaps being able print via the CUPS Snap got merged into snapd (to be released into edge tomorrow and into stable in snapd 2.55 in 2-3 weeks).

It is supposed to work as follows:

The CUPS Snap has a slot named cups, defined as follows

slots:
  cups:
    interface: cups
    cups-socket-directory: $SNAP_COMMON/run

and in the directory $SNAP_COMMON/run the socket cups.sock is defined (note that this is not yet committed to the CUPS Snap as it requires snapd 2.55).

An application Snap, which wants to print simply needs to plug the cups interface. When such a Snap gets installed from the Snap Store snapd will do the following:

  • If the CUPS Snap is not installed (or a too old version), the CUPS Snap gets automatically installed in addition to the application Snap, like a package dependency.
  • In the application Snap’ sandbox the CUPS Snap’s $SNAP_COMMON/run directory gets mounted to /var/cups, this way the application Snap has access to the CUPS Snap’s cups.sock.
  • The application Snap’s cups plug gets auto-connected to the CUPS Snap’s cups slot.
  • In the application Snap the environment variable CUPS_SERVER=/var/cups/cups.sock gets set, so that the application directs all its print requests to the CUPS daemon of the CUPS Snap.

The CUPS Snap will do the following then:

  • If the system has already a classically installed CUPS (via DEB, RPM, source, …) the CUPS Snap will run in proxy mode, meaning that it will work as a proxy/firewall between the application Snaps and the system’s CUPS. It will replicate the system’s queues and pass jobs through to the system’s CUPS. So the user will have the same queues and printer drivers as he is used to for both printing from classically installed applications and application Snaps.
  • If there is no CUPS installed on the system the CUPS Snap will run in standalone mode, listening not only on $SNAP_COMMON/run/cups.sock but also on /run/cups/cups.sock. This way all applications, both classically installed or snapped, print via the CUPS Snap. Queues have to be created on the snapped CUPS, drivers have to be Printer Applications. Also here the user sees the same print queues for both classic and snapped applications.
  • The CUPS daemon of the CUPS Snap does not accept administrative requests through the cups interface (only through the cups-control interface), so our application Snap cannot create or remove queues, change drivers pr permissions, remove anybody else’s jobs, … it can only print, not mess with CUPS. This is implemented in cupsd of upstream CUPS. So in both cases we can safely let developers upload applications which print as long as they only plug cups AND NOT cups-control.

TODO List

What still needs to be done is the following:

  • snapd 2.55 release: The new snapd with the included cups interface needs to get released to the stable channel of the Snap Store. This will happen in 2-3 weeks from now.
  • Snap Store permissions: [DONE] Request permissions from the Snap Store team for the CUPS Snap to use the cups-socket-directory attribute and for auto-connection of any Snap’s cups plug to the cups slot of the CUPS Snap. I have already posted the request here on the forum.
  • /var/snap in base Snaps: [IN PROGRESS] @ijohnson will post PRs (Update: has posted, see below, and they got all merged) on the base Snaps to create the /var/snap directory. @ijohnson: There are some other PR’s I will file to the base snaps which will behind the scenes make using the cups interface more efficient, but that will not change how it’s used at all (it will just change the mount namespace setup by creating the /var/cups directory there so we don’t have to do lots of mount tricks)
  • CUPS Snap auto-installation: [DONE via placeholder content interface] When an application Snap plugging cups gets installed from the Snap Store and the CUPS Snap is not installed (or a too old version), the CUPS Snap gets automatically installed in addition to the application Snap, like a package dependency. @ijohnson plans to adapt the default-provider setting to also work for the cups interface here: We have to either manually enable this using a transitional placeholder content interface or adapt the default-provider setting to also work for the cups interface. I plan on doing the latter at some point soon so I don’t think that the former needs to be implemented.
  • Snapcraft extension to auto-set CUPS_SERVER environment variable: [NOT NEEDED ANY MORE] Currently in the snapcraft.yaml of an application Snap using the cups interface we have to set the CUPS_SERVER environment variable to /var/cups/cups.sock via environment: entry. This can be made automatic by a snapcraft extension. @ijohnson: That environment variable setting could probably be done with a snapcraft extension, which is not added to snapd, but rather to snapcraft itself. The extension could look like this and also cover the CUPS Snap auto-install. Update: The automatic setting of the CUPS_SERVER environment variable is already done by snapd.
  • Changes in CUPS Snap: The CUPS Snap needs to have the correct cups slot definition, as shown in my previous post, with the “cups-socket-directory: $SNAP_COMMON/run” line added and also in the beginning of the snapcraft.yaml a line “assumes: [snapd2.55]” added as then the Snap will not work with older snapd versions any more. This I only will do when snapd 2.55 gets actually released to the stable channel, to make sure that the CUPS Snap can always be downloaded and used.

With all this done we will be able to launch the new cups interface and tell all the snappers out there to use it for their printing applications, like LibreOffice, Darktable, evince, gedit, …

1 Like

Sneak Preview

Update: No need to set the CUPS-SERVER environment variable in snapcraft.yaml of application Snaps.

For all the snappers around here, I want to give you a sneak preview, so that you can try out the new interface and also test your application Snaps. This way you do not need to wait for the release of snapd 2.55.

  1. Install the snapd Snap with cups interface: A snapshot of snapd with cups interface is available in the Snap Store, in the Edge channel. So you need to switch your snapd to the edge channel: sudo snap refresh snapd --edge --amend
  2. Get CUPS Snap source from OpenPrinting GitHub: The CUPS Snap currently in the Snap Store does not support the new cups interface. The change mentioned in the previous post needs to get applied. Therefore download the source of the Snap so that you can modify and rebuild it: git clone https://github.com/OpenPrinting/cups-snap.git
  3. Edit snapcraft.yaml: Look for the definition of the slots (slots:) and add the line “cups-socket-directory: $SNAP_COMMON/run” to the cups: entry (patch below). DO NOT add “assumes: [snapd2.55]” as the snapshot of snapd still has a 2.54. … version.
  4. Rebuild the CUPS Snap: To rebuild your modified Snap you need to have snapcraft installed. Then you run the command snapcraft snap
  5. Make sure the CUPS Snap from the Snap Store is installed: If it is not done yet, install the CUPS Snap from the Snap Store, to make all its interfaces auto-connected: sudo snap install --edge cups
  6. Install your modified CUPS Snap on your system: You “upgrade” from the Snap Store’s version. The interfaces stay connected. Use the command sudo snap install --dangerous cups_0.1.0_amd64.snap

This is the patch for snapcraft.yaml of the CUPS Snap:

--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -66,6 +66,7 @@ slots:
     interface: cups-control
   cups:
     interface: cups
+    cups-socket-directory: $SNAP_COMMON/run
 
 apps:
   cupsd:

Now we have the new CUPS Snap environment up and running, so we can do the first simple test:

Copy the following snapcraft.yaml:

name: cups-admin-test-no-control
base: core20 # The base snap is the execution environment for this snap
version: 0.1.0
summary: CUPS admin and non-admin tasks out of a Snap 
description: Testing interfaces for the CUPS Snap (no cups-control plugging, so admin tasks should fail)
grade: stable
confinement: strict

apps:
  lpinfo:
    command: usr/sbin/lpinfo
    plugs: [network, cups]
  lpadmin:
    command: usr/sbin/lpadmin
    plugs: [network, avahi-observe, home, cups]
  lpstat:
    command: usr/bin/lpstat
    plugs: [network, avahi-observe, cups]
  lpoptions:
    command: usr/bin/lpoptions
    plugs: [network, home, cups]
  lp:
    command: usr/bin/lp
    plugs: [network, home, cups]
  cancel:
    command: usr/bin/cancel
    plugs: [network, cups]
  lpmove:
    command: usr/sbin/lpmove
    plugs: [network, cups]
  cupsenable:
    command: usr/sbin/cupsenable
    plugs: [network, cups]
  cupsdisable:
    command: usr/sbin/cupsdisable
    plugs: [network, cups]
  cupsaccept:
    command: usr/sbin/cupsaccept
    plugs: [network, cups]
  cupsreject:
    command: usr/sbin/cupsreject
    plugs: [network, cups]
  accept:
    command: usr/sbin/cupsaccept
    plugs: [network, cups]
  reject:
    command: usr/sbin/cupsreject
    plugs: [network, cups]
  cupsctl:
    command: usr/sbin/cupsctl
    plugs: [network, cups]

parts:
  cups-client:
    plugin: dump
    source: .
    stage-packages:
        - cups-client
        - libcups2
    prime:
        - usr/bin/*
        - usr/sbin/*
        - usr/lib/*

Put it into a new, empty directory, build, install it, and connect the interfaces:

snapcraft snap
sudo snap install --dangerous cups-admin-test-no-control_0.1.0_amd64.snap
sudo snap connect cups-admin-test-no-control:avahi-observe
sudo snap connect cups-admin-test-no-control:cups cups:cups

The Snap simply puts together the command line tools of CUPS, exactly the ones of the CUPS package which comes with Ubuntu 20.04 LTS, what core20 is based on. Some commands are administrative, like lpadmin or lpinfo and others are not, like lp or lpstat.

In the snapcraft.yaml you see how easy it is to use the interface. If one of the contained applications (apps: section) wants to print or otherwise non-administratively access CUPS (for example to list print queues and/or jobs) you only need to add cups to the application’s list of plugs:. If you have an own Snap with an application which prints but not a printer setup tool, simply replace cups-control in the plugs: by cups.

Now let us execute some of the commands in this Snap:

$ cups-admin-test-no-control.lpstat -H
/var/cups/cups.sock
$ cups-admin-test-no-control.lpstat -v
device for printer_on_snap: ...
$ cups-admin-test-no-control.lpinfo -v
lpinfo: Forbidden
$ cups-admin-test-no-control.lpadmin -x printer_on_snap
lpadmin: Forbidden
$ cups-admin-test-no-control.lpstat -v
device for printer_on_snap: ...
$

You see that you get a Forbidden on administrative commands and the others work normally. Also the Snap’s commands are talking to the snapped CUPS (cups-admin-test-no-control.lpstat -H gives /var/cups/cups.sock and not /run/cups/cups.sock). You only see your system’s CUPS queues because the CUPS Snap is running in proxy mode.

If you use the cups-control interface instead of cups for the plugs:, your Snap will talk directly to your system’s CUPS and the administrative commands will work.

Now you could try out one of your application Snaps, like a GUI application where one can print via a print dialog. If you do the changes correctly and manually connect your Snap’s cups interface with cups:cups, the print dialog should work normally, listing the available print queues and allowing you to set options and print the job.

Do not try this with a printer setup tool like system-config-printer, as a result it will show you the current print queues but you will not be able to modify them or add new one. Such applications have to continue using cups-control.

Also do never use the cups interface for one application in your Snap and the cups-control interface in another. As you will request manual permission for your Snap to auto-connect cups-control anyway, use cups-control for all the applications. Or create two Snaps, one containing only the non-administrative applications and another one only the administrative ones.

Please post your experiences and questions here in this thread.

About the needed environment: in the application Snap’s snapcraft.yaml I have added an item to the TODO list.

About the fact that one should not use cups and cups-control in the same Snap I have already posted in the PR on Nov 2, 2021 and today @jdstrand has commented on this that this could be a checked in the review-tools and asked @alexmurray about this idea and he agreed with it.

Thanks, @jdstrand and @alexmurray.

this is not necessary, snapd will now automatically set CUPS_SERVER for the snap that has plugs: [cups] in it, please do not add CUPS_SERVER definition inside snaps. This is so that some day we may change this to use /run/cups/cups.sock instead, we want to be able to do this transparently from inside snapd without changing application snaps

1 Like

@ijohnson Thanks, I have updated the TODO list and also the testing instructions now.

Some updates:

  • The Snap Store team has no granted auto-connect of the cups slot of the CUPS Snap.
  • No special permission is needed for using the cups-socket-directory attribute.
  • For the CUPS Snap auto-installation to work, there must be a version of the CUPS Snap in the stable channel of the Snap Store. This I have done now.
  • For the CUPS Snap we will later on look into adding appropriate functionality to snapd that it is simply done if a Snap is installed which plugs cups. For the time being there is a workaround which does not require any change in the snapcraft.yaml of the CUPS Snap, it is enough to add the default-provider: cups to the snapcraft.yaml files of the client Snaps which plug cups using a placeholder content interface as shown below.
  • Later on default-provider support should be added directly to the cups interface. @mvo, could you work on this with me?

Here is what has to be added to client application Snaps which plug cups in order to auto-install the CUPS Snap (if it is not already installed):

plugs:
  foo-install-cups:
    interface: content
    content: foo
    target: $SNAP_DATA/foo
    default-provider: cups

What is mainly still missing is:

  • snapd 2.55 release
  • /var/snap directory to be created in the base Snaps
  • Add cups-socket-directory: $SNAP_COMMON/run and assumes: [snapd2.55] to snapcraft.yaml of CUPS Snap (required snapd 2.55 release)

TODO list above updated.

@ijohnson @mvo how will we proceed?

Thanks for the update of the status and the outstanding tasks.

Given the workaround above I would like to put this particular task into the roadmap for the next cycle.

We (@ijohnson, @mvo, and me) had a meeting Ysterday on the Canonical Sprint in Frankfurt and coordinated the tasks:

  1. @ijohnson will add the /var/snap directories to the base Snap. This will happen rather quickly, some days.
  2. @ijohnson and @mvo will release snapd 2.55.0. This will take 2-4 weeks from now, as there will be put out another bug fix release (2.54.4) before.
  3. As soon as snapd 2.55 has landed I will add cups-socket-directory: $SNAP_COMMON/run and assumes: [snapd2.55] to snapcraft.yaml of CUPS Snap and commit this.
  4. Announcement of the cups interface and its usage here on the forums (snapcraft and snapd).

Notr that what I wrote above is the first approach of the cups interface working as designed, but there is still an ugliness. The packager of a client application cannot simply only plug cups but he also needs to add the content interface plug definition with default-provider: cups to snapcraft.yaml Therefore we have also

  1. Add default-provider: cups supoort directly to the cups interface in snapd. This @mvo has added to the roadmap of the upcoming development cycle. It will be done in April or May.
  2. Post an update announcement telling that plugging cups is all which have to be done so that a client application can print.

So user application snappers will be able to use the cups interface in 2-4 from now, but in some months it will get really easy to use it.

For testing the cups interface @ijohnson has created a small test Snap (Snap Store) and it shows that auto-download of the CUPS Snap and auto-connection of the cups interface to the CUPS Snap’s slot actually work. Actual printing and communication with CUPS could not be tested as the Snap has a little bug.

@ijohnson has posted PRs to the core Snaps to add /var/cups: core, core-base, core18, core20.

The PRs got all merged now, thanks @ijohnson and @mvo.

As the review-tools of the Snap Store already require the “cups-socket-directory: $SNAP_COMMON/run” line in snapcraft.yaml I have committed its addition in the CUPS Snap now. Technically the CUPS Snap is completed now for the new cups interface, but to assure that the cups interface will work for everyone, we will add the assumes: [snapd2.55] as soon as snapd 2.55 makes it into the stable channel of the Snap Store.

Recent tests have revealed the following bug:

In a client application I observed that the CUPS_SERVER environment variable did not get set and therefore the snapped application tries to access CUPS on the standard socket of the system’s CUPS, and not on the mounted socket of the CUPS Snap.

After @ijohnson told me that he cannot reproduce the bug I have reported on Launchpad (previous comment) I tried testing again, snapd having auto-updated to version 2.54.4+git1079.gbf8d25c (from the Edge channel of the Snap Store), and this time everything worked again. We have closed the bug report now.

As the editability of the original TODO list has expired (after 1 month) I am re-posting it here to document the further development:

TODO List

What still needs to be done is the following:

  • snapd 2.55 release: The new snapd with the included cups interface needs to get released to the stable channel of the Snap Store. This will happen in 2-3 weeks from now.
  • Snap Store permissions: [DONE] Request permissions from the Snap Store team for the CUPS Snap to use the cups-socket-directory attribute and for auto-connection of any Snap’s cups plug to the cups slot of the CUPS Snap. I have already posted the request here on the forum.
  • /var/snap in base Snaps: [DONE] @ijohnson will post PRs (Update: has posted, see below, and they got all merged) on the base Snaps to create the /var/snap directory. @ijohnson: There are some other PR’s I will file to the base snaps which will behind the scenes make using the cups interface more efficient, but that will not change how it’s used at all (it will just change the mount namespace setup by creating the /var/cups directory there so we don’t have to do lots of mount tricks)
  • CUPS Snap auto-installation: [DONE via placeholder content interface] When an application Snap plugging cups gets installed from the Snap Store and the CUPS Snap is not installed (or a too old version), the CUPS Snap gets automatically installed in addition to the application Snap, like a package dependency. @ijohnson plans to adapt the default-provider setting to also work for the cups interface here: We have to either manually enable this using a transitional placeholder content interface or adapt the default-provider setting to also work for the cups interface. I plan on doing the latter at some point soon so I don’t think that the former needs to be implemented.
  • Snapcraft extension to auto-set CUPS_SERVER environment variable: [NOT NEEDED ANY MORE] Currently in the snapcraft.yaml of an application Snap using the cups interface we have to set the CUPS_SERVER environment variable to /var/cups/cups.sock via environment: entry. This can be made automatic by a snapcraft extension. @ijohnson: That environment variable setting could probably be done with a snapcraft extension, which is not added to snapd, but rather to snapcraft itself. The extension could look like this and also cover the CUPS Snap auto-install. Update: The automatic setting of the CUPS_SERVER environment variable is already done by snapd.
  • Changes in CUPS Snap: [IN PROGRESS] The CUPS Snap needs to have the correct cups slot definition, as shown in my earlier post, with the “cups-socket-directory: $SNAP_COMMON/run” line added (Update: This line is added now, was needed by Snap Store’s review-tools to accept the CUPS Snap) and also in the beginning of the snapcraft.yaml a line “assumes: [snapd2.55]” added (Update: This line is commented out now, as snapd 2.55.2 got withdrawn from the stable channel) as then the Snap will perhaps not work with older snapd versions any more. The latter I only will do when snapd 2.55 gets actually released to the stable channel, to make sure that the CUPS Snap can always be downloaded and used.

With all this done we will be able to launch the new cups interface and tell all the snappers out there to use it for their printing applications, like LibreOffice, Darktable, evince, gedit, … And we are really close, only the release of snapd 2.55 is missing and probably the next landing of the core Snaps.

Update: snapd 2.55.2 is NOT yet in the stable channel, seems that it got put and withdrawn right after.

1 Like

Some news about our progress:

  • Core Snaps with /var/cups directories, at least core18 and core20, are in the stable channel now.
  • snapd 2.55.2 will reach the stable channel on Tue, April 5, 2022
  • We have found a bug in snapd, snapd setting the CUPS_SERVER environment variable to /var/cups/cups.sock not only when the cups interface is plugged but always, on any Snap, and so breaks the CUPS Snap and Snaps which plug cups-control and not cups. A PR on snapd, at least to address the case of the CUPS Snap, got posted by @mvo. Thanks a lot for working with me on the fix and posting the PR. He is also working on a solution for the cups-control case. In the proxy-mode the CUPS Snap works correctly even with this bug present.