Files

15 KiB

Fruix plan and package model notes

This document captures the current Fruix direction for package identity, base artifacts, ports integration, installer/product milestones, and a few open issues discovered during current work.

Current package model

Fruix currently has three practical lanes.

1. FreeBSD base artifacts

These are the packages that represent FreeBSD base/system pieces:

  • kernel
  • world
  • runtime slice
  • bootloader slice
  • headers / SDK slice

Today there are two forms.

A. Host-staged bootstrap/prototype packages

Examples:

  • freebsd-kernel
  • freebsd-userland
  • freebsd-runtime
  • freebsd-bootloader
  • freebsd-rc-scripts
  • freebsd-networking
  • freebsd-openssh

These use copy-build-system and stage files from the current host system. They are useful for bootstrap, recovery, and validating the Fruix store/system model before all artifacts are built natively by Fruix.

B. Native Fruix-built base artifacts

Examples:

  • freebsd-native-kernel
  • freebsd-native-world
  • freebsd-native-runtime
  • freebsd-native-bootloader
  • freebsd-native-headers

These use freebsd-kernel-build-system or freebsd-world-build-system and are built from a declared freebsd-base, which itself points at a declared freebsd-source.

This is the real long-term direction.

2. FreeBSD ports-style packages

Examples today:

  • freebsd-gmake
  • freebsd-bash
  • freebsd-autotools
  • freebsd-openssl
  • freebsd-zlib
  • freebsd-nodejs
  • freebsd-npm
  • freebsd-ripgrep
  • freebsd-tmux
  • freebsd-neovim

These are currently hand-written host-staged packages that mostly repackage host-installed files from /usr/local into Fruix store objects.

They are useful as a bootstrap path, but they are still host-dependent and can fail when the builder host does not have the exact expected files installed.

3. Other Fruix packages

Long term, packages that are not part of FreeBSD base or directly mirrored from Ports should just become normal Fruix packages.

This should not be treated as a special "Guix compatibility" category forever. The likely end state is simply:

  • Fruix has its own package model
  • packages may be inspired by, imported from, or rewritten from Guix packages
  • but they become normal Fruix packages with Fruix-native metadata and build policy

Naming direction

Current names reflect history:

  • freebsd-* usually means host-staged/bootstrap/prototype packages
  • freebsd-native-* means Fruix-native built base artifacts

That naming is likely backwards for the long term.

Proposed future rename

When the native path is complete enough to become the canonical path:

  • rename freebsd-native-* -> freebsd-*
  • rename current host-staged/bootstrap packages to something like:
    • freebsd-bootstrap-*
    • or freebsd-host-staged-*

Recommendation:

  • do not rename immediately while both paths are still actively in use
  • first finish the native base path and make it the default serious workflow
  • then do a compatibility transition with aliases/deprecation

That keeps user-facing names aligned with the intended product identity:

  • freebsd-* should mean the real Fruix-managed thing
  • bootstrap/prototype packages should say so explicitly

Replacement plan already visible in the code

The current host-staged replacement order already suggests the intended migration:

  1. first-wave
    • freebsd-kernel
    • freebsd-bootloader
  2. second-wave
    • freebsd-runtime
    • freebsd-libc
    • freebsd-userland
    • freebsd-rc-scripts
  3. third-wave
    • freebsd-networking
    • freebsd-openssh
  4. fourth-wave
    • freebsd-kernel-headers
    • freebsd-clang-toolchain
  5. fifth-wave
    • development/user packages like:
      • freebsd-gmake
      • freebsd-autotools
      • freebsd-openssl
      • freebsd-zlib
      • freebsd-nodejs
      • freebsd-ripgrep
      • freebsd-neovim
      • etc.

The broad strategy should remain:

  • bootstrap with host-staged packages
  • move base/system pieces to native Fruix builds from declared source
  • then replace brittle host-staged development/profile/package layers

FreeBSD version/base story

Fruix already has the core abstractions needed to build arbitrary declared FreeBSD bases:

  • freebsd-source
  • freebsd-base
  • freebsd-native-kernel-for
  • freebsd-native-world-for
  • freebsd-native-runtime-for
  • freebsd-native-bootloader-for
  • freebsd-native-headers-for

That means Fruix can already express things like:

  • FreeBSD 14.2 source
  • a 14.2 base definition
  • a 14.2 runtime/world/kernel artifact build

