Files
fruix/docs/GUIX_DIFFERENCES.md

14 KiB

Fruix differences for Guix sysadmins

Date: 2026-04-06

This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs.

The short version is:

  • Fruix is strongly Guix-inspired
  • it tries to preserve the important semantic properties
  • but it does not copy Guix mechanically where FreeBSD or Fruix-specific concerns make a different representation clearer

Big picture

Fruix keeps the core Guix ideas you would expect:

  • declarative inputs
  • content-addressed store paths
  • immutable build outputs
  • rollback-friendly deployment identity
  • provenance tied to the deployed closure rather than mutable in-place state

But Fruix differs in at least four major ways today:

  1. it targets FreeBSD, not GNU/Linux
  2. its system frontend is currently smaller:
    • fruix system build|rootfs|image|install|installer|installer-iso
  3. it treats FreeBSD source provenance as an explicit deployment concern
  4. its installed-system generation model is still earlier-stage than Guix's mature system-generation workflow

Similarities to Guix

If you know Guix System, these Fruix properties should feel familiar.

Immutable deployment identity

A deployed Fruix system is identified primarily by its closure path in /frx/store, not by mutable files under /etc or /usr/local.

/run/current-system

Fruix keeps the Guix-like runtime convention:

  • /run/current-system

That path remains the active runtime boundary used by activation and service wiring.

Rollback-friendly semantics

Fruix avoids in-place mutation of an older deployed closure.

The validated rollback story now has two layers:

  • declaration-level rollback by rebuilding/redeploying an earlier declaration
  • installed-system rollback between already-recorded generations on the target itself

That is Guix-like in spirit, although Fruix still exposes a smaller installed-system workflow than Guix's more mature reconfigure model.

Generation-style metadata and roots

Fruix now records explicit installed-system generation state under:

  • /var/lib/fruix/system

and explicit retention roots under:

  • /frx/var/fruix/gcroots

This preserves the basic Guix idea that deployment state and reachability should be represented explicitly rather than inferred from whatever happens to be on disk.

Important differences from Guix

1. Fruix does not mirror Guix's on-disk generation layout 1:1

This is intentional.

Guix heavily reuses its profile-generation model and represents a lot of meaning through symlink structure such as profile links and system generation links.

Fruix keeps the semantics but uses a more explicit metadata-oriented layout for installed systems.

Current Fruix layout starts as:

/var/lib/fruix/system/
  current -> generations/1
  current-generation
  generations/
    1/
      closure -> /frx/store/...-fruix-system-...
      metadata.scm
      provenance.scm
      install.scm

After a validated installed-system switch, Fruix also records:

/var/lib/fruix/system/
  rollback -> generations/1
  rollback-generation
  generations/
    2/
      closure -> /frx/store/...-fruix-system-...
      metadata.scm
      provenance.scm
      install.scm

Why Fruix does this:

  • it makes deployment state easier to inspect directly
  • it gives FreeBSD-specific install and provenance details a clearer home
  • it keeps room for richer operator tooling later without losing the Guix properties

So the rule of thumb is:

  • same semantics as Guix where practical
  • not necessarily the same directory names or symlink vocabulary

2. Fruix currently keeps /run/current-system simple

Even though Fruix now records explicit generation metadata under /var/lib/fruix/system, it still keeps:

  • /run/current-system -> /frx/store/...

rather than making /run/current-system point through a generation directory first.

This was chosen to preserve compatibility with the already-validated activation and runtime model while adding explicit generation metadata separately.

3. Fruix treats source provenance more explicitly

Guix sysadmins are used to derivation/store provenance. Fruix adds an extra emphasis because its current FreeBSD story depends on explicit source selection and materialization.

Fruix routinely records:

  • declared FreeBSD source object metadata
  • materialized source store paths
  • source materialization metadata files
  • closure-level store layout metadata
  • install metadata on the target system

This is more prominent in Fruix than most Guix system docs because FreeBSD base/source identity has been an active design concern for this project.

4. Fruix has installer-media workflows as first-class system actions

Guix has installation media and image workflows, but Fruix's current system frontend makes these especially explicit because they are still part of the active architectural bring-up story.

