645 lines
29 KiB
Markdown
645 lines
29 KiB
Markdown
# Clawdie ISO Builder
|
|
|
|
On `xfce-operator-usb`, builds a bootable FreeBSD 15.0 operator USB image with:
|
|
|
|
- XFCE desktop
|
|
- pre-SDDM live GPU detection
|
|
- Firefox browser
|
|
- Tailscale
|
|
- NetworkMgr launched via `mdo`, not `sudo`
|
|
- native Wi-Fi firmware bundle for NetworkMgr-managed wireless adapters
|
|
- bundled npm globals (`pi` only); `codex` ships as a FreeBSD pkg
|
|
instead of npm; `claude-code` is skipped on the live image because
|
|
its native deps have no FreeBSD binary (see `install_live_npm_globals`)
|
|
- bash as the default operator shell, with zsh + packaged oh-my-zsh available as optional user shell tooling
|
|
- Blender for Python-capable 3D/operator workflows
|
|
- offline package repository
|
|
- bundled Clawdie-AI tarball for later phases
|
|
- Colibri Rust control-plane service when prebuilt artifacts are available
|
|
|
|
The ISO version is independent from the bundled Clawdie-AI ref:
|
|
|
|
```sh
|
|
ISO_VERSION="0.1.0"
|
|
BUILD_CHANNEL="dev" # dev | release
|
|
CLAWDIE_REF="main" # validation default
|
|
```
|
|
|
|
Release builds must pin a Clawdie-AI tag with `--clawdie-version X.Y.Z`.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
On the FreeBSD build host:
|
|
|
|
```sh
|
|
sudo pkg install -y curl node24 npm-node24 sudo
|
|
```
|
|
|
|
Also required:
|
|
|
|
- FreeBSD 15.0+
|
|
- 150 GB free build space recommended
|
|
- root or `sudo` for image assembly
|
|
- 32 GB USB key minimum for the current default `IMAGE_SIZE=28G`
|
|
- 64 GB or larger recommended when you want more offline growth headroom
|
|
- prebuilt Colibri release artifacts when `FEATURE_COLIBRI=YES` (default)
|
|
|
|
Tailscale is packaged for live operator access. You can either bake
|
|
`--tailscale-auth-key` for one-shot first-boot autojoin or authenticate later
|
|
from the running USB with `mdo -u root tailscale up`.
|
|
|
|
### Colibri artifacts
|
|
|
|
The build stages Colibri into the image when `FEATURE_COLIBRI=YES`.
|
|
It expects prebuilt FreeBSD release binaries; it does not compile Rust while
|
|
the image is mounted.
|
|
|
|
Default adjacent-checkout flow:
|
|
|
|
```sh
|
|
(cd /home/clawdie/ai/colibri && cargo build --workspace --release)
|
|
sudo ./build.sh --skip-fetch
|
|
```
|
|
|
|
Override locations when needed:
|
|
|
|
```sh
|
|
COLIBRI_REPO=/home/clawdie/ai/colibri sudo ./build.sh --skip-fetch
|
|
COLIBRI_ARTIFACT_DIR=/path/to/colibri-target/release sudo ./build.sh --skip-fetch
|
|
```
|
|
|
|
To build an image without Colibri while debugging unrelated ISO issues:
|
|
|
|
```sh
|
|
FEATURE_COLIBRI=NO sudo ./build.sh --skip-fetch
|
|
```
|
|
|
|
When enabled, the image includes:
|
|
|
|
```text
|
|
/usr/local/bin/colibri-daemon
|
|
/usr/local/bin/colibri
|
|
/usr/local/bin/colibri-smoke-agent
|
|
/usr/local/bin/colibri-tui # if present in the artifact dir
|
|
/usr/local/etc/rc.d/colibri_daemon
|
|
/var/db/colibri
|
|
/var/run/colibri
|
|
/var/log/colibri
|
|
```
|
|
|
|
The build also creates the `colibri` service user/group and writes rc.conf
|
|
values for `colibri_daemon_enable`, paths, and `colibri_cost_mode`. Operator
|
|
USB builds stage Colibri by default but keep `colibri_daemon_enable=NO` unless
|
|
the build explicitly overrides `COLIBRI_DAEMON_ENABLE=YES`, so the daemon cannot
|
|
block SDDM/XFCE boot while service supervision is still being validated.
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
```sh
|
|
# Confirm you are building the intended branch/head first.
|
|
git status --short --branch
|
|
git log --oneline -5
|
|
|
|
# Full build: fetch + assemble.
|
|
sudo ./build.sh
|
|
```
|
|
|
|
Output:
|
|
|
|
```text
|
|
tmp/output/clawdie-xfce-quindecim-usb-DD.MM.YY-abcdef0.img
|
|
```
|
|
|
|
Published/downloaded artifacts are compressed as `.img.gz`. Stream the
|
|
compressed image directly into `dd`:
|
|
|
|
```sh
|
|
gzip -dc clawdie-xfce-quindecim-usb-DD.MM.YY-abcdef0.img.gz | sudo dd of=/dev/daX bs=1M status=progress conv=fsync
|
|
sync
|
|
```
|
|
|
|
For Linux or FreeBSD downloads from the published HTTPS path, prefer resumable
|
|
`curl` with retries before flashing:
|
|
|
|
```sh
|
|
curl -fL --continue-at - --retry 5 --retry-delay 5 --progress-bar -O \
|
|
https://osa.smilepowered.org/downloads/iso/clawdie-xfce-quindecim-usb-DD.MM.YY-abcdef0.img.gz
|
|
curl -fL --retry 5 --retry-delay 5 -O \
|
|
https://osa.smilepowered.org/downloads/iso/clawdie-xfce-quindecim-usb-DD.MM.YY-abcdef0.img.gz.sha256
|
|
```
|
|
|
|
For a build-local uncompressed image, plain `dd` is also fine:
|
|
|
|
```sh
|
|
sudo dd if=tmp/output/clawdie-xfce-quindecim-usb-DD.MM.YY-abcdef0.img of=/dev/daX bs=1M status=progress conv=fsync
|
|
sync
|
|
```
|
|
|
|
Use the whole USB device (`/dev/daX`), not a partition. See [FLASHING.md](FLASHING.md)
|
|
for Linux commands, checksum verification, and stale-label cleanup.
|
|
|
|
---
|
|
|
|
## Useful Build Modes
|
|
|
|
```sh
|
|
# Download/cache FreeBSD, packages, npm globals, and Clawdie-AI only.
|
|
./build.sh --fetch-only
|
|
|
|
# Assemble using cached inputs.
|
|
sudo ./build.sh --skip-fetch
|
|
|
|
# Fetch packages but reuse the cached FreeBSD memstick image.
|
|
sudo ./build.sh --skip-memstick-fetch
|
|
|
|
# Dev/test image: set live user clawdie password to quindecim.
|
|
sudo ./build.sh --live-default-password
|
|
|
|
# Bake an SSH public key for pubkey-only live USB access.
|
|
sudo ./build.sh --ssh-key "$(cat ~/.ssh/id_ed25519.pub)"
|
|
|
|
# Autojoin a tailnet on first boot via a one-shot self-deleting service.
|
|
sudo ./build.sh --tailscale-auth-key "$TAILSCALE_AUTH_KEY"
|
|
|
|
# Bundle a specific branch/tag/commit ref.
|
|
sudo ./build.sh --clawdie-ref main
|
|
sudo ./build.sh --clawdie-ref f04f35eb4e16b50150ae73bad2e271388dc88f82
|
|
|
|
# Release build from a pinned Clawdie-AI tag.
|
|
BUILD_CHANNEL=release sudo ./build.sh --clawdie-version 0.10.0
|
|
```
|
|
|
|
`--skip-fetch` is provenance-safe for Clawdie-AI: moving refs are resolved to a
|
|
commit and cached by that commit. If the commit cannot be resolved safely, the
|
|
script refuses a moving-ref skip-fetch build rather than producing misleading
|
|
manifest data.
|
|
|
|
### SSH key note for live-debug builds
|
|
|
|
`build.sh` accepts `--ssh-key` for the live USB. The key is installed into
|
|
`/home/clawdie/.ssh/authorized_keys` so the operator can pull logs without
|
|
hand-transcribing them from a tty.
|
|
|
|
Find an existing public key:
|
|
|
|
```sh
|
|
ls -l ~/.ssh/*.pub
|
|
cat ~/.ssh/id_ed25519.pub
|
|
```
|
|
|
|
Create a dedicated key for distributable images:
|
|
|
|
```sh
|
|
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/clawdie-live-usb -C "clawdie-live-usb"
|
|
sudo ./build.sh --ssh-key "$(cat ~/.ssh/clawdie-live-usb.pub)"
|
|
```
|
|
|
|
**Listening policy on the live USB:** `sshd` runs unconditionally and listens
|
|
on all interfaces — both the LAN path and the tailnet path, so an operator
|
|
without Tailscale can still SSH in from the local network. Auth is restrictive
|
|
regardless: pubkey only, no passwords, no root. The daemon runs even when
|
|
`--ssh-key` is not provided (no authorized key → no one can authenticate;
|
|
harmless but predictable).
|
|
|
|
**Tailscale coupling:** when the build also receives a Tailscale auth key, the
|
|
live USB autojoins the tailnet on first boot and is reachable by MagicDNS
|
|
hostname (`ssh clawdie@clawdie-live`). That's the preferred path; the LAN
|
|
path remains a fallback. Recommend passing both for hardware testing:
|
|
|
|
```sh
|
|
sudo ./build.sh \
|
|
--live-default-password \
|
|
--ssh-key "$(cat ~/.ssh/clawdie-live-usb.pub)" \
|
|
--tailscale-auth-key "$TAILSCALE_AUTH_KEY"
|
|
```
|
|
|
|
Current implementation:
|
|
|
|
- `--ssh-key` prepopulates `/home/clawdie/.ssh/authorized_keys` (mode 0600,
|
|
`.ssh/` mode 0700, owned `clawdie:clawdie`).
|
|
- `sshd_enable="YES"` in the live rc.conf, unconditionally.
|
|
- Policy lives in a drop-in at
|
|
`/etc/ssh/sshd_config.d/clawdie-live.conf`, with the base
|
|
`/etc/ssh/sshd_config` including that directory. Contents:
|
|
`PubkeyAuthentication yes`, `PasswordAuthentication no`,
|
|
`KbdInteractiveAuthentication no`, `ChallengeResponseAuthentication no`,
|
|
`PermitRootLogin no`.
|
|
- `build-manifest.json` records the baked-in key fingerprint
|
|
(`ssh-keygen -lf <pubkey>`) so the operator can verify the running image
|
|
matches expectation.
|
|
|
|
**Distribution-safety note:** public keys are public, so baking one in is
|
|
not a confidentiality issue — but anyone with the image file can SSH into a
|
|
live boot of it. For dev/test builds that's the right tradeoff. For public
|
|
release images, do not pass `--ssh-key` at all; operators add a key
|
|
themselves after first boot or build their own image. The dedicated
|
|
`clawdie-live-usb` key avoids leaking personal keys into shareable images.
|
|
|
|
See `doc/LIVE-SESSION-REVIEW.md` for the full pre-build plan and TESTING
|
|
hooks.
|
|
|
|
### LAN discovery (mDNS / Avahi)
|
|
|
|
The live USB advertises itself on the local network as
|
|
`clawdie-live.local` so the operator can SSH/`scp` without finding the
|
|
DHCP-assigned IP each boot. Two access paths in priority order:
|
|
|
|
```sh
|
|
# Preferred when Tailscale was passed at build time:
|
|
ssh clawdie@clawdie-live # MagicDNS
|
|
|
|
# Always-on LAN discovery — no Tailscale required:
|
|
ssh clawdie@clawdie-live.local # mDNS / Avahi
|
|
|
|
# Last-resort fallback if multicast is blocked on the network:
|
|
ssh clawdie@<dhcp-ip-from-router>
|
|
```
|
|
|
|
The implementation contract:
|
|
|
|
- Explicit packages in `packages/pkg-list-live-operator.txt`:
|
|
`avahi-app` (already transitive in the closure, list explicitly to
|
|
pin the contract) and `nss_mdns`.
|
|
- `avahi_daemon_enable="YES"` in live rc.conf. `dbus_enable="YES"` is
|
|
already set; Avahi depends on it.
|
|
- `/etc/nsswitch.conf` `hosts:` line set to
|
|
`files mdns_minimal [NOTFOUND=return] dns mdns` so `.local` names
|
|
resolve from the live USB itself.
|
|
- No config changes to the packaged Avahi `ssh.service` — its
|
|
`_ssh._tcp` advertisement is what we want.
|
|
|
|
**Scope discipline:** mDNS is **only** for LAN discovery of the live
|
|
USB itself. Clawdie's internal service names continue to live under
|
|
`home.arpa` (`ai.home.arpa`, `cms.home.arpa`, `git.home.arpa`,
|
|
`<tenant>.home.arpa`). Do not switch internal services to `.local` —
|
|
RFC 6762 reserves `.local` for mDNS, and mixing the two namespaces
|
|
breaks both.
|
|
|
|
**Network caveat:** some corporate networks, hotel Wi-Fi, and isolated
|
|
guest VLANs block multicast traffic. In those environments
|
|
`clawdie-live.local` won't resolve and the operator falls back to the
|
|
Tailscale path (if configured) or the DHCP IP path. Worth knowing
|
|
before debugging "mDNS doesn't work" on a hostile network.
|
|
|
|
### PF firewall on the live USB
|
|
|
|
PF is enabled by default with a minimal, permissive baseline: outbound
|
|
open, inbound limited to SSH, mDNS, ICMP, DHCP-client, and Tailscale's
|
|
UDP 41641. The actual access restriction on SSH is carried by `sshd`
|
|
auth policy (pubkey only), not PF interface scoping.
|
|
|
|
**No logging stack on disk or in memory by default.** `pflog_enable`
|
|
and `pflogd_enable` are both left at their `NO` defaults. The ruleset
|
|
has no `log` keywords, so there is nothing to capture anyway. When
|
|
debugging from a booted live USB, the operator brings the stack up by
|
|
hand:
|
|
|
|
```sh
|
|
mdo -u root kldload pflog
|
|
mdo -u root ifconfig pflog0 create
|
|
mdo -u root tcpdump -ni pflog0
|
|
```
|
|
|
|
Post-disk-install both knobs can be flipped to `YES` from `sysrc` for
|
|
forensic log capture. Not the live USB default.
|
|
|
|
The ruleset lives at `live/operator-session/pf-live.conf` (separate
|
|
from the installed-system policy in `firstboot/shell-pf.sh`, which has
|
|
different threat-model assumptions).
|
|
|
|
See `doc/LIVE-SESSION-REVIEW.md` for the full ruleset and TESTING hooks.
|
|
|
|
### Targeted tmpfs for write-heavy paths
|
|
|
|
The live USB targets 8 GB RAM minimum hardware. Mirror the proven
|
|
NomadBSD desktop-live pattern for the system paths, then add one
|
|
Clawdie-specific Firefox cache optimization:
|
|
|
|
| Path | Backing | Why |
|
|
| ---------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
| `/tmp` | tmpfs | X sockets, screenshot tools, browser/app temp files; NomadBSD ships this for SDDM/XFCE live media |
|
|
| `/var/log` | tmpfs | avoid steady-state USB writes from syslog, sshd, Avahi, dbus, and desktop services |
|
|
| `/home/clawdie/.cache` | symlink to `/tmp/clawdie/cache` | Firefox cache2, GTK icon cache, thumbnails — dominant desktop-session USB writer |
|
|
|
|
Keep `tmpmfs="NO"` and `varmfs="NO"`. The historical Clawdie breakage came
|
|
from the stock memstick's blanket rc.d `tmpmfs`/`varmfs` overlays, which hid
|
|
important `/var` content. Controlled fstab tmpfs entries for only `/tmp` and
|
|
`/var/log` are a different mechanism and match NomadBSD's long-running live
|
|
desktop design.
|
|
|
|
`/var/tmp` stays on disk. NomadBSD does not tmpfs it, and applications often
|
|
use `/var/tmp` for longer-lived crash-recovery state.
|
|
|
|
**Not adopted in this phase:** unionfs root / "XFCE from RAM". The
|
|
FreeBSD UFS buffer cache already keeps hot reads in RAM after first
|
|
load, and a full unionfs overlay would regress SDDM/SSH-host-key
|
|
persistence. Revisit when disk install + USB-removable mode become a
|
|
real requirement.
|
|
|
|
See `doc/LIVE-SESSION-REVIEW.md` for the rationale, the explicit
|
|
"what stays on disk" list, and TESTING hooks.
|
|
|
|
### Tailscale auth-key build flag
|
|
|
|
`build.sh` accepts `--tailscale-auth-key` for the live USB. When present, the
|
|
key is written to a root-only secret file inside the image and consumed by a
|
|
one-shot rc.d service on first boot. The service runs:
|
|
|
|
```text
|
|
tailscale up --auth-key=... --hostname=clawdie-live --ssh=false
|
|
```
|
|
|
|
Then it deletes the key file and disables itself. The operator can still join
|
|
manually after boot when no auth key was baked:
|
|
|
|
```sh
|
|
mdo -u root tailscale up
|
|
```
|
|
|
|
The auth key never lands in `build-manifest.json` or `build.cfg` — only the
|
|
boolean `tailscale_auth_key_baked: true|false` is recorded in the manifest.
|
|
|
|
---
|
|
|
|
## Build Output and Provenance
|
|
|
|
The build header shows:
|
|
|
|
```text
|
|
ISO : 0.1.0-dev
|
|
FreeBSD : 15.0-RELEASE amd64
|
|
Clawdie : main
|
|
Clawdie commit: <resolved-sha>
|
|
```
|
|
|
|
The image contains:
|
|
|
|
```text
|
|
/usr/local/share/clawdie-iso/build-manifest.json
|
|
```
|
|
|
|
The installed system receives the same manifest. It records:
|
|
|
|
- ISO version and build channel
|
|
- FreeBSD version/arch
|
|
- bundled Clawdie-AI ref and commit
|
|
- ISO repo commit and dirty state
|
|
- UTC build timestamp
|
|
|
|
The final size output distinguishes:
|
|
|
|
- **Image size** — logical size that must fit on the USB key
|
|
- **Allocated** — sparse bytes used on the build host
|
|
|
|
---
|
|
|
|
## Build Configuration
|
|
|
|
Edit `build.cfg` for persistent defaults:
|
|
|
|
```sh
|
|
ISO_VERSION="0.1.0"
|
|
BUILD_CHANNEL="${BUILD_CHANNEL:-dev}"
|
|
IMAGE_SIZE="28G"
|
|
CLAWDIE_REF="${CLAWDIE_REF:-main}"
|
|
DEFAULT_PKG_BRANCH="latest"
|
|
FEATURE_TAILSCALE="${FEATURE_TAILSCALE:-YES}"
|
|
```
|
|
|
|
Notes:
|
|
|
|
- Host pkg repo branch and ISO package branch are independent.
|
|
- `DEFAULT_PKG_BRANCH=latest` is the ISO package source by default.
|
|
- Provider keys, Telegram, and disk deployment are deferred on this branch.
|
|
|
|
---
|
|
|
|
## Packages Deferred to Disk Install
|
|
|
|
The live operator USB is intentionally a lean substrate: bring up the
|
|
desktop, the browser, the agent CLIs, networking, and jails — nothing
|
|
more. Packages listed below are fetched into the offline repository via
|
|
`packages/pkg-list-disk-install-extras.txt`, but are intentionally not
|
|
installed onto the live rootfs.
|
|
|
|
**Two distinct categories live in the same file** with the same code
|
|
path (fetched, not installed on live), but different long-term homes:
|
|
|
|
1. **Desktop-spin leftovers** (telegram-desktop, mpv, abiword, gnumeric,
|
|
simplescreenrecorder, …). Inherited from the earlier all-in-one
|
|
desktop image. Don't match the operator-USB role. **Long-term home:
|
|
disk-install only.** Not returning to the live USB.
|
|
|
|
2. **Roadmap-essential, deferred for stabilization** (`blender`).
|
|
First-class operator capability, not a leftover. Clawdie's product
|
|
scope explicitly covers parametric design → CAD/CAM → CNC fabrication
|
|
for OSA-style geodesic work
|
|
([osa.smilepowered.org](https://osa.smilepowered.org/)), and
|
|
Blender's bundled `bpy` Python module is the skill substrate for
|
|
those workflows. Held off the live USB for now only because its
|
|
ffmpeg/libpulse/mesa-libs/boost surface would partially un-do the
|
|
lean-rootfs payoff we want during the early hardware-validation
|
|
cycle. **Long-term home: back on the live USB** once dependency
|
|
surface is audited and operator workflow exists. Do not categorise
|
|
it as desktop-spin cruft in a future audit.
|
|
|
|
**Intent:** keep this table as the authoritative "what we will deploy
|
|
to disk" reference so the work is not lost between commits. The
|
|
disk-install consumer still needs to install
|
|
`pkg-list-disk-install-extras.txt` when that path lands.
|
|
|
|
| Package | Role | Direct pkg size | Installed size | Category |
|
|
| ---------------------- | ------------------------------------------------------ | -----------------------------: | -------------: | ----------------- |
|
|
| `blender` | Parametric 3D modelling + Python `bpy` skill substrate | (large; measure on next image) | (large) | roadmap-essential |
|
|
| `telegram-desktop` | Messaging | 52.08 MiB | 236.90 MiB | leftover |
|
|
| `gnumeric` | Spreadsheet | 13.14 MiB | 45.91 MiB | leftover |
|
|
| `abiword` | Word processor | 4.75 MiB | 21.60 MiB | leftover |
|
|
| `mpv` | Media player | 1.60 MiB | 6.96 MiB | leftover |
|
|
| `simplescreenrecorder` | Screen/audio capture | 1.31 MiB | 3.83 MiB | leftover |
|
|
| `xls2txt` | Spreadsheet text converter | 0.55 MiB | 2.14 MiB | leftover |
|
|
| `antiword` | `.doc` text converter | 0.15 MiB | 0.65 MiB | leftover |
|
|
| `epdfview` | PDF viewer | 0.12 MiB | 0.40 MiB | leftover |
|
|
| `catdoc` | `.doc` / `.xls` converter | 0.08 MiB | 0.60 MiB | leftover |
|
|
| `p5-docx2txt` | `.docx` converter | 0.02 MiB | 0.06 MiB | leftover |
|
|
| `odt2txt` | `.odt` converter | 0.02 MiB | 0.04 MiB | leftover |
|
|
|
|
Removing the leftover bundle from the live USB shrinks the image and
|
|
drops the PulseAudio + PipeWire transitive surface — see "Audio surface
|
|
implications" below. The bundle is still useful on a full installed
|
|
operator desktop, hence **deferred** rather than **deleted**.
|
|
|
|
Blender's deferral is different: it's a _commitment_ to the
|
|
CNC/parametric-fabrication roadmap, parked off the live USB only until
|
|
the dep-set audit completes.
|
|
|
|
### Audio surface implications
|
|
|
|
The audio stack is the main architectural payoff of the bundle move and
|
|
Firefox browser swap, separate from the size win. Confirmed by direct
|
|
package-dep inspection:
|
|
|
|
| Package | Pulls in (audio-relevant) |
|
|
| ---------------------- | ---------------------------------------------------------------------------------- |
|
|
| `telegram-desktop` | `qt6-declarative`, `qt6-wayland`, `qt6-svg`, `qt6-lottie`, `kf6-*`, **`pipewire`** |
|
|
| `simplescreenrecorder` | **`pulseaudio`**, **`pipewire`**, `qt6-base`, `ffmpeg` |
|
|
| `mpv` | `ffmpeg`, `libplacebo`, `mesa-libs`, `wayland`, `vulkan-loader` |
|
|
| `gnumeric` | `python311`, `py311-pygobject`, `goffice` |
|
|
| `abiword` | `goffice`, `libgsf`, `wv` |
|
|
|
|
`simplescreenrecorder` is the worst offender — a single niche tool drags
|
|
in both the PulseAudio daemon _and_ the PipeWire daemon. Removing the
|
|
bundle from the live rootfs restores the FreeBSD-native OSS default by
|
|
construction, not by config-tuning.
|
|
|
|
Replacing Chromium with Firefox also removes the Chromium →
|
|
`speech-dispatcher` → `pulseaudio` dependency chain. Firefox keeps a
|
|
modern browser on the live USB without installing the PulseAudio daemon.
|
|
|
|
`xfce4-mixer`'s GStreamer-plugin chain typically pulls
|
|
`gstreamer1-plugins-pulse`. This is the remaining live-USB libpulse
|
|
puller after the bundle move and is tracked under "Audit candidates"
|
|
below.
|
|
|
|
### Audit candidates (live USB, keep for now)
|
|
|
|
These stay on the live USB, but they're flagged for a second-pass audit
|
|
once the desktop is proven stable on hardware:
|
|
|
|
| Package | Reason to revisit |
|
|
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `xfce4-mixer` | wraps GStreamer; the GStreamer plugin bundle on FreeBSD typically pulls libpulse. The OSS-native intent of picking xfce4-mixer is partially undermined by this. Replacement candidates: `mixer(8)` from a terminal launcher, or a tiny OSS-only mixer applet. |
|
|
| `pcmanfm` | file-manager fallback while `xfdesktop` reliability is being validated. Drop once `xfdesktop` is the proven path. |
|
|
| `bitchx` | IRC client; desktop-era leftover. Kept on the live USB for now — size impact is negligible (~few hundred KiB) and the operator may want a quick chat tool on a fresh boot. |
|
|
|
|
### Currently essential (both live and disk)
|
|
|
|
Listed for completeness so the lean/full split stays honest:
|
|
|
|
- **Desktop session:** `xfce4-desktop`, `xfce4-panel`, `xfce4-session`,
|
|
`xfce4-settings`, `xfce4-wm`, `xfce4-terminal`,
|
|
`xfce4-whiskermenu-plugin`, `sddm`, `xinit`, `xterm`.
|
|
- **GTK / desktop integration:** `gsettings-desktop-schemas`,
|
|
`adwaita-icon-theme`, `adwaita-icon-theme-legacy`,
|
|
`gtk-update-icon-cache`, `shared-mime-info`, `desktop-file-utils`.
|
|
- **Browser + agent runtime:** `firefox`, `node24`, `npm-node24` (with
|
|
bundled `pi` npm globals). `codex` is included as a FreeBSD
|
|
pkg, not via npm. `claude-code` is excluded from the live image —
|
|
its native deps have no FreeBSD binary.
|
|
- **Networking:** `tailscale`, `networkmgr`, `wifi-firmware-kmod`,
|
|
`FreeBSD-fwget`.
|
|
- **Jails:** `bastille`.
|
|
- **Operator diagnostics (tmux screenshot skill):** `tmux`, `screen`,
|
|
`mc`, `zip`, `unzip`, `7-zip`, `python311`, `py311-pillow`, `dejavu`.
|
|
- **Xorg base:** `xorg-minimal`, `xkeyboard-config`, `xkbcomp`,
|
|
`xf86-input-libinput`, `xf86-video-scfb`, `xf86-video-intel`,
|
|
`xf86-video-amdgpu`, `xf86-video-ati`, `drm-kmod`,
|
|
`gpu-firmware-intel-kmod-geminilake`,
|
|
`gpu-firmware-intel-kmod-kabylake`.
|
|
- **System bus:** `dbus`.
|
|
|
|
### Decisions implemented for next build
|
|
|
|
These decisions are now reflected in `packages/pkg-list-*.txt` and the
|
|
live XFCE launcher/mime defaults. The next build should validate the new
|
|
closure and hardware behavior.
|
|
|
|
| Decision | Status | Notes |
|
|
| ---------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Drop `chromium`, add `firefox` | **implemented** | Browser swap applied in `packages/pkg-list-live-operator.txt`, `packages/pkg-list-jails.txt`, README, the bootstrap page, the launch helper, and the panel launcher. Firefox on FreeBSD avoids the Chromium → `speech-dispatcher` → `pulseaudio` chain. |
|
|
| Split deferred bundle into `packages/pkg-list-disk-install-extras.txt` | **implemented** | The list remains in `pkg_list_all()` so archives land in the offline repo, but is absent from `pkg_list_live_installer()` so it stays off the live rootfs. The future disk-install consumer still needs to install it explicitly. |
|
|
| `simplescreenrecorder`: defer or drop outright? | **deferred to disk extras for now** | Kept in `pkg-list-disk-install-extras.txt` with the rest of the desktop bundle so the package remains available offline; revisit before implementing the disk-install consumer. |
|
|
| PulseAudio installable as an option on disk-install target? | **open** | On the live USB the answer is "no daemon, OSS default." On the _installed_ operator desktop, do we ship `pulseaudio` as an opt-in install (e.g. via a setup-time toggle, or as part of `pkg-list-disk-install-extras.txt`), or commit to FreeBSD-native OSS/sndio across the board? Decision affects whether the deferred-bundle file should include `pulseaudio` as an explicit entry. |
|
|
|
|
---
|
|
|
|
## Build Process
|
|
|
|
1. Fetch FreeBSD memstick and verify checksum.
|
|
2. Fetch all package archives for offline install.
|
|
3. Build local pkg repository metadata.
|
|
4. Fetch Clawdie-AI by resolved ref and prepare offline `node_modules` tarball.
|
|
5. Create/attach the working image.
|
|
6. Inject firstboot scripts, packages, XFCE live-session assets, bundled npm
|
|
globals, Clawdie-AI tarball, build config, and manifest.
|
|
7. Copy the final sparse image into `tmp/output/`.
|
|
|
|
The build is intentionally cache-friendly. If in doubt before validation, run the
|
|
full `sudo ./build.sh` once after pulling current `main`.
|
|
|
|
---
|
|
|
|
## Boot Flow Produced by the Image
|
|
|
|
1. USB boots to XFCE live session.
|
|
2. `/usr/local/etc/rc.d/clawdie_live_gpu` runs before SDDM and selects a conservative GPU path.
|
|
3. SDDM presents the greeter; the operator logs in as `clawdie` to launch the Clawdie XFCE session.
|
|
4. A desktop launcher opens the static Clawdie bootstrap page.
|
|
5. The operator verifies browser, `pi`, Tailscale, and local networking.
|
|
6. Later phases will add persistence, disk deployment, and upgrade/rescue flows.
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
Default verification order for the operator USB is now:
|
|
|
|
1. static artifact verification on the build host
|
|
2. bhyve boot verification on the ML350p virtualization lane
|
|
3. physical hardware validation for the final acceptance pass
|
|
|
|
Use bhyve before writing to hardware whenever the lane is available:
|
|
|
|
```sh
|
|
sudo ./scripts/bhyve-pf-allow.sh
|
|
sudo ./scripts/bhyve-test.sh
|
|
```
|
|
|
|
Current ML350p bhyve plan:
|
|
|
|
- host RAM budget: ZFS ARC 6G + Poudriere tmpfs 4G + headroom 6G
|
|
- bhyve RAM budget: FreeBSD ISO test 4G + Linux cross-compile 4G + FreeBSD builder 4G + spare 4G
|
|
- intended VM roles:
|
|
- ISO boot verification after each build
|
|
- Linux target validation
|
|
- FreeBSD/Poudriere test lane
|
|
|
|
Use bhyve to catch boot, SDDM/XFCE, and service-start regressions early. Keep
|
|
real hardware as the final proof for GPU quirks, Wi-Fi, audio, touchpad, and
|
|
display/panel behavior.
|
|
|
|
See [TESTING.md](TESTING.md) for the full validation checklist.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
**`ERROR: missing package archive for <pkg>`**
|
|
|
|
A package was added after your cache was created. Run a full fetch/build:
|
|
|
|
```sh
|
|
sudo ./build.sh
|
|
```
|
|
|
|
**`pkg` not found during unprivileged fetch**
|
|
|
|
`build.sh` now sets a known FreeBSD tool PATH internally. If your shell still
|
|
cannot find pkg manually, add `/usr/local/sbin` to your login PATH.
|
|
|
|
**`npm ci` falls back to `npm install`**
|
|
|
|
The bundled Clawdie-AI ref has a package-lock mismatch. The build warns and
|
|
falls back so validation can continue, but release refs should have a clean lock.
|
|
|
|
**Image says small allocated size**
|
|
|
|
The image is sparse on the build host. Use the logical image size when choosing
|
|
a USB key.
|
|
|
|
---
|
|
|
|
**Last updated:** 16.maj.2026
|