Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c308b7e286 | ||
|
|
2eff62f9fd | ||
|
|
0f91a79ba6 | ||
|
|
6514c4d859 |
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
# Overridable so a packager can pass the version it is actually building
|
# Overridable so a packager can pass the version it is actually building
|
||||||
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
||||||
# straight from a checkout, and is what a release tag has to carry.
|
# straight from a checkout, and is what a release tag has to carry.
|
||||||
VERSION ?= 1.0.6
|
VERSION ?= 1.1.0
|
||||||
|
|
||||||
PREFIX ?= /usr
|
PREFIX ?= /usr
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
|
|||||||
@@ -30,9 +30,14 @@ That opens a menu with the two switches there are:
|
|||||||
[3] Update now
|
[3] Update now
|
||||||
[4] Show log
|
[4] Show log
|
||||||
[5] Show current conditions
|
[5] Show current conditions
|
||||||
|
[6] Settings
|
||||||
[q] Quit
|
[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
|
The interface is fully translated; on a German system everything above appears
|
||||||
in German.
|
in German.
|
||||||
|
|
||||||
@@ -55,8 +60,15 @@ business rebuilding somebody's system before being asked.
|
|||||||
| Flatpak | system and per-user installations |
|
| Flatpak | system and per-user installations |
|
||||||
| AppImages | via [Gear Lever](https://github.com/mijorus/gearlever), if installed |
|
| AppImages | via [Gear Lever](https://github.com/mijorus/gearlever), if installed |
|
||||||
|
|
||||||
`-git`/`-devel` AUR packages, cache trimming and orphan removal exist as
|
It also trims the pacman package cache after each run (`paccache`, keeping the
|
||||||
options but are off by default.
|
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
|
## When it holds back
|
||||||
|
|
||||||
@@ -116,6 +128,48 @@ A leftover `db.lck` from a crashed transaction is never deleted automatically
|
|||||||
guessing wrong there corrupts a live transaction. After it has been seen
|
guessing wrong there corrupts a live transaction. After it has been seen
|
||||||
unheld on several consecutive runs, you get a notification instead.
|
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
|
||||||
|
replaced in place by the result when the run finishes, so it costs one bubble
|
||||||
|
rather than two. This exists because of what the next paragraph does *not* do.
|
||||||
|
|
||||||
|
**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`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`/etc/cachy-auto-update/cachy-auto-update.conf`, one `Key=Value` per line, every
|
`/etc/cachy-auto-update/cachy-auto-update.conf`, one `Key=Value` per line, every
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ updated in the background, with no password prompt and no terminal.
|
|||||||
|
|
||||||
Run without a command it opens a small interactive menu with the two switches
|
Run without a command it opens a small interactive menu with the two switches
|
||||||
that matter - automatic updates on/off and notifications on/off - plus the
|
that matter - automatic updates on/off and notifications on/off - plus the
|
||||||
current status.
|
current status, and a settings screen covering every remaining option so the
|
||||||
|
configuration file never has to be edited by hand. In the settings screen the
|
||||||
|
arrow keys select, Space or Right changes a value, and _q_ goes back; every
|
||||||
|
change is written out immediately.
|
||||||
|
|
||||||
The actual work is done by a systemd system service. The timer ticks hourly;
|
The actual work is done by a systemd system service. The timer ticks hourly;
|
||||||
whether a tick does anything is decided by _UpdateInterval_ (daily by default).
|
whether a tick does anything is decided by _UpdateInterval_ (daily by default).
|
||||||
@@ -100,6 +103,39 @@ running are skipped.
|
|||||||
The machine is never restarted automatically. When a kernel update makes a
|
The machine is never restarted automatically. When a kernel update makes a
|
||||||
restart necessary, a notification says so.
|
restart necessary, a notification says so.
|
||||||
|
|
||||||
|
# INTERRUPTED UPDATES
|
||||||
|
|
||||||
|
Before a transaction starts, a notification says an update is running and asks
|
||||||
|
for the machine to be left on. It is replaced in place by the result once the
|
||||||
|
run finishes.
|
||||||
|
|
||||||
|
While a transaction is running, *cachy-auto-update* holds a
|
||||||
|
*systemd-inhibit*(1) lock on _sleep_ and _shutdown_ in blocking mode, so a
|
||||||
|
suspend, a lid close or a normal shutdown request cannot cut it short.
|
||||||
|
|
||||||
|
What a user sees when they try anyway is worth knowing: logind refuses the
|
||||||
|
request and falls back to the polkit action
|
||||||
|
_org.freedesktop.login1.power-off-ignore-inhibit_, which is _auth_admin_keep_,
|
||||||
|
so the desktop presents an administrator password prompt reading "Power off the
|
||||||
|
system while an application is inhibiting this". That string is shipped
|
||||||
|
untranslated by systemd and does not mention updates, and no KDE dialog
|
||||||
|
explains the situation either - only *systemctl*(1) names the inhibitor and its
|
||||||
|
reason. Hence the notification.
|
||||||
|
|
||||||
|
A hard power-off - holding the power button, or losing mains power - is not
|
||||||
|
preventable. On the next run a leftover _/var/lib/pacman/db.lck_ is removed if
|
||||||
|
it is older than the current boot, since no process able to hold it can still
|
||||||
|
exist; the upgrade is then repeated and pacman reinstalls whatever was caught
|
||||||
|
half-written. A lock file that is unheld but was created during the current
|
||||||
|
boot is reported rather than removed, because there is no way to prove it is
|
||||||
|
abandoned.
|
||||||
|
|
||||||
|
Without that recovery a single power cut would leave a lock that makes every
|
||||||
|
subsequent run defer, silently stopping updates for good.
|
||||||
|
|
||||||
|
On Btrfs with *snapper*(8) and *snap-pac*, each pacman transaction is bracketed
|
||||||
|
by a pre and post snapshot, so a broken upgrade remains rollbackable.
|
||||||
|
|
||||||
# FILES
|
# FILES
|
||||||
|
|
||||||
_/etc/cachy-auto-update/cachy-auto-update.conf_
|
_/etc/cachy-auto-update/cachy-auto-update.conf_
|
||||||
|
|||||||
@@ -257,3 +257,81 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "The last run was stopped before it finished."
|
msgid "The last run was stopped before it finished."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Finishing an interrupted update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The last update was cut short, most likely because the machine was switched off. It is being finished now."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Installing updates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "%d packages are being updated. Please leave the computer switched on until this is done."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "(none)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Up/Down select - Space or Right changes - q goes back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Package names separated by spaces, empty to clear:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify when an update starts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify after a successful update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify when something goes wrong"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify when a restart is needed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Time between update runs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Postpone while a game is running"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Only update on mains power"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Minimum battery level (%)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update AUR packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update Flatpaks"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update AppImages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Also rebuild -git packages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "AUR helper"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Resolve package conflicts automatically"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Trim the package cache"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cached versions to keep"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Remove packages nothing needs any more"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Never update these packages"
|
||||||
|
msgstr ""
|
||||||
@@ -258,3 +258,82 @@ msgstr "Zurückgehalten: %s"
|
|||||||
|
|
||||||
msgid "The last run was stopped before it finished."
|
msgid "The last run was stopped before it finished."
|
||||||
msgstr "Der letzte Lauf wurde abgebrochen, bevor er fertig war."
|
msgstr "Der letzte Lauf wurde abgebrochen, bevor er fertig war."
|
||||||
|
|
||||||
|
msgid "Finishing an interrupted update"
|
||||||
|
msgstr "Abgebrochenes Update wird beendet"
|
||||||
|
|
||||||
|
msgid "The last update was cut short, most likely because the machine was switched off. It is being finished now."
|
||||||
|
msgstr "Das letzte Update wurde unterbrochen, vermutlich weil der Rechner ausgeschaltet wurde. Es wird jetzt zu Ende geführt."
|
||||||
|
|
||||||
|
msgid "Installing updates"
|
||||||
|
msgstr "Updates werden installiert"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "%d packages are being updated. Please leave the computer switched on until this is done."
|
||||||
|
msgstr "%d Pakete werden gerade aktualisiert. Bitte den Rechner so lange eingeschaltet lassen."
|
||||||
|
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Einstellungen"
|
||||||
|
|
||||||
|
msgid "(none)"
|
||||||
|
msgstr "(keine)"
|
||||||
|
|
||||||
|
msgid "Up/Down select - Space or Right changes - q goes back"
|
||||||
|
msgstr "Hoch/Runter wählen – Leertaste oder Rechts ändert – q zurück"
|
||||||
|
|
||||||
|
msgid "Package names separated by spaces, empty to clear:"
|
||||||
|
msgstr "Paketnamen mit Leerzeichen getrennt, leer zum Löschen:"
|
||||||
|
|
||||||
|
msgid "Notify when an update starts"
|
||||||
|
msgstr "Melden, wenn ein Update beginnt"
|
||||||
|
|
||||||
|
msgid "Notify after a successful update"
|
||||||
|
msgstr "Melden nach erfolgreichem Update"
|
||||||
|
|
||||||
|
msgid "Notify when something goes wrong"
|
||||||
|
msgstr "Melden, wenn etwas schiefgeht"
|
||||||
|
|
||||||
|
msgid "Notify when a restart is needed"
|
||||||
|
msgstr "Melden, wenn ein Neustart nötig ist"
|
||||||
|
|
||||||
|
msgid "Time between update runs"
|
||||||
|
msgstr "Abstand zwischen Update-Läufen"
|
||||||
|
|
||||||
|
msgid "Postpone while a game is running"
|
||||||
|
msgstr "Verschieben, solange ein Spiel läuft"
|
||||||
|
|
||||||
|
msgid "Only update on mains power"
|
||||||
|
msgstr "Nur am Stromnetz aktualisieren"
|
||||||
|
|
||||||
|
msgid "Minimum battery level (%)"
|
||||||
|
msgstr "Mindest-Akkustand (%)"
|
||||||
|
|
||||||
|
msgid "Update AUR packages"
|
||||||
|
msgstr "AUR-Pakete aktualisieren"
|
||||||
|
|
||||||
|
msgid "Update Flatpaks"
|
||||||
|
msgstr "Flatpaks aktualisieren"
|
||||||
|
|
||||||
|
msgid "Update AppImages"
|
||||||
|
msgstr "AppImages aktualisieren"
|
||||||
|
|
||||||
|
msgid "Also rebuild -git packages"
|
||||||
|
msgstr "Auch -git-Pakete neu bauen"
|
||||||
|
|
||||||
|
msgid "AUR helper"
|
||||||
|
msgstr "AUR-Helfer"
|
||||||
|
|
||||||
|
msgid "Resolve package conflicts automatically"
|
||||||
|
msgstr "Paketkonflikte automatisch auflösen"
|
||||||
|
|
||||||
|
msgid "Trim the package cache"
|
||||||
|
msgstr "Paket-Cache beschneiden"
|
||||||
|
|
||||||
|
msgid "Cached versions to keep"
|
||||||
|
msgstr "Behaltene Versionen im Cache"
|
||||||
|
|
||||||
|
msgid "Remove packages nothing needs any more"
|
||||||
|
msgstr "Nicht mehr benötigte Pakete entfernen"
|
||||||
|
|
||||||
|
msgid "Never update these packages"
|
||||||
|
msgstr "Diese Pakete nie aktualisieren"
|
||||||
@@ -66,22 +66,41 @@ AutoResolveConflicts=yes
|
|||||||
IgnorePkg=
|
IgnorePkg=
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Housekeeping - all off by default
|
# Housekeeping
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# Trim the pacman package cache after an update.
|
# Trim the pacman package cache after an update: drop the older versions of
|
||||||
CleanCache=no
|
# installed packages, and every cached version of packages that are no longer
|
||||||
|
# installed. Nothing that is currently installed is ever touched, so this only
|
||||||
|
# costs the ability to downgrade further back than KeepOldPackages.
|
||||||
|
# Without it /var/cache/pacman/pkg grows forever - tens of gigabytes on a
|
||||||
|
# machine with a few large packages.
|
||||||
|
CleanCache=yes
|
||||||
|
|
||||||
# How many old versions per package to keep when CleanCache is on.
|
# How many old versions per package to keep when CleanCache is on. 3 is the
|
||||||
|
# Arch default and leaves room to downgrade. Set it to 1 if disk space matters
|
||||||
|
# more than that; on a machine with a handful of multi-gigabyte packages the
|
||||||
|
# difference is easily ten gigabytes.
|
||||||
KeepOldPackages=3
|
KeepOldPackages=3
|
||||||
|
|
||||||
# Remove packages that were installed as dependencies and are no longer needed.
|
# Remove packages nothing depends on any more: pacman packages that were pulled
|
||||||
|
# in as dependencies and are now unreferenced, plus Flatpak runtimes no
|
||||||
|
# installed application uses.
|
||||||
|
# Off by default, and unlike CleanCache this one deletes installed software:
|
||||||
|
# "orphaned" only means no other package requires it, which is also true of
|
||||||
|
# something installed deliberately as a dependency of nothing.
|
||||||
RemoveOrphans=no
|
RemoveOrphans=no
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Notification detail
|
# Notification detail
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Say that an update has started. Worth keeping on: while one runs, a shutdown
|
||||||
|
# request is refused and the desktop answers with an untranslated polkit
|
||||||
|
# password prompt that never mentions updates. This message is replaced by the
|
||||||
|
# result when the run finishes, so it costs one bubble, not two.
|
||||||
|
NotifyOnStart=yes
|
||||||
|
|
||||||
# Say something after a successful update. Nothing is ever shown when there
|
# Say something after a successful update. Nothing is ever shown when there
|
||||||
# was nothing to do.
|
# was nothing to do.
|
||||||
NotifyOnSuccess=yes
|
NotifyOnSuccess=yes
|
||||||
|
|||||||
@@ -117,6 +117,15 @@ if (( ! FORCE )); then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# A lock left behind by a power cut during a previous update. It is cleared
|
||||||
|
# before the busy check, because otherwise every future run would defer on it
|
||||||
|
# forever and the machine would quietly stop updating.
|
||||||
|
if cau_recover_stale_lock; then
|
||||||
|
cau_notify normal \
|
||||||
|
"Finishing an interrupted update" \
|
||||||
|
"The last update was cut short, most likely because the machine was switched off. It is being finished now."
|
||||||
|
fi
|
||||||
|
|
||||||
# This one is checked even with --force: proceeding anyway would just hand the
|
# This one is checked even with --force: proceeding anyway would just hand the
|
||||||
# user a lock error instead of doing anything useful.
|
# user a lock error instead of doing anything useful.
|
||||||
CAU_SKIP_REASON=''
|
CAU_SKIP_REASON=''
|
||||||
@@ -204,7 +213,7 @@ cau_state_write last_counts \
|
|||||||
if (( failed )); then
|
if (( failed )); then
|
||||||
cau_state_write last_result failed
|
cau_state_write last_result failed
|
||||||
cau_error "Update run finished with errors"
|
cau_error "Update run finished with errors"
|
||||||
[[ $CFG_NOTIFY_ERROR == yes ]] && cau_notify critical \
|
[[ $CFG_NOTIFY_ERROR == yes ]] && cau_notify_tagged run yes critical \
|
||||||
"Update failed" \
|
"Update failed" \
|
||||||
"Something went wrong while updating. Run 'cachy-auto-update log' for details."
|
"Something went wrong while updating. Run 'cachy-auto-update log' for details."
|
||||||
else
|
else
|
||||||
@@ -213,7 +222,7 @@ else
|
|||||||
cau_info "Update run finished successfully ($total item(s) updated)"
|
cau_info "Update run finished successfully ($total item(s) updated)"
|
||||||
|
|
||||||
if (( total > 0 )) && [[ $CFG_NOTIFY_SUCCESS == yes ]]; then
|
if (( total > 0 )) && [[ $CFG_NOTIFY_SUCCESS == yes ]]; then
|
||||||
cau_notify low "System updated" "%d updates were installed." "$total"
|
cau_notify_tagged run yes low "System updated" "%d updates were installed." "$total"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+24
-3
@@ -75,13 +75,34 @@ cau_msg() {
|
|||||||
cau_msg_in "$(cau_ui_locale)" "$@"
|
cau_msg_in "$(cau_ui_locale)" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Translations are memoized. Every gettext lookup is a fork, and the settings
|
||||||
|
# screen redraws forty-odd labels per keypress; without this the redraw takes
|
||||||
|
# long enough that a keystroke arriving during it is lost when the terminal
|
||||||
|
# switches back to single-character mode.
|
||||||
|
declare -A CAU_MSG_CACHE=()
|
||||||
|
|
||||||
# cau_msg_in <locale> <msgid> [printf args...]
|
# cau_msg_in <locale> <msgid> [printf args...]
|
||||||
cau_msg_in() {
|
cau_msg_in() {
|
||||||
local locale="$1" msgid="$2" translated
|
local locale="$1" msgid="$2" translated cachekey
|
||||||
shift 2
|
shift 2
|
||||||
|
|
||||||
translated="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)"
|
cachekey="${locale}"$'\x1f'"${msgid}"
|
||||||
[[ -n $translated ]] || translated="$msgid"
|
if [[ -n ${CAU_MSG_CACHE[$cachekey]+set} ]]; then
|
||||||
|
translated="${CAU_MSG_CACHE[$cachekey]}"
|
||||||
|
else
|
||||||
|
translated="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)"
|
||||||
|
[[ -n $translated ]] || translated="$msgid"
|
||||||
|
CAU_MSG_CACHE[$cachekey]="$translated"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# With no arguments the message is plain text, not a format string. Feeding
|
||||||
|
# it to printf anyway turns any literal percent sign in it - "Battery (%)",
|
||||||
|
# "100 % done" - into an invalid conversion, and that is a trap every
|
||||||
|
# translator would eventually walk into.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
printf '%s' "$translated"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2059 # the format string is the translated message
|
# shellcheck disable=SC2059 # the format string is the translated message
|
||||||
printf -- "$translated" "$@"
|
printf -- "$translated" "$@"
|
||||||
|
|||||||
+25
-4
@@ -6,14 +6,33 @@
|
|||||||
# root-run daemon, and sourcing it would turn a stray line into arbitrary code
|
# root-run daemon, and sourcing it would turn a stray line into arbitrary code
|
||||||
# execution. The format is one "Key=Value" per line, '#' starts a comment.
|
# execution. The format is one "Key=Value" per line, '#' starts a comment.
|
||||||
|
|
||||||
|
# The file is cached and parsed in-process rather than shelled out to sed on
|
||||||
|
# every lookup. The settings screen reads every key on every redraw, and a fork
|
||||||
|
# per key made the redraw slow enough to swallow keystrokes.
|
||||||
|
CAU_CONFIG_CACHE=''
|
||||||
|
CAU_CONFIG_CACHED=0
|
||||||
|
|
||||||
|
_cau_config_slurp() {
|
||||||
|
(( CAU_CONFIG_CACHED )) && return 0
|
||||||
|
CAU_CONFIG_CACHE=''
|
||||||
|
[[ -r $CAU_CONFIG ]] && CAU_CONFIG_CACHE="$(< "$CAU_CONFIG")"
|
||||||
|
CAU_CONFIG_CACHED=1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# cau_config_get <Key> [default]
|
# cau_config_get <Key> [default]
|
||||||
cau_config_get() {
|
cau_config_get() {
|
||||||
local key="$1" default="${2:-}" val
|
local key="$1" default="${2:-}" val='' line
|
||||||
|
|
||||||
[[ -r $CAU_CONFIG ]] || { printf '%s\n' "$default"; return; }
|
[[ -r $CAU_CONFIG ]] || { printf '%s\n' "$default"; return; }
|
||||||
|
_cau_config_slurp
|
||||||
|
|
||||||
val="$(sed -nE "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*(.*)$/\\1/p" \
|
# last assignment wins, matching the previous sed|tail behaviour
|
||||||
"$CAU_CONFIG" 2>/dev/null | tail -n1)"
|
while IFS= read -r line; do
|
||||||
|
[[ $line == *"$key"* ]] || continue
|
||||||
|
[[ $line =~ ^[[:space:]]*"$key"[[:space:]]*=(.*)$ ]] || continue
|
||||||
|
val="${BASH_REMATCH[1]}"
|
||||||
|
done <<< "$CAU_CONFIG_CACHE"
|
||||||
|
|
||||||
# strip a trailing comment and surrounding whitespace/quotes
|
# strip a trailing comment and surrounding whitespace/quotes
|
||||||
val="${val%%#*}"
|
val="${val%%#*}"
|
||||||
@@ -78,6 +97,7 @@ cau_config_set() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
mv -f "$tmp" "$CAU_CONFIG"
|
mv -f "$tmp" "$CAU_CONFIG"
|
||||||
|
CAU_CONFIG_CACHED=0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -96,8 +116,9 @@ cau_config_load() {
|
|||||||
CFG_APPIMAGE=no; cau_config_bool UpdateAppImages yes && CFG_APPIMAGE=yes
|
CFG_APPIMAGE=no; cau_config_bool UpdateAppImages yes && CFG_APPIMAGE=yes
|
||||||
CFG_DEVEL=no; cau_config_bool UpdateDevel no && CFG_DEVEL=yes
|
CFG_DEVEL=no; cau_config_bool UpdateDevel no && CFG_DEVEL=yes
|
||||||
CFG_RESOLVE_CONFLICTS=no; cau_config_bool AutoResolveConflicts yes && CFG_RESOLVE_CONFLICTS=yes
|
CFG_RESOLVE_CONFLICTS=no; cau_config_bool AutoResolveConflicts yes && CFG_RESOLVE_CONFLICTS=yes
|
||||||
CFG_CLEAN_CACHE=no; cau_config_bool CleanCache no && CFG_CLEAN_CACHE=yes
|
CFG_CLEAN_CACHE=no; cau_config_bool CleanCache yes && CFG_CLEAN_CACHE=yes
|
||||||
CFG_REMOVE_ORPHANS=no; cau_config_bool RemoveOrphans no && CFG_REMOVE_ORPHANS=yes
|
CFG_REMOVE_ORPHANS=no; cau_config_bool RemoveOrphans no && CFG_REMOVE_ORPHANS=yes
|
||||||
|
CFG_NOTIFY_START=no; cau_config_bool NotifyOnStart yes && CFG_NOTIFY_START=yes
|
||||||
CFG_NOTIFY_SUCCESS=no; cau_config_bool NotifyOnSuccess yes && CFG_NOTIFY_SUCCESS=yes
|
CFG_NOTIFY_SUCCESS=no; cau_config_bool NotifyOnSuccess yes && CFG_NOTIFY_SUCCESS=yes
|
||||||
CFG_NOTIFY_ERROR=no; cau_config_bool NotifyOnError yes && CFG_NOTIFY_ERROR=yes
|
CFG_NOTIFY_ERROR=no; cau_config_bool NotifyOnError yes && CFG_NOTIFY_ERROR=yes
|
||||||
CFG_NOTIFY_REBOOT=no; cau_config_bool NotifyReboot yes && CFG_NOTIFY_REBOOT=yes
|
CFG_NOTIFY_REBOOT=no; cau_config_bool NotifyReboot yes && CFG_NOTIFY_REBOOT=yes
|
||||||
|
|||||||
+48
-4
@@ -60,11 +60,55 @@ cau_package_manager_busy() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# cau_pacman_lock_is_stale
|
||||||
|
# True only when the lock provably cannot belong to anything alive.
|
||||||
|
#
|
||||||
|
# The rigorous test is the boot time: no process that existed before the
|
||||||
|
# current boot can still be running, so a db.lck older than boot is abandoned
|
||||||
|
# by definition - which is exactly what a power cut during an update leaves
|
||||||
|
# behind. A lock that is merely unheld *within* this boot is not provable in
|
||||||
|
# the same way, so it is only reported (see cau_track_stale_lock) and never
|
||||||
|
# removed; guessing wrong there would corrupt a live transaction.
|
||||||
|
#
|
||||||
|
# The fuser check is kept as a second condition purely to survive a backwards
|
||||||
|
# clock jump making a live lock look pre-boot.
|
||||||
|
cau_pacman_lock_is_stale() {
|
||||||
|
local boot lock
|
||||||
|
|
||||||
|
[[ -e $CAU_PACMAN_LOCK ]] || return 1
|
||||||
|
|
||||||
|
boot="$(awk '/^btime /{print $2}' /proc/stat 2>/dev/null)"
|
||||||
|
[[ $boot =~ ^[0-9]+$ ]] || return 1
|
||||||
|
|
||||||
|
lock="$(stat -c %Y "$CAU_PACMAN_LOCK" 2>/dev/null)" || return 1
|
||||||
|
[[ $lock =~ ^[0-9]+$ ]] || return 1
|
||||||
|
|
||||||
|
(( lock < boot )) || return 1
|
||||||
|
[[ -z "$(cau_pacman_lock_holder)" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# cau_recover_stale_lock
|
||||||
|
# Clears a provably abandoned lock so an interrupted update can be finished on
|
||||||
|
# the next run. Without this, one power cut during an update stops every future
|
||||||
|
# update permanently and silently - the worst possible outcome for a machine
|
||||||
|
# nobody is watching.
|
||||||
|
cau_recover_stale_lock() {
|
||||||
|
cau_pacman_lock_is_stale || return 1
|
||||||
|
|
||||||
|
cau_warn "Found a pacman lock older than this boot - an update was cut short"
|
||||||
|
rm -f "$CAU_PACMAN_LOCK" 2>/dev/null || {
|
||||||
|
cau_error "Could not remove the stale pacman lock"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
cau_state_clear stale_lock_count
|
||||||
|
cau_info "Stale lock removed; the interrupted update will be finished now"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# cau_track_stale_lock
|
# cau_track_stale_lock
|
||||||
# A db.lck with no process behind it is left over from a crashed transaction.
|
# A db.lck with no process behind it but created during this boot: a crashed
|
||||||
# Removing it automatically would be reckless - if the guess is wrong it
|
# pacman rather than a power cut. Not provable, so it is counted, and after
|
||||||
# corrupts a live transaction - so instead it is counted, and after enough
|
# enough consecutive sightings the user is told to clean it up.
|
||||||
# consecutive sightings the user is told to clean it up.
|
|
||||||
CAU_STALE_LOCK_RUNS=3
|
CAU_STALE_LOCK_RUNS=3
|
||||||
|
|
||||||
cau_track_stale_lock() {
|
cau_track_stale_lock() {
|
||||||
|
|||||||
+302
-18
@@ -7,6 +7,279 @@
|
|||||||
|
|
||||||
CAU_UNIT="cachy-auto-update.timer"
|
CAU_UNIT="cachy-auto-update.timer"
|
||||||
|
|
||||||
|
# Terminal mode.
|
||||||
|
#
|
||||||
|
# bash flips the terminal into non-canonical mode for each `read -sn1` and back
|
||||||
|
# out again in between. That gap matters: in canonical mode DEL is the ERASE
|
||||||
|
# character, so the line discipline eats it instead of delivering it, and a
|
||||||
|
# backspace typed while the interface was between reads simply vanished.
|
||||||
|
# Holding non-canonical mode for the whole interface removes the gap.
|
||||||
|
CAU_TERM_SAVED=''
|
||||||
|
|
||||||
|
cau_ui_term_raw() {
|
||||||
|
cau_have stty || return 0
|
||||||
|
[[ -t 0 ]] || return 0
|
||||||
|
[[ -n $CAU_TERM_SAVED ]] && return 0
|
||||||
|
|
||||||
|
CAU_TERM_SAVED="$(stty -g 2>/dev/null)" || { CAU_TERM_SAVED=''; return 0; }
|
||||||
|
stty -icanon -echo min 1 time 0 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
cau_ui_term_restore() {
|
||||||
|
[[ -n $CAU_TERM_SAVED ]] || return 0
|
||||||
|
stty "$CAU_TERM_SAVED" 2>/dev/null || true
|
||||||
|
CAU_TERM_SAVED=''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs an action with the terminal handed back to normal line mode, so anything
|
||||||
|
# it prints - or prompts for - behaves the way a program expects.
|
||||||
|
cau_ui_cooked() {
|
||||||
|
cau_ui_term_restore
|
||||||
|
"$@"
|
||||||
|
local rc=$?
|
||||||
|
cau_ui_term_raw
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
# cau_read_key
|
||||||
|
# One keypress, resolved to a symbolic name: a literal character, or one of
|
||||||
|
# up/down/left/right/enter/space/escape. Arrow keys arrive as ESC [ A, so the
|
||||||
|
# tail of the sequence is consumed here rather than being mistaken for three
|
||||||
|
# separate presses.
|
||||||
|
cau_read_key() {
|
||||||
|
local k rest
|
||||||
|
|
||||||
|
IFS= read -rsn1 k || return 1
|
||||||
|
|
||||||
|
case "$k" in
|
||||||
|
$'\e')
|
||||||
|
if IFS= read -rsn2 -t 0.05 rest; then
|
||||||
|
case "$rest" in
|
||||||
|
'[A') printf 'up\n' ;;
|
||||||
|
'[B') printf 'down\n' ;;
|
||||||
|
'[C') printf 'right\n' ;;
|
||||||
|
'[D') printf 'left\n' ;;
|
||||||
|
*) printf 'escape\n' ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
printf 'escape\n'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
# Enter is an empty read in cooked mode and a carriage return in raw
|
||||||
|
# mode, depending on whether the terminal is translating it.
|
||||||
|
''|$'\r') printf 'enter\n' ;;
|
||||||
|
$'\x7f'|$'\b') printf 'backspace\n' ;;
|
||||||
|
' ') printf 'space\n' ;;
|
||||||
|
*) printf '%s\n' "$k" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# cau_ui_read_line <initial>
|
||||||
|
# A minimal line editor built on cau_read_key, with the result in
|
||||||
|
# CAU_LINE_RESULT.
|
||||||
|
#
|
||||||
|
# This exists instead of bash's own `read -r` because mixing line mode into a
|
||||||
|
# single-key interface breaks it: after one cooked-mode read the following
|
||||||
|
# `read -sn1` stops receiving keystrokes entirely, reproducibly, on a real pty.
|
||||||
|
# Never leaving single-character mode side-steps that completely.
|
||||||
|
CAU_LINE_RESULT=''
|
||||||
|
|
||||||
|
cau_ui_read_line() {
|
||||||
|
local buf="${1:-}" key
|
||||||
|
|
||||||
|
CAU_LINE_RESULT=''
|
||||||
|
printf '%s' "$buf"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
key="$(cau_read_key)" || { printf '\n'; return 1; }
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
enter)
|
||||||
|
printf '\n'
|
||||||
|
CAU_LINE_RESULT="$buf"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
escape)
|
||||||
|
printf '\n'
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
backspace)
|
||||||
|
if [[ -n $buf ]]; then
|
||||||
|
buf="${buf%?}"
|
||||||
|
printf '\b \b'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
space)
|
||||||
|
buf+=' '
|
||||||
|
printf ' '
|
||||||
|
;;
|
||||||
|
up|down|left|right) ;; # no cursor movement in this editor
|
||||||
|
*)
|
||||||
|
# a single printable character; control keys arrive as names
|
||||||
|
[[ ${#key} -eq 1 ]] || continue
|
||||||
|
buf+="$key"
|
||||||
|
printf '%s' "$key"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything that can be changed without opening a text editor.
|
||||||
|
# Format: Key|type|default|label-msgid
|
||||||
|
# type is bool, choice:<space separated values>, or text.
|
||||||
|
CAU_SETTINGS=(
|
||||||
|
"NotifyOnStart|bool|yes|Notify when an update starts"
|
||||||
|
"NotifyOnSuccess|bool|yes|Notify after a successful update"
|
||||||
|
"NotifyOnError|bool|yes|Notify when something goes wrong"
|
||||||
|
"NotifyReboot|bool|yes|Notify when a restart is needed"
|
||||||
|
"UpdateInterval|choice:6h 12h 1d 2d 1w|1d|Time between update runs"
|
||||||
|
"SkipWhenGaming|bool|yes|Postpone while a game is running"
|
||||||
|
"RequireAC|bool|no|Only update on mains power"
|
||||||
|
"MinBatteryPercent|choice:0 20 30 40 50 60 70 80|40|Minimum battery level (%)"
|
||||||
|
"UpdateAUR|bool|yes|Update AUR packages"
|
||||||
|
"UpdateFlatpak|bool|yes|Update Flatpaks"
|
||||||
|
"UpdateAppImages|bool|yes|Update AppImages"
|
||||||
|
"UpdateDevel|bool|no|Also rebuild -git packages"
|
||||||
|
"AURHelper|choice:auto paru yay pikaur|auto|AUR helper"
|
||||||
|
"AutoResolveConflicts|bool|yes|Resolve package conflicts automatically"
|
||||||
|
"CleanCache|bool|yes|Trim the package cache"
|
||||||
|
"KeepOldPackages|choice:0 1 2 3 5|3|Cached versions to keep"
|
||||||
|
"RemoveOrphans|bool|no|Remove packages nothing needs any more"
|
||||||
|
"IgnorePkg|text||Never update these packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
_cau_is_true() {
|
||||||
|
case "${1,,}" in
|
||||||
|
yes|y|true|1|on|enabled) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# _cau_setting_display <type> <value>
|
||||||
|
_cau_setting_display() {
|
||||||
|
local type="$1" value="$2"
|
||||||
|
|
||||||
|
case "$type" in
|
||||||
|
bool)
|
||||||
|
if _cau_is_true "$value"; then
|
||||||
|
printf '%s%s%s' "$CAU_C_GREEN" "$(cau_msg "ON")" "$CAU_C_RESET"
|
||||||
|
else
|
||||||
|
printf '%s%s%s' "$CAU_C_DIM" "$(cau_msg "OFF")" "$CAU_C_RESET"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
text)
|
||||||
|
if [[ -n $value ]]; then
|
||||||
|
printf '%s' "$value"
|
||||||
|
else
|
||||||
|
printf '%s%s%s' "$CAU_C_DIM" "$(cau_msg "(none)")" "$CAU_C_RESET"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) printf '%s' "$value" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# _cau_setting_cycle <type> <value> <direction: 1|-1>
|
||||||
|
# The next value for this setting. Choices wrap around, so one key is enough to
|
||||||
|
# reach everything without needing a second one for the other direction.
|
||||||
|
_cau_setting_cycle() {
|
||||||
|
local type="$1" value="$2" dir="$3"
|
||||||
|
|
||||||
|
if [[ $type == bool ]]; then
|
||||||
|
_cau_is_true "$value" && printf 'no\n' || printf 'yes\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -a choices
|
||||||
|
read -r -a choices <<< "${type#choice:}"
|
||||||
|
(( ${#choices[@]} )) || { printf '%s\n' "$value"; return; }
|
||||||
|
|
||||||
|
local i idx=0
|
||||||
|
for i in "${!choices[@]}"; do
|
||||||
|
[[ ${choices[i]} == "$value" ]] && { idx=$i; break; }
|
||||||
|
done
|
||||||
|
|
||||||
|
idx=$(( (idx + dir + ${#choices[@]}) % ${#choices[@]} ))
|
||||||
|
printf '%s\n' "${choices[idx]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# cau_ui_settings
|
||||||
|
# A cursor list rather than a numbered menu: there are eighteen settings, and
|
||||||
|
# numbering them would run out of digits and force paging.
|
||||||
|
cau_ui_settings() {
|
||||||
|
local cursor=0 key spec name type default label value line pad
|
||||||
|
local count=${#CAU_SETTINGS[@]}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
clear 2>/dev/null || true
|
||||||
|
cau_head " $(cau_msg "Settings")"
|
||||||
|
|
||||||
|
local i=0
|
||||||
|
for spec in "${CAU_SETTINGS[@]}"; do
|
||||||
|
IFS='|' read -r name type default label <<< "$spec"
|
||||||
|
value="$(cau_config_get "$name" "$default")"
|
||||||
|
|
||||||
|
if (( i == cursor )); then
|
||||||
|
line="${CAU_C_BLUE}▸${CAU_C_RESET} "
|
||||||
|
else
|
||||||
|
line=" "
|
||||||
|
fi
|
||||||
|
|
||||||
|
local text
|
||||||
|
text="$(cau_msg "$label")"
|
||||||
|
pad=$(( 42 - ${#text} ))
|
||||||
|
(( pad < 0 )) && pad=0
|
||||||
|
|
||||||
|
printf ' %s%s%*s %s\n' "$line" "$text" "$pad" '' \
|
||||||
|
"$(_cau_setting_display "$type" "$value")"
|
||||||
|
i=$(( i + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n %s%s%s\n' "$CAU_C_DIM" \
|
||||||
|
"$(cau_msg "Up/Down select - Space or Right changes - q goes back")" "$CAU_C_RESET"
|
||||||
|
|
||||||
|
key="$(cau_read_key)" || return 0
|
||||||
|
|
||||||
|
IFS='|' read -r name type default label <<< "${CAU_SETTINGS[cursor]}"
|
||||||
|
value="$(cau_config_get "$name" "$default")"
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
|
||||||
|
down|j) cursor=$(( (cursor + 1) % count )) ;;
|
||||||
|
space|enter|right|l)
|
||||||
|
if [[ $type == text ]]; then
|
||||||
|
cau_ui_edit_text "$name" "$value"
|
||||||
|
else
|
||||||
|
cau_config_set "$name" "$(_cau_setting_cycle "$type" "$value" 1)" \
|
||||||
|
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
left|h)
|
||||||
|
[[ $type == text ]] || cau_config_set "$name" \
|
||||||
|
"$(_cau_setting_cycle "$type" "$value" -1)" \
|
||||||
|
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
||||||
|
;;
|
||||||
|
q|Q|escape) return 0 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# cau_ui_edit_text <key> <current>
|
||||||
|
# The one setting that is a free-text list rather than a choice.
|
||||||
|
cau_ui_edit_text() {
|
||||||
|
local name="$1" current="$2"
|
||||||
|
|
||||||
|
printf '\n %s\n' "$(cau_msg "Package names separated by spaces, empty to clear:")"
|
||||||
|
printf ' > '
|
||||||
|
|
||||||
|
# pre-filled with the current value so it can be corrected rather than
|
||||||
|
# retyped; Escape leaves it unchanged
|
||||||
|
if cau_ui_read_line "$current"; then
|
||||||
|
cau_config_set "$name" "$CAU_LINE_RESULT" \
|
||||||
|
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# _cau_row <label> <value>
|
# _cau_row <label> <value>
|
||||||
# printf's %-28s pads by bytes, so a label containing "ü" comes out one column
|
# printf's %-28s pads by bytes, so a label containing "ü" comes out one column
|
||||||
# short. ${#s} counts characters in a UTF-8 locale, so the padding is computed
|
# short. ${#s} counts characters in a UTF-8 locale, so the padding is computed
|
||||||
@@ -141,6 +414,10 @@ cau_ui_status_conditions() {
|
|||||||
cau_ui_menu() {
|
cau_ui_menu() {
|
||||||
local choice
|
local choice
|
||||||
|
|
||||||
|
cau_ui_term_raw
|
||||||
|
# restore the terminal even if this exits through Ctrl-C or an error
|
||||||
|
trap 'cau_ui_term_restore' EXIT INT TERM
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
cau_config_load
|
cau_config_load
|
||||||
|
|
||||||
@@ -153,45 +430,52 @@ cau_ui_menu() {
|
|||||||
printf ' [3] %s\n' "$(cau_msg "Update now")"
|
printf ' [3] %s\n' "$(cau_msg "Update now")"
|
||||||
printf ' [4] %s\n' "$(cau_msg "Show log")"
|
printf ' [4] %s\n' "$(cau_msg "Show log")"
|
||||||
printf ' [5] %s\n' "$(cau_msg "Show current conditions")"
|
printf ' [5] %s\n' "$(cau_msg "Show current conditions")"
|
||||||
|
printf ' [6] %s\n' "$(cau_msg "Settings")"
|
||||||
printf ' [q] %s\n' "$(cau_msg "Quit")"
|
printf ' [q] %s\n' "$(cau_msg "Quit")"
|
||||||
printf '\n > '
|
printf '\n > '
|
||||||
|
|
||||||
# One keypress, no Enter. -s keeps the raw character out of the
|
# One keypress, no Enter. A failing read means EOF (Ctrl-D, or a
|
||||||
# display so the echo below is the only thing printed, and a failing
|
# script piping input), which quits.
|
||||||
# read means EOF (Ctrl-D, or a script piping input) - that quits.
|
choice="$(cau_read_key)" || {
|
||||||
read -rsn1 choice || { printf '\n'; return 0; }
|
printf '\n'; cau_ui_term_restore; trap - EXIT INT TERM; return 0
|
||||||
|
}
|
||||||
|
case "$choice" in
|
||||||
|
enter|space|up|down|left|right|escape) choice='' ;;
|
||||||
|
esac
|
||||||
printf '%s\n' "$choice"
|
printf '%s\n' "$choice"
|
||||||
|
|
||||||
|
# Actions run in normal line mode: they print program output and some
|
||||||
|
# of them prompt, neither of which behaves in raw mode.
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
# The two switches flip and return straight to the menu, where the
|
# The two switches flip and return straight to the menu, where the
|
||||||
# status block shows the result. Only a warning or an error holds
|
# status block shows the result. Only a warning or an error holds
|
||||||
# the screen (see CAU_UI_NEEDS_ACK).
|
# the screen (see CAU_UI_NEEDS_ACK).
|
||||||
1)
|
1)
|
||||||
CAU_UI_NEEDS_ACK=''
|
CAU_UI_NEEDS_ACK=''
|
||||||
if [[ $CFG_ENABLED == yes ]]; then cau_do_disable; else cau_do_enable; fi
|
if [[ $CFG_ENABLED == yes ]]; then
|
||||||
|
cau_ui_cooked cau_do_disable
|
||||||
|
else
|
||||||
|
cau_ui_cooked cau_do_enable
|
||||||
|
fi
|
||||||
if [[ -n $CAU_UI_NEEDS_ACK ]]; then cau_pause; fi
|
if [[ -n $CAU_UI_NEEDS_ACK ]]; then cau_pause; fi
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
CAU_UI_NEEDS_ACK=''
|
CAU_UI_NEEDS_ACK=''
|
||||||
if [[ $CFG_NOTIFICATIONS == yes ]]; then
|
if [[ $CFG_NOTIFICATIONS == yes ]]; then
|
||||||
cau_do_notifications off
|
cau_ui_cooked cau_do_notifications off
|
||||||
else
|
else
|
||||||
cau_do_notifications on
|
cau_ui_cooked cau_do_notifications on
|
||||||
fi
|
fi
|
||||||
if [[ -n $CAU_UI_NEEDS_ACK ]]; then cau_pause; fi
|
if [[ -n $CAU_UI_NEEDS_ACK ]]; then cau_pause; fi
|
||||||
;;
|
;;
|
||||||
3) cau_do_run --force; cau_pause ;;
|
3) cau_ui_cooked cau_do_run --force; cau_pause ;;
|
||||||
4) cau_do_log; cau_pause ;;
|
4) cau_ui_cooked cau_do_log; cau_pause ;;
|
||||||
5) cau_ui_status_conditions; cau_pause ;;
|
5) cau_ui_status_conditions; cau_pause ;;
|
||||||
q|Q) return 0 ;;
|
6) cau_ui_settings ;;
|
||||||
$'\e')
|
q|Q) cau_ui_term_restore; trap - EXIT INT TERM; return 0 ;;
|
||||||
# Arrow keys and friends arrive as ESC [ X. Swallow the rest so
|
# Anything else - Enter, arrow keys, stray characters - just
|
||||||
# one keypress does not redraw the menu three times. Escape is
|
# redraws. Escape is deliberately not a quit key, so a mistyped
|
||||||
# deliberately not a quit key: that would make a stray arrow
|
# arrow key cannot close the menu.
|
||||||
# key close the menu.
|
|
||||||
read -rsn2 -t 0.05 _ 2>/dev/null || true
|
|
||||||
;;
|
|
||||||
# Anything else, Enter included, just redraws.
|
|
||||||
*) ;;
|
*) ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|||||||
+29
-5
@@ -19,10 +19,23 @@ CAU_NOTIFY_QUEUE_MAX=20
|
|||||||
# Never fails: a machine without libnotify, or with nobody logged in, is a
|
# Never fails: a machine without libnotify, or with nobody logged in, is a
|
||||||
# normal state, not an error.
|
# normal state, not an error.
|
||||||
cau_notify() {
|
cau_notify() {
|
||||||
local urgency="$1" title="$2" body="$3"
|
cau_notify_tagged '' yes "$@"
|
||||||
shift 3
|
}
|
||||||
|
|
||||||
|
# cau_notify_tagged <tag> <queue: yes|no> <urgency> <title> <body> [args...]
|
||||||
|
#
|
||||||
|
# A tag makes this notification replace the previous one carrying the same tag
|
||||||
|
# rather than stacking a second bubble beside it - that is what turns
|
||||||
|
# "installing updates" into "system updated" in place instead of leaving two
|
||||||
|
# messages that contradict each other.
|
||||||
|
#
|
||||||
|
# queue=no is for messages that only mean anything while somebody is looking.
|
||||||
|
# Telling a user at next login that an update started an hour ago is noise.
|
||||||
|
cau_notify_tagged() {
|
||||||
|
local tag="$1" queue="$2" urgency="$3" title="$4" body="$5"
|
||||||
|
shift 5
|
||||||
local -a args=("$@")
|
local -a args=("$@")
|
||||||
local delivered=0 user uid locale t b
|
local delivered=0 user uid locale t b prev newid
|
||||||
|
|
||||||
[[ $CFG_NOTIFICATIONS == yes ]] || return 0
|
[[ $CFG_NOTIFICATIONS == yes ]] || return 0
|
||||||
|
|
||||||
@@ -34,17 +47,28 @@ cau_notify() {
|
|||||||
t="$(cau_msg_in "$locale" "$title")"
|
t="$(cau_msg_in "$locale" "$title")"
|
||||||
b="$(cau_msg_in "$locale" "$body" "${args[@]}")"
|
b="$(cau_msg_in "$locale" "$body" "${args[@]}")"
|
||||||
|
|
||||||
if cau_as_user "$user" "$uid" notify-send \
|
prev=0
|
||||||
|
if [[ -n $tag ]]; then
|
||||||
|
prev="$(cau_state_read "notify_id_${tag}_${user}" 0)"
|
||||||
|
[[ $prev =~ ^[0-9]+$ ]] || prev=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if newid="$(cau_as_user "$user" "$uid" notify-send \
|
||||||
--app-name="$CAU_PRETTY" \
|
--app-name="$CAU_PRETTY" \
|
||||||
--icon="$CAU_NOTIFY_ICON" \
|
--icon="$CAU_NOTIFY_ICON" \
|
||||||
--urgency="$urgency" \
|
--urgency="$urgency" \
|
||||||
-- "$t" "$b" 2>/dev/null
|
--print-id --replace-id="$prev" \
|
||||||
|
-- "$t" "$b" 2>/dev/null)"
|
||||||
then
|
then
|
||||||
delivered=1
|
delivered=1
|
||||||
|
if [[ -n $tag && $newid =~ ^[0-9]+$ ]]; then
|
||||||
|
cau_state_write "notify_id_${tag}_${user}" "$newid"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(cau_active_session_users)
|
done < <(cau_active_session_users)
|
||||||
|
|
||||||
(( delivered )) && return 0
|
(( delivered )) && return 0
|
||||||
|
[[ $queue == yes ]] || return 0
|
||||||
|
|
||||||
cau_notify_enqueue "$urgency" "$title" "$body" "${args[@]}"
|
cau_notify_enqueue "$urgency" "$title" "$body" "${args[@]}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ cau_flatpak_update() {
|
|||||||
fi
|
fi
|
||||||
done < <(cau_human_users)
|
done < <(cau_human_users)
|
||||||
|
|
||||||
if [[ $CFG_CLEAN_CACHE == yes ]]; then
|
# Unused runtimes are the Flatpak equivalent of orphaned packages - this is
|
||||||
|
# removal of installed software, not cache trimming, so it belongs behind
|
||||||
|
# RemoveOrphans rather than CleanCache.
|
||||||
|
if [[ $CFG_REMOVE_ORPHANS == yes ]]; then
|
||||||
cau_run_logged flatpak uninstall --system --unused --noninteractive --assumeyes || true
|
cau_run_logged flatpak uninstall --system --unused --noninteractive --assumeyes || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+22
-1
@@ -133,6 +133,20 @@ cau_pacman_update() {
|
|||||||
cau_info "Running a full system upgrade (pending list unavailable)"
|
cau_info "Running a full system upgrade (pending list unavailable)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Say so before the transaction starts, not after it finishes.
|
||||||
|
#
|
||||||
|
# While an upgrade runs, a shutdown request is refused by logind and the
|
||||||
|
# desktop answers with a polkit password prompt reading "Power off the
|
||||||
|
# system while an application is inhibiting this" - which never mentions
|
||||||
|
# updates and, on a German system, is not even translated. Somebody who was
|
||||||
|
# simply told beforehand does not end up staring at that.
|
||||||
|
if [[ $CFG_NOTIFY_START == yes ]]; then
|
||||||
|
cau_notify_tagged run no normal \
|
||||||
|
"Installing updates" \
|
||||||
|
"%d packages are being updated. Please leave the computer switched on until this is done." \
|
||||||
|
"${CAU_PACMAN_COUNT:-0}"
|
||||||
|
fi
|
||||||
|
|
||||||
mapfile -t flags < <(cau_pacman_flags)
|
mapfile -t flags < <(cau_pacman_flags)
|
||||||
log="$(mktemp)" || return 1
|
log="$(mktemp)" || return 1
|
||||||
|
|
||||||
@@ -254,7 +268,14 @@ cau_pacman_cleanup() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $CFG_CLEAN_CACHE == yes ]] && cau_have paccache; then
|
if [[ $CFG_CLEAN_CACHE == yes ]] && cau_have paccache; then
|
||||||
cau_info "Trimming the package cache"
|
# paccache's dry run reports what a real run would reclaim; logging it
|
||||||
|
# first is the only way to tell afterwards whether trimming is doing
|
||||||
|
# anything, since the real run says little.
|
||||||
|
local reclaim
|
||||||
|
reclaim="$( { paccache -d --nocolor -k"$CFG_KEEP_OLD"; paccache -du --nocolor -k0; } 2>&1 \
|
||||||
|
| grep -oE 'disk space saved: [^)]*' | paste -sd', ' -)"
|
||||||
|
|
||||||
|
cau_info "Trimming the package cache (keeping $CFG_KEEP_OLD old version(s))${reclaim:+ - $reclaim}"
|
||||||
cau_run_logged paccache -r --nocolor -k"$CFG_KEEP_OLD" || cau_warn "paccache -r failed"
|
cau_run_logged paccache -r --nocolor -k"$CFG_KEEP_OLD" || cau_warn "paccache -r failed"
|
||||||
cau_run_logged paccache -ru --nocolor -k0 || cau_warn "paccache -ru failed"
|
cau_run_logged paccache -ru --nocolor -k0 || cau_warn "paccache -ru failed"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in new issue
Block a user