Current Fruix actions include:

  • fruix system install
  • fruix system installer
  • fruix system installer-iso

That is a little more deployment-medium-oriented than the mental model many Guix users start with.

5. Device naming is more environment-sensitive than Guix users may expect

Because Fruix is on FreeBSD, the install target device naming is not the same as on Linux.

Validated examples:

  • installer disk-image path under QEMU:
    • /dev/vtbd1
  • installer ISO path under QEMU:
    • /dev/vtbd0
  • installer ISO path under XCP-ng:
    • /dev/ada0

So compared with Guix-on-Linux intuition, Fruix operators should be more explicit about target-device selection during installation and installer-media validation.

6. Fruix now has a minimal installed-system generation command surface, but it is still smaller than Guix's

This remains the biggest operational gap, but it is no longer a complete gap.

Installed Fruix systems now provide a larger in-guest helper surface:

  • fruix system build
  • fruix system reconfigure
  • fruix system status
  • fruix system switch /frx/store/...-fruix-system-...
  • fruix system rollback

What this gives you today:

  • explicit current-generation tracking
  • explicit rollback-generation tracking
  • in-place switching between already-staged closures on the installed target
  • rollback without reinstalling the whole system image again
  • a validated in-node build path that can read the embedded current declaration inputs
  • a validated in-node reconfigure path that can build a candidate closure locally and stage it as the next generation

What it still does not give you yet compared with Guix:

  • a mature end-to-end upgrade story for advancing declared inputs automatically
  • automatic closure transfer/fetch as part of switch
  • a higher-level deploy workflow across multiple machines or targets
  • the broader generation-management UX Guix operators expect

So if you come from Guix, assume that Fruix now has:

  • strong closure/store semantics
  • explicit install artifacts
  • explicit generation metadata roots
  • a real installed-system build/reconfigure/switch/rollback surface
  • but not yet the fuller long-term node/deployment UX that Guix users may expect

7. Fruix keeps Guix-like store semantics, but not Guix/Nix hash-prefix machinery exactly

Fruix still uses immutable store paths under:

  • /frx/store

and it still treats a store path as a deployment identity boundary.

But Fruix now intentionally differs from Guix/Nix in how the visible store-path prefix is constructed.

Current Fruix policy is:

  • centralize store-path naming behind shared helpers
  • hash a small semantic identity record rather than copying Nix's historical path-hash formula exactly
  • include at least:
    • object kind
    • logical/display name
    • output name
    • payload or manifest identity
    • hash-scheme version marker
  • truncate the visible SHA-256 prefix to 160 bits
  • render that visible prefix as 40 hex characters

Why Fruix does this instead of copying Guix/Nix exactly:

  • the main goal was shorter store prefixes, not Nix compatibility for its own sake
  • distinct outputs should have distinct identities because out, lib, debug, and doc are semantically different artifacts
  • Fruix wanted one central policy point that can be swapped later without touching every materializer again
  • Fruix did not want to inherit Nix's legacy details unless they provide clear value here
    • custom base32 alphabet and bit ordering
    • compressed/XOR-folded path hashes
    • exact historical output:out / source path-hash conventions

So compared with Guix:

  • the important semantic property is the same:
    • different store objects should get different immutable identities
  • the exact printable prefix algorithm is intentionally simpler in Fruix today

For Guix-familiar operators, the practical takeaway is:

  • still think of /frx/store/... paths as immutable deployment identities
  • do not assume Fruix store prefixes are byte-for-byte comparable to Guix/Nix ones
  • expect Fruix to prefer a simpler, centralized naming policy unless exact Guix/Nix behavior becomes necessary later

8. Fruix can expose a separate in-system development profile overlay

For the validated Phase 20.1 path, Fruix can now expose development tooling separately from the main runtime profile.

On those systems, Fruix exposes:

  • /run/current-system/development-profile
  • /run/current-development
  • /usr/local/bin/fruix-development-environment
  • /usr/include -> /run/current-system/development-profile/usr/include
  • /usr/share/mk -> /run/current-system/development-profile/usr/share/mk

