Skip to content

NixBSD Image

omfreebdy normally installs the imperative way — pkg install, copy configs, enable services in rc.conf. The NixBSD image is the same desktop expressed declaratively: one Nix flake that pins every package, config file, script, and service, then cross-compiles the whole thing from a Linux box and boots it as a NixBSD virtual machine — a real FreeBSD 15 kernel and userland, managed NixOS-style.

No FreeBSD machine is touched to build it. nix build on Linux produces a bootable x86_64-unknown-freebsd image with the i3 desktop, the omfreebdy theme set, every user script, and a full CLI/dev toolchain baked in.

omfreebdy i3 desktop rendering live on a NixBSD FreeBSD 15 VM: the gruvbox theme wallpaper on the left, an st terminal running fastfetch on the right showing NixOS 26.05 / FreeBSD 15.0-RELEASE and full CPU/GPU/memory/disk detection, with the i3 status bar across the top

The Nix-built omfreebdy i3 desktop, rendered live on a FreeBSD 15 NixBSD VM — gruvbox wallpaper, i3 + status bar, and fastfetch reporting the FreeBSD kernel, all cross-built from a single declarative flake on Linux. Theme switched live with theme-set gruvbox.

# cross-build the image from Linux (uses the cppnix evaluator + nixbsd cache)
$ nix build .#omfreebdy-x11-vm
# boot it under QEMU
$ ./result/bin/run-omfreebdy-vm
KernelNB-KFreeBSD 15.0-RELEASEReal BSD kernel + userland, not Linux
ManagementNB-MNixBSDNixOS-style declarative config on the BSD kernel
DesktopNB-Di3 / X11i3 + i3status + picom + rofi, all 18 themes
Build hostNB-BLinux → FreeBSDCross-compiled; no FreeBSD machine needed
Render pathNB-RXvfb (software)Headless X server — no GPU or DRM required
OutputNB-OQEMU VM imageBoots straight to the desktop over VNC

The image isn’t a stripped-down demo — it reproduces omfreebdy’s actual install: every script in bin/, the full config tree, the user shell, services, and a real toolchain.

LayerIncluded
Window manageri3, i3status, picom, rofi, xterm + st
Wallpaperxwallpaper paints the catppuccin background on the Xvfb root
ThemesAll 18 omfreebdy themes (catppuccin, gruvbox, everforest, kanagawa, …)
ScriptsThe complete bin/ set — theme-set, theme-list, every helper
ConfigsSeeded verbatim from config/ into ~/.config on first boot
CLI toolsbtop, fastfetch, starship, tmux, zoxide, fzf, eza, bat, fd, jq, ripgrep, lazygit, gh, dust, …
Dev toolchainGo, Python 3.11, Node 22, Ruby, GNU Make, ImageMagick
LoginAutologin getty → user shell → X session

Identical to the rest of omfreebdy — the theme scripts ship in the image:

# list available themes
$ theme-list
# switch the whole desktop instantly
$ theme-set gruvbox

theme-set rewrites the i3, i3status, picom, and rofi colors and reloads them — exactly as on a pkg-installed system. See the Themes guide for authoring your own.

The build runs entirely on Linux. Two non-obvious pieces make the FreeBSD cross-compile work:

EvaluatorCPPcppnixThe rhelmot/nix fork — stock Nix infinite-recurses on the FreeBSD stdenv bootstrap
CacheSUBnixbsd atticPre-built FreeBSD bootstrap from the NixBSD project’s binary cache
# the flake exposes the VM as a Linux package output
$ nix build .#packages.x86_64-linux.omfreebdy-x11-vm
# run it — QEMU boots the FreeBSD VM, X session comes up over VNC
$ ./result/bin/run-omfreebdy-vm
# connect a VNC viewer to the forwarded port to see the desktop
$ vncviewer 127.0.0.1:5900

