Password for snap refresh

Hi all,

Why do I need my password to refresh the snaps manually and not for refreshing the debs through Ubuntu update manager?
Why do I need this password since snaps are auto-updated anyway?

You could work around this with snap login (using an Ubuntu SSO account address), though note that this will make you the “snap administrator” on that machine and you will not need any sudo at all anymore for any of the snap commands…

But snaps are refreshed through Ubuntu update manager without any password so it should exist a way to make the same with the Snap Store snap?
Without giving to me too much power, I mean :wink: .

Well, you could file a bug against snapd and see what the architects say :wink:

I fear the answer but let’s go. :wink:

1 Like

Not an authority on the subject, but there is a very relevant discussion about this here.

1 Like

This is way over-simplified, but I’ll explain the mechanism here:

When it’s auto-refreshed, it’s run by a systemd service running as root, so it doesn’t need a password to make system-level changes.

When you initiate a refresh, it needs your password to run that same process as root. It’s essentially getting permission to make system-level changes.

1 Like

But the Ubuntu upgrader refreshes snaps without I enter my pwd, isn’t it?
So it has the polkit auth?

It may be over-simplified but I also think that sometimes offering users a “simple” answer is actually the most helpful way to paint the picture, so to speak.

1 Like

When you see the Ubuntu upgrader “updating snaps” it does not mean what you think it means. In that case, it’s simply doing a “cleanup”, if you will.

But yes, update-manager historically has had polkit access, at least as I understand it.

Sorry but I don’t understand what “cleanup” means.
What snap command is executed by the Ubuntu upgrader?

That’s a great question, but “cleanup” is what was explained to me. That said, it might not necessarily be executing a snap command, but just cleaning some old caches and what-not. I do know that it’s not executing “snap refresh” as you might be led to believe as that would duplicate the effort of unattended-upgrades, which is the process I wrote about earlier.

Yes that clearly was what I believed Ub upgrader does!
So maybe I could rather fill a bug against Ub upgrader since I find this label clearly misleading. :slight_smile:

1 Like

The source package for that would be update-manager. ubuntu-release-upgrader is completely different and is used to upgrade from one release to another, so don’t get the terms mixed-up.

That said, I could be completely wrong, but what was explained to me is that it’s certainly not running snap refresh.

Well, snapd already does provide features/commands you can use without password (snap list, snap find, snap info etc)

It wouldn’t be unreasonable if it would allow a refresh of already installed snaps without password, that would be harmless (unlike install, remove, revert etc where you actually make changes to the system that should only be done by an administrator)

That would then just be possible to use by UI tools the same way

def update_snaps(self):
        # update status and progress bar
        def update_status(status):
            GLib.idle_add(self.label_details.set_label, status)

        def update_progress(progress_bar):
            progress_bar.pulse()
            return True

        update_status(_("Updating snaps"))

        progress_bar = None
        progress_timer = None

        progress_bars = self.progressbar_slot.get_children()
        if progress_bars and isinstance(progress_bars[0], Gtk.ProgressBar):
            progress_bar = progress_bars[0]
            progress_timer = GLib.timeout_add(
                100, update_progress, progress_bar
            )

        # populate snap_list with deb2snap transitions
        snap_list = self.get_snap_transitions()

        if progress_timer:
            GLib.source_remove(progress_timer)
            progress_bar.set_fraction(0)

        # (un)install (un)seeded snap(s)
        try:
            client = Snapd.Client()
            client.connect_sync()
            index = 0
            count = len(snap_list)
            for snap, snap_object in snap_list.items():
                command = snap_object["command"]
                if command == "refresh":
                    update_status(_("Refreshing %s snap" % snap))
                    client.refresh_sync(
                        snap,
                        snap_object["channel"],
                        self.update_snap_cb,
                        progress_callback_data=(index, count, progress_bar),
                    )
                elif command == "remove":
                    update_status(_("Removing %s snap" % snap))
                    client.remove_sync(
                        snap,
                        self.update_snap_cb,
                        progress_callback_data=(index, count, progress_bar),
                    )
                else:
                    update_status(_("Installing %s snap" % snap))
                    client.install_sync(
                        snap,
                        snap_object["channel"],
                        self.update_snap_cb,
                        progress_callback_data=(index, count, progress_bar),
                    )
                index += 1
        except GLib.Error as e:
            logging.debug("error updating snaps (%s)" % e)
            GLib.idle_add(
                self.window_main.start_error,
                False,
                _("Upgrade only partially completed."),
                _(
                    "An error occurred while updating snaps. "
                    "Please check your network connection."
                ),
            )
            return

        # continue with the rest of the updates
        GLib.idle_add(self.window_main.start_available)

but snap_list comes from:

def get_snap_transitions(self):
        # populate snap_list with deb2snap transitions
        snap_list = {}
        seeded_snaps, unseeded_snaps = self.get_snap_seeds()

        for snap, (deb, to_channel) in seeded_snaps.items():
            snap_object = {}
            # check if the snap is already installed
            snap_info = subprocess.Popen(
                ["snap", "info", snap],
                universal_newlines=True,
                stdout=subprocess.PIPE,
            ).communicate()
            if re.search("^installed: ", snap_info[0], re.MULTILINE):
                logging.debug("Snap %s is installed" % snap)
                continue
            elif deb in self.window_main.duplicate_packages:
                # install the snap if the deb was just marked delete
                snap_object["command"] = "install"
                snap_object["channel"] = to_channel
                snap_list[snap] = snap_object

        for snap, (from_channel) in unseeded_snaps.items():
            snap_object = {}
            # check if the snap is already installed
            snap_info = subprocess.Popen(
                ["snap", "info", snap],
                universal_newlines=True,
                stdout=subprocess.PIPE,
            ).communicate()
            if re.search("^installed: ", snap_info[0], re.MULTILINE):
                logging.debug("Snap %s is installed" % snap)
                # its not tracking the release channel so don't remove
                re_channel = "stable/ubuntu-[0-9][0-9].[0-9][0-9]"
                if not re.search(
                    r"^tracking:.*%s" % re_channel, snap_info[0], re.MULTILINE
                ):
                    logging.debug(
                        "Snap %s is not tracking the release channel" % snap
                    )
                    continue

                snap_object["command"] = "remove"

                # check if this snap is being used by any other snaps
                conns = subprocess.Popen(
                    ["snap", "connections", snap],
                    universal_newlines=True,
                    stdout=subprocess.PIPE,
                ).communicate()

                for conn in conns[0].split("\n"):
                    conn_cols = conn.split()
                    if len(conn_cols) != 4:
                        continue
                    plug = conn_cols[1]
                    slot = conn_cols[2]

                    if slot.startswith(snap + ":"):
                        plug_snap = plug.split(":")[0]
                        if (
                            plug_snap != "-"
                            and plug_snap not in unseeded_snaps
                        ):
                            logging.debug(
                                "Snap %s is being used by %s. "
                                "Switching it to stable track"
                                % (snap, plug_snap)
                            )
                            snap_object["command"] = "refresh"
                            snap_object["channel"] = "stable"
                            break

                snap_list[snap] = snap_object

        return snap_list

So if I understand that correctly, it seems intended to install/upgrade (channel) ubuntu snaps listed in manifest or something like that?

That doesn’t look like snapd code, where is that from?

update-manager
backend/__init__.py