updates
Pending package upgrades (pacman, optionally AUR), install progress and whether a reboot is due.
mantle.updates:configure({ interval = 3600 })
button {
on_click = function() mantle.updates:check() end,
children = {
text {
content = mantle.updates:map(function(updates)
if updates == nil or updates.checking then
return "…"
end
return updates.count > 0 and (updates.count .. " updates") or "up to date"
end),
},
},
}
State
mantle.updates:get() returns UpdatesState, nil before the first push. A field marked ? may be absent.
mantle.updates’s payload.
| Field | Type | Description |
|---|---|---|
aur_error? | string | Why AUR packages are missing: the last check’s AUR query failed, or aur is on with no aur_helper; nil otherwise. packages still holds the repos’ answer. |
aur_helper? | string | AUR helper found at start, "paru" or "yay", or nil; used only once configure sets aur. |
check_error? | string | Why the last check failed, or nil after a success. A check never modifies the system. |
checking | boolean | A check is running. |
consecutive_check_failures | integer | Check failures in a row; a success resets it to 0. |
count | integer | Always #packages. |
install_current_package | string | Package being installed; empty before the first step line. |
install_current_step | integer | 1-based number of the package being installed, e.g. pacman’s (2/5); 0 before the first. |
install_error? | string | Why the package manager could not be run or waited on, or nil. Its own failures are install_exit_code. |
install_exit_code? | integer | Package manager’s exit code for the last install (0 success); nil while running, before one, or when a signal killed it. |
install_finished_at? | integer | Unix seconds when the last install’s process ended, whatever its status; nil while running, before one, or when it failed to spawn. |
install_log | string[] | The last 200 lines of install output, stdout and stderr interleaved, newest last; cleared when an install starts. |
install_total_steps | integer | Packages in the transaction; 0 until the first step line, so draw progress as indeterminate. |
installing | boolean | An install is running; the install_* fields describe the latest run. |
last_successful_check? | integer | Unix seconds of the last successful check (or the checked_at seed), else nil. |
package_manager? | string | Package manager, e.g. "pacman", from the first push, which comes at start; nil when none is supported, and then every action is ignored. |
packages | UpdateCandidate[] | Pending upgrades. A failed check keeps the last good list. |
reboot_required | boolean | /run/mantle-reboot-required exists, watched live. Mantle never writes it; anything you set up may, a pacman hook for example, and /run empties on reboot. |
UpdateCandidate
One installed package with a newer version.
| Field | Type | Description |
|---|---|---|
download_size | integer | Bytes to fetch; 0 when already cached. |
installed_size | integer | Bytes the new version occupies installed; not a delta. |
name | string | Package name. |
new_version | string | Version on offer. |
old_version | string | Installed version. |
repository? | string | Source repository, e.g. "extra" or "aur"; empty in a seeded list that lacks it. |
Actions
Call each as mantle.updates:<action>(arguments...); ? marks an argument you may omit.
| Action | Arguments | Description |
|---|---|---|
check | Checks for upgrades now, even when dormant; ignored while checking. | |
configure | config: UpdatesConfigure | Sets the check schedule and AUR use, and seeds a remembered check. |
install | Runs a full upgrade through pkexec: pacman -Syu --noconfirm, or aur_helper when aur is on; dnf upgrade -y --refresh; apt-get update then apt-get upgrade --with-new-pkgs. Ignored while installing. Does not recheck afterwards. |
UpdatesConfigure
configure’s table. One wrong-typed key drops the whole call.
| Field | Type | Description |
|---|---|---|
aur? | boolean | Also check the AUR and install through aur_helper. Sends every foreign package name to aur.archlinux.org and builds without PKGBUILD review. |
checked_at? | integer | Persisted Unix seconds of the last successful check. Seeds last_successful_check only while that is nil, so a restart need not recheck at once. |
interval | integer | Seconds between scheduled checks, the first at once unless last_successful_check is younger; 0 checks only on check. |
packages? | UpdateCandidate[] | Persisted packages from that check, seeded on the same terms; ignored without checked_at. |
Backend
The first manager that owns / wins, each on PATH at start: pacman with packages in
/var/lib/pacman/local, else apt-get with packages in /var/lib/dpkg/status, else dnf. With
none, package_manager is nil and every action is ignored. The dnf and apt backends are
verified in containers only, untested on a Fedora or Ubuntu install for now.
| Part | pacman (Arch) | dnf (Fedora) | apt (Debian, Ubuntu) |
|---|---|---|---|
| Check | curl fetches each repo database pacman-conf lists into $XDG_RUNTIME_DIR/mantle/pacman, skipping one the mirror reports unchanged. pacman -Qu, -Sp and -Si read it against /var/lib/pacman/local | dnf repoquery --refresh --upgrades, then --installed for old_version; metadata goes to your cache (~/.cache/libdnf5) | apt-get update into $XDG_RUNTIME_DIR/mantle/apt with its hooks cleared, then apt-get -s upgrade --with-new-pkgs and apt-cache show for sizes |
| AUR | With aur = true, pacman -Qm names the foreign packages, one curl POST to the AUR RPC (30 s timeout) covers them, and vercmp orders the versions. aur_helper is paru, else yay | None: aur_helper is nil, aur only sets aur_error | Same as dnf |
| Install | env LC_ALL=C pkexec pacman -Syu --noconfirm, or env LC_ALL=C <aur_helper> -Syu --noconfirm --sudo pkexec with aur on | pkexec env LC_ALL=C dnf upgrade -y --refresh | pkexec env LC_ALL=C DEBIAN_FRONTEND=noninteractive sh -c "apt-get update && apt-get -y ... upgrade --with-new-pkgs"; a changed config file keeps your copy |
| Progress | pacman’s (2/5) upgrading name lines; nothing while downloading, since pacman draws no progress without a tty | [ 4/12] Upgrading name-... lines. The total also counts removals of the old versions and, on dnf5, two setup steps; dnf5 cuts a long name to fit its column | Each Setting up name line against the 4 upgraded, 1 newly installed summary; 0 while downloading |
Every check runs as you and leaves the system’s package databases untouched. pkexec asks the
session’s polkit agent; the polkit rule makes that one approval
per run for wheel users of pacman, and dnf and apt install in one pkexec call, so one prompt.
reboot_required mirrors /run/mantle-reboot-required through an inotify watch on /run, whatever
the manager.
What packages holds differs by manager:
| Field | pacman | dnf | apt |
|---|---|---|---|
repository | The repo, or "aur" | The repo id | The suites carrying the new version, comma-joined: "noble-updates,noble-security" |
download_size | 0 once cached | The package size | The .deb’s size even when cached |
installed_size | From -Si, rounded to ~5 KiB | Exact | Exact to the KiB |
| Left out | IgnorePkg entries | excludepkgs entries | New dependencies the upgrade pulls in; a package whose upgrade would remove another, and Ubuntu’s phased updates not yet offered, are held back, listed and installed by neither. sudo apt full-upgrade takes those |
| Duplicates | None | One entry per architecture when two of one package upgrade | None |
How do I…
Remember the last check across restarts
Save each successful check in a persistent_table, and
send configure only once the file has loaded, so its seed lands before the first scheduled check.
local dir = (os.getenv("HOME") or "") .. "/.local/state/myshell"
local cache = persistent_table { path = dir, name = "updates.json" }
local file = dir .. "/updates.json"
mantle.storage:on_change(function(storage, previous)
local saved = storage.files[file]
if saved and not (previous and previous.files[file]) then
mantle.updates:configure({
interval = 3600,
checked_at = saved.checked_at,
packages = saved.packages,
})
end
end)
mantle.updates:on_change(function(updates, previous)
local at = updates.last_successful_check
if at and at ~= (previous and previous.last_successful_check) then
cache:set("checked_at", at)
cache:set("packages", updates.packages)
end
end)
return text {
content = mantle.updates:map(function(updates)
return updates and tostring(updates.count) or ""
end),
}
Gotchas
| Trap | Fix |
|---|---|
packages still lists everything after a successful install | install does not recheck. Invoke check from on_change when installing falls with install_exit_code == 0 |
install ends at once with install_exit_code 127 | No polkit agent answered pkexec. Read mantle.polkit and draw its prompt (polkit), or run another agent. 126 means the prompt was dismissed |