feat(aur): list and upgrade AUR packages through paru or yay on Arch

On the pacman backend, an installed AUR helper (paru, else yay;
--aur-helper=paru|yay|none, --no-aur) adds the AUR updates to the review
list. The helper never runs as root: when the tool is root through sudo it
runs as SUDO_USER (runuser -u, else sudo -u, with that user's HOME, from
that home); unprivileged it runs as is; a root login gets no AUR, with a
warning. The banner names the helper ("+ AUR via paru").

- listing: <helper> -Qua (also with --no-refresh), rows with repository
  "aur"; [ignored] is shown [held]; a failing -Qua warns and the session
  goes on with the repository rows
- every row has a marker column after the number: "*" for AUR rows, a
  space otherwise (on every backend)
- N?? merges pacman -Qi with <helper> -Sia; N?, N+, Nx stay with pacman;
  N- excludes through the helper's --ignore
- <enter>: the repository step exactly as before, then <helper> -Sua
  --ignore <excluded> as the invoking user (--noconfirm only when stdin is
  not a terminal); pacman is skipped when only AUR rows are left; a helper
  failure is reported and its status returned; --dry-run prints it
- other backends never run a helper; --aur-helper is ignored with a warning

paru 2.1.0 and yay 13.0.1 formats verified in discovery/pacman.md section
15 (proof: discovery/pacman/aur.sh). Unit tests replay the recorded output
(fake paru, yay, runuser, getent); the archlinux E2E run installs the
foreign su-dummy-aur and drives a fake paru through a fake sudo built on
passwordless su for wheel.
This commit is contained in:
bryanpedini committed 2026-10-10 15:06:18 +02:00
1 parent 875c8294bf
commit def537af58
39 files changed
+1903 -115

No files matched your search

