radagast: ML ports clean rebuild recipe

Step-by-step recipe for rebuilding the full Python ML stack on radagast after a clean /usr/local. Written after the 2026-10 /usr/local symlink incident. Every section records a real error we hit and how to prevent it. See: symlink-rm incident

Step 0 — verify /usr/local is a real directory

ls -la /usr/ # must NOT show: local -> anything cd /usr/local && pwd -P # must print /usr/local, not /usr/local.new or similar

If pwd -P differs from pwd, /usr/local is a symlink. Stop. Fix the symlink before proceeding. rm -rf on a symlink target deletes everything — this is how the incident happened.

Step 1 — bootstrap pkg (chicken-and-egg)

ports-mgmt/pkg cannot register itself because /usr/local/sbin/pkg doesn't exist yet to do the registration. Fix: copy the stage-built binary first.

mkdir -p /usr/local/sbin /usr/local/lib /usr/local/etc /usr/local/share /usr/local/bin cp /usr/ports/ports-mgmt/pkg/work/stage/usr/local/sbin/pkg /usr/local/sbin/pkg cp /usr/ports/ports-mgmt/pkg/work/stage/usr/local/sbin/pkg-static /usr/local/sbin/pkg-static # Wipe any stale pkg db from a prior install: rm -f /var/db/pkg/local.sqlite* mkdir -p /var/db/pkg # Now the install will actually register: cd /usr/ports/ports-mgmt/pkg && make install BATCH=yes FORCE_PKG_REGISTER=yes

Step 2 — global make.conf and environment

Add to /etc/make.conf before any build:

FORCE_PKG_REGISTER=yes

Export in the build shell:

export FORCE_PKG_REGISTER=yes export ASSUME_ALWAYS_YES=yes

Step 3 — write ALL options files before building anything

The ports system reads options files when a port is first pulled in as a dep. If you haven't written the options file yet, the dep gets default options — which may trigger rust, LLVM, or other multi-hour builds. Write ALL of these before starting any build.

graphics/opencv — V4L and EIGEN off

mkdir -p /var/db/ports/graphics_opencv cat > /var/db/ports/graphics_opencv/options << 'EOF' _OPTIONS_READ=opencv-4.13.0_9 OPTIONS_FILE_UNSET+=V4L OPTIONS_FILE_UNSET+=EIGEN OPTIONS_FILE_UNSET+=GDAL OPTIONS_FILE_UNSET+=GDCM OPTIONS_FILE_UNSET+=DC1394 OPTIONS_FILE_UNSET+=VULKAN OPTIONS_FILE_UNSET+=GPHOTO2 OPTIONS_FILE_UNSET+=OBSENSOR OPTIONS_FILE_SET+=GSTREAMER OPTIONS_FILE_SET+=OPENBLAS OPTIONS_FILE_SET+=OPENJPEG EOF

V4L=off: no Video4Linux in a VM. EIGEN=off: removes ceres-solver dep (huge build). Parallel build ordering issues are expected — let it fail, rerun; ninja keeps progress, takes 2-3 runs.

graphics/py-pillow — IMAGEQUANT off (CRITICAL: prevents rust)

mkdir -p /var/db/ports/graphics_py-pillow cat > /var/db/ports/graphics_py-pillow/options << 'EOF' _OPTIONS_READ=py312-pillow-12.3.0 OPTIONS_FILE_UNSET+=IMAGEQUANT OPTIONS_FILE_SET+=AVIF OPTIONS_FILE_SET+=FREETYPE OPTIONS_FILE_SET+=JPEG2000 OPTIONS_FILE_SET+=JPEG OPTIONS_FILE_SET+=LCMS2 OPTIONS_FILE_SET+=PNG OPTIONS_FILE_SET+=TIFF OPTIONS_FILE_SET+=WEBP OPTIONS_FILE_SET+=ZLIB EOF

IMAGEQUANT default is ON. Pulls in libimagequant (rust crate) → 2-3 hour rust build. Must be unset.

databases/arrow — GANDIVA+ZSTD both off (plist bug)

mkdir -p /var/db/ports/databases_arrow cat > /var/db/ports/databases_arrow/options << 'EOF' OPTIONS_FILE_UNSET+=GANDIVA OPTIONS_FILE_UNSET+=ZSTD OPTIONS_FILE_UNSET+=S3 OPTIONS_FILE_UNSET+=FLIGHT OPTIONS_FILE_UNSET+=FLIGHT_SQL OPTIONS_FILE_UNSET+=HDFS OPTIONS_FILE_UNSET+=TESTING OPTIONS_FILE_UNSET+=TENSORFLOW EOF

