Felitendo 6aa9b147b8 Put a progress bar on the desktop, and repair the notifications around it
An unattended run takes twenty minutes and said nothing at all while it worked.
The notification area now carries a live entry for the duration - which step is
running, which package is being unpacked, item count, percentage. It is a job
in the sense of org.kde.JobViewServer, the same mechanism Dolphin uses while
copying files, rather than a notification, which is what makes it a real bar
instead of a line of text.

That mechanism needs a process of its own. The desktop ties a job to the D-Bus
connection that requested it and withdraws it the moment that connection
closes, so gdbus, busctl and dbus-send cannot drive one at all - every
invocation is a fresh connection that closes again immediately. A small helper
therefore runs inside each graphical session for the length of the update,
holding the connection open and taking instructions on stdin. It needs
python-gobject; without it there is no bar and nothing else changes. Position
within the repository step is read from pacman's own "(120/260) upgrading foo"
lines. Its other (n/m) sequences are ignored on purpose: checking keys, package
integrity and loading files each count up to the same total, and following them
would run the bar to the end three times before the first package was unpacked.

The "system updated" notification never arrived, which is what prompted looking
at any of this. Tagged notifications were posted with --replace-id naming the
start message, and Plasma silently drops a Notify() whose replaces_id points at
an expired notification: no bubble, no error, and the id it hands back is the
dead one it just ignored. The start bubble times out in seconds and a run lasts
minutes, so the result fell into exactly that hole every single time. The
previous message is now withdrawn and a fresh one posted.

How long a message stays is set per message rather than left to the daemon.
Anything reporting that the machine still needs a person - a failed update, a
locked package database, packages that had to be held back - waits until it is
dismissed. Everything else times out on its own, a successful update included;
nothing should have to be clicked away for having gone right. Daemons do keep
critical-urgency messages up and the specification asks them to, but that is a
should, it says nothing about the normal-urgency messages here that still need
somebody to act, and urgency separately governs sound and do-not-disturb.

"Restart recommended" is gone, and NotifyReboot with it. The running kernel
loses its module tree the moment pacman unpacks the new one, so the notice
fired while the run was still building AUR packages and pulling Flatpaks, where
it reads as an invitation to restart in the middle of an update. The state is
still recorded and cachy-auto-update status still reports it. The option went
rather than only its default, because an installed system keeps its own
configuration file and would have carried on notifying regardless.
2026-08-14 10:49:02 +02:00
2026-08-08 01:29:40 +02:00

cachy-auto-update

Unattended background updates for CachyOS — no password prompt, no terminal, nothing to remember.

Built for the machine you set up for somebody else and would rather not have to maintain: it updates pacman packages, AUR packages, Flatpaks and AppImages by itself, stays out of the way while they are gaming or on battery, never touches the package database while they are using pacman by hand, and only speaks up when something actually needs a human.

sudo cachy-auto-update

That opens a menu with the two switches there are:

  CachyOS Auto-Update

  Automatic updates            ON
  Notifications                ON

  Last check                   3 hours ago
  Last successful update       Sat 08 Aug 2026 04:12:03 CEST (23 packages)
  Next scheduled run           Sat 08 Aug 2026 05:00:00 CEST

  [1] Toggle automatic updates
  [2] Toggle notifications
  [3] Update now
  [4] Show log
  [5] Show current conditions
  [6] Settings
  [q] Quit

Everything is configurable from [6] Settings — a cursor list covering all eighteen options, so nothing needs a text editor. Arrow keys select, Space or Right changes a value, q goes back; changes are written immediately.

The interface is fully translated; on a German system everything above appears in German.

Install

paru -S cachy-auto-update
sudo cachy-auto-update enable

Updates are off until you enable them — a freshly installed package has no business rebuilding somebody's system before being asked.

What it updates

Repository packages pacman -Syu, after checkupdates confirms there is work
AUR paru or yay, whichever is installed
Flatpak system and per-user installations
AppImages via Gear Lever, if installed

It also trims the pacman package cache after each run (paccache, keeping the 3 most recent versions), because otherwise /var/cache/pacman/pkg grows forever — tens of gigabytes on a machine with a few large packages. Set KeepOldPackages=1 if disk space matters more than the ability to downgrade.

