Classic confinement request: communitheme-set-default

@didrocks We quickly talked on IRC and @jdstrand asked to postpone that to Tuesday, so he has time to prepare more details about the potential update-alternatives interface.

@jdstrand, @niemeyer: hey, any update?

Last Friday didn’t work for me; we’ll discuss this soon.

@niemeyer and I had a call today and came up with a scoped design for an update-alternatives interface that should work for communitheme-set-default and as a general mechanism. @niemeyer, please read and approve the design since I had to make a small adjustment (not linking into the mounted squash) and had ideas for future iterations (alternatives for snap commands, link groups).

Design

The overall design is as follows:

  • on systems that have the ‘update-alternatives’ command, an implicit slot named ‘alternatives’ will be provided by snapd (eg implicitOnClassic: osutil.FileExists("/usr/bin/update-alternatives") (probably something smarter ;). Eventually can extend this to other commands that provide similar functionality on other distros
  • snaps desiring to use this mechanism will ‘plugs’ the ‘alternatives’ interface
  • upon interface connect, snapd will call update-alternatives with appropriate arguments and default priority to install the alternative on behalf of the application
  • upon interface disconnect, snapd will call update-alternatives with appropriate arguments to remove the alternative on behalf of the application
  • since the alternatives mechanism allows switching out system data, binaries, etc to that of the snap, the interface will be manually connected (snap declarations can of course override this).
  • to avoid race conditions with a snap’s squashfs mount and use of the alternative, on snap install/refresh snapd will copy the alternative to /var/lib/snapd/alternatives/snap.name (eg, to avoid situations like gdm not starting because it came up before the communitheme-set-default snap was mounted)

Constraints

  • snapd shall not allow creating a brand new alternative and therefore the alternative must already exist (eg, update-alternatives --list gdm3.css must return without error). Put another way, the ‘alternatives’ interface is only allowed to ‘update’. snapd may offer this in the future.
  • snapd shall not support ‘link groups’ initially (where multiple files can be updated as a group) since the target of the symlink cannot be automatically determined. snapd may offer this in the future
  • snapd shall honor ‘automatic’ and ‘manual’ mode and behave like Debian maintainer scripts (ie, update the links as needed if the link group is in ‘automatic’ mode, otherwise it will leave it untouched)
  • the update-alternatives command only works with files, not directories, so the snapd implementation will also only support files, not directories

Store Reviews

As mentioned, the interface will be manually connected. The interface is designed in part to facilitate store reviews where a reviewer should be able to easily see the name of the alternative and what the snap is providing for it. In this manner we can guard against, say, a snap declaring an alternative for ‘vim’ and having it point to a binary in the snap which would be executed outside of confinement.

A future iteration could support the concept of using the alternatives interface with one of the snap’s commands, such that instead of copying the file from the snap to /var/lib/snapd/alternatives/snap.foo.cmd/bar/file, it symlinks /var/lib/snapd/alternatives/snap.foo.cmd/bar/file to /snap/bin/foo.cmd.

Implementation

snap.yaml:

name: foo
plugs:
  alternatives:
    linkname1:
      path: $SNAP/bar
    linkname2:
      path: $SNAP/baz
      priority: 30
    linkname3:
      priority: 30
      command: true            # ??? not implemented in phase 1
    linkname-with-link-group:  # not implemented in phase 1
      group:
      - $SNAP/norf
      - $SNAP/corge
      - ...

apps:
  cmd:
    plugs:
    - alternatives

For a snap with the above snap.yaml, snap install foo will have snapd do:

  1. copy $SNAP/bar from the snap to /var/lib/snaps/alternatives/snap.foo.cmd/linkname1/bar
  2. copy $SNAP/baz from the snap to /var/lib/snaps/alternatives/snap.foo.cmd/linkname2/baz

For a snap with the above snap.yaml, snap connect foo:alternatives will have snapd do (pseudo code):

for lname in linkname1 linkname2:
    # make sure the alternative exists
    if ! `update-alternatives --list <lname>`:
        continue

    priority = 20
    if lname specified priority:
        priority = <lname's priority>

    # link source from the snap
    path = /var/lib/snapd/alternatives/snap.foo/<lname>/<lname's path>

    # auto-detect the link target from existing alternative on the system
    target = `update-alternatives --display <lname>` | grep '  link <lname> is ' | awk '{print $4}'`

    # add the snap alternative for the detected link target
    `update-alternatives --install <target> <lname> <path> <priority>`

(a proper implementation would detect the ‘target’ much more robustly :wink: )

For a snap with the above snap.yaml, snap disconnect foo:alternatives will similarly have:

for lname in linkname1 linkname2:
    path = <lname's path>
    `update-alternatives --remove <lname> <path>`

With the above implementation, the communitheme-set-default snap would specify:

plugs:
  alternatives:
    gdm3.css:
      path: $SNAP/path/to/my.css
      priority: 15

apps:
  communitheme-set-default:
    ...

Then on snap install communitheme-set-default, snapd:

  1. copies $SNAP/path/to/my.css from the snap to /var/lib/snapd/alternatives/snap.communitheme-set-default.communitheme-set-default/gdm3.css/my.css

Then on snap connect communitheme-set-default:alternatives, snapd:

  1. checks if ‘gdm3.css’ is an alternative, if so…
  2. update-alternatives --install /usr/share/gnome-shell/theme/gdm3.css gdm3.css /var/lib/snapd/alternatives/snap.communitheme-set-default.communitheme-set-default/gdm3.css/my.css 15

Then on snap disconnect communitheme-set-default:alternatives, snapd:

  1. update-alternatives --remove gdm3.css /var/lib/snapd/alternatives/snap.communitheme-set-default.communitheme-set-default/gdm3.css/my.css

Implementation notes:

The ‘alternatives’ interface (snapd.git/interfaces/built/alternatives.go) should use a new ‘alternatives’ backend (snapd.git/interfaces/alternatives/…) where alternatives.go calls out to it.

1 Like

Any progress on the implementation and a potential ETA for releasing? (Note that the request was initially opened 2 months ago)

I don’t know if this is assigned to anyone (I suspect not yet, but @niemeyer can comment authoritatively). That said, now that there is a design, the implementation can be done by anyone.

I have one concern / question:

Should Yaru ever take over as the default theme you “must” consider implementing plymouth in to the theme as well. The problem with that would be:

update-grub2
update-initramfs

I am working on a way to change up some colors and was waiting for this implementation, but as I was going to include plymouth this would still require a classic confinement.