How to get this pending updates notification in Gnome?

One feature I’ve missed in
@nixos_org
is a “pending updates” ability to preview what an upgrade will do. The new nix store diff-closures makes this possible! It’s extremely cool that “pending changes” in this case is a literal system-level diff:

https://twitter.com/leothrix/status/1461901592597172230

@tylerjl

Similar thread Notification on update when channel advances

This is awesome by itself, TIL.

But how do you diff against yet-to-be-installed updates? Or is this a case of running the upgrade without switch first?

You can get it like this:

$ sudo -i
$ nix-channel --update
$ nixos-rebuild build
# building...
$ ls -l result
lrwxrwxrwx 1 root root 90 11-30 22:58 result -> /nix/store/zcb2k726r6b81ip4jzd0p3prhxiasqqv-nixos-system-msi-laptop-21.05.4394.2553aee74fe
$ nix store diff-closures /run/current-system ./result
elfutils: 0.185 → 0.186, +45.7 KiB
ell: 0.44 → 0.45
epoxy: -1785.7 KiB
gawk: ∅ → 5.1.1, +190.6 KiB
harfbuzz: 2.8.2 → 3.0.0, +46.8 KiB
libdrm: 2.4.107 → 2.4.108, +21.8 KiB
libepoxy: ∅ → 1.5.4, +1785.7 KiB
libpsl: ∅ → 0.21.1
nss: ∅ → 3.72
publicsuffix-list: ∅ → 2021-09-03, +235.6 KiB
spidermonkey: ∅ → 78.15.0
tracker: 3.2.0 → 3.2.1, +29.1 KiB
util-linux: ∅ → 2.37.2, +143.4 KiB
$ nixos-rebuild switch
building Nix...
building the system configuration...
stopping the following units: accounts-daemon.service
NOT restarting the following changed units: systemd-fsck@dev-disk-by\x2dlabel-debian.service, systemd-fsck@dev-disk-by\x2duuid-DCF7\x2d772A.service
activating the configuration...
elfutils: 0.185 → 0.186, +45.7 KiB
ell: 0.44 → 0.45
epoxy: -1785.7 KiB
gawk: ∅ → 5.1.1, +190.6 KiB
harfbuzz: 2.8.2 → 3.0.0, +46.8 KiB
libdrm: 2.4.107 → 2.4.108, +21.8 KiB
libepoxy: ∅ → 1.5.4, +1785.7 KiB
libpsl: ∅ → 0.21.1
nss: ∅ → 3.72
publicsuffix-list: ∅ → 2021-09-03, +235.6 KiB
spidermonkey: ∅ → 78.15.0
tracker: 3.2.0 → 3.2.1, +29.1 KiB
util-linux: ∅ → 2.37.2, +143.4 KiB
setting up /etc...
reloading user units for roman...
setting up tmpfiles
reloading the following units: dbus.service
restarting the following units: polkit.service
starting the following units: accounts-daemon.service

But I don’t know how it is done in this twitter example. Is it like some service constantly updating channel and building?

I have this for post-build in my configuration.nix:

{
  system.activationScripts.diff = ''
    ${pkgs.nixUnstable}/bin/nix store \
        --experimental-features 'nix-command' \
        diff-closures /run/current-system "$systemConfig"
  '';
}
3 Likes

So, basically, yeah - without switch (and a neat way of avoiding trying to figure out generation links of what just got built). Cool.

