15 Commits
Author SHA1 Message Date
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
Felitendo a17aed4b3d Lower the battery threshold to 30% and default the cachy-update prompt to yes
The prompt offering to silence cachy-update's own update notification now
defaults to yes: once updates install themselves that notification is nothing
but noise, so the common answer should be the one you get by pressing Enter.

It also accepts "j" now. The prompt is translated, so a German user reads
"[J/n]" and types the German letter - which the old check for "y" alone
silently read as a refusal.
2026-08-08 16:30:20 +02:00
Felitendo 2fa830284e Make the settings screen redraw 50x faster
Arrow-key navigation took 435 ms per keypress, which reads as the whole console
reloading on every press - because it effectively was. The cost was forks: each
frame ran a command substitution per label for the value, the translation and
the rendered state, 54 subshells for eighteen rows.

The frame is now assembled in memory and written once. Translations, the split
specs and the terminfo clear string are resolved before the loop; values are
re-read only after something actually changes, not on cursor movement. Helpers
on that path assign to a variable instead of printing, since printing is what
forced the substitution.

Measured on the same machine: 435 ms -> 7.5 ms per keypress.
2026-08-08 16:25:09 +02:00
Felitendo c308b7e286 Add a settings screen so nothing needs a text editor
All eighteen options are now reachable from the menu as a cursor list: arrows
select, Space or Right cycles a value, q goes back, changes are written
immediately. A numbered menu would have run out of digits.

Three real bugs surfaced while building it, each found by testing against an
actual pty rather than a pipe:

- Labels went through printf as format strings, so the percent sign in
  "Minimum battery level (%)" was an invalid conversion. cau_msg_in now only
  treats a message as a format string when arguments were actually passed -
  otherwise any literal % a translator writes is a trap.

- Mixing bash's line-mode read into a single-key interface left the following
  read -sn1 receiving nothing at all, reproducibly, so the screen froze after
  editing the package list. Replaced with a small line editor built on the same
  single-character reader.

- Backspace was being swallowed: in canonical mode DEL is the ERASE character
  and the line discipline consumes it, and bash returns to canonical mode
  between each read -sn1. The interface now holds non-canonical mode for its
  whole lifetime and hands the terminal back only around actions that print or
  prompt, with a trap restoring it on Ctrl-C.

Translations and config reads are memoized; the screen redraws every label on
every keypress and a fork per lookup was the reason the redraw was slow enough
to matter.
2026-08-08 16:18:59 +02:00
Felitendo 2eff62f9fd Say an update is running before it starts, not only after
Asked what happens if someone shuts the laptop down mid-update, the honest
answer turned out to be poor. The inhibitor does stop them - logind refuses and
falls back to org.freedesktop.login1.power-off-ignore-inhibit, which is
auth_admin_keep - so the desktop answers with an administrator password prompt
reading "Power off the system while an application is inhibiting this". systemd
ships that string untranslated, it never mentions updates, and no KDE catalog
contains any shutdown-blocked text at all. Only systemctl names the reason.

So a notification now goes out before the transaction, asking for the machine to
be left on. Notifications gained a tag: the result replaces the start message in
place rather than stacking a second, contradictory bubble beside it.

Tagged notifications also carry a queue flag. "An update is starting" is only
meaningful while somebody is looking, so unlike the result it is not spooled for
delivery at next login.
2026-08-08 15:48:55 +02:00
Felitendo 0f91a79ba6 Recover from a pacman lock left behind by a power cut
A machine switched off mid-update leaves /var/lib/pacman/db.lck behind. Nothing
removed it, so every subsequent run deferred on it - one power cut would have
stopped updates permanently and silently, which on an unattended machine is the
worst outcome there is.

A lock older than the current boot is provably abandoned: no process that could
hold it still exists. Those are now removed and the interrupted upgrade is
repeated, with pacman reinstalling anything caught half-written. A lock that is
merely unheld within the same boot stays untouched and is only reported, since
removing it could corrupt a live transaction; the boot-time test is what makes
the difference between a proof and a guess. A fuser check is kept alongside it
so a backwards clock jump cannot make a live lock look abandoned.

Documented what each layer can actually promise: suspend and normal shutdown
are blocked by the existing inhibitor, a hard power-off cannot be prevented by
anything, and snap-pac's pre/post snapshots remain the backstop.
2026-08-08 15:40:07 +02:00
Felitendo 6514c4d859 Trim the package cache by default, and split it from orphan removal
CleanCache was off, so nothing ever pruned /var/cache/pacman/pkg - 23 GB on the
machine this was found on, with three 3.2 GB copies of one package. Trimming
only drops older versions of installed packages and cached versions of
uninstalled ones, so the cost is downgrade depth, not working software.

