25 KiB
Fruix System Review
An external review of the Fruix operating system: a hybrid combining the FreeBSD kernel and userland with GNU Guix-style declarative package management and GNU Shepherd as the init system.
Review date: 2026-04-07
1. Philosophical Fit
Do FreeBSD and Guix Complement Each Other?
Yes — and more naturally than one might expect. The two projects share a deeper kinship than their surface differences suggest:
-
Clean boundaries. FreeBSD's base/ports split is structurally analogous to Guix's separation of the system definition from the package graph. Both projects value knowing exactly what "the system" is versus what the user has layered on top. Fruix exploits this directly: FreeBSD's base becomes the declared
freebsd-baserecord, and everything above it is Guix-managed store content. -
Whole-system coherence. FreeBSD builds its base as a single coordinated unit (
make world). Guix builds its system as a single coordinated closure. Fruix's three-layer profile model (runtime/development/build) maps cleanly onto both traditions — FreeBSD developers will recognise the split as familiar, and Guix users will recognise the closure semantics. -
Conservative engineering. Both upstream projects favour correctness over novelty. FreeBSD's resistance to unnecessary abstraction pairs well with Guix's emphasis on reproducibility over convenience. The result is a system where "works the same way every time" is a shared first principle, not a bolted-on afterthought.
-
Auditability. FreeBSD's single-source-tree base is already unusually auditable for a Unix. Adding content-addressed store paths and explicit provenance metadata makes it more auditable, not less. The Fruix design of embedding
metadata/freebsd-source.scmand materialisation records into every closure is a genuine improvement over either upstream's status quo.
The combination is not merely compatible — it is arguably a better expression of both projects' values than either achieves alone. FreeBSD gains reproducibility and declarative configuration. Guix gains a mature, coherent kernel and userland with superior network stack, jail isolation, and ZFS. Neither project currently offers what the hybrid provides.
Tension Points
The tensions are real but tractable. Each one has already been partially addressed in the current codebase:
Mutable /etc vs. store-based immutability. FreeBSD expects
/etc to be a mutable directory owned by the administrator. Guix
expects it to be a rendered artifact derived from the system
declaration. Fruix currently renders /etc files at activation time
from the closure's metadata — this is the correct approach, but it
will need careful handling as the system grows. The risk is drift: if
an operator hand-edits /etc/rc.conf and the next reconfigure
silently overwrites it, trust erodes. The solution (used by NixOS) is
to make the rendered files clearly machine-managed and to provide a
sanctioned escape hatch for local overrides. Fruix should plan for
this now, before the habit of hand-editing /etc becomes entrenched
among early users.
FreeBSD's boot/rc model vs. Shepherd. The dual-boot-mode design
(freebsd-init+rc.d-shepherd and shepherd-pid1) is a pragmatic
and intelligent decision. It allows incremental migration: operators
can start with FreeBSD init managing rc.d scripts (familiar) and
transition to Shepherd as PID 1 (more Guix-native) when confidence
grows. The tension here is that maintaining two boot paths doubles the
surface area for boot bugs. Over time, one mode should become the
default and the other should be explicitly marked as legacy.
Recommendation: shepherd-pid1 should be the default for new
installations once it has been validated on a wider range of hardware.
The role of the base system. In stock Guix, the kernel and core
userland are packages in the store like any other. In Fruix, the
FreeBSD base is a privileged entity: it is built via make world
rather than through the Guix build system, and it has its own
source-materialisation and promotion pipeline. This asymmetry is
necessary (FreeBSD's build system is not trivially expressible as a
Guix derivation), but it creates a conceptual gap. Users coming from
Guix will ask "why can't I just guix package -i kernel?" and users
coming from FreeBSD will ask "why is my kernel in /frx/store?"
Clear documentation explaining the boundary and the reasons for it
is essential.
rc.conf semantics. The operating-system model includes
rc-conf-entries, which is a direct concession to FreeBSD's
configuration idiom. This is fine as a compatibility bridge, but it
introduces a second configuration surface alongside Shepherd service
declarations. Over time, the system should converge on one
authoritative service-configuration mechanism. The current design
wisely keeps rc.conf entries as a rendered output of the declaration
rather than a primary input.
What Does the Hybrid Offer?
The value proposition, stated plainly:
-
For FreeBSD users: Declarative, reproducible system configuration with transactional upgrades and rollback — things FreeBSD has never had. The ability to define an entire system in a single Scheme file and materialise it deterministically is transformative for FreeBSD operations.
-
For Guix/NixOS users: FreeBSD's kernel, network stack, jails, ZFS, DTrace, and Capsicum — mature subsystems that Linux either lacks or implements differently. Plus a genuinely unified base system rather than a collection of independently-packaged GNU components.
-
For both: A system where provenance is tracked from source commit through build through deployment, with explicit generation metadata and rollback. Neither upstream currently offers the full chain that Fruix is building.
The value proposition is clear. The question is not "is this worth doing?" but "can a small team sustain the maintenance burden of a hybrid?" That is an execution risk, not a design flaw.
Is Shepherd a Good Fit?
Shepherd is a reasonable choice, with caveats:
Strengths: Scheme-native service definitions compose naturally
with the Guix-style system declaration. Shepherd's service graph
model is more expressive than rc.d's linear ordering. The existing
FreeBSD-specific service helpers (freebsd-rc-service,
freebsd-tmpfs-service, etc.) demonstrate that Shepherd can drive
FreeBSD's native tools without friction.
Concerns: FreeBSD's kernel hands off to /sbin/init with
specific expectations about early boot (single-user mode, fsck,
console configuration). Shepherd-as-PID-1 must handle these
correctly or the system will be fragile in recovery scenarios.
FreeBSD jails expect their own init process and service tree — jail
integration will need either per-jail Shepherd instances or a clean
delegation model. The current implementation does not yet address
jail service management, and this should be a design priority before
jails are documented as a supported feature.
The rc.d bridge mode provides a safe fallback, which is wise. But the long-term health of the project depends on Shepherd-as-PID-1 being robust enough that operators trust it for production FreeBSD workloads. This requires focused testing of edge cases: kernel panic recovery, single-user boot, fsck failures, and unclean shutdown handling.
2. Documentation Strategy
Documentation Layers Needed
The system requires five distinct documentation layers, in priority order:
Layer 1: Conceptual Guide ("What is Fruix?") Priority: Critical. This is the first thing any visitor encounters. It must explain:
- What the system is and why it exists
- The FreeBSD base + Guix store + Shepherd init architecture
- How it differs from stock FreeBSD and stock Guix
- The mental model: declarations, closures, generations, store paths
- Who this is for and what the tradeoffs are
This cannot be derived from either upstream's documentation. It must be written from scratch. Target length: 10-15 pages. This is the single highest-priority documentation deliverable.
Layer 2: Quick-Start / Installation Guide Priority: Critical. A new user must be able to:
- Obtain or build an installer image
- Install Fruix on a machine (real or virtual)
- Write a minimal system declaration
- Build and deploy it
- Understand what happened and what to do next
This is partially derivable from the existing phase reports but needs
to be written as a clean, linear narrative. The bootstrap procedure
(prepare-builder) should be documented separately as a contributor
guide, not as the primary installation path.
Layer 3: System Declaration Reference
Priority: High. A complete reference for the operating-system
record and all its constituent types:
freebsd-base,freebsd-source- Package lists and profiles
storage-layout, partitions, filesystems- Users, groups, services
- Shepherd service declarations
- Init modes, loader entries, rc.conf entries
This is the equivalent of the NixOS options reference or the Guix system configuration reference. It should be generated or semi-generated from the Scheme record definitions where possible.
Layer 4: Command Reference
Priority: High. Every fruix subcommand documented:
fruix system build|image|install|deploy|reconfigure|switch|rollbackfruix source materializefruix native-build promote- Node-local commands (
fruix system status,fruix system build-base)
Format: man pages (see tooling recommendation below).
Layer 5: FreeBSD Integration Guide Priority: Medium. For users with FreeBSD background:
- What changed from stock FreeBSD and why
- Where familiar tools still work (
ifconfig,gpart,service) - Where they don't (hand-editing
/etcfiles) - How jails, ZFS, DTrace, and other FreeBSD features interact with the Guix layer
- Migration guide from stock FreeBSD
Reconciling Upstream Documentation
FreeBSD man pages: The stock FreeBSD man pages for kernel
interfaces, system calls, library functions, and file formats
(sections 2, 3, 4, 5, 7, 9) can be shipped largely unchanged. Section
1 (user commands) and section 8 (system administration) need auditing:
commands that Fruix overrides or wraps should get Fruix-specific man
pages. Section 5 pages for files that Fruix renders (e.g., fstab(5),
rc.conf(5), loader.conf(5)) should include a note that these are
managed by the system declaration and should not be hand-edited.
Guix manual: The Guix manual's conceptual sections on the store, derivations, profiles, and generations are excellent educational material but describe GNU/Linux-specific implementation details. The concepts can be adapted; the specifics must be rewritten. The Guix system configuration reference is a good structural model but every concrete example needs FreeBSD equivalents. The package management sections are largely inapplicable until Fruix has its own package management story beyond the base system.
What requires full rewrites:
- Boot process documentation (entirely different from both upstreams)
- Service management (Shepherd on FreeBSD is unique to Fruix)
- System installation (Fruix's installer is its own thing)
- Package definitions (FreeBSD native builds differ from Guix derivations)
- Store layout (
/frx/storevs./gnu/store, different hash scheme)
What can be adapted with light editing:
- FreeBSD kernel and hardware documentation
- Networking fundamentals (FreeBSD's stack is unchanged)
- Filesystem semantics (UFS/ZFS behaviour is unchanged)
- Guix conceptual material on functional package management
Highest-Priority Gaps
For FreeBSD users arriving:
- "Where is
rc.conf?" — How system configuration works now - "How do I install software?" — The package/profile model
- "Where is
/usr/local?" — The store and profiles explained - "How do I update the system?" — Generations and reconfigure
For Guix/NixOS users arriving:
- "Where is
guix system reconfigure?" — The Fruix equivalents - "Why is the kernel special?" — The FreeBSD base boundary
- "Where are the packages?" — The current ecosystem scope
- "How do services work?" — Shepherd on FreeBSD specifics
Tooling Recommendation
Man pages for command references and file format references. Use
mdoc(7) format (FreeBSD's native macro set). Reasons:
- Offline availability without extra tooling
- Consistent with the FreeBSD base tradition
- Discoverable via
apropos(1) - Well-understood by the target audience
A static-site handbook for conceptual guides, the quick-start, and the system declaration reference. Recommended format: AsciiDoc processed with Asciidoctor. Reasons:
- Richer structure than Markdown (admonitions, cross-references, includes, conditional content)
- Generates HTML for a project website and PDF for offline use
- Lower barrier to contribution than Texinfo
- Used successfully by FreeBSD's own handbook toolchain (though they use DocBook/AsciiDoc)
Not Texinfo. While Guix uses Texinfo and it has strengths (Info reader integration, structured cross-referencing), it is an unfamiliar format for FreeBSD contributors and adds friction for a project that needs to attract contributors from both communities. The Guix community knows Texinfo; the FreeBSD community does not. AsciiDoc is a pragmatic middle ground.
Man Pages vs. Separate Handbook
Both. They serve different purposes:
- Man pages for "how do I use this command right now?" — terse,
reference-oriented, available offline via
man(1). - Handbook for "how does this system work and how do I configure it?" — narrative, conceptual, cross-referenced.
Consider a new man section (e.g., section fruix or a subsection
of section 8) for Fruix-specific system administration. This would
include:
fruix(8)— overview and subcommand indexfruix-system(8)— system management commandsfruix-deploy(8)— deployment workflowshepherd-freebsd(8)— Shepherd integration specificsoperating-system.scm(5)— system declaration file format
3. Completeness Assessment
Installer / System Installation
Current state: A working non-interactive installer (fruix system install) and a prototype Newt TUI. Bootable installer images and
UEFI ISOs can be produced. Storage layout supports GPT + EFI + UFS +
optional swap.
Gaps:
- ZFS root support (high demand from FreeBSD users, model prepared but not implemented)
- Disk encryption (GELI/LUKS equivalent)
- Network-based installation (PXE, remote image fetch)
- Post-install first-boot wizard
Blocking? No — the current installer is functional for basic use.
Upstream reuse? FreeBSD's bsdinstall is not easily adapted
(tightly coupled to rc.conf-based configuration). The Guix installer
is structurally similar but Linux-specific. The current from-scratch
approach is correct.
Priority: Medium. The installer works. ZFS support should be high
priority given the FreeBSD audience.
Bootstrap and Cross-Compilation
Current state: A prepare-builder script that builds a local
Guile, Shepherd, and Guix checkout on a vanilla FreeBSD host.
Produces a reusable builder at ~/.local/opt/fruix-builder. No
cross-compilation support.
Gaps:
- No automated builder provisioning (e.g., a single
curl | shbootstrap like Nix or Guix) - No cross-compilation from non-FreeBSD hosts
- Builder preparation is slow and requires manual steps
- Guile subprocess stability workarounds suggest fragility
Blocking? Partially — the current bootstrap works but is brittle
and underdocumented. A new contributor cannot easily get started.
Upstream reuse? Guix's bootstrap story is heavily Linux-specific.
FreeBSD's pkg bootstrap is irrelevant. This must be solved
independently.
Priority: High. The bootstrap experience is the first thing any
contributor or tester encounters.
Init and Service Management Completeness
Current state: Shepherd runs as PID 1 or as an rc.d-launched service. A handful of FreeBSD-specific service helpers exist (loopback, tmpfs, user/group, rc-service wrapper). Basic services (SSH, logging) are configured.
Gaps:
- No Shepherd services for: cron, syslog (native), NTP, firewall (pf/ipfw), devd, automount, powerd, jail management
- No service dependency graph for full FreeBSD boot sequence
- No
herdequivalent for interactive service management documentation - No watchdog or health-check integration
- Jail service isolation not designed
Blocking? Yes, for production use. The current set is sufficient
for a demo VM but not for a real workload.
Upstream reuse? Guix's service definitions are a structural model
but every concrete service must be rewritten for FreeBSD. FreeBSD's
rc.d scripts can be wrapped via freebsd-rc-service as a bridge.
Priority: High. Service completeness directly determines whether
the system is usable for real tasks.
System Configuration Interface
Current state: System declarations are Scheme files loaded by
the fruix CLI. There is no interactive configuration tool, no
guix system reconfigure equivalent that reads from a canonical
system path, and no guix system vm for testing.
Gaps:
- No canonical system declaration location (e.g.,
/etc/fruix/system.scm) - No
fruix system reconfigurethat reads from a default location (the node-local version exists but requires the declaration to be embedded in the closure) - No
fruix system vmfor testing declarations without deploying - No configuration validation beyond storage layout checks
- No configuration diffing ("what would change if I reconfigure?")
Blocking? Partially. The current workflow (explicit paths) works
but is not ergonomic for daily use.
Upstream reuse? Guix's guix system subcommands are the direct
model. NixOS's nixos-rebuild is another.
Priority: Medium-high. Ergonomic configuration management is a
core value proposition.
Security Model and Updates
Current state: Store paths are content-addressed. FreeBSD sources can be SHA256-pinned. No signing, no binary cache, no substitute mechanism, no trust model.
Gaps:
- No cryptographic signing of store paths or closures
- No binary substitute/cache infrastructure
- No trust model for third-party packages
- No security advisory integration
- No automatic update mechanism
- No verified boot chain
Blocking? Not for development use. Blocking for any multi-user
or production deployment.
Upstream reuse? Guix's substitute mechanism and signing
infrastructure are well-designed and could be adapted. FreeBSD's
pkg signing could inform the trust model.
Priority: Medium for now, becomes critical before any production
use.
Jail and Virtualisation Integration
Current state: Jails are mentioned in the bootstrap reports (jail-based build isolation was explored) but no jail management is integrated into the system model or Shepherd services. bhyve is blocked by nested VT-x under Xen.
Gaps:
- No
jailrecord type in the operating-system model - No Shepherd service for jail lifecycle management
- No jail-specific store sharing (read-only nullfs mounts of
/frx/storeinto jails) - No jail networking integration (vnet, epair)
- No
fruix jailsubcommands - No bhyve integration
Blocking? Not for basic use, but jails are a flagship FreeBSD
feature and a major reason to choose FreeBSD. Users will expect
them.
Upstream reuse? FreeBSD's jail(8) and jail.conf(5) are the
foundation. Guix has no jail equivalent. This must be designed from
scratch, but the operating-system model provides a natural extension
point.
Priority: Medium-high. Jail integration is part of the value
proposition. A basic jail record type with Shepherd lifecycle
services should be an early post-MVP feature.
Networking Configuration
Current state: rc-conf-entries in the operating-system model
handle network interface configuration via the traditional rc.conf
mechanism. No declarative networking model exists.
Gaps:
- No first-class network interface declarations in the system model
- No declarative firewall (pf/ipfw) configuration
- No VLAN, bridge, or lagg configuration
- No wireless configuration
- No DNS resolver configuration beyond rc.conf
- Network configuration split between rc.conf entries and potential Shepherd services
Blocking? Partially. Basic DHCP works via rc.conf. Static configuration works via rc.conf entries. But the lack of a declarative network model means networking is the least "Guix-like" part of the system. Upstream reuse? Guix's networking services (for Linux) are a structural model. FreeBSD's rc.conf networking is the current backend and works. The gap is the declarative layer on top. Priority: Medium. Networking works via rc.conf bridge. A declarative model is important for the vision but not blocking.
User-Facing Tooling
Current state: The fruix CLI exists with subcommands for
system building, deployment, source management, and native builds.
Node-local commands exist for build, reconfigure, switch, rollback,
and status. No guix pull equivalent.
Gaps:
- No channel/repository update mechanism (
guix pullequivalent) - No user-level package management (
guix installequivalent) - No user profiles or per-user environments
- No
fruix searchfor finding packages - No garbage collection command (
fruix gc) - No generation listing or management UI
- No system diff/dry-run tooling
Blocking? Partially. The system can be managed but the
experience is manual and requires understanding internals.
Upstream reuse? Guix's CLI is the direct model for all of
these. The commands exist in Guix; they need FreeBSD-specific
implementations.
Priority: High for gc and generation management. Medium for
user profiles. Channel updates become critical when there is a
package ecosystem to update.
Release Engineering / Reproducible Build Infrastructure
Current state: The system can produce disk images and ISOs. No CI, no automated testing, no release process, no reproducibility verification.
Gaps:
- No CI/CD pipeline
- No automated build-and-boot testing
- No reproducibility verification (build twice, compare)
- No release versioning scheme
- No changelog generation
- No upgrade path between releases
Blocking? Not for development. Blocking for any claim of "reproducibility" — without verification, it is an aspiration, not a property. Upstream reuse? Guix's build farm (Cuirass) is the model but is Linux-specific. FreeBSD's release engineering toolchain could inform the process. Priority: Medium. Should be established before the project seeks external users.
Testing and CI
Current state: A collection of shell-script tests in
fruix-bootstrap/tests/ covering build system phases, native
builds, and system materialisation. Validation is done manually on
XCP-ng VMs and locally via QEMU. No automated CI.
Gaps:
- No automated test suite that runs on commit
- No boot-level testing (VM boots, services come up, assertions pass)
- No unit tests for Scheme modules
- No integration tests for the CLI
- No regression tests for store-path identity stability
Blocking? Not for a solo developer. Blocking for accepting contributions — without CI, contributions cannot be validated. Upstream reuse? Guix's test infrastructure is extensive but Linux-specific. FreeBSD's CI (Phabricator/GitHub Actions) could host the pipeline. Priority: High. Even a minimal "build an image and boot it in QEMU" CI pipeline would significantly improve development velocity and confidence.
Overall Verdict
This project is worth pursuing. The philosophical fit between FreeBSD and Guix is stronger than it appears on the surface, and the implementation demonstrates that the core ideas translate cleanly. The codebase shows thoughtful engineering: the store-path design, the three-layer profile model, the executor abstraction, the source-provenance tracking, and the safety boundaries around storage operations are all evidence of considered design rather than expedient hacking.
The project has crossed the hardest threshold: proving that the hybrid can work. Shepherd runs as PID 1 on FreeBSD. The store semantics function. Systems can be declared, built, deployed, and rolled back. Installer media can be produced. The remaining work is substantial but straightforward — it is engineering, not research.
The Single Most Important Next Step
Write the conceptual guide. The project's biggest risk right now is not technical — it is legibility. A capable developer encountering this project today would face 93 markdown files, 44 Scheme modules, and no clear entry point explaining what the system is, how it works, or how to use it. The phase reports are a development journal, not documentation.
A 10-15 page document explaining the architecture, the mental model, and the basic workflows would:
- Enable contributors to onboard without archaeology
- Force design decisions to be stated explicitly (which sometimes reveals inconsistencies worth fixing)
- Provide a foundation for all other documentation
- Make the project presentable to the FreeBSD and Guix communities
The second priority is the bootstrap experience. A contributor should be able to go from "I have a FreeBSD machine" to "I have built and booted a Fruix image" in under an hour, with clear instructions at every step. The current bootstrap pipeline works but is not documented as a user-facing workflow.
Everything else — more packages, more services, jails, ZFS, CI — is important but secondary. Without documentation and an accessible onboarding path, the project remains a brilliant proof of concept that only its author can operate.