+66 -7
View File
@@ -20,13 +20,44 @@ Order (first match wins, overridable with `--backend=<name>`):
| backend | detected by | notes |
|----------|-----------------------------|--------------------------------------------|
| `pacman` | `pacman` in PATH | `pman` present => label shown as `pman`, same commands; honor pacman `IgnorePkg` (`pacman -Qu` marks them `[ignored]`); the refresh syncs a throwaway `--dbpath` copy; at `<enter>` its reviewed databases are installed and applied with `pacman -Su` |
| `pacman` | `pacman` in PATH | `pman` present => label shown as `pman`, same commands; honor pacman `IgnorePkg` (`pacman -Qu` marks them `[ignored]`); the refresh syncs a throwaway `--dbpath` copy; at `<enter>` its reviewed databases are installed and applied with `pacman -Su`; AUR packages through `paru` (else `yay`) when installed, see "AUR" below |
| `apt` | `apt-get` in PATH | Debian/Ubuntu |
| `dnf` | `dnf` in PATH (else `yum`) | dnf4 (EL9) and dnf5 (EL10) differences hidden in the backend |
| `zypper` | `zypper` in PATH | `dist-upgrade` on Tumbleweed (ID=opensuse-tumbleweed), `update` on Leap; on Tumbleweed the review list is the `dist-upgrade --dry-run` plan |
Each backend implements the same function set (see "Backend contract").
### AUR (pacman backend only)
| helper | detected by | overridden by |
|--------|-------------|---------------|
| `paru` | `paru` in PATH (first) | `--aur-helper=paru` (an error when it is not in PATH) |
| `yay` | `yay` in PATH (when there is no paru) | `--aur-helper=yay` (idem) |
| none | neither installed | `--aur-helper=none`, `--no-aur` |
The helper is detected with the backend (before any sudo re-exec). The banner then reads
`Backend: pman (Arch Linux) + AUR via paru`. The other backends never look a helper up:
`--aur-helper=paru|yay` is accepted there and ignored with a warning (`--aur-helper=paru is
ignored: AUR helpers only apply to the pacman backend ...`), `--no-aur`/`--aur-helper=none` silently.
The helper never runs as root (paru refuses to install AUR packages as root, makepkg refuses to
build as root). Decided once, after the root handling (the banner decides it; the listing runs in
subshells):
| we are | the helper runs |
|--------|-----------------|
| not root (`list --no-refresh`, `info`, `show`, `--dry-run`) | as we are: `paru ARGS` |
| root, `SUDO_USER` names a user whose uid (`getent passwd`, else `/etc/passwd`) is not 0 - our sudo re-exec, or any sudo | as that user, from that user's home: `runuser -u "$SUDO_USER" -- env HOME=<home> paru ARGS`, `sudo -u "$SUDO_USER" -- env HOME=<home> ...` when there is no runuser |
| root without `SUDO_USER` (a root login), `SUDO_USER` root or unknown, neither runuser nor sudo | not at all: no AUR row, one warning (`aur: paru is never run as root and there is no invoking user (SUDO_USER is not set): AUR packages are neither listed nor upgraded ...`) |
Helper commands (formats verified in `discovery/pacman.md` section 15, paru 2.1.0 and yay 13.0.1):
| need | command | notes |
|------|---------|-------|
| list | `<helper> -Qua` | `name old -> new`, then ` [ignored]` (paru; yay leaves `IgnorePkg` packages out) or yay's age (` [122d13h]`, ignored); devel packages show `latest-commit` as the new version; escape sequences and other lines are dropped. Exit 1 with nothing on stdout and stderr = nothing to upgrade; any other failure: the helper's stderr lines as warnings, then `aur: <helper> -Qua failed, AUR packages not listed`, and no AUR row. Asked on every run, also with `--no-refresh` (the helper always compares with the AUR RPC) |
| `N??` candidate | `<helper> -Sia PKG` | for a package in no repository (`pacman -Sl`); paru's record aligns its keys like `pacman -Qi`, yay's are wider (re-padded by the merge) |
| upgrade | `<helper> -Sua [--noconfirm] [--ignore a,b]` | see "After `<enter>`" |
## Statefile
Directory: `/var/lib/skeptical-updater/` (override: `SKEPTICAL_UPDATER_STATE_DIR`).
@@ -85,9 +116,9 @@ Backend: apt (Debian GNU/Linux 12)
Refreshing package lists... done.
12 upgradable, 9 trusted (hidden, use --all to show), 3 to review:
[1] nodejs 18.19.0+dfsg-6 -> 18.20.4+dfsg-1 bookworm-security
[2] libnode108 18.19.0+dfsg-6 -> 18.20.4+dfsg-1 bookworm-security
[3] zsh 5.9-4 -> 5.9-4+deb12u1 bookworm
[1] nodejs 18.19.0+dfsg-6 -> 18.20.4+dfsg-1 bookworm-security
[2] libnode108 18.19.0+dfsg-6 -> 18.20.4+dfsg-1 bookworm-security
[3] zsh 5.9-4 -> 5.9-4+deb12u1 bookworm
<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever
> 1?
@@ -108,6 +139,19 @@ Command grammar (one line, whitespace-separated tokens, each `N` is the number i
| `q` | quit without upgrading (exit 0) |
| `h`, `?` | help |
List format: a marker column (`-` excluded, `=` trusted with `--all`, else a space), the number in
brackets right-aligned to the widest one, then one column that is `*` for an AUR row and a space for
every other row (on every backend: the columns stay aligned), a space, then name, old version, `->`,
new version, repository (`aur` for AUR rows) and the flags (`[held]`):
```
[1] su-dummy-a 1.0-1 -> 2.0-1 su-dummy
-[2]* su-dummy-aur 1.0-1 -> 2.0-1 aur
```
On AUR rows `N?`, `N+` and `Nx` act through pacman like on any installed package; `N??` merges
`pacman -Qi` with `<helper> -Sia PKG`; `N-` excludes the row through the helper's `--ignore`.
Numbers are stable for the whole session (removed rows leave a gap, never renumber).
All tokens of a line are validated first: an unknown token or a number that is not listed
prints an error, runs nothing of that line and re-prompts. EOF on stdin aborts without
@@ -142,7 +186,7 @@ ones (dnf5: 16 installed, 15 available), the candidate lines are re-padded to th
| backend | installed record | upstream candidate |
|---------|------------------|--------------------|
| pacman | `pacman -Qi PKG` | `pacman -Si REPO/PKG`, REPO = the first repository `pacman -Sl` lists PKG in (as for the review list) |
| pacman | `pacman -Qi PKG` | `pacman -Si REPO/PKG`, REPO = the first repository `pacman -Sl` lists PKG in (as for the review list); a package in no repository: the AUR record, `<helper> -Sia PKG`, when a helper is in use (should it not merge, `pacman -Qi` is followed by the AUR record with every line marked `> `) |
| apt | `dpkg-query -s PKG` (Status, Conffiles, always the long description; `apt-cache show PKG=<installed>` gives the archive's record when that version is still listed: no Status/Conffiles, short description only); a `Status` of `not-installed`/`config-files` = not installed | `apt-cache show --no-all-versions PKG` |
| dnf | `dnf -q info --installed PKG` | `dnf -q info --upgrades PKG` (installed architecture only; `--available` also lists other architectures and, for an up-to-date package, other builds); not installed: `--available` |
| zypper | - | - (`rpm -qi` and `zypper info` share almost no keys - Version vs version-release, Arch, byte sizes, source names, description layout - and zypper cannot print the installed record by itself: `zypper info -r @System` is refused) |
@@ -188,6 +232,17 @@ After `<enter>`:
plain `pacman -Su` applies the existing databases. Throwaway databases carry their owner's
pid in the name (`/tmp/skeptical-updater-db.<pid>.XXXXXX`); those of runs that are gone are
removed at the start of the next root run.
AUR rows (pacman backend): pacman gets `--ignore` with the excluded repository rows only. When
no repository row is left to upgrade (every one excluded or `[held]`, no pending note) but AUR
rows are, (a)-(c) are skipped ("No repository package to upgrade: pacman is not run, only
paru."). Otherwise (a)-(c) run first, and only after they succeeded (d)
`<helper> -Sua --ignore <every excluded row, repository and AUR>` runs as the invoking user (see
"AUR"), when an AUR row that is neither excluded nor `[held]` remains: with stdin a terminal the
helper gets it and asks as usual (paru's PKGBUILD review, yay's menus, its sudo password);
otherwise `--noconfirm` and stdin from `/dev/null` (yay exits 1 at its first menu on a closed
stdin). A helper failure is reported as `the AUR upgrade failed: paru exited with status N (the
repository packages were upgraded)` and its status is the exit status; `--dry-run` prints the
helper's command line.
4. Restore holds/locks, exit with the package manager's exit code.
Trusted packages are upgraded silently along with everything else; they are only
@@ -215,6 +270,8 @@ options:
--dry-run print native commands instead of running mutating ones
--state-dir=DIR override /var/lib/skeptical-updater
--no-color disable ANSI colours (also honours NO_COLOR, non-tty stdout)
--aur-helper=NAME pacman backend: paru|yay|none (default: paru, else yay, when installed)
--no-aur same as --aur-helper=none
--version, --help
```
@@ -236,7 +293,8 @@ Every backend is a set of bash functions with a common prefix. All output is
```
<be>_refresh # update metadata; non-fatal on failure (warn)
<be>_upgradable # stdout: name<TAB>old<TAB>new<TAB>repo<TAB>flags (flags: "" | "held"; old "-" = new package)
<be>_upgradable # stdout: name<TAB>old<TAB>new<TAB>repo<TAB>flags (flags: space-separated words:
# "held", "aur" (pacman: an AUR row, shown with "*"); old "-" = new package)
# plus "#<note>" lines for pending work that is not a row
<be>_describe PKG # stdout: one-line summary
<be>_full_info PKG # stdout: native full dump (candidate version if available)
@@ -248,7 +306,8 @@ Every backend is a set of bash functions with a common prefix. All output is
<be>_purge_plan PKG # stdout: every package a purge of PKG and its now-unneeded dependencies removes
# (simulation, no cascade); non-zero = refused
<be>_purge PKG... # non-interactive native purge/remove of exactly the planned packages
<be>_upgrade EXCLUDED... # non-interactive native upgrade skipping EXCLUDED (handles hold/lock save+restore)
<be>_upgrade EXCLUDED... # non-interactive native upgrade skipping EXCLUDED (handles hold/lock save+restore;
# pacman: then the AUR helper, see "After <enter>")
<be>_label # "apt" / "pman" / ...
```