Maintaining port modifications in FreeBSD

Introduction

Every so often, customization options provided by FreeBSD's ports system do not suffice. In such an occasion, the need to maintain a fork of the desired port arises. Unfortunately, considering the pace at which ports update necessitates a constant and time-consuming watch on the stream of updates so the child port can be rebased on the parent port to keep the system up-to-date. In this post we'll explore three ways that a small number of ports can be maintained with out-of-tree modifications. The inspiration for this post was the missing mmrm1stspace module in sysutils/rsyslog8 which is, by default, disabled with no option to enable it.

Separate port using custom categories

By creating a new category in the ports tree, it is possible to copy existing ports, and modify and install them separately or in place of the original port. It may also be possible to have a modified port with virtual categories by copying the port to the same directory with a new name and adding a virtual category to the Makefile of the new port. Make sure to still update VALID_CATEGORIES as stated. See this patch, this patch and this note to learn how virtual categories are built and used.

To create a physical ports category, create a directory under the ports tree (in my case, it is /usr/ports/custom) and copy the desired port under it (/usr/ports/custom/rsyslog8). Modify the CATEGORIES field in Makefile under the new port to the new category you created (custom).

Then, modify /etc/make.conf (make.conf(5)) and add VALID_CATEGORIES+=<category name> as a new line, replacing <category name> accordingly.

To distinguish between the new port and the original port when installing with pkg, just specify the category name like so: $ pkg install custom/rsyslog8.

If you're using poudriere, edit /usr/local/etc/poudriere.d/*-make.conf instead of /etc/make.conf.

Patch using poudriere's postupdate hook

Poudriere can run scripts at various points called hooks, which are located under /usr/local/etc/poudriere.d/hooks/HOOKNAME.sh. The hook we're most interested in is called ports_update which is run after ports are updated with ports -u. This hook can then do anything to the ports tree including patch arbitrary ports.

In my case, I maintain a set of patches in a separate directory following the ports tree directory structure. This allows diff(1) and patch(1) to work without extra hand-holding. To prepare a patch, copy files that need modification to another directory with the same directory structure as the original port, make changes, run diff, and save the patch in the same directory. I have my port tree under /poudriere/ports/HEAD, so the following commands work. Make sure to modify your paths accordingly.

cp /poudriere/ports/HEAD/sysutils/rsyslog8/Makefile{,.orig}
# make changes to Makefile
diff --text -U 3 /poudriere/ports/HEAD/sysutils/rsyslog8/Makefile{.orig,} > /my/patches/sysutils--rsyslog8--Makefile.patch

ports_update.sh then should be able patch easily. To understand the following line, see patch(1) manpage, focusing on the Filename Determination section. The -p flag strips slashes from file path, so make sure to set the appropriate number for your paths. For example, in my case, -p4 is appropriate since 4 slashes need to be removed to get from /poudriere/ports/HEAD/sysutils/rsyslog8/ to sysutils/rsyslog8. The -d flag then specifies the directory which contains sysutils. The contents of ports_update.sh can then be reduced to the following.

#!/bin/sh
find /my/patches -type f -name '*.patch' -exec patch -d /poudriere/ports/HEAD -p4 -i {} \;

Make sure ports_update.sh has appropriate permissions (e.g. execute). Now every time poudriere updates its ports with ports -u, your personal patches are applied.

Manual rebasing with git

If you have your ports managed with git, you can create a branch, modify any file, commit, and rebase on origin whenever you need to update the ports tree. This way you'll be able to resolve any conflicts as they occur and have git manage your modifications.

git checkout master
git pull origin master
git checkout my-modifications
git rebase master

Using ports overlays

Recently, Portshaker's overlay functionality has been ported to ports. I have not tested this, but it seems adding OVERLAYS=path1 path2 to make.conf (be it poudriere's <jailname>-make.conf or /etc/make.conf) makes poudriere or ports use path1 or path2 (in that order) instead of the original ports tree if there is a port or dependency that is needed that exists in the overlay. The essence of the patches appears to be for overlay in ${dp_OVERLAYS} ${PORTSDIR}; do which treats overlays similarly to original ports tree but does not require all ports to be present under overlay.

Final thoughts

I ended up creating a separate physical category in the ports tree named custom. This has worked well so far, and allows me to have both the original port and the modified one where I have the modified one installed in one jail and the original installed elsewhere. If you use any other workflow or have any additions to the above, let me know. You can find me as fengshaun on libera.chat irc.