Skip to content

Blog

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.

Remuxing MKVs to MP4, dev notes, day 2

It looks like I can crib most of what I need from Jellyfin's DynamicHlsController, which --

Goodness, this API is giving me flashbacks to every API controller I've ever worked on professionally that had 300 million parameters. At least they're documented.

Anyway, I think I just need to make one of these giant VideoRequestDto objects, pass it to StreamingHelpers.GetStreamingState, and then give that to ITranscodeManager.StartFfmpeg, which starts my process. This all feels suspiciously like a leaky abstraction over a bare call to FFmpeg, but whatever, it's not my code (and since Jellyfin itself is just a fork of Emby, it probably wasn't their code either).

[30 minutes later]

Oh no. I have to build all of these DTOs and then build the command-line arguments to FFmpeg myself? What's the point of the abstraction then?

I've become really disillusioned with REST APIs over the years. I don't think they were ever a good fit for most applications beyond simple CRUD operations; REST always felt too academic, like trying to make your entire database meet third normal form but then needing a ton of complex stored procedures to actually manipulate it. The "stateless" part especially kills me, because you need to ship giant blobs of JSON around in order to avoid hitting the database for the same information multiple times (and is that really such a huge problem?), but then you end up with absolutely massive, poorly documented JSON schemas -- kind of like this Jellyfin API. Fun in theory, but what does it actually accomplish?

Also, trying to pack application-level semantics into HTTP response codes is a disaster. I spent a few hours once trying to figure out why an API was returning 404 to me, only to discover that it wasn't the API itself, it was actually a load balancer in front of the API. The second T in "HTTP" stands for "transport," not "this customer's account has a moratorium in effect."

I'm convinced REST is near the root of reasons why I burned out. Not to say I'm not guilty of rejecting pull requests because pushes up glasses this should actually return 409 Conflict, not 400 Bad Request. I like to think I've grown since then.

Anyway

Maybe I'm a masochist, but I love this part of writing code against legacy codebases -- write some code, run it, hit a null reference exception, go look at the code to figure out which property I didn't set correctly, set it correctly, get a little farther into the method you're calling, repeat ad nauseam.

Eventually, you don't get a null reference exception, and you stare at the debugger, waiting for it to break, and...

You get a Dolby Vision MP4 file remuxed from an MKV! Yay!