# `scoutfs-dnf-plugin`

A small dnf plugin that **automatically installs the matching
`kmod-scoutfs-<sanitized-kver>` after a kernel update**. Without it,
operators have to re-run `setup-scoutfs.sh` (or manually `dnf
install` the right kmod) after every kernel upgrade that breaks
kABI compatibility.

It's installed by default (via `Recommends:` on both `scoutfs-X.Y`
and `scoutfs-X.Y-dkms` metapackages), and is **fully removable**.
This doc describes what it does, when you might want to remove it,
and how.

---

## What it does

On every dnf transaction, the plugin's `transaction()` hook fires
once the rpm DB commit completes. It:

1. Walks the just-completed transaction looking for installed/upgraded
   `kernel-core` (or `kernel`) packages.
2. For each new kernel version, computes the corresponding scoutfs
   kmod package name using the same sanitization rule as the
   build:
   `5.14.0-611.45.1.el9_7.x86_64` → `kmod-scoutfs-5.14.0_611.45.1.el9_7`.
3. Skips entries already installed (`rpm -q --quiet`).
4. Appends remaining entries to `/var/lib/scoutfs/pending-kmods.txt`.
5. Fires `systemctl start --no-block scoutfs-install-pending.service`
   to drain the queue **after** the parent dnf process exits and
   releases the rpm DB lock.

The systemd unit (`scoutfs-install-pending.service`, oneshot, no
timer — fired on demand) reads the queue, dedupes, and runs
`dnf -q -y install <kmod>` for each. Failures are logged to the
journal under tag `scoutfs-dnf-plugin`; the plugin doesn't retry —
operators inspecting the journal can re-run setup-scoutfs.sh or
install manually.

### Why a separate process

dnf plugins fire while the parent `dnf` process still holds the
rpm DB lock. Nesting another dnf transaction inside the hook would
deadlock. Dispatching the install to a systemd unit lets the parent
dnf finish first, release the lock, and let the unit grab it cleanly.

### What it does NOT do

- Does NOT run on a timer. It only fires when dnf does a transaction
  that includes a kernel install/upgrade.
- Does NOT touch existing kmods. Only installs missing ones.
- Does NOT retry on failure. Errors land in the journal; remediation
  is manual.
- Does NOT pin/version-lock anything. The kmods follow the metapackage's
  Requires.
- Does NOT do anything for DKMS hosts in practice — DKMS already
  rebuilds scoutfs.ko on kernel install via its own scriptlets.
  The plugin just no-ops on DKMS systems (kmod-scoutfs-<sanitized>
  isn't published as a separate package they need).

---

## Inspecting it in action

Trigger a kernel update (or simulate one):

```sh
sudo dnf update kernel-core
# ... transaction completes ...
journalctl -t scoutfs-dnf-plugin --since '5 minutes ago'
# Expect:
#   scoutfs-dnf-plugin: queued 1 kmod(s) for install: kmod-scoutfs-5.14.0_611.50.1.el9_7
#   scoutfs-dnf-plugin: installed kmod-scoutfs-5.14.0_611.50.1.el9_7
```

Check the systemd unit state:

```sh
systemctl status scoutfs-install-pending.service
# Status: oneshot; exited cleanly on last run.
```

Read the queue file (usually empty unless mid-install):

```sh
cat /var/lib/scoutfs/pending-kmods.txt 2>/dev/null || echo "(queue empty)"
```

---

## Disabling it

### Temporary disable (single dnf invocation)

```sh
sudo dnf --setopt=plugins.scoutfs.enabled=0 upgrade
```

### Persistent disable without removing the package

Edit `/etc/dnf/plugins/scoutfs.conf`:

```ini
[main]
enabled=0
```

The file is `%config(noreplace)`, so this edit survives package
upgrades.

### Remove it entirely

```sh
sudo dnf remove scoutfs-dnf-plugin
```

The metapackage Recommends (not Requires) the plugin, so removing
it doesn't drag scoutfs itself out. After removal:

- `/etc/dnf/plugins/scoutfs.conf` goes away.
- The Python module is uninstalled.
- The systemd unit is removed.
- The pending-kmods queue dir at `/var/lib/scoutfs/` stays around
  (it's marked `%dir` in the spec — only the contents disappear if
  the queue is empty, the directory itself persists).

To reinstall later:

```sh
sudo dnf install scoutfs-dnf-plugin
```

Or just re-run `setup-scoutfs.sh`, which pulls it back in via
the metapackage's Recommends.

---

## When you might want to remove it

- **Air-gapped hosts** where the plugin can never actually fetch
  RPMs. The dnf failure logged in the journal would be noise.
- **Policy environments** where every dnf install must be approved
  manually (the plugin would silently install kmods).
- **Test environments** where you want to observe what happens
  without a kmod for a new kernel (the manual error from
  `setup-scoutfs.sh` is more visible).
- **DKMS-only hosts**: harmless but redundant. DKMS hooks already
  rebuild scoutfs.ko on kernel install.

---

## Operational notes

### Queue file persistence

The queue at `/var/lib/scoutfs/pending-kmods.txt` is appended to
on every kernel-install dnf transaction and atomically renamed
into `pending-kmods.txt.processing.$$` by the systemd unit before
it processes entries. If the install service is killed mid-flight
(SIGKILL, host crash), the `.processing.$$` file remains and the
next service invocation may double-attempt those installs —
`rpm -q --quiet` short-circuits the wasted call.

### Journal noise

The plugin logs at INFO level to the dnf logger and via `logger -t
scoutfs-dnf-plugin` to syslog. To filter:

```sh
journalctl -t scoutfs-dnf-plugin -f          # tail live
journalctl -t scoutfs-dnf-plugin --since today
```

Disable verbose dnf-plugin logging by setting the dnf log level
to WARN in `/etc/dnf/dnf.conf` if the plugin's INFO lines are
unwanted (this also suppresses other plugins' info logs).

### Failure modes the plugin doesn't cover

- Network down when the systemd unit fires. dnf install fails;
  unit exits non-zero; queue is consumed (single attempt). Operator
  gets one journal line. Manual `dnf install <kmod>` later recovers.
- License key rotation (rare). The plugin uses the
  `/etc/yum.repos.d/scoutfs-elN.repo` config, which carries the key.
  If the key is invalid, dnf install fails; same outcome as above.
- Kernel installed in a chroot or container without systemd.
  The `systemctl start --no-block` in the plugin returns non-zero;
  the queue file fills but never drains. Harmless — just doesn't help.