Plist bug: GANDIVA=off + ZSTD=on lists lib/cmake/Gandiva/FindzstdAlt.cmake in PLIST_FILES but cmake never generates it. Registration fails. BOTH must be off.

math/Imath — PYTHON off (plist bug)

mkdir -p /var/db/ports/math_Imath cat > /var/db/ports/math_Imath/options << 'EOF' OPTIONS_FILE_UNSET+=PYTHON OPTIONS_FILE_UNSET+=DOCS EOF

PYTHON=on expects imath.so and imathnumpy.so (require boost-python). Build succeeds, registration fails.

math/py-numpy — OPENBLAS on, NETLIB off

mkdir -p /var/db/ports/math_py-numpy cat > /var/db/ports/math_py-numpy/options << 'EOF' _OPTIONS_READ=py312-numpy-2.4.6_1,1 OPTIONS_FILE_UNSET+=NETLIB OPTIONS_FILE_SET+=OPENBLAS EOF

Stale options files (e.g. py27 era) often have NETLIB=on. OpenBLAS: 182 GFLOP/s vs 5.8 GFLOP/s with Netlib — 31× speedup. Delete any options file referencing an old py2x version before writing this.

devel/hwloc2 — L0 off

mkdir -p /var/db/ports/devel_hwloc2 cat > /var/db/ports/devel_hwloc2/options << 'EOF' OPTIONS_FILE_UNSET+=L0 EOF

L0 = Intel Level Zero GPU. Not in a VM.

graphics/py-imageio — FFMPEG off

mkdir -p /var/db/ports/graphics_py-imageio cat > /var/db/ports/graphics_py-imageio/options << 'EOF' OPTIONS_FILE_UNSET+=FFMPEG OPTIONS_FILE_SET+=TIFFFILE EOF

math/py-networkx — heavy optional deps off

mkdir -p /var/db/ports/math_py-networkx cat > /var/db/ports/math_py-networkx/options << 'EOF' OPTIONS_FILE_UNSET+=GDAL OPTIONS_FILE_UNSET+=GRAPHVIZ OPTIONS_FILE_UNSET+=SCIPY EOF

Step 4 — build function (with .install_done cookie fix)

CRITICAL: If any port was previously installed and its work/ dir still exists, the file work/.install_done.<portname>._usr_local will be present. make install checks for this cookie and silently returns 0 without installing anything. Files won't land in /usr/local and nothing gets registered. This is invisible — the stamp shows EXIT:0 but pkg shows nothing.

Fix: delete the cookie before every make install:

Launch scripts nohup-style so they survive SSH disconnect:

nohup sh -c '/root/rebuildN.sh; logger -p local1.info -t ports-build "rebuildN done"' \ > /root/rebuildN_nohup.log 2>&1 &

Logs appear on pomelo at /var/log/radagast-builds.log via logger bridge.

Step 5 — Phase 2: install all C/C++ library deps

These all have work/stage/ dirs from previous builds. With the cookie fix, they install in seconds each. Order matters for a few: suitesparse-* must be in dependency order; fast_float and tlfloat before gdal; gettext-runtime before perl.

Ports that are not obvious but REQUIRED before python312 (learned from errors):

Complete Phase 2 list (run with the build() function above):