flatpak uninstall --unused moves from CleanCache to RemoveOrphans, where it
belongs: unused runtimes are the Flatpak equivalent of orphaned packages, and
removing installed software should not hide behind a flag named for cache
trimming. RemoveOrphans stays off - 'orphaned' only means nothing depends on
it, which is also true of something installed deliberately.

The log now reports what a trim reclaimed, since a real paccache run says
almost nothing and there was otherwise no way to tell it was working.
2026-08-08 15:33:03 +02:00
Felitendo 243115ea19 Show what a run is doing, and record when one is cut short
A run that held back a blocker then upgraded 214 packages printed one line and
then nothing for nearly five minutes while pacman downloaded and installed. It
looked hung, so it got killed - during pacman's uninterruptible commit phase,
which meant the packages landed but our bookkeeping never did. The menu then
kept showing a failure from a previous run on a fully up-to-date machine.

pacman, the AUR helper and flatpak now stream their output when a person is
watching, and the progress bar is left enabled for that case. Timer runs are
unchanged: quiet, --noprogressbar, everything captured in the log.

Interactivity is decided once at startup rather than tested at the point of
use. cau_pacman_flags runs inside a process substitution, so its stdout is
always a pipe and a -t 1 check there would have silently always been false.

INT/TERM/HUP now record last_result=interrupted, so a run that is stopped says
so instead of leaving the previous verdict standing.
2026-08-08 15:24:42 +02:00
Felitendo 9fd2458d20 Hold back blocking packages instead of failing the whole upgrade
A repo package that replaces something an installed AUR package still depends
on aborted the entire transaction, and would have done so on every subsequent
run - one stale AUR package was enough to cut a machine off from all updates
indefinitely. Observed in the wild: percona-server-clients replaces
libperconaserverclient without providing it, while heidisql-qt6-bin hard-depends
on it, blocking 213 unrelated package updates.

pacman names the offending package in its dependency errors, so it is now
extracted and passed to --ignore for one retry: the other 213 packages go
through and the blocker is reported. The hold is per-run, never written to
IgnorePkg, so it disappears by itself once upstream catches up.

The single retry is also now a bounded recovery loop, because fixing one
problem regularly uncovers the next - a conflict resolved with --ask=20 can
surface a dependency error behind it. Each remedy is applied at most once.

The held-back set is shown in the menu and notified only when it changes, so a
blocker waiting on an upstream fix does not produce the same message daily.
2026-08-08 15:12:43 +02:00
Felitendo f593cc1933 Menu: flip the switches without asking for a keypress
Toggling automatic updates or notifications returned to a 'press any key'
prompt for no reason - the status block at the top of the menu already shows
the new state. cau_bad and cau_note now flag that they printed something, so
the acknowledgement only appears when there is genuinely something to read
(a failed sudoers check, a missing AUR helper) instead of after every toggle.
2026-08-08 15:05:38 +02:00
Felitendo 20fbadbc95 Menu: act on a single keypress instead of waiting for Enter
Also stops Enter from quitting the menu (it used to share the quit branch with
q) and swallows the tail of an escape sequence, so one arrow key redraws once
rather than three times and never closes the menu.
2026-08-08 14:58:28 +02:00
Felitendo e42fb4f61c Make the reported version follow the version actually being built
VERSION is now overridable, so the PKGBUILD passes $pkgver into make and the
two can no longer disagree. v1.0.1 shipped a Makefile that still said 1.0.0, so
'cachy-auto-update --version' reported the wrong release.
2026-08-08 14:45:07 +02:00
Felitendo 459cbbf8e7 Fix locale handling, menu re-entry and empty-checkupdates logging
- The runner now forces LC_ALL=C itself instead of relying on the unit's
  Environment=. pacman failures are classified by matching its output, so a
  run started by hand on a German system was misclassifying every failure.
- 'Update now' in the menu exec'd the runner, which terminated the menu.
- Log a distinct message when checkupdates is unavailable, instead of
  claiming zero pending packages before a full upgrade.
2026-08-08 02:35:34 +02:00
Felitendo ceb024d7be Add cachy-auto-update: unattended background updates for CachyOS
A root systemd service applies pacman, AUR, Flatpak and AppImage updates on
its own, gated on battery state, gaming activity and whether anybody else is
using the package system. The CLI is deliberately two switches plus status.

No user password is stored anywhere: pacman runs as root directly, and the AUR
step - which makepkg forbids running as root - drops to a locked system account
that sudoers permits to call pacman without a password.
2026-08-08 02:27:04 +02:00
Felitendo 327da1dae3 Initial commit 2026-08-08 01:29:40 +02:00