11 KiB
Self-hosted Fruix development
This guide describes the current human-facing path for:
- building a Fruix installer ISO for the self-hosted development system
- installing that system onto a VM or machine
- turning the installed node into a practical Fruix development host
This path is now validated most strongly in VM workflows, especially the current XCP-ng loop, but the same basic process applies to any machine that can boot the generated installer ISO.
What this system is for
examples/system/self-hosted-dev.scm defines a Fruix node intended to:
- run Fruix locally
- expose SSH for operator access
- provide a development profile with the current editor/runtime tooling
- provide a sanitized build profile for native base/package work
- serve as a host for the pi-agent-style development workflow
Today that development profile includes the currently recovered developer tools, notably:
- Clang toolchain
- GNU make + FreeBSD make files
- Autotools
- OpenSSL + zlib
- sh + bash
- Node.js + npm
- ripgrep
- tmux
- neovim
Before you build
1. Prepare a Fruix builder host
The commands below assume:
- you are on a FreeBSD host
fruix-bootstraphas already prepared a builder root- this repo is checked out locally
The checkout entrypoint prefers a prepared builder root at:
~/.local/opt/fruix-builder
So if that builder exists, you can usually invoke Fruix from the checkout with:
./bin/fruix ...
2. Copy the example declaration and customize it
Start from the example:
cp examples/system/self-hosted-dev.scm my-self-hosted-dev.scm
At minimum, edit:
#:host-name#:root-authorized-keysfor remote SSH access- any user/account details you want to change
A minimal customization usually looks like:
(use-modules (fruix system freebsd)
(fruix packages freebsd))
(define self-hosted-development-operating-system
(operating-system
#:host-name "fruix-dev-1"
#:rc-conf-entries '(("clear_tmp_enable" . "YES")
("sendmail_enable" . "NONE")
("sshd_enable" . "YES"))
#:root-authorized-keys
'("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... you@example")
#:development-packages %freebsd-development-profile-packages
#:build-packages %freebsd-development-profile-packages))
If you skip #:root-authorized-keys, the installed node may still be usable
from the console, but it will not be ready for the normal remote development
loop.
3. Pick a store directory with real space
Image and installer builds can be large. Prefer a store directory with plenty of space, for example:
mkdir -p /var/tmp/fruix-selfhosted-store
Build the installer ISO
Run:
./bin/fruix system installer-iso ./my-self-hosted-dev.scm \
--system self-hosted-development-operating-system \
--store /var/tmp/fruix-selfhosted-store \
> /tmp/self-hosted-installer-iso.out
The command prints metadata rather than just one path. Recover the ISO path from the output like this:
iso=$(sed -n 's/^iso_image=//p' /tmp/self-hosted-installer-iso.out | sed -n '$p')
printf 'ISO: %s\n' "$iso"
Other useful fields in that output are:
iso_store_path=installer_closure_path=target_closure_path=installer_state_path=
If you want to inspect the whole result later, keep the metadata file:
less /tmp/self-hosted-installer-iso.out
Boot and install
1. Boot the ISO
Use the generated installer.iso in whichever way is convenient for your
hardware or VM platform:
- attach it as virtual media in a VM
- boot it as a CD/DVD image
- or write it to removable media using your usual FreeBSD workflow
For development use, give the target disk enough headroom. In practice, something in the 20-40 GiB range is a much better starting point than a minimal test disk.
2. Use the installer
The current intended human path from the booted installer is:
/usr/local/bin/fruix system installer-tui
That starts the current experimental Newt-based installer flow.
Notes:
- this is still marked experimental
- the ISO was built from your declaration, so this is the path to try first
- if you are installing in a VM, use the VM console for the TUI
3. If you need the direct CLI install path
If you want an explicit non-interactive install from the booted installer
environment, use fruix system install and pass the declaration file
explicitly.
Direct installs to /dev/... targets are intentionally gated. In the installer
shell, when you are already root, the simplest current pattern is:
export FRUIX_ASSEMBLY_PRIVILEGED_COMMAND=env
export FRUIX_ASSEMBLY_ALLOW_BLOCK_DEVICE_TARGETS=1
/usr/local/bin/fruix system install /run/current-system/metadata/system-declaration.scm \
--system self-hosted-development-operating-system \
--target /dev/vtbd0
Adjust /dev/vtbd0 to the real target device.
If you use this path, remember:
- it is destructive for the selected target
- block-device installs are opt-in on purpose
- the TUI path is the friendlier default for humans
First boot after installation
Once the installed system boots, log in on the console or over SSH and do a quick sanity check:
/usr/local/bin/fruix system status
You should see metadata describing the current generation, closure, and default declaration paths.
If you added root SSH keys in the declaration, verify remote access from your operator machine.
Development environment on the installed node
The installed self-hosted system already includes two important helper scripts.
1. General development shell
For editor/runtime/package-development work:
eval "$(/usr/local/bin/fruix-development-environment)"
That exports a development-oriented environment pointing at:
/run/current-system/development-profile- compiler/toolchain paths
- Node/npm paths
- runtime library paths
- man pages and other tool metadata
After enabling it, useful smoke checks are:
cc --version
node --version
npm --version
rg --version
tmux -V
nvim --version | sed -n '1p'
2. Sanitized build shell
For native FreeBSD base rebuild work and stricter build testing:
eval "$(/usr/local/bin/fruix-build-environment)"
That switches to the build-profile-oriented environment and drops the normal development-shell overrides that would interfere with more controlled builds.
Useful quick checks:
echo "$FRUIX_BUILD_PROFILE"
make -V .CURDIR >/dev/null
3. Optional native base rebuild helper
The self-hosted system also installs:
/usr/local/bin/fruix-self-hosted-native-build
and exposes it through:
/usr/local/bin/fruix system build-base
That is the current node-local helper for rebuilding FreeBSD world/kernel artifacts from the staged source tree. It is useful, but it is a heavier, longer-running workflow than ordinary declaration or package iteration.
Working on Fruix itself from the installed node
There are two different Fruix entrypoints on an installed self-hosted node, and it helps to use the right one.
1. The installed node CLI
/usr/local/bin/fruix
Use this for node-local lifecycle operations such as:
fruix system statusfruix system reconfigurefruix system rollbackfruix system switch
This CLI is bundled into the installed system closure and is the right tool for managing the node you are currently running.
2. The checkout CLI
If you are editing Fruix source code in a working tree, use the checkout entrypoint from that tree:
/path/to/fruix/bin/fruix
That path evaluates the Fruix source in your checkout and is what you want when changing:
- Fruix modules
- package definitions
- system render logic
- installer logic
- build/materialization code
3. Getting a checkout onto the node
The current self-hosted development profile does not yet provide a packaged
git, so the easiest current options are:
- copy an existing checkout onto the node with
scp/rsync - mount or share a dataset that contains the checkout
- or add your own temporary host-side transfer step
4. Preparing the checkout environment
For checkout-based work, prepare the node the same way as any other Fruix build host:
- create or copy in a Fruix checkout
- prepare
~/.local/opt/fruix-builderusingfruix-bootstrap - run the checkout via
./bin/fruix
The checkout entrypoint already prefers ~/.local/opt/fruix-builder when it is
present.
A practical day-to-day loop
Declaration-only changes
If you are only changing the system declaration used by the running node:
/usr/local/bin/fruix system reconfigure /path/to/my-self-hosted-dev.scm \
--system self-hosted-development-operating-system
That builds a new closure using the node's bundled Fruix payload and switches node metadata to the new generation.
After a successful reconfigure, plan on a reboot.
If you need to undo it:
/usr/local/bin/fruix system rollback
Fruix source-tree changes
If you changed Fruix code in a checkout, build with that checkout:
/path/to/fruix/bin/fruix system build /path/to/my-self-hosted-dev.scm \
--system self-hosted-development-operating-system \
--store /var/tmp/fruix-dev-store \
> /tmp/fruix-build.out
Recover the closure path:
closure=$(sed -n 's/^closure_path=//p' /tmp/fruix-build.out | sed -n '$p')
printf 'closure: %s\n' "$closure"
Then switch the running node to that closure:
/usr/local/bin/fruix system switch "$closure"
Again, plan on a reboot after switching.
Recommended post-install checklist
For a node that should serve as a real self-hosted Fruix development box, the useful immediate checklist is:
- confirm SSH access works with your real operator key
- run
/usr/local/bin/fruix system status - enable the development environment and check
cc,node,npm,tmux, andnvim - copy a Fruix checkout onto the machine
- prepare
~/.local/opt/fruix-builderon the node - verify the checkout entrypoint works:
./bin/fruix --help - keep a scratch store directory for checkout builds, e.g.
/var/tmp/fruix-dev-store - do one full
reconfigureand onerollbackbefore treating the node as a daily driver
Current limitations and expectations
This workflow is useful now, but it is still an actively developing path.
Important current expectations:
- the self-hosted path is real and usable, but not yet the final polished Fruix product workflow
- the installed-node lifecycle is farther along than the broader deploy/upgrade story
- the development profile is practical, but still intentionally small and transitional
- some rough edges remain; treat the current system as a serious development environment, not yet a finished distribution experience