385 lines
14 KiB
Markdown
385 lines
14 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
/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:
|
|
|
|
```text
|
|
/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 separate in-system development and build overlays
|
|
|
|
For the validated Phase 20 path, Fruix now distinguishes three layers instead of two:
|
|
|
|
- runtime profile
|
|
- development profile
|
|
- build profile
|
|
|
|
On those systems, Fruix exposes:
|
|
|
|
- development:
|
|
- `/run/current-system/development-profile`
|
|
- `/run/current-development`
|
|
- `/usr/local/bin/fruix-development-environment`
|
|
- build:
|
|
- `/run/current-system/build-profile`
|
|
- `/run/current-build`
|
|
- `/usr/local/bin/fruix-build-environment`
|
|
- canonical base-build compatibility links:
|
|
- `/usr/include -> /run/current-system/build-profile/usr/include`
|
|
- `/usr/share/mk -> /run/current-system/build-profile/usr/share/mk`
|
|
|
|
The intent is:
|
|
|
|
- keep the main runtime profile lean
|
|
- keep the development helper interactive and convenient
|
|
- keep the build helper narrower, more sanitized, and closer to the actual `buildworld` / `buildkernel` contract
|
|
- avoid treating a development-heavy or build-heavy image as the default runtime shape
|
|
|
|
Compared with Guix, this is conceptually similar to keeping development-oriented and build-oriented state separate from the main runtime identity, but Fruix currently expresses it as system-attached overlays rather than through Guix's broader profile/tooling model.
|
|
|
|
Fruix still has the guest self-hosted native-build prototype helper at:
|
|
|
|
- `/usr/local/bin/fruix-self-hosted-native-build`
|
|
|
|
But that helper now explicitly uses the build helper contract instead of trying to reuse the full interactive development shell. The practical Fruix takeaway is:
|
|
|
|
- the development overlay makes native base work possible inside the system
|
|
- the build overlay gives Fruix a stricter, more reproducible contract for real base builds
|
|
|
|
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
|