-git/-devel AUR packages and orphan removal exist as options but are off by default. Orphan removal deletes installed software, and "orphaned" only means nothing else depends on it — which is also true of something installed deliberately.

When it holds back

A run is postponed — and retried an hour later — when:

  • the battery is below 30 % (ignored on mains power; desktops without a battery are never affected),
  • a game is running: GameMode, a known game process, or anything holding a blocking idle inhibitor,
  • pacman's database is locked, or pacman/yay/paru/pamac is running.

cachy-auto-update status prints every one of these individually, which is the fastest way to find out why nothing is happening.

The machine is never restarted on its own. A kernel update that needs a restart is reported by cachy-auto-update status, not by a notification: the running kernel loses its module tree the moment pacman unpacks the new one, so a bubble would arrive while the run is still building AUR packages and pulling Flatpaks — and reads as an invitation to restart in the middle of it.

About the password question

The obvious way to automate yay/paru is to store the user's password somewhere. This does not do that, and deliberately so: anything the daemon can decrypt is exactly what an attacker who reaches the daemon already has, so the encryption would be decoration.

Instead:

  • The updater is a system service running as root, so pacman needs no escalation at all.
  • makepkg, paru and yay refuse to run as root, so the AUR step drops to a dedicated locked system account (cachy-auto-update, no password, /usr/bin/nologin, its own home under /var/lib). Only root can become it.
  • That account gets one line in /etc/sudoers.d/cachy-auto-update allowing it to call /usr/bin/pacman without a password — which is what lets the helper install what it built.

No user password is stored, encrypted or otherwise.

If you would rather not have that sudoers rule on the machine, set UpdateAUR=no in the config; everything else keeps working, and the rule becomes inert.

Coexisting with manual package management

Before touching anything, the updater checks for /var/lib/pacman/db.lck and for a running pacman, yay, paru, pamac, pikaur, octopi or makepkg, and postpones if it finds one. On a day with no pending repository updates the real pacman lock is never taken at all, because checkupdates works against a private temporary database.

The reverse direction has an honest limit: if you start pacman while an update is already running, you will get the usual "unable to lock database" message. Nothing outside pacman can prevent that. What the updater does do is keep the window short, run at low priority, and hold a systemd-inhibit lock so a suspend or shutdown cannot land in the middle of a transaction.

A leftover db.lck from a crashed transaction is never deleted automatically — guessing wrong there corrupts a live transaction. After it has been seen unheld on several consecutive runs, you get a notification instead.

What if the machine is switched off mid-update

Three layers, in order of how much they can actually promise:

A notification goes out before the transaction starts — "Installing updates, please leave the computer switched on until this is done" — and is withdrawn again when the result arrives, so it costs one bubble rather than two. This exists because of what the next paragraph does not do.

A progress bar sits in the notification area for the whole run, the same one Dolphin puts there while it copies files: which step is running, which package is being unpacked, how far along the whole thing is. A twenty-minute run that shows nothing looks indistinguishable from a hung one, and that is what gets a machine switched off in the middle of a transaction. See The progress bar.

Suspend and a normal shutdown are blocked. The run holds a systemd-inhibit --what=sleep:shutdown --mode=block lock, so closing the lid or picking "Shut down" cannot interrupt a transaction. Be aware of what that looks like, though: logind refuses the request and requires the polkit action org.freedesktop.login1.power-off-ignore-inhibit, which is auth_admin_keep. The desktop therefore answers a shutdown attempt with an administrator password prompt reading "Power off the system while an application is inhibiting this" — a string systemd ships untranslated, and one that never mentions updates. No KDE dialog explains the situation. Only systemctl poweroff in a terminal names the reason. That prompt is exactly why the notification above is on by default.

A hard power-off cannot be prevented by anything. Holding the power button or pulling the plug cuts power in firmware. What limits the damage is that pacman's commit phase is short (about a minute even for a 200-package upgrade) and that most of a run is downloading, where an interruption costs nothing but a partial file.

The next run repairs it. A db.lck left behind is detected and removed — but only when it is provably dead, meaning it is older than the current boot, so no process that could hold it still exists. The interrupted upgrade is then simply run again; pacman reinstalls anything that was caught half-written. A lock that is merely unheld within the same boot is never removed, only reported, because there the guess could be wrong.

This last part matters more than it sounds: without it, a single power cut during an update would leave a lock file that makes every future run defer, and the machine would stop updating silently and permanently.