I also rather like how nix store diff-closures /run/*-system happens to sort in the right order, since most of the time the main thing I really want to know is whether it’s worth rebooting.

Thanks for the note @rofrol! I’ll offer as much as I can here, my plan is to eventually publish my home-manager configuration once it’s complete, but I’m not quite done yet and it needs some additional polish (technically, scrubbing) before I upload it.

Here’s the relevant details:

Technically I’m using the excellent devos by @nrdxp, so I symlink /etc/nixos to my local checkout of my devos repository. So /etc/nixos is a devos repository, but can still serve as a place to nix build, etc.

The interface in the screenshot is i3, polybar, and dunst. The widget in the lower left is this bit of polybar configuration:

[module/updates]
click-left=kill -USR1 %pid%
click-right=kill -USR2 %pid%
exec=/nix/store/jh57cw9p3p40z1c9c4ncvp6bli5ay3n8-packages/bin/packages --icon /nix/store/8qxzbn358fm7nq55w202ypjkizsg0dmc
-nixos-icons-2021-02-24/share/icons/hicolor/scalable/apps/nix-snowflake.svg
format-prefix=" "
format-prefix-foreground=${colors.foreground-alt}
format-suffix=" ↑"
format-suffix-foreground=${colors.foreground-alt}
format-underline=#4bffdc
tail=true
type=custom/script

The packages executable is a python script I have that checks for updates on various distros. I suppose I’ll just copypasta that here, it’s a private script that I haven’t put anywhere else so it probably makes some assumptions that may break for others, but it may be helpful regardless.

packages.py
from pathlib import Path
from subprocess import DEVNULL, PIPE, run
from threading import Event

import argparse
import crayons
import dbus
import distro
import os
import signal
import sys

parser = argparse.ArgumentParser(
    description="Mini-daemon for *bar update widgets")
parser.add_argument(
    "--icon", default='system-software-update',
    type=Path, help="Which icon to use"
)
args = parser.parse_args()

os.environ['PATH'] += os.pathsep + os.pathsep.join([
    '/run/current-system/sw/bin',
    os.path.sep.join([os.path.expanduser('~'), '.nix-profile', 'bin'])
])

bus = "org.freedesktop.Notifications"
delay = 60 * 60

AVAILABLE = []
CACHE = Path.home() / '.cache' / 'packages-pending'
RUN = Event()
BUS_INTERFACE = dbus.Interface(
    dbus.SessionBus().get_object(bus, "/"+bus.replace(".", "/")), bus)

distribution = distro.name()
if 'Arch' in distribution:
    CHECKER = ['checkupdates']
elif 'Fedora' in distribution:
    CHECKER = ['dnf', 'updateinfo', '-q', '--list']
elif 'Nix' in distribution:
    CHECKER = ['nix-updateinfo']
else:
    print("Unknown distro")
    sys.exit(1)


def discharging():
    for supply in Path('/sys/class/power_supply').iterdir():
        if '/usb' in str(supply.resolve()):
            continue
        status = supply / 'status'
        if status.exists():
            with status.open('r') as f:
                if f.read().strip() == 'Discharging':
                    return True
    return False


def updates():
    global CHECKER
    return crayons.clean(
        run(CHECKER, stdout=PIPE, stderr=DEVNULL).stdout.decode('utf-8')
    ).split('\n')


def check():
    print("↺", flush=True)
    global AVAILABLE
    global CACHE
    cached = False
    if discharging() and CACHE.exists():
        with CACHE.open('r') as f:
            AVAILABLE = list(map(lambda x: x.strip(), f.readlines()))
            cached = True
    else:
        try:
            AVAILABLE = list(filter(bool, updates()))
        except Exception:
            print("!", flush=True)
            return

    results = len(AVAILABLE)
    if results > 0:
        print(f"{results}{'💤' if cached else ''}", flush=True)
    else:
        print("✓", flush=True)

    with CACHE.open('w') as f:
        f.write('\n'.join(AVAILABLE))


def handle_force(sig, frame):
    global RUN
    RUN.set()


def handle_notify(sig, frame):
    global AVAILABLE
    global BUS_INTERFACE
    global args
    BUS_INTERFACE.Notify(
        "System Updates", 0, str(args.icon), "Available Updates",
        '\n'.join(AVAILABLE), [], {"urgency": 1}, 5000)


signal.signal(signal.SIGUSR1, handle_force)
signal.signal(signal.SIGUSR2, handle_notify)

RUN.set()
while True:
    RUN.wait(delay)
    check()
    RUN.clear()

nix-updateinfo is actually pretty short, this is the script. I’ll include what I have in my home-manager config verbatim:

nix-updates-script = pkgs.writers.writeBashBin "nix-updateinfo" ''
  tempdir=$(mktemp -d /tmp/tmp.nix-updateinfo.XXX)
  ${pkgs.git}/bin/git clone --reference /etc/nixos /etc/nixos $tempdir
  cd $tempdir
  # Disable the noisy devshell git hook provisioner
  sed -i 's/true/false/' shell/hooks/default.nix || true
  ${pkgs.nixUnstable}/bin/nix-shell --run "bud update latest"
  ${pkgs.nixUnstable}/bin/nix-shell --run "bud build toplevel"
  ${pkgs.nixUnstable}/bin/nix store diff-closures /run/current-system ./result | awk '/[0-9] →|→ [0-9]/ && !/nixos/' || echo
  cd ~-
  rm -rf "$tempdir"
'';

For those not using devos, you can achieve the “update nixpkgs” step by either calling nix flake update <your nixpkgs input> or whatever the equivalent is for !flakes. Running nix-updateinfo from a normal shell should yield pending updates outside of the polybar widget.

The visual notification is from dunst; my configuration for that is pretty large but I’ll drop it here so that anyone can truly reproduce the setup if they want:

~/.config/dunst/dunstrc
[experimental]
per_monitor_dpi=yes

[global]
alignment="left"
always_run_script=yes
browser="/nix/store/kqdhiygq1rzbq1cpnhix29dq7p6a97z5-firefox-94.0.1/bin/firefox -new-tab"
class="Dunst"
corner_radius=5
dmenu="/nix/store/yf9sdd85amgn63kjkzhgi9gpm6lm2sbi-rofi-1.7.0/bin/rofi -dmenu -p Notification"
ellipsize="middle"
follow="mouse"
font="fixed 12"
format="<b>%s</b>\n\n%b\n\n<sub>%a</sub>"
frame_color="#282a36"
frame_width=3
hide_duplicate_count=no
history_length=20
horizontal_padding=20
icon_path="/run/current-system/sw/share/icons/Adwaita/32x32/actions:/run/current-system/sw/share/icons/hicolor/32x32/actions:/run/current-system/sw/share/icons/Adwaita/32x32/animations:/run/current-system/sw/share/icons/hicolor/32x32/animations:/run/current-system/sw/share/icons/Adwaita/32x32/apps:/run/current-system/sw/share/icons/hicolor/32x32/apps:/run/current-system/sw/share/icons/Adwaita/32x32/categories:/run/current-system/sw/share/icons/hicolor/32x32/categories:/run/current-system/sw/share/icons/Adwaita/32x32/devices:/run/current-system/sw/share/icons/hicolor/32x32/devices:/run/current-system/sw/share/icons/Adwaita/32x32/emblems:/run/current-system/sw/share/icons/hicolor/32x32/emblems:/run/current-system/sw/share/icons/Adwaita/32x32/emotes:/run/current-system/sw/share/icons/hicolor/32x32/emotes:/run/current-system/sw/share/icons/Adwaita/32x32/filesystem:/run/current-system/sw/share/icons/hicolor/32x32/filesystem:/run/current-system/sw/share/icons/Adwaita/32x32/intl:/run/current-system/sw/share/icons/hicolor/32x32/intl:/run/current-system/sw/share/icons/Adwaita/32x32/legacy:/run/current-system/sw/share/icons/hicolor/32x32/legacy:/run/current-system/sw/share/icons/Adwaita/32x32/mimetypes:/run/current-system/sw/share/icons/hicolor/32x32/mimetypes:/run/current-system/sw/share/icons/Adwaita/32x32/places:/run/current-system/sw/share/icons/hicolor/32x32/places:/run/current-system/sw/share/icons/Adwaita/32x32/status:/run/current-system/sw/share/icons/hicolor/32x32/status:/run/current-system/sw/share/icons/Adwaita/32x32/stock:/run/current-system/sw/share/icons/hicolor/32x32/stock:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/actions:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/actions:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/animations:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/animations:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/apps:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/apps:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/categories:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/categories:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/devices:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/devices:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/emblems:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/emblems:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/emotes:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/emotes:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/filesystem:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/filesystem:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/intl:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/intl:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/legacy:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/legacy:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/mimetypes:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/mimetypes:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/places:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/places:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/status:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/status:/etc/profiles/per-user/tylerjl/share/icons/Adwaita/32x32/stock:/etc/profiles/per-user/tylerjl/share/icons/hicolor/32x32/stock:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/actions:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/actions:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/animations:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/animations:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/apps:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/apps:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/categories:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/categories:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/devices:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/devices:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/emblems:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/emblems:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/emotes:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/emotes:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/filesystem:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/filesystem:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/intl:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/intl:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/legacy:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/legacy:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/mimetypes:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/mimetypes:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/places:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/places:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/status:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/status:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/Adwaita/32x32/stock:/nix/store/8hp8qiqps1gm3ibscmjgz5wa4a2q851v-adwaita-icon-theme-41.0/share/icons/hicolor/32x32/stock:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/actions:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/actions:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/animations:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/animations:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/apps:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/apps:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/categories:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/categories:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/devices:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/devices:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/emblems:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/emblems:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/emotes:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/emotes:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/filesystem:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/filesystem:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/intl:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/intl:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/legacy:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/legacy:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/mimetypes:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/mimetypes:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/places:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/places:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/status:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/status:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/Adwaita/32x32/stock:/nix/store/gz1r222x6s65lj091fl67c35g11hismj-hicolor-icon-theme-0.17/share/icons/hicolor/32x32/stock"
icon_position="left"
idle_threshold=120
ignore_newline="no"
indicate_hidden="yes"
line_height=0
markup="full"
max_icon_size=64
monitor=0
mouse_left_click="close_current"
mouse_middle_click="close_all"
mouse_right_click="do_action"
notification_limit="5"
offset="20x75"
origin="bottom-right"
padding=8
separator_color="frame"
separator_height=2
show_age_threshold=60
show_indicators="yes"
shrink="no"
sort="yes"
stack_duplicates=yes
sticky_history="yes"
title="Dunst"
transparency=30
width="(0, 800)"
word_wrap="yes"

[urgency_critical]
background="#ab4642"
foreground="#e8e8e8"
frame_color="#ff0000"
timeout=0

[urgency_low]
background="#282828"
foreground="#585858"
timeout=10

[urgency_normal]
background="#282a3690"
foreground="#f5f5f5"
timeout=10

Note that all of these configs (polybar, dunst, etc.) are all configured via the relevant home-manager settings so all of this lives in a .nix file in my machine’s configuration. That should be enough (I think?) to explain the setup.

I hope that’s helpful, let me know if there’s anything else I can clarify!

2 Likes

So now I have run

$ sudo nixos-rebuild build
$ sudo nixos-rebuild switch

And mongodb is being built again. How can I switch to ./result?

Edit: I probably should do sth like this:

$ sudo ./result/bin/switch-to-configuration switch
1 Like