The intent is:

  • keep the main runtime profile lean
  • expose headers, usr/share/mk, and selected toolchain commands explicitly
  • satisfy native FreeBSD buildworld/buildkernel expectations for canonical system paths when development support is enabled
  • avoid treating a development-heavy system image as the default runtime shape

Compared with Guix, this is conceptually similar to keeping development-oriented state separate from the main runtime identity, but Fruix currently expresses it as a system-attached development overlay rather than through Guix's broader profile/tooling model.

Fruix now also has a narrow guest self-hosted native-build prototype helper at:

  • /usr/local/bin/fruix-self-hosted-native-build

That helper does not just reuse the whole exported development shell wholesale. The validated prototype had to sanitize development-oriented variables such as MAKEFLAGS, CPPFLAGS, CFLAGS, CXXFLAGS, and LDFLAGS before buildworld, because those are convenient for smaller development tasks but can poison the FreeBSD world/kernel bootstrap path.

The practical Fruix takeaway is:

  • the development overlay makes native base work possible inside the system
  • but real guest self-hosted base builds still need their own stricter build contract

Fruix now also makes an explicit distinction between:

  • mutable native-build staging/results under:
    • /var/lib/fruix/native-builds
  • immutable promoted native-build identities under:
    • /frx/store

So a guest self-hosted run can stage a result tree locally, while the host can later promote that result into first-class Fruix store objects such as:

  • /frx/store/...-fruix-native-world-...
  • /frx/store/...-fruix-native-kernel-...
  • /frx/store/...-fruix-native-headers-...
  • /frx/store/...-fruix-native-bootloader-...
  • /frx/store/...-fruix-native-build-result-...

Compared with Guix, this is a more explicit split between:

  • a mutable result/staging area for native build execution
  • and the immutable store identities that Fruix treats as the real promoted result

Fruix also now models native-build placement more explicitly as an executor choice.

Current executor kinds are:

  • host
  • ssh-guest
  • self-hosted

So instead of treating:

  • host-driven builds
  • host-initiated guest builds
  • guest self-hosted builds

as three unrelated architectural forks, Fruix is moving toward one native-build result model with different executor policies attached to it.

Fruix also now lets system declarations consume a promoted native-build result bundle directly.

That means Fruix can now distinguish more explicitly between:

  • a mutable build/result root used during execution
  • an immutable promoted native-build identity in /frx/store
  • and a later system declaration that points at that promoted identity as an input

Compared with Guix, this is still Guix-like in the important sense that the deployed system is identified by immutable store objects, but Fruix is making the FreeBSD native-base result set itself a more explicit first-class declaration input than it was before.

Where Fruix is intentionally trying to improve on Guix's representation

Fruix is not trying to improve on Guix's core semantics. Guix already got those right.

Where Fruix is intentionally experimenting is mostly the representation layer:

  • make generation state more legible to operators
  • make provenance more visible without needing to reconstruct it mentally from symlink layout alone
  • separate:
    • runtime entry point (/run/current-system)
    • installed deployment state (/var/lib/fruix/system)
    • retention roots (/frx/var/fruix/gcroots)

That is why Fruix currently prefers a small per-generation metadata directory instead of only a bare generation link.

Practical operator advice for Guix users

If you are already comfortable with Guix, the safest Fruix mental model today is:

  1. think in terms of immutable closures and declarations first
  2. use fruix system build as the canonical starting point
  3. treat image/install/installer/installer-iso as deployment materializers built from the same declaration
  4. identify a deployment by:
    • closure path
    • source provenance metadata
    • install metadata
  5. think of rollback in two layers:
    • if the target already has the desired closure staged locally:
      • use fruix system rollback
    • otherwise:
      • redeploy the earlier declaration again

Status summary

Today Fruix is closest to Guix in:

  • store and closure semantics
  • declarative deployment identity
  • rollback-friendly immutability
  • /run/current-system runtime model

It differs most from Guix in:

  • FreeBSD platform details
  • source-provenance emphasis
  • installer-medium-oriented workflows
  • generation-layout representation
  • and an installed-system generation command surface that now exists, but is still much smaller than Guix's