Files
fruix/docs/self-hosted-dev.md

11 KiB

Self-hosted Fruix development

This guide describes the current human-facing path for:

  1. building a Fruix installer ISO for the self-hosted development system
  2. installing that system onto a VM or machine
  3. 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-bootstrap has 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-keys for 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 status
  • fruix system reconfigure
  • fruix system rollback
  • fruix 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-builder using fruix-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, and nvim
  • copy a Fruix checkout onto the machine
  • prepare ~/.local/opt/fruix-builder on 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 reconfigure and one rollback before 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