Skip to content

fettle 1.16.0

Choose a tag to compare

@github-actions github-actions released this 26 Aug 22:21
· 17 commits to main since this release

[1.16.0] — the audit stops looking in the wrong place, and stops hanging

Four fixes since 1.12.0, and three of them are the same bug wearing different clothes:
software that belongs to a person was being checked as though it belonged to the
machine
.
A maintenance run is usually root, because most of it has to be — and at that
moment the personal half of the host goes quiet.

Every one was found by reading real run logs on a real workstation, not by re-reading code.

version what had been happening
1.13.0 GNOME extensions reported NOT audited for a week straight, on a desktop with 24 of them working. gnome-extensions asks the session bus, and sudo had discarded the address.
1.14.0 Four of six audit providers printed their coverage sentence and nothing else — "examined 24 and cleared them all" looked exactly like "never ran".
1.15.0 11 rootless podman images had never once been audited. Not an error: as root, podman answers from root's store, which was empty. No report fettle had ever written mentioned podman. --user flatpak apps were invisible the same way.
1.16.0 A stopped snapd made fettle hang forever-c, -P and -a, --dry-run included.

The distinction that hid three of them

Being in fettle's no-root set does not mean an action executes unprivileged. It means
it does not elevate on its own. Run it inside -a and the process has already re-exec'd
under sudo for the mutating half, so everything after that is root — including
pkg-audit, which reads three things that live in your account.

Those three are now asked as the invoking user regardless of how the run started. podman is
asked twice, once per store, because running containers as root is ordinary on a server
and picking one identity would simply move the blind spot. docker is deliberately left
alone: it is one system daemon behind a group-owned socket that root can always reach and an
ordinary user often cannot, so dropping privileges there would break it.

The measure that matters: an elevated fettle -P and an unprivileged one now return the
same findings.
The audit describes the machine, not the way it was launched.

What you will notice

Every provider now states what it looked at, so a clean result stops being silent:

[gnome]     24 extensions examined — all traceable to a package; 5 enabled
              enabled and running inside gnome-shell (5 of 24): …
[container] 22 container images examined — across docker, podman, podman(paulda)
[snap]      nothing to examine — no snaps installed

Four outcomes stay distinct, because collapsing any two is how an audit starts lying: not
installed
· installed but nothing to examine · examined N, all clean · could not
look
.

Upgrading from 1.12.0 is a drop-in replacement — no configuration changes, no report
format changes. You may see findings you have not seen before, on a machine that has not
changed: those were always there.

Honest gaps

  • The flatpak fix is unverified end to end. The host it was written on has no flatpak
    apps to observe the difference against. The code path is identical to podman's, which
    is verified — but identical-looking is not verified.
  • The snap probe's healthy-path latency is unmeasured; its timeouts are conservative
    choices, not fitted ones.

The snapd hang in detail

Every bug fixed this week was fettle giving a wrong answer. This one made fettle
stop responding entirely, and it was live on the reporting host.

Found by accident: the test suite stopped returning while verifying an unrelated fix.
A stack dump rather than a guess showed a real snap command blocked in
subprocess.communicate:

subprocess.run → command.run → _prune_disabled_snaps → clean_caches → _clean

Reproduced outside fettle entirely, on a host where snapd had been deliberately
disabled:

/usr/bin/snap present, so fettle uses it
snapd.service / snapd.socket inactive (dead), service disabled
snap list, snap list --all, snap version never return

Not an exotic configuration. On Arch and Manjaro snapd ships preset: disabled, so
anyone who installs it and never runs systemctl enable --now snapd.socket has a
permanently hanging snap binary — and had a permanently hanging fettle. -c, -P and
-a all wedged, --dry-run included, because the snap inventory is a read-only query
and read-only queries deliberately bypass the dry-run gate.

The obvious probe is wrong, and was measured before being built on

/run/snapd.socket still exists while snapd is disabled — a stale file left behind
from the last time the service ran. "Does the socket exist?" therefore answers yes on
exactly the host where snap does not work. A plausible-sounding fix that does nothing.

Two parts

command.run gained an opt-in timeout=. There is no default and that is
deliberate: pacman -Syu legitimately runs for twenty minutes and rpm -Va for several,
so a blanket limit would kill the very commands fettle exists to run. A timeout returns
Proc(124) — the status GNU timeout uses, so callers match a convention rather than an
invented number — keeps whatever partial output the tool managed first, and never raises.

A cached util.snap_ready() probe asks snap version under a short clock, once per
run. Every snap call site gates on it:

action before after
clean hung forever skips the prune, warns why
pkg-audit hung forever UNVERIFIABLEsnaps were NOT audited
update hung forever skips the refresh, warns why

examined deliberately stays None for the audit: "could not look" is not "examined
zero"
, and recording it as an empty examination would put this host in the same bucket
as one with no snaps at all — the exact false clean this model exists to prevent.

Measured after

fettle -c --dry-run completes in 5.2 seconds where it previously never returned.
fettle -P reports snaps were NOT audited — distinct from the nothing to examine — no
snaps installed
it correctly printed that same morning, while snapd was still alive.

Honest gap: the healthy-path latency is unmeasured, because no working snapd was
available to time against. 5s (probe) and 30s (inventory) are conservative choices, not
fitted ones. Erring short reports "could not look", which is the safe direction; erring
long is what the old behaviour did, forever.

A test-isolation lesson came with it

The probe caches per process — right in production, where a wedged host would otherwise
pay the timeout at three separate call sites. It also leaks between tests: one test that
resolved it to False left every later snap test quietly skipping its own subject,
passing in isolation and failing in the suite. Reset by an autouse fixture in
conftest.py. Twenty-two test files also had their command.run fakes widened to accept
the new keyword — kept explicit rather than **kwargs, because a fake that silently
swallows new arguments cannot catch a call site that forgot to pass one.


What to download

file for
fettle_<version>_all.deb Debian, Ubuntu, and derivatives
fettle-<version>-1.noarch.rpm RHEL, Rocky, AlmaLinux, Fedora
fettle-<version>-1-any.pkg.tar.zst Arch, Manjaro, EndeavourOS
fettle-<version>-zipapp.tar.gz / .zip anything else — runs on any Linux with python 3.11+
fettle.pyz the zipapp on its own, if you do not want the archive

fettle is pure standard library: it needs python 3.11 or newer and nothing else.
On RHEL/Rocky/Alma 9 and Ubuntu 22.04 the system python3 is older than that, so the
packages pull in a suitable interpreter and the launcher finds it for you.

Verifying what you downloaded

sha256sum -c SHA256SUMS --ignore-missing