What is still missing is a polished user-facing workflow for this.

Desired user-facing capability

Eventually Fruix should make this easy:

  • build FreeBSD 14.2 runtime/world/kernel as first-class store objects
  • assemble a runnable environment from those artifacts
  • run tools like ls from that declared base/runtime cleanly

Today this is possible in principle through Scheme-level composition, but not as a polished one-line CLI workflow.

Ports strategy

A pragmatic path for Ports support is probably best.

Phase 1: make them work first

Start with a simple path that fits Fruix reasonably well and actually works:

  • repackage installed host Ports or pkg artifacts into Fruix store objects
  • record provenance and host/source assumptions clearly
  • keep them usable inside Fruix profiles and system closures

This is not perfect or fully reproducible, but it is practical.

Phase 2: auto-package installed/pkg artifacts

Add a more automated import story for Ports-derived software, for example by:

  • reading installed package metadata
  • reading package file lists/manifests
  • generating Fruix package definitions or manifests automatically

This would automate the current hand-written host-staged ports package pattern.

Phase 3: source-built Ports under Fruix control

Longer term:

  • build Ports in jails under Fruix executor policy
  • import resulting artifacts into the Fruix store
  • make them first-class Fruix package identities

This is a bigger project and should likely wait until the native base and closure/image stories are more mature.

Native build identity and executor model

These remain core product ideas.

Native base builds should be first-class Fruix objects

Native build results should feel like real immutable Fruix identities:

  • world
  • kernel
  • bootloader
  • headers / SDK

Not just files in a temporary result area.

Desired model:

  • staging/result roots are temporary build/result areas
  • /frx/store/... remains the real immutable identity
  • promoted native artifacts record:
    • executor
    • source identity
    • provenance
    • build policy

Status: mostly implemented / in progress.

Fruix already has native build result promotion and store objects, but the full user-facing workflow still needs more polish.

Executor model

The intended executor abstraction should cover the same declared build with multiple execution backends:

  • host
  • jail
  • ssh-guest
  • self-hosted
  • later maybe remote-builder

Desired mental model:

  • same declared source
  • same expected outputs
  • same provenance shape
  • executor is a policy choice, not an architecture fork

Status: substantially in progress.

We already have a real executor model and jail-backed execution is becoming a first-class local primitive.

Promotion step

Desired lifecycle:

  1. build runs somewhere
  2. result lands in a staging/result root
  3. Fruix validates result shape and metadata
  4. Fruix promotes successful results into store objects
  5. system declarations consume promoted artifacts

Status: mostly implemented / in progress.

Jails as the normal local isolation primitive

A strong Fruix-on-FreeBSD direction is to treat jails as the normal local build and assembly isolation primitive.

Current progress:

  • copy builds can run in jails
  • native build commands can run in jails
  • package profile materialization uses jailed merge policy
  • system closure profile/development-profile/build-profile assembly uses jailed union-tree policy
  • rootfs population helpers now run through the jailed helper path too
  • image/installer/ISO assembly now records staged jail metadata and uses a dedicated assembly privileged policy surface
  • install-time storage layout application is now executed as a rendered privileged helper script under that policy instead of many scattered host-side privileged calls
  • remaining image resize and raw-file install attach/detach paths now also run through dedicated assembly privileged helper scripts, further shrinking the host-side privileged call surface
  • network is disabled by default in these jailed paths
  • union assembly mounts are now much smaller and omit host /etc and devfs
  • direct block-device system install is now an explicit opt-in under the assembly policy instead of the default path

Next likely steps:

  • keep pushing filesystem/image construction toward file-backed or jailed helper paths where practical
  • decide whether any remaining assembly-time host assumptions should move behind an even narrower dedicated helper or runner boundary

Runtime / development / build separation

Fruix should keep three layers conceptually distinct:

  • runtime profile
  • development profile
  • build profile / build environment

The build layer should be:

  • more sanitized
  • more reproducible
  • less interactive
  • closer to the exact contract needed for buildworld/buildkernel and other reproducible builds

Status: partially present, but likely still needs clearer user-facing and internal modeling.

Installed systems as real Fruix nodes

Installed systems should increasingly feel like managed Fruix machines rather than deployed images with helper scripts.

Desired node-local lifecycle:

  • fruix system build
  • fruix system reconfigure
  • fruix system upgrade
  • fruix system build-base
  • fruix system deploy
  • status
  • switch
  • rollback

