Re: pkg-be-plugin: auto-create ZFS boot environments before pkg transactions

From: Ronald Klop <ronald-lists_at_klop.ws>
Date: Thu, 14 May 2026 11:59:59 UTC
Looks useful!

Are you planning to create a port for this?

Regards,
Ronald.

 
Van: Sasha Karcz <sasha@starnix.net>
Datum: donderdag, 14 mei 2026 07:30
Aan: freebsd-pkg@freebsd.org
Onderwerp: pkg-be-plugin: auto-create ZFS boot environments before pkg transactions
> 
> Hello,
> 
> I've written a pkg(8) plugin that automatically creates a ZFS boot environment before each install, upgrade, and deinstall transaction. If a transaction leaves the system in a broken state, the pre-transaction BE is there to boot into.
> 
> The plugin is called pkg-be-plugin and installs as be.so. It uses libbe(3) directly — no exec of bectl(8) or zfs(8).
> 
> Behaviour
> 
> On each covered transaction, the plugin calls libbe_init() and be_create() to snapshot the current BE under a timestamped name (default prefix: pre-pkg, e.g. pre-pkg-20260514-091532). After creation, it prunes older auto-created BEs to keep the count at or below a configurable limit, with a minimum-age guard so recent rollback points aren't destroyed even when over the limit.
> 
> All activity is logged to syslog(3) at LOG_NOTICE for normal operations and LOG_WARNING/LOG_ERR for failures, so admins can grep /var/log/messages to find BE names for rollback after a bad transaction.
> 
> Configuration (via /usr/local/etc/pkg/be.conf, UCL format)
> 
> BE_PLUGIN_ENABLED — master switch (default: true)
> BE_PLUGIN_KEEP — maximum BEs to retain (default: 5)
> BE_PLUGIN_NAME_PREFIX — name prefix (default: pre-pkg)
> BE_PLUGIN_MIN_AGE — minimum age before pruning (default: 7d; protects recent rollback points from being destroyed when count exceeds KEEP)
> BE_PLUGIN_STRICT — abort transaction on BE creation failure (default: false)
> BE_PLUGIN_SKIP_TRANSACTIONS — comma-separated list of transaction types to skip (install, upgrade, deinstall)
> Non-ZFS systems
> 
> libbe_init() fails on UFS roots and in jails without ZFS access. In non-strict mode (the default) this is logged as a warning and the transaction proceeds normally. Strict mode causes a fail-closed abort, which may be appropriate for ZFS-only fleets.
> 
> Testing
> 
> Tested on FreeBSD 15.0-RELEASE-p5 with the install/upgrade/deinstall transaction types, including multi-package transactions, the prune path (over-KEEP and under-min-age scenarios), and strict-mode behaviour. Unit tests cover the config parser and prune sort/filter logic.
> 
> Source
> 
> https://github.com/usenix17/pkg-be-plugin
> 
> Feedback welcome. Specific things I'd appreciate eyes on:
> 
> pkg plugin API usage — particularly the hook lifecycle (init multiple hooks shutdown) and whether PKG_PLUGIN_HOOK_PRE_{INSTALL,UPGRADE,DEINSTALL} are the right hooks for this purpose, or whether there's a less-surprising place to do BE creation.
> libbe nvlist property access — the creation property is stored as a string of decimal Unix epoch seconds rather than a uint64. I worked this out via integration testing; if this is documented somewhere I missed, pointers welcome.
> Prune semantics — currently the count can drift above KEEP if all candidate BEs are under MIN_AGE. Trade-off chosen for the homelab-friendly "never destroy a recent rollback" property. If list consensus prefers strict-count enforcement, the policy is a one-line change.
> Sasha Karcz
> 
>  
>