LIBDNF 5 plugins and Etckeeper

Posted on 21 April 2025
Table of Contents:

When DNF package manager 5 replaced DNF 4 in Fedora 41, I noticed that integration between Etckeeper and the package manager stopped working. I searched bugzilla and found that the problem has been already reported as Fedora BZ 2326283 and that there was nobody working on it so far. Since Etckeeper is important part of my setup, I postponed upgrade to Fedora 41 on my main machine, and started looking into how DNF 5 plugin API differs from the previous version, and how could one get the Etckeeper integration working again.

What does the Etckeeper plugin actually do?

The upstream python source code of the plugin which works with version <= 4 of DNF shows that not much is needed (Fedora patches that a bit to improve error checking and reporting though). It runs etckeeper pre-install command before the transaction, and if this fails it raises dnf.exceptions.Error exception so that DNF won’t continue with it. This is useful when you use AVOID_COMMIT_BEFORE_INSTALL=1 in your etckeeper configuration to make sure DNF refuses to install (or uninstall) anything until you manually commit any uncommitted changes in /etc directory. And in a similar fashion when if the transaction finishes, it runs etckeeper post-install command. That’s all.

What has changed in DNF 5?

Quick review of DNF 5 documetation about writing plugins reveals that now the plugins needs to be implemented in C++ only, since the whole project is implemented in C++ and it’s no longer acceptable for DNF to depend on Python or any other runtime for it’s functionality. And it makes sense: this assures that the minimal system installations are small and easier to maintain (think about minimal buildroots or container images). That said, it also means that the Etckeeper plugin will need to implemented again from scratch.

How could be the Etckeeper LIBDNF 5 plugin implemented?

To create a similar plugin for DNF 5, one need to use LIBDNF5 Plugin API, which allows to implement callbacks at particular breakpoints in DNF5 workflow.

When I wanted to check what plugins already exist to better understand how it all works, I noticed that list of LIBDNF5 Plugins is not exactly large. Fortunately one of them, so called Actions Plugin, looked like something I can try to use to implement the functionality I need without writing my own plugin.

Using Actions Plugin for Etckeeper integration

The Actions Plugin reads so called “action” files in /etc/dnf/libdnf5-plugins/actions.d/ directory, and executes hooks defined in them. Since I need to execute etckeeper pre or post install commands at given breakpoints, this seems ideal.

My first attempt looked like this:

# cat /etc/dnf/libdnf5-plugins/actions.d/etckeeper.actions
pre_transaction::::etckeeper pre-install
post_transaction::::etckeeper post-install

And for a positive use case it worked already good enough. But when I tried to figure out how to pass a failure of etckeeper pre-install command to stop DNF5 transaction in case of unclean /etc directory, I realized there is no way to do so. For a while I was entertaining an idea of implementing a new LIBDNF5 plugin, but there I also noticed that I’m not sure how to pass the error properly.

Eventually I opened new feature request for DNF5 asking for an option (in actions file format) to make DNF5 to raise an error when command returns non zero return code. This was shortly implemented, so that since DNF5 5.2.11.0, it’s possible to use new raise_error=1 option to enable processing of return code of a command in an actions file.

When I played with this new feature, I settled down on the following actions file:

$ cat /etc/dnf/libdnf5-plugins/actions.d/etckeeper.actions
# to be placed in /etc/dnf/libdnf5-plugins/actions.d
# requires dnf5 5.2.11.0 or later
pre_base_setup:::raise_error=1:etckeeper pre-install
post_transaction::::etckeeper post-install

It uses pre_base_setup for the first hook so that the DNF complains immediately when /etc directory is not clean. But I don’t use raise_error=1 option for the post_transaction hook, since it’s not necessary and when used the Actions Plugin complains that output coming from etckeeper post-install doesn’t follow the expected format (obviously).

Example of usage

I’m using the etckeeper.actions file as listed above for few days and so far it seems to work well. I haven’t run into any further problem.

When I have some uncommitted changes in /etc directory DNF5 will immediately report and error as expected:

# time dnf install caddy

** etckeeper detected uncommitted changes in /etc prior to dnf run
** Aborting dnf run. Manually commit and restart.

File "/etc/dnf/libdnf5-plugins/actions.d/etckeeper.actions" on line 3: Exit code: 1

real    0m2.347s
user    0m2.205s
sys     0m0.167s

Then when I revert or commit the changes to make /etc clean again and retry, I can see that after the package was installed, appropriate commit in /etc git repository was created:

# git log -1 @
commit 7282591c86c9fe5dd6e7ad28c9c86feb43297da0
Author: root <root@localhost>
Date:   Sat Apr 19 15:36:02 2025 +0200

    committing changes in /etc made by "dnf install caddy"
    
    Package changes:
    +0:caddy-2.9.1-3.fc42.x86_64
    +0:fedora-logos-httpd-42.0.1-1.fc42.noarch
    +1:julietaula-montserrat-fonts-7.222-10.fc42.noarch

Next steps

I submitted the config file to the upstream, so hopefully it will reach Fedora in next Etckeeper release, and will be provided in etckeeper-dnf5 subpackage later. In the meantime if you use Fedora 41 or 42 and want to use Etckeeper there, you can already use the action file I posted above until that happens, just make sure you have libdnf5-plugin-actions package installed. And if you run into some problem, let me know in the upstream discussion.

Do you have a comment or question? You can contact me.