# gettext-runtime FIRST (before gdbm/perl) build devel/gettext-runtime # Core build tools build devel/cmake-core build devel/pkgconf build lang/perl5.42 build security/openssl # Libraries (order: deps before dependents) build archivers/zlib-ng build archivers/minizip build archivers/zopfli build devel/gflags build devel/glog build devel/googletest build devel/json-c build devel/nlohmann-json build devel/libgit2 build devel/libinotify build devel/libudev-devd build devel/libdaemon build devel/libevdev build devel/evdev-proto build devel/swig build devel/pybind11 build devel/base64 build devel/fxdiv build devel/highway build devel/dbus build devel/dbus-glib build devel/cargo-c build devel/onetbb build devel/hwloc2 build devel/dmlc-core build math/fast_float build math/tlfloat build math/z3 build math/sleef build math/fftw3 build math/fftw3-float build math/eigen3 build math/libdivide build math/qhull build math/suitesparse-config build math/suitesparse-amd build math/suitesparse-camd build math/suitesparse-colamd build math/suitesparse-ccolamd build math/suitesparse-btf build math/suitesparse-cholmod build math/suitesparse-umfpack build math/suitesparse-klu build math/suitesparse-spqr build math/suitesparse-cxsparse build math/ceres-solver build security/libgpg-error build security/libgcrypt build security/libsodium build security/libfido2 build security/fakeroot build security/munge build security/s2n-tls build security/aws-c-cal build security/aws-c-auth build devel/aws-c-common build devel/aws-c-compression build devel/aws-c-io build devel/aws-c-http build devel/aws-c-mqtt build devel/aws-c-event-stream build devel/aws-c-s3 build devel/aws-c-sdkutils build devel/aws-checksums build devel/aws-crt-cpp build net/pmix build net/prrte build net/openmpi build net/avahi-app build net/uriparser build audio/libogg build audio/libvorbis build audio/libmad build audio/flac build audio/lame build audio/mpg123 build audio/opus build audio/libsndfile build databases/gdbm build databases/lmdb build databases/tinycdb build databases/arrow build graphics/libglvnd build graphics/vulkan-headers build graphics/vulkan-loader build graphics/spirv-tools build graphics/shaderc build graphics/libplacebo build graphics/libavif build graphics/libjxl build graphics/libyuv build graphics/openjpeg build graphics/geos build graphics/proj build graphics/libgeotiff build graphics/libertiff build graphics/freeimage build textproc/freexl build science/netcdf # If netcdf fails with static symbol error (ncx.m4 clash), wipe work dir first: # rm -rf /usr/ports/science/netcdf/work build graphics/webp build textproc/xerces-c3 build databases/sqlite3 build lang/tcl86 build devel/libedit # gdal: POPPLER/SFCGAL/SPATIALITE/PGSQL disabled (saves 56 heavy deps: poppler, NSS, glib2, postgresql chain) build graphics/gdal OPTIONS_UNSET="POPPLER SFCGAL SPATIALITE PGSQL" build graphics/frei0r build graphics/gdk-pixbuf2 build graphics/wayland build graphics/librsvg2-rust build graphics/opencv build lang/lua53 build lang/lua54 build lang/duktape-lib build lang/vala build lang/python312 build multimedia/ffnvcodec-headers build multimedia/v4l_compat build multimedia/libv4l build multimedia/libva build multimedia/libvdpau build multimedia/aom build multimedia/dav1d build multimedia/svt-av1 build multimedia/vmaf build multimedia/libvpx build multimedia/libx264 build multimedia/x265 build multimedia/xvid build multimedia/libtheora build multimedia/libdvdcss build multimedia/libdvdread build multimedia/libdvdnav build multimedia/libdc1394 build misc/onnx build misc/shared-mime-info build net/freebsd-uucp build print/libraqm build science/libkml build sysutils/consolekit2 build sysutils/gnome_subr build sysutils/polkit build sysutils/xxd build www/llhttp build www/srt build x11/libXScrnSaver build x11/libXfixes build x11/libXi build x11/libXrandr build x11/libXtst build x11-toolkits/tk86 build mail/libtlsrpt

Step 6 — Phase 3: Python ML packages (full source builds)

These have no stage dirs and must be compiled from source. python312 must already be installed (Step 5). Major time costs: devel/py-llvmlite needs LLVM22 (~1-2 hours); misc/py-pytorch needs rust/maturin (~2-3 hours if rust isn't pre-built).

librsvg2-rust plist split-line bug

Generated plist has a spurious entry from %%PORTDOCS%% substitution that splits across lines. Symptom: pkg registration fails with 'file not found: vg-2.0/index.json'.

sed -i '' 's|^vg-2.0/index.json$|@comment vg-2.0/index.json|' \ /usr/ports/graphics/librsvg2-rust/work/.PLIST.librsvg2-rust

Non-obvious dep chains (know these before starting)

Build time estimates (for planning)

Chain everything sequentially in one background script. Do not wait interactively between ports.

Port conflict resolution

When a port is already registered and FORCE_PKG_REGISTER doesn't help:

pkg delete -fy <portname> # then re-run build()

Benchmark reference (2026-10)

Still to verify / potential unknowns

This section will be updated as the rebuild proceeds.

version 3  ·  created 2026-10-05  ·  updated 2026-10-05  ·  tags radagast, freebsd, ports, ml, pytorch, numpy, opencv