Document self-hosted development workflow
This commit is contained in:
@@ -39,6 +39,10 @@ The direct checkout entrypoint now also prefers a prepared builder root at:
|
||||
|
||||
when `fruix-bootstrap` has already prepared one.
|
||||
|
||||
## Guides
|
||||
|
||||
- `docs/self-hosted-dev.md` — build the self-hosted development installer ISO and prepare an installed Fruix node for development work
|
||||
|
||||
## Intended lifecycle
|
||||
|
||||
```text
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
# 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:
|
||||
|
||||
```sh
|
||||
./bin/fruix ...
|
||||
```
|
||||
|
||||
### 2. Copy the example declaration and customize it
|
||||
|
||||
Start from the example:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```scheme
|
||||
(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:
|
||||
|
||||
```sh
|
||||
mkdir -p /var/tmp/fruix-selfhosted-store
|
||||
```
|
||||
|
||||
## Build the installer ISO
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
./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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
/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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
/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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
/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
|
||||
|
||||
```sh
|
||||
/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:
|
||||
|
||||
```sh
|
||||
/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:
|
||||
|
||||
```sh
|
||||
/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:
|
||||
|
||||
```sh
|
||||
/usr/local/bin/fruix system rollback
|
||||
```
|
||||
|
||||
### Fruix source-tree changes
|
||||
|
||||
If you changed Fruix code in a checkout, build with that checkout:
|
||||
|
||||
```sh
|
||||
/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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
/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
|
||||
Reference in New Issue
Block a user