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:
1 parent
875c8294bf
commit
def537af58
39 files changed
+1903
-115
No files matched your search
@@ -6,7 +6,9 @@ upgradable packages, lets the user interrogate each one, and only then runs the
|
||||
native upgrade. Packages the user has explicitly acknowledged are remembered in a
|
||||
statefile and never questioned again. It is one file with no runtime dependencies
|
||||
beyond bash >= 4.3 and the distro's package manager, and it never reimplements
|
||||
package management: every action shells out to the native tool.
|
||||
package management: every action shells out to the native tool. On Arch Linux and
|
||||
its derivatives, AUR packages join the review when `paru` or `yay` is installed
|
||||
(see [AUR packages](#aur-packages-arch-linux-paru-or-yay)).
|
||||
|
||||
The specification is [docs/DESIGN.md](docs/DESIGN.md); the per-family command
|
||||
research it is built on lives in [discovery/](discovery/).
|
||||
@@ -27,7 +29,9 @@ on first use, not by `make install`; `make uninstall` leaves it in place.
|
||||
Runtime needs: bash >= 4.3, the package manager, coreutils, sed and grep (plus awk,
|
||||
which the apt and pacman backends use and which those distros always ship). `sudo`
|
||||
is optional: when the tool needs root and you are not root, it re-runs itself
|
||||
through `sudo` with the same arguments, or fails with a clear message.
|
||||
through `sudo` with the same arguments, or fails with a clear message. On the pacman
|
||||
backend `paru` or `yay` is optional too (AUR packages); the helper is run as the user
|
||||
who invoked `sudo`, with `runuser` (util-linux, always there on Arch) or else `sudo -u`.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -51,6 +55,9 @@ options:
|
||||
--dry-run print native commands instead of running mutating ones
|
||||
--state-dir=DIR override /var/lib/skeptical-updater (env: SKEPTICAL_UPDATER_STATE_DIR)
|
||||
--no-color disable ANSI colours (also honours NO_COLOR and non-tty stdout)
|
||||
--aur-helper=NAME pacman backend: list and upgrade AUR packages with paru or yay, or
|
||||
none (default: paru, else yay, when installed; other backends ignore it)
|
||||
--no-aur same as --aur-helper=none
|
||||
-h, --help this help
|
||||
-V, --version print the version
|
||||
```
|
||||
@@ -60,7 +67,8 @@ otherwise the exit status of the package manager's upgrade step.
|
||||
|
||||
What needs root: the metadata refresh, the upgrade, a purge, and writing a state
|
||||
directory you cannot write. `--dry-run`, `list --no-refresh`, `trusted`, `info` and
|
||||
`show` work unprivileged (as long as the state directory is readable).
|
||||
`show` work unprivileged (as long as the state directory is readable); there an AUR
|
||||
helper simply runs as you.
|
||||
|
||||
### Example session
|
||||
|
||||
@@ -73,9 +81,9 @@ Backend: apt (Debian GNU/Linux 12 (bookworm))
|
||||
Refreshing package lists... done.
|
||||
3 upgradable, 0 trusted (hidden, use --all to show), 3 to review:
|
||||
|
||||
[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo
|
||||
[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo
|
||||
[3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]
|
||||
[1] su-dummy-a 1.0 -> 2.0 su-fixture:su-repo
|
||||
[2] su-dummy-b 1.0 -> 2.0 su-fixture:su-repo
|
||||
[3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]
|
||||
|
||||
<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever
|
||||
> 2?
|
||||
@@ -89,7 +97,7 @@ excluded from this run: su-dummy-c
|
||||
> l
|
||||
3 upgradable, 2 trusted (hidden, use --all to show), 1 to review:
|
||||
|
||||
-[3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]
|
||||
-[3] su-dummy-c 1.0 -> 2.0 file:/srv/su-repo ./ [held]
|
||||
>
|
||||
Excluded from this run: su-dummy-c
|
||||
+ apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o DPkg::Lock::Timeout=60 --with-new-pkgs upgrade
|
||||
@@ -115,7 +123,8 @@ upgrade; end of input without one aborts with exit status 2).
|
||||
`[held]` marks packages the package manager will not upgrade on its own: apt
|
||||
holds and "kept back" packages, pacman `IgnorePkg` (`[ignored]` in `pacman -Qu`).
|
||||
dnf `exclude=`/versionlock and zypper locks hide packages from the native listing,
|
||||
so they do not appear at all.
|
||||
so they do not appear at all. The column right after the number is a `*` for an AUR
|
||||
package (pacman backend with paru or yay, see below) and a space on every other row.
|
||||
|
||||
### Command grammar
|
||||
|
||||
@@ -188,6 +197,73 @@ replacements only the pacman transaction (`-Sup`) shows, packages dnf installs t
|
||||
others, packages a Tumbleweed `dist-upgrade` removes. With no row but such work, the list
|
||||
says "0 to review, but ... reports pending changes" and `<enter>` still runs the upgrade.
|
||||
|
||||
### AUR packages (Arch Linux: paru or yay)
|
||||
|
||||
On the pacman backend (Arch and its derivatives, `pman` included) AUR packages are reviewed
|
||||
with the rest when an AUR helper is installed: `paru`, else `yay`. `--aur-helper=paru|yay`
|
||||
picks one (an error if it is not installed), `--aur-helper=none` or `--no-aur` turns the AUR
|
||||
off. The banner says which helper is in use, the AUR rows get a `*` right after the number
|
||||
and `aur` as their repository:
|
||||
|
||||
```
|
||||
$ skeptical-updater (as a normal user: it re-runs itself through sudo)
|
||||
Backend: pacman (Arch Linux) + AUR via paru
|
||||
Refreshing package lists... done.
|
||||
4 upgradable, 0 trusted (hidden, use --all to show), 4 to review:
|
||||
|
||||
[1] su-dummy-a 1.0-1 -> 2.0-1 su-dummy
|
||||
[2]* su-dummy-aur 1.0-1 -> 2.0-1 aur
|
||||
[3] su-dummy-b 1.0-1 -> 2.0-1 su-dummy
|
||||
[4] su-dummy-c 1.0-1 -> 2.0-1 su-dummy
|
||||
|
||||
<enter> upgrade | N? what is it | N?? full info | N- exclude this run | Nx purge now | N+ trust forever
|
||||
> 3-
|
||||
excluded from this run: su-dummy-b
|
||||
>
|
||||
Excluded from this run: su-dummy-b
|
||||
Checking the reviewed transaction (pacman -Sup)... ok.
|
||||
+ cp -p -- /tmp/skeptical-updater-db.4242.AbC123/sync/su-dummy.db /var/lib/pacman/sync/
|
||||
+ pacman -Su --noconfirm --ignore su-dummy-b
|
||||
...
|
||||
+ runuser -u alice -- env HOME=/home/alice paru -Sua --ignore su-dummy-b
|
||||
...
|
||||
Upgrade finished.
|
||||
```
|
||||
|
||||
(`pman` installed: `Backend: pman (Arch Linux) + AUR via paru`.)
|
||||
|
||||
- **Listing**: `paru -Qua` / `yay -Qua` (`name old -> new`). The helper asks the AUR every time,
|
||||
so `--no-refresh` lists the AUR rows too; when it fails (no network, the AUR is down) its
|
||||
message is shown with `aur: paru -Qua failed, AUR packages not listed` and the session goes on
|
||||
with the repository rows. A package in `IgnorePkg` is listed `[held]` by paru and left out by
|
||||
yay. Devel (`-git`) packages are listed when the helper checks them (paru does by default, yay
|
||||
with `--devel`), with `latest-commit` as the new version. A name a repository has wins over an
|
||||
AUR row (a package that moved from the AUR into a repository).
|
||||
- **`N?`, `N+`, `Nx`** work as for any installed package, through pacman (`pacman -Qi`,
|
||||
the statefile with backend `pacman`, `pacman -Rns`). **`N??`** merges `pacman -Qi` with the
|
||||
AUR record (`paru -Sia` / `yay -Sia`), `>` lines as usual: `> Version`, `> Repository : aur`,
|
||||
`> Maintainer`, `> Votes`, ... (`show PKG` does the same for a package in no repository).
|
||||
- **`N-`** excludes an AUR row through the helper's `--ignore`.
|
||||
- **`<enter>`** upgrades the repository packages first, exactly as without the AUR (pre-check,
|
||||
the reviewed databases, `pacman -Su`), then, if AUR rows are left to upgrade, runs
|
||||
`paru -Sua --ignore <every excluded row>` (or yay). The helper runs only after a successful
|
||||
repository step; if it fails, its exit status is the tool's (`the AUR upgrade failed: paru
|
||||
exited with status 1 (the repository packages were upgraded)`). When no repository row is
|
||||
left to upgrade, pacman is not run at all, only the helper. `--dry-run` prints the helper's
|
||||
command line.
|
||||
- **Which user**: AUR helpers must not run as root (paru refuses to build as root, makepkg too).
|
||||
When the tool is root because of its own `sudo` re-exec (or any `sudo`: `SUDO_USER` names a
|
||||
non-root user), every helper command runs as that user: `runuser -u "$SUDO_USER" -- env
|
||||
HOME=<that user's home> paru ...` from that home (`sudo -u` when `runuser` is missing). Run
|
||||
unprivileged (`list --no-refresh`, `info`, `show`, `--dry-run`) the helper runs as you. A root
|
||||
login without `SUDO_USER` gets no AUR, with a warning (`aur: paru is never run as root and
|
||||
there is no invoking user ...`).
|
||||
- **PKGBUILD review**: when stdin is a terminal the helper gets it and asks as usual - paru's
|
||||
PKGBUILD review, yay's diff menu, and `sudo` asking for your password for its `pacman -U` if
|
||||
the sudo timestamp has expired meanwhile. When stdin is not a terminal (a piped session) the
|
||||
helper runs with `--noconfirm`: nothing is reviewed, and its `sudo pacman -U` must work without
|
||||
a password.
|
||||
|
||||
### Line editing and history
|
||||
|
||||
When stdin is a terminal, the prompt is read with bash's readline and its defaults: Left/Right
|
||||
@@ -263,12 +339,14 @@ Permanent holds belong to the package manager (`apt-mark hold`, `IgnorePkg`,
|
||||
|---------|---------|---------|-------------------|-------|
|
||||
| apt | `apt-get update -qq -o APT::Update::Error-Mode=any` | `apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o DPkg::Lock::Timeout=60 --with-new-pkgs upgrade` | temporary `apt-mark hold` of the excluded packages that are *not already held*, journalled in `holds.added` before holding; afterwards exactly those are released (also on failure, Ctrl-C, TERM, HUP, QUIT, a second signal during the restore, or a closed stdout; a journal left by `kill -9` is replayed at the start of the next root run). Holds you placed are never touched. `apt-mark` ignores `DPkg::Lock::Timeout`, so it is retried for up to 60 s while dpkg's lock is busy. | plan: `apt-get -s purge --auto-remove PKG` minus what `apt-get -s autoremove` already listed (`--auto-remove` is system-wide), then `apt-get -y purge` of exactly that set: PKG and the dependencies this purge orphans; no `autoremove` |
|
||||
| pacman / pman | `pacman -Sy --dbpath <throwaway copy>` (with `local` linked to the real local database, seeded with the system's `*.db` and `*.db.sig`): listing, `q`, end of input and `--dry-run` never touch `/var/lib/pacman/sync`; errors pacman prints about it are shown | check `pacman -Sup --noconfirm --ignore ... --dbpath <throwaway>` (on failure: nothing changed, its exit status), install the reviewed `*.db`/`*.db.sig` into `/var/lib/pacman/sync` under `db.lck`, all-or-nothing, never over a newer system database (exit 2), then `pacman -Su --noconfirm` - exactly the reviewed metadata, no second sync; with `--no-refresh` plain `pacman -Su` | `--ignore a,b` | plan: `pacman -Rs --print`; then `pacman -Rns --noconfirm -- ...` |
|
||||
| AUR (pacman backend, paru or yay) | none needed: `-Qua` asks the AUR every time | after the repository step: `paru -Sua` (`--noconfirm` when stdin is not a terminal), as the user who invoked sudo | the helper's `--ignore a,b` (all excluded rows) | through pacman, like any installed package |
|
||||
| dnf / yum | `dnf -q --refresh --setopt=skip_if_unavailable=0 makecache` | `dnf -y --setopt=skip_if_unavailable=0 --setopt=*.metadata_expire=-1 upgrade` (the reviewed metadata; the repo glob is needed because a repo's own `metadata_expire` overrides the main one) | `--exclude=a,b` (adds to `exclude=` from the configuration) | plan: `dnf --assumeno remove PKG`; then `dnf -y remove ...` |
|
||||
| zypper | `zypper --non-interactive --quiet refresh` | `zypper --non-interactive --no-refresh update` (Leap/SLE) or `dist-upgrade` (Tumbleweed/Slowroll, by `ID=` in os-release) | locks in a *private* `ZYPP_CONF`: the effective configuration (main zypp.conf plus the `zypp.conf.d` drop-ins of `/usr/etc` and `/etc`) with `locksfile.path` pointing at a copy of the effective lock file (also when a drop-in sets it), plus `addlock` of the exclusions; deleted afterwards: the real lock file is never written | plan: `zypper --xmlout remove --dry-run --clean-deps PKG`; then `zypper --non-interactive --no-refresh remove --clean-deps ...` |
|
||||
|
||||
Every package-manager call runs with `LC_ALL=C` (and `DEBIAN_FRONTEND=noninteractive`;
|
||||
`COLUMNS=0` for pacman), with stdin from `/dev/null` so that it can never eat the
|
||||
commands you pipe in. The upgradable list comes from `apt-get -s --with-new-pkgs
|
||||
commands you pipe in - except an AUR helper's upgrade on a terminal, which gets the terminal for
|
||||
its own prompts. The upgradable list comes from `apt-get -s --with-new-pkgs
|
||||
upgrade` (Inst lines + the "kept back" block), `pacman -Qu` joined with `pacman -Sl`,
|
||||
`dnf -q check-update` (+ `rpm -qa` for the installed versions) and
|
||||
`zypper --xmlout list-updates`, or on Tumbleweed (as root) the real plan
|
||||
@@ -303,8 +381,8 @@ runs a subset of distros; `E2E_FAIL_FAST=1` stops at the first failing distro.
|
||||
|
||||
No root, no network, no real package manager. The applet runs with a hermetic
|
||||
`PATH`: the fakes in `tests/unit/fakes/<backend>/` (`apt-get`, `apt-cache`,
|
||||
`dpkg-query`, `apt-mark`, `pacman`, `pman`, `dnf`, `yum`, `rpm`, `zypper`, `sudo`)
|
||||
in front of symlinks to the few system tools it needs. The fakes replay the real
|
||||
`dpkg-query`, `apt-mark`, `pacman`, `pman`, `dnf`, `yum`, `rpm`, `zypper`, `sudo`, and for the
|
||||
AUR `paru`, `yay`, `runuser`, `getent`) in front of symlinks to the few system tools it needs. The fakes replay the real
|
||||
outputs recorded in `discovery/*.md` (data in `tests/unit/data/`), keep their state
|
||||
(holds, purged packages) under the test's temp dir and log every invocation, so the
|
||||
tests assert the exact commands issued. Covered: CLI parsing and exit codes, backend
|
||||
@@ -320,9 +398,14 @@ database install / `-Su` sequence and stale throwaway databases, statefile locki
|
||||
the `mkdir` lock fallback (EPERM owner alive, no busy loop), an inaccessible state directory, the
|
||||
sudo re-exec of the file itself and its loop guard), the `N??` merge on recorded pacman, apt, dnf4
|
||||
and dnf5 records (changed, identical, installed-only, candidate-only and multi-line fields, apt's
|
||||
short candidate descriptions, dnf5's padding, the fallbacks), and the prompt on a real pty (`script`):
|
||||
cursor editing, Up/Down history recall, the stashed line, the bell at both ends, no history file.
|
||||
About 30 seconds.
|
||||
short candidate descriptions, dnf5's padding, the fallbacks), the prompt on a real pty (`script`):
|
||||
cursor editing, Up/Down history recall, the stashed line, the bell at both ends, no history file,
|
||||
and the AUR (`tests/unit/aur.bats`, on recorded paru 2.1.0 and yay 13.0.1 output): helper detection
|
||||
and overrides, the user the helper runs as (`runuser -u` / `sudo -u` with `SUDO_USER`, unprivileged,
|
||||
a root login), the `*` column on pacman and apt rows, `-Qua` parsing (`[ignored]`, yay's age suffix
|
||||
and root warning, devel packages, failures), `N?`/`N??`/`N-`/`N+`/`Nx` on AUR rows, the upgrade order
|
||||
and arguments, an AUR-only upgrade, a failing helper, `--dry-run`, and `--noconfirm` only without a
|
||||
terminal. About 35 seconds.
|
||||
|
||||
### End-to-end tests (`tests/e2e/`)
|
||||
|
||||
@@ -354,6 +437,20 @@ review is not installed), apt
|
||||
holds waiting for a busy dpkg lock, zypper honouring a lock file set in a drop-in and applying
|
||||
the reviewed metadata although a newer package appeared after the review.
|
||||
|
||||
`tests/e2e/aur.bats` covers the AUR. On archlinux the fixture (`FIXTURE_AUR=1`) also installs
|
||||
`su-dummy-aur` 1.0 with `pacman -U` (a foreign package: in no repository) and keeps its 2.0
|
||||
package file, creates the user `su-tester` and makes `su` passwordless for the `wheel` group;
|
||||
the tests install `fixtures/fake-paru.sh` as `/usr/local/bin/paru` (it emulates paru's `-Qua`,
|
||||
`-Sia` and `-Sua` for that package, offline, refusing to install as root like paru) and
|
||||
`fixtures/fake-sudo.sh` as `/usr/local/bin/sudo` (the image has no sudo: `su`, with `SUDO_USER`
|
||||
set like sudo does). The applet runs as `su-tester` and re-executes itself through that sudo, or
|
||||
as root with `SUDO_USER=su-tester`: the `*` row with repository `aur`, paru running as su-tester
|
||||
with its HOME, `N?` and `N??` on it, `N-` keeping it at 1.0 while the repository packages upgrade,
|
||||
a plain `<enter>` upgrading it to 2.0 after them, an AUR-only upgrade that leaves pacman alone,
|
||||
`--dry-run`, an unprivileged `list --no-refresh`, a root login (warning, no AUR) and `--no-aur`.
|
||||
On the other distros it checks the space column and that no AUR helper ever runs, even with
|
||||
`--aur-helper=paru` (ignored with a warning).
|
||||
|
||||
The matrix (`tests/docker/images.txt`):
|
||||
|
||||
| label | image | backend |
|
||||
@@ -442,6 +539,26 @@ From the discovery reports (`discovery/*.md`), all reproduced there:
|
||||
the current closures of all your trusted packages wholesale. A package shared by
|
||||
several trusted roots is recorded under the first one only, so `untrust` of that
|
||||
root drops it until the next `refresh`.
|
||||
- **AUR** (pacman backend with paru or yay):
|
||||
- The helper is run as the user named by `SUDO_USER` only: a root login (no `SUDO_USER`, or
|
||||
`doas`, `run0`, `pkexec`) lists and upgrades no AUR package, with a warning.
|
||||
- A piped session upgrades AUR packages with `--noconfirm`: no PKGBUILD review, and the
|
||||
helper's own `sudo pacman -U` must not ask for a password (NOPASSWD, or a sudo timestamp that
|
||||
is still valid). Review AUR updates on a terminal, or exclude their rows. On a terminal the
|
||||
helper may ask for your password again if its sudo timestamp expired during a long review.
|
||||
- The AUR listing needs the network on every run (also with `--no-refresh`); without it the
|
||||
session goes on with the repository rows (warning). `N??` on an AUR row asks the AUR too.
|
||||
- The helper decides what is foreign from the system's sync database, not from the reviewed
|
||||
throwaway copy; a package that moved from the AUR into a repository is listed from the
|
||||
repository (its AUR row is dropped).
|
||||
- What the helper reports is what you get: yay leaves `IgnorePkg` packages out (paru lists them
|
||||
`[held]`), devel packages are listed only when the helper checks them (paru by default, yay
|
||||
with `--devel`) and show `latest-commit` as the new version; the helper's own configuration
|
||||
(`paru.conf`, `yay --save`) applies to its upgrade.
|
||||
- The AUR upgrade is one helper run after `pacman -Su`: a helper failure leaves the repository
|
||||
packages upgraded (reported, its exit status returned). The E2E suite drives a fake paru
|
||||
(offline); the formats of real paru 2.1.0 and yay 13.0.1 are verified in
|
||||
`discovery/pacman.md` section 15.
|
||||
- The upgradable lists show what the native listing shows: new dependencies are rows on apt
|
||||
and on Tumbleweed (`-` as the old version); on pacman and dnf new dependencies,
|
||||
replacements and obsoletes only appear as a note, and removals only on Tumbleweed (note);
|
||||
|
||||
Reference in new issue
Block a user