Disclaimer:

TrueNAS CORE 13.3-RELEASE has been released, and if you update to it, you can just run pkg install py-paperless-ngx and profit!

If, for some reason, you can’t update to 13.3, this guide might still be useful.

Since February of 2024, thanks to Michael Gmelin (grembo), Paperless-ngx is available as an official port for FreeBSD: deskutils/py-paperless-ngx.

But upon closer inspection, one can see that the port is only available for FreeBSD versions 14 and 15.

FreeBSD 14 is available since November 2023, so users have had some time to upgrade to it and, therefore, there’s no problem with py-paperless-ngx not being available for FreeBSD 13, right?

Well… that could be the case, were it not for some of us, users of TrueNAS CORE, currently on version 13.0-U6.1, which runs on FreeBSD 13.1, and will see its final version, 13.3, which was expected to launch in Q2 2024, running on FreeBSD 13.3. So we are somewhat stuck with FreeBSD 13. At least until something like zVault is able to offer an upgrade path to TrueNAS CORE users.

FreeBSD 13

For those who are on FreeBSD 13.3 (and us TrueNAS users, when the final version is launched), installing the port, although it isn’t available as a pre-compiled package (click here for info on port vs package), should be as easy as compiling and installing, which would be something like this:

portsnap fetch extract
cd /usr/ports/deskutils/py-paperless-ngx
make install clean

Technically, you could benefit from some of the optimizations I suggest on the next section, so keep reading if you encounter any compilation problems or just want the whole thing to be done faster.

FreeBSD 13.2

I have created a Gist with a script and the patches I needed to install the port: freebsd_13.2_py-paperless-ngx_patch.md.

Python version:

As of writing, port compilation was defaulting Python to version 3.9. Now it is using 3.11. I haven’t tested this with 3.11, bear that in mind if you want to give it a go. If not, add "DEFAULT_VERSIONS= python=3.9" to your make.conf file, or to the make install statement.

The install script is as follows:

#!/bin/sh

set DEPS_LIST="py39-pip py39-bleach py39-concurrent-log-handler \
  py39-dj42-django-cors-headers py39-dj42-django-filter py39-filelock \
  py39-gunicorn py39-h2 py39-hiredis py39-httptools py39-langdetect \
  py39-mysqlclient py39-ocrmypdf py39-pdftotext py39-psycopg2 \
  py39-python-dotenv py39-python-gnupg py39-python-magic \
  py39-setproctitle py39-sqlite3 py39-uvicorn py39-uvloop py39-watchfiles \
  py39-websockets py39-whitenoise py39-whoosh py39-yaml \
  gnupg liberation-fonts-ttf optipng tesseract unpaper \
  blas openblas cmake gcc13 poppler-utils zbar ImageMagick7"

echo $DEPS_LIST | xargs pkg install -y
# 51.332u 8.035s 3:56.50 25.0%    3001+234k 3+184933io 1479pf+0w

portsnap fetch extract
# 59.524u 86.625s 2:39.78 91.4%   50+165k 1+202655io 40pf+0w

# Patch os.sched_getaffinity()
curl https://gist.githubusercontent.com/tinsukE/81898d120a13a84ee02db2e9ffdf055d/raw/cb2634c2d8e9db983ad3cf36e8d99c9674a9fc17/os.sched_getaffinity.patch > os.sched_getaffinity.patch
patch -ruN -V none -p0 < os.sched_getaffinity.patch
# Patch py-threadpoolctl for FreeBSD 13.2
curl https://gist.githubusercontent.com/tinsukE/81898d120a13a84ee02db2e9ffdf055d/raw/982b7ee866feb4da1451614c88d9ba57085a251d/py-threadpoolctl.patch > py-threadpoolctl.patch
patch -ruN -V none -p0 < py-threadpoolctl.patch

setenv BATCH yes
cd /usr/ports/deskutils/py-paperless-ngx
make install
# 2324.296u 166.586s 21:57.22 189.1%      50524+764k 4+429944io 79972pf+0w

echo $DEPS_LIST | xargs pkg set -A 1 -y
pkg autoremove -y
cd && rm -rf /usr/ports

First, we go about installing the packages for many of the port’s dependencies that are available for FreeBSD 13 and some big sub-dependencies of the dependencies we’ll have to compile ourselves. We do this in the interest of time.

Note that not all dependencies can be installed from packages. Many of them aren’t available for FreeBSD 13, and others are, but on lower, incompatible, versions.

Then, we download the ports tree, it’ll live in /usr/ports.

After that, it is patching time! The first patch, os.sched_getaffinity.patch, will make sure to avoid an OverflowError that would be thrown by Python when some code would call os.sched_getaffinity(). I don’t know if it is something about my system (FreeBSD 13.1 running a 13.2 jail), but it is easily reproducible, and it looks like this:

Traceback (most recent call last):
  ...
  File "/usr/local/lib/python3.9/site-packages/numpy/distutils/misc_util.py", line 93, in get_num_build_jobs
    cpu_count = len(os.sched_getaffinity(0))
OverflowError: could not allocate a large enough CPU set
*** Error code 1

Thankfully, the affected code pieces already expect a NotImplementedError exception, and the patch only makes them catch OverflowError as well.

The second patch, py-threadpoolctl.patch, is more involved, as py-threadpoolct won’t work on FreeBSD <= 13.2, and a fix was only made on FreeBSD 13.3. Thankfully, there was a dropped PR to threadpoolctl (dropped because FreeBSD fixed the issue on its side) that I replicated on the patch: https://github.com/joblib/threadpoolctl/pull/148.

After patching, we are ready to install the port!

setenv BATCH yes is there to avoid user interaction when a port offers any type of configuration and just uses the default options.

Close to the end we clean the build filesand remove the whole port tree.

We then wrap it all up with some magic to mark the dependencies that we previously manually installed as automatic. Meaning that if we were to uninstall the brand new py-paperless-ngx, a future call to pkg autoremove would also remove those dependencies, as they are not marked as explicitly installed anymore, being there just to fulfill dependencies. Then we remove any build-only dependency.

Conclusion

I have been running the port for a few months and already sent some bug reports to the author, who promptly addressed them, and all seems to work fine. The whole OCR and ML powered categorization is doing its magic as it should.

My previous Paperless-ngx installation was running version 1.17.4, and I managed to export my documents and import them into the new port just fine. Oh, and that installation was powered by my own porting efforts, which are now rendered redundant, RIP.