Getting omfreebdy onto a FreeBSD kernel from a Linux builder meant teaching nixpkgs a handful of FreeBSD facts. None of these are omfreebdy bugs — the same software builds cleanly on real FreeBSD via pkg. The gap is purely nixpkgs’ Linux-centric cross-packaging.

Wayland first — and the GPU wall

The first target was the real Hyprland desktop. It cross-compiled completely — mesa/llvmpipe, aquamarine, Hyprland 0.53.1, foot, fuzzel, swaybg, polkit, seatd — and booted, reading its activation-seeded hyprland.conf and bringing up its full manager stack.

NixBSD serial console: Hyprland on a FreeBSD 15 kernel creating its EventLoop/Hook/Keybind/Animation/Config managers, then terminating because no DRM render node exists

Hyprland booting on the FreeBSD-kernel VM — it builds every manager, then dies at GPU init.

Then it hit a hard wall: aquamarine’s only buffer allocator is GBM, which requires a DRM render node (/dev/dri/renderD*). FreeBSD 15 in QEMU has no virtio-gpu KMS driver, so there’s no /dev/dri at all — Cannot open backend: no allocator available. This isn’t a packaging bug; it needs real GPU hardware with drm-kmod. So for the VM we pivoted to omfreebdy’s i3/X11 profile, which software-renders under Xvfb with no GPU.

The versionless triple

nixpkgs targets FreeBSD with the versionless triple x86_64-unknown-freebsd — the clang-standard NixBSD identity. That’s the platform’s identity; changing it would rebuild the entire package set. Most things accept it; the GNU toolchain’s config.gcc doesn’t (it extracts FBSD_MAJOR from the triple and aborts when there’s no version). A one-line patch defaults it to 15.

Trimming Linux-only dependencies

A scattering of packages pull deps that exist only on Linux (or are glibc-compat shims). Each is a peripheral feature, not a core component — so the fix is to drop the dep, not the package:

  • i3status — drop the libnl (network) and pulseaudio (volume) modules; time/cpu/disk/load stay.
  • i3 — drop the Perl X11::XCB/Inline::C helper-script stack; the WM binary doesn’t need it.
  • scrot — drop libbsd (a glibc strlcpy shim; FreeBSD libc already has it).
  • plocate — drop systemd (only used to place a .timer unit).
  • feh — use plain imlib2, supply a strverscmp implementation, re-expose BSD extensions with -D__BSD_VISIBLE=1.
  • fftw (via ImageMagick) — drop its optional Fortran interface; the C FFT library needs no Fortran.
  • btop / dust / bat — add FreeBSD’s libproc / libgeom.
  • gh — disable cgo. ripgrep — skip the install-check that runs the target binary on the Linux builder.
Three NixBSD fixes so the unmodified omfreebdy config just works

The goal was to run omfreebdy’s actual config unchanged — fix NixBSD, don’t fork the desktop:

  1. /bin/sh must be ash — NixBSD points /bin/sh at bash, but FreeBSD’s wordexp(3) (used by i3’s include, exec, and status_command) needs the real ash shell. Set environment.binsh to freebsd.bin’s sh.
  2. Locale data — NixBSD defaults LANG=en_US.UTF-8 but ships no /usr/share/locale, so setlocale() fails → i3status exits and rofi segfaults. Point PATH_LOCALE at freebsd.locales.
  3. ~/.cache — rofi segfaults writing its run/drun history to a missing cache dir; create it and set XDG_CACHE_HOME.

A few things build on FreeBSD via pkg but aren’t cross-packaged in nixpkgs yet. They’re tracked, not abandoned — the genuine frontier is cross-compiling compilers.

ItemWhy deferred
gfortran / OpenBLASThe cross-GCC nixpkgs builds is glibc-wired — no FreeBSD libc. The config.gcc triple patch is a correct starting point but not sufficient on its own.
flangLLVM Fortran cascades into MLIR cross-build scope surgery (cross mlir-tblgen).
rustcBootstrap stage runs a target binary on the Linux builder.
neovimRuns the freshly-built nvim during its own build.