Files
fruix/docs/review.md
T
2026-04-08 10:52:02 +02:00

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-base record, 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.scm and 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:

  1. 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.

  2. 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.

  3. 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|rollback
  • fruix source materialize
  • fruix 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 /etc files)
  • 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/store vs. /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:

  1. "Where is rc.conf?" — How system configuration works now
  2. "How do I install software?" — The package/profile model
  3. "Where is /usr/local?" — The store and profiles explained
  4. "How do I update the system?" — Generations and reconfigure

For Guix/NixOS users arriving:

  1. "Where is guix system reconfigure?" — The Fruix equivalents
  2. "Why is the kernel special?" — The FreeBSD base boundary
  3. "Where are the packages?" — The current ecosystem scope
  4. "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 index
  • fruix-system(8) — system management commands
  • fruix-deploy(8) — deployment workflow
  • shepherd-freebsd(8) — Shepherd integration specifics
  • operating-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 | sh bootstrap 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 herd equivalent 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 reconfigure that reads from a default location (the node-local version exists but requires the declaration to be embedded in the closure)
  • No fruix system vm for 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 jail record type in the operating-system model
  • No Shepherd service for jail lifecycle management
  • No jail-specific store sharing (read-only nullfs mounts of /frx/store into jails)
  • No jail networking integration (vnet, epair)
  • No fruix jail subcommands
  • 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 pull equivalent)
  • No user-level package management (guix install equivalent)
  • No user profiles or per-user environments
  • No fruix search for 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:

  1. Enable contributors to onboard without archaeology
  2. Force design decisions to be stated explicitly (which sometimes reveals inconsistencies worth fixing)
  3. Provide a foundation for all other documentation
  4. 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.