Key requirement:

  • no hidden dependence on a bootstrap checkout
  • no hidden dependence on ambient host paths
  • no hidden dependence on ad hoc bootstrap-only mechanics

Status: partial / in progress.

See also: docs/plan_2.md for the follow-on lifecycle plan focused on node-local management, deploy hardening, and the later source/pin/upgrade path.

Source / pin / update / lock workflow

Source provenance should not only be recorded after the fact; it should become part of the operator workflow.

Desired lifecycle:

  • source object
  • pin/lock/update decision
  • build result
  • promoted base artifacts
  • deployed system generation

This is very Fruix, but it does not have to be the first user-facing milestone.

Status: mostly open.

Publication / substitutes / build once deploy many

Before scaling self-hosting too far, Fruix should have at least a minimal story for:

  • store export/import
  • artifact publication
  • fetch/substitute-like reuse
  • trust/provenance verification

Goal:

  • build once
  • publish/store/export once
  • deploy many

Status: open.

Product/operator workflow

A good small canonical Fruix lifecycle is still a useful north star:

  1. update/pin source inputs
  2. build base artifacts
  3. build system closure
  4. publish/promote results
  5. deploy/switch
  6. rollback if needed

Fruix feels most like Fruix when the operator thinks in terms of:

  • declared sources
  • promoted base artifacts
  • immutable system generations
  • executor policy

and not in terms of raw make buildworld mechanics.

fruix / fruix-bootstrap boundary

The contract should be:

fruix-bootstrap

  • foreign FreeBSD bring-up
  • installer media
  • host-side bootstrap/install/deploy helpers
  • enough machinery to seed the first Fruix node onto disk

fruix

  • package definitions
  • system definitions
  • node CLI
  • build/reconfigure/build-base/deploy logic
  • future source/update/lock/publish/substitute logic

The installer should install a pinned Fruix revision, and after first boot the node should move forward using Fruix itself, not the bootstrap repo.

Status: needs to be made explicit.

Installer and pinned Fruix workflow

A near-term product milestone should remain:

  • install a pinned Fruix onto a machine through a small TUI installer
  • then have the booted node depend only on Fruix from there onward

Suggested phases:

Phase A — establish source of truth

  • define fruix vs fruix-bootstrap
  • make installed systems point at pinned Fruix

Phase B — guided installation

  • TUI installer on the current installer ISO
  • generate declaration + pin + install metadata
  • boot into a usable Fruix node

Phase C — operational usability

  • ensure node-local lifecycle is complete enough
  • add basic export/import or publish/fetch for artifacts

Phase D — Fruix-native lifecycle

  • source update/lock/compare
  • later upgrade policy
  • later substitutes/cache/trust refinement
  • later deeper jail executor refinement

Current open issues and recent lessons

1. Host-staged inputs are still brittle

Recent examples:

  • unreadable host file inside /etc/defaults (devfs.rules) broke system build hashing until Fruix stopped staging the entire directory
  • freebsd-autotools currently assumes host paths such as /usr/local/share/autoconf2.72 exist

This reinforces the plan:

  • host-staged packages are bootstrap tools
  • they should not remain the canonical product identity forever

2. System build validation is still exposing host-assumption bugs

Recent validation has moved past one host-input bug only to reveal others. This is useful and should continue:

  • fix host-assumption bugs while bootstrap packages still exist
  • keep pushing toward native-built base artifacts and less host dependence

3. Union-tree mount minimization is the right direction

Recent work reduced union/closure/profile assembly jail mounts to a much smaller runtime surface.

That direction has now continued into:

  • rootfs population helpers
  • image/installer/ISO assembly under a dedicated privileged builder policy

The next refinement is to reduce the remaining privileged host-side surface further, especially around final image/device manipulation.

Summary

Short version:

  • native base artifacts are the real Fruix direction
  • current freebsd-* host-staged packages are bootstrap/prototype tools
  • once the native path is complete enough, rename so the real Fruix-managed artifacts get the canonical freebsd-* names
  • Ports should probably start with a simple "works first" integration path before aiming for full source-built ports under Fruix control
  • packages imported or inspired from Guix should ultimately just become normal Fruix packages
  • the product center of gravity should remain:
    • declared source identity
    • promoted immutable artifacts
    • FreeBSD-native execution/isolation
    • small opinionated operator workflows