On a Btrfs system with snapper and snap-pac — the CachyOS default — every pacman transaction is bracketed by a pre and post snapshot, so a genuinely broken upgrade can still be rolled back with snapper rollback.

How long notifications stay

A message that means the machine still needs you — an update failed, the package database is locked, packages had to be held back — stays until you dismiss it. That kind of message is only worth sending if it is still there when you come back to the machine.

Everything else times out on its own, a successful update included. Nothing should have to be clicked away for having gone right.

Set per message rather than left to the notification daemon. Daemons do keep critical-urgency messages up and the spec asks them to, but that is a should, it says nothing about the normal-urgency messages here that still need somebody to act, and urgency separately controls sound and do-not-disturb bypass — a different question. Queued messages delivered at the next login keep the same distinction.

The progress bar

While a run is working, the notification area carries a live entry — headline, item count, percentage, and the package currently being unpacked under Details. It is not a notification but a job, the same mechanism Dolphin uses for file copies, which is what gets you a bar rather than a line of text.

Two things about how it is put together:

  • The desktop ties a job to the D-Bus connection that asked for it, and withdraws the job the moment that connection closes. One-shot bus clients — gdbus, busctl, dbus-send — therefore cannot drive one at all, since every invocation is a fresh connection that closes immediately. So a small helper (cachy-auto-update-progress) runs inside each graphical session for the length of the update, holding the connection open and taking instructions on stdin. It needs python-gobject; without it there is simply no bar and nothing else changes.
  • The position inside the repository step comes from pacman's own (120/260) upgrading foo lines. pacman's other (n/m) sequences — checking keys, package integrity, loading files — each count to the same total, so only the transaction verbs are followed; otherwise the bar would reach the end three times before the first package was unpacked.

This is Plasma's job interface. On a desktop that does not implement it the helper exits quietly and the ordinary notifications carry on as before.

Configuration

/etc/cachy-auto-update/cachy-auto-update.conf, one Key=Value per line, every option documented in the file. The file is parsed rather than sourced, so a stray line cannot turn into code executed by root.

Enabled=yes
Notifications=yes
UpdateInterval=1d
MinBatteryPercent=40
SkipWhenGaming=yes
UpdateAUR=yes
UpdateFlatpak=yes
UpdateAppImages=yes
AutoResolveConflicts=yes
IgnorePkg=

How package conflicts are handled

pacman -Syu --noconfirm already answers "Replace X with Y?" affirmatively, so ordinary replacements happen silently — which is the point.

What --noconfirm declines is :: X and Y are in conflict. Remove Y? [y/N], and that aborts the whole transaction. With AutoResolveConflicts=yes (the default) the transaction is retried once with that question answered too, and everything removed is written to the log.

Two things are deliberately not automated:

  • File conflicts (exists in filesystem) — forcing --overwrite could silently destroy something that was put there on purpose.
  • Reboots — status tells you one is due, never a surprise restart.

Signature failures trigger one keyring refresh and one retry, since a stale keyring blocks everything else until it is fixed.

Logs

cachy-auto-update log        # the last run in full
cachy-auto-update log -a     # the rolling log
journalctl -u cachy-auto-update

Both work without root.

Building from source

make && sudo make install
sudo systemd-sysusers && sudo systemd-tmpfiles --create
sudo cachy-auto-update enable

make check runs bash -n over every shell file, py_compile over the progress helper, shellcheck when available, and validates the sudoers drop-in with visudo -c.

Everything is optional at runtime and degrades to doing less rather than failing: pacman-contrib for checkupdates, an AUR helper, flatpak, Gear Lever, libnotify for notifications, and python-gobject for the progress bar.

Relationship to cachy-update

cachy-update is CachyOS's interactive updater; every one of its steps sits behind a prompt and its timer only ever checks for updates and notifies. This tool is the unattended counterpart and reimplements the same command sequence non-interactively, adding the battery, gaming and lock awareness that unattended operation needs.

The two coexist. cachy-auto-update enable offers once to switch off cachy-update's own "N updates available" notification, since it becomes noise when updates install themselves.

License

GPL-3.0-or-later.

S
Description
No description provided
Readme GPL-3.0
289 KiB
0 Stars 1 Watchers 0 Forks
Languages
Shell 91.4%
Python 4.4%
Makefile 4.2%