Skip to content

Blog

Building .NET apps on FreeBSD

While trying to build Kavita to run in a FreeBSD jail, I ran into a minor snag with the native .NET port lang/dotnet.

For Google: the .NET runtime identifier for FreeBSD 14 64-bit is freebsd.14-x64. You can see the installed runtime packs here:

ls -l /usr/local/share/dotnet/packs/
total 4
drwxr-xr-x  3 root wheel 3 May 18 09:27 Microsoft.AspNetCore.App.Ref
drwxr-xr-x  3 root wheel 3 May 18 09:27 Microsoft.AspNetCore.App.Runtime.freebsd.14-x64
drwxr-xr-x  3 root wheel 3 May 18 09:27 Microsoft.NETCore.App.Host.freebsd.14-x64
drwxr-xr-x  3 root wheel 3 May 18 09:27 Microsoft.NETCore.App.Ref
drwxr-xr-x  3 root wheel 3 May 18 09:27 Microsoft.NETCore.App.Runtime.freebsd.14-x64
drwxr-xr-x  3 root wheel 3 May 18 09:27 NETStandard.Library.Ref
drwxr-xr-x  3 root wheel 3 May 18 09:27 runtime.freebsd.14-x64.Microsoft.DotNet.ILCompiler

I would assume this will soon become freebsd.15-x64.

On Ports, Poudriere, and PNPM

Node- and TypeScript-powered software seems to have a bit of a reputation among FreeBSD port maintainers, I think due to their complex dependency management and the number of files that need to be shipped as part of the build output.

Recently I tried my hand at creating a FreeBSD port for autobrr, a torrent automation tool. They do publish a FreeBSD binary, but since I'm me, I tried to have the port build from source rather than simply install the binary. This ended up being a giant pain in the ass, and I'm moving back to the binary installation method, but I learned a lot of cool things about the FreeBSD ports system in the process, some of which may be helpful to other porters.

On Nomad, Ruby, and Bundler

As a chronic doer-of-things-the-hard-way, I've come to prefer using Nomad instead of Kubernetes, and native installs instead of container images.

This makes some things easier (less YAML, no Dockerfiles), and everything considerably harder, especially when working with languages which don't compile to a single statically-linked build artifact.

Kernel module errors while upgrading to Fedora 42

I have the xpadneo driver installed on my gaming PC so that I can use my XBox controller with Steam games (which works wonderfully).

However, when upgrading to Fedora 42, I got this error during the kernel-core install:

Error in pre-uninstall scriptlet: kernel-core-0:6.13.5-200.fc41.x86_64
Scriptlet output:
dkms: removing module hid-xpadneo/v0.9-196-g227c101 for kernel 6.13.5-200.fc41.x86_64 (x86_64)
Module hid-xpadneo/v0.9-196-g227c101 for kernel 6.13.5-200.fc41.x86_64 (x86_64):
dkms.conf: Error! Unsupported AUTOINSTALL value 'Y'

Error! Bad conf file.
File:  does not represent a valid dkms.conf file.

It has something to do with the newer DKMS-style kernel module system, which xpadneo uses.

In the end, I only needed to update the DKMS file that was mentioned in another related error message (your version number in the path will probably be different from mine):

--- /var/lib/dkms/hid-xpadneo/v0.9-196-g227c101/source/dkms.conf.orig   2025-06-05 16:38:33.015388072 -0400
+++ /var/lib/dkms/hid-xpadneo/v0.9-196-g227c101/source/dkms.conf    2025-06-05 16:36:56.062935972 -0400
@@ -13,7 +13,7 @@
 BUILD_EXCLUSIVE_KERNEL_MIN="4.18.0"
 BUILD_EXCLUSIVE_CONFIG="CONFIG_HID CONFIG_INPUT_FF_MEMLESS CONFIG_POWER_SUPPLY"

-AUTOINSTALL="Y"
+AUTOINSTALL="YES"

 POST_INSTALL="dkms.post_install"
 POST_REMOVE="dkms.post_remove"

And then run dnf reinstall kernel-core to reinstall the module correctly.

I think this was also causing initramfs-related errors on boot when I first tried upgrading, because I did the update from the CLI this time around and that's when I noticed the error messages.

Jellyfin plugin configuration for the lazy

In order to make a configuration page for your Jellyfin plugin, you have to:

That's a lot of effort, and I just wanted a quick, gross way to get a configuration set up for my plugin.

What I did instead:

  • Added a property to my configuration class:

    public class PluginConfiguration : BasePluginConfiguration
    {
        public string? OnlyRemuxLibraries { get; set; }
    }
    

  • Initialized the property from my plugin's constructor and called SaveConfiguration:

    public DoViRemuxPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
        : base(applicationPaths, xmlSerializer)
    {
        Configuration.OnlyRemuxLibraries = "hello, world!";
        SaveConfiguration();
    }
    

  • Launched Jellyfin, which calls into the constructor and writes the initial configuration file.

  • Removed the code in the constructor, so we're not overwriting the file after initializing it.

  • Declared IPluginManager as a constructor dependency in the plugin code, and accessed the plugin instance to get its configuration:

    var plugin = (DoViRemuxPlugin)_pluginManager.GetPlugin(DoViRemuxPlugin.OurGuid).Instance;
    var configuration = plugin.Configuration;
    

  • Changed the config values in the file, which now lives at $jellyfin_app_dir/plugins/configurations/Jellyfin.Plugin.DoViRemux.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <OnlyRemuxLibraries>super cool new config value</OnlyRemuxLibraries>
    </PluginConfiguration>
    

  • $jellyfin_app_dir is /Users/katie/Library/Application Support/jellyfin on MacOS.

Restarting Jellyfin will load the new config values (there's a change tracking mechanism, but it only seems to work for changes made within Jellyfin).

Side note: this is kind of silly. I feel like it should be relatively straightforward to just reflect on the PluginConfiguration class and use standard .NET attributes from System.ComponentModel, like DisplayName and Description, to dynamically build a configuration page. If you want to do more advanced stuff in JavaScript or whatever, the existing mechanism would work, but it's a lot of overhead if you just need a page of keys and values.