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
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.
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
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
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.
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.
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.
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.
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.
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.
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.
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
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
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.
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
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).
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
Chain everything sequentially in one background script. Do not wait interactively between ports.
When a port is already registered and FORCE_PKG_REGISTER doesn't help:
pkg delete -fy <portname>
# then re-run build()
This section will be updated as the rebuild proceeds.