- build.cfg: FREEBSD_VERSION -> ${FREEBSD_VERSION:-15.1-RELEASE}. The memstick
URL, checksum URL, cache path, and build-manifest all derive from it, so the
live-USB build bumps from this one line. The :- form also lets an operator
override at build time (FREEBSD_VERSION=15.2-RELEASE ./build.sh) without
editing git — previously a plain assignment clobbered any env value.
- docs: de-version README/BUILD/REQUIREMENTS + iso-build skill from '15.0' to
'15.x' so they stop drifting on every point release.
Verified: default derives the 15.1 memstick URL (HTTP 200, dated 12.jun.2026);
env override cascades to 15.2. ABI stays FreeBSD:15:amd64 (same major — no
package rebuild needed). build-vps.sh (mfsbsd) and poudriere keep their own
version knobs and are intentionally untouched (ABI-compatible, separate paths).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
32 KiB
Clawdie ISO Builder
On xfce-operator-usb, builds a bootable FreeBSD 15.x operator USB image with:
- XFCE desktop
- pre-SDDM live GPU detection
- Firefox browser
- Tailscale
- NetworkMgr launched via
mdo, notsudo - native Wi-Fi firmware bundle for NetworkMgr-managed wireless adapters
- bundled npm globals (
pionly);codexships as a FreeBSD pkg instead of npm;claude-codeis skipped on the live image because its native deps have no FreeBSD binary (seeinstall_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 for the live USB
- deployed-system
clawdieservice lane for future disk/server installs
The ISO carries its own product version, independent of any component (zot,
Colibri, Clawdie-AI). Component versions are recorded in build-manifest.json.
ISO_VERSION="0.11.0" # explicit product version (required; no zot-tracking)
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:
sudo pkg install -y curl node24 npm-node24 sudo
Also required:
- FreeBSD 15.x
- 150 GB free build space recommended
- root or
sudofor 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 for the live USB control plane
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 operator USB stages Colibri as the live control plane. It expects prebuilt FreeBSD release binaries; Rust compilation takes place before image mounting, and only prebuilt binaries land on the mounted image.
Default adjacent-checkout flow:
(cd /home/clawdie/ai/colibri && cargo build --workspace --release)
sudo ./build.sh --skip-fetch
Override locations when needed:
COLIBRI_REPO=/home/clawdie/ai/colibri sudo ./build.sh --skip-fetch
COLIBRI_ARTIFACT_DIR=/path/to/colibri-target/release sudo ./build.sh --skip-fetch
Temporary debugging escape hatch while the flag still exists:
FEATURE_COLIBRI=NO sudo ./build.sh --skip-fetch
Operator-USB validation builds use the Colibri-backed default lane. Set
FEATURE_COLIBRI=NO only for focused staging/debug work. The image includes:
/usr/local/bin/colibri-daemon # always-on Colibri control-plane daemon
/usr/local/bin/colibri # operator CLI for status, tasks, skills, and spawning
/usr/local/bin/colibri-mcp # MCP bridge for Zed/Claude Code/Cursor
/usr/local/bin/colibri-tui # optional terminal dashboard if present in artifacts
/usr/local/bin/colibri-test-agent # dev/validation only when COLIBRI_STAGE_TEST_AGENT=YES
/usr/local/etc/rc.d/colibri_daemon # FreeBSD service wrapper for colibri-daemon
/var/db/colibri # persistent SQLite/state directory
/var/run/colibri # runtime socket and PID directory
/var/log/colibri # daemon logs
The build also creates the colibri service user/group and writes rc.conf
values for colibri_daemon_enable, paths, and colibri_daemon_cost_mode. Operator USB
validation builds enable colibri_daemon_enable=YES; the service starts after
the login milestone and is the lightweight control plane for the live USB.
colibri-mcp is staged out of the box for MCP-capable editors and assistants.
It defaults to read-only tools; launch it with COLIBRI_MCP_WRITE=1 only for a
trusted write-capable MCP profile. Example MCP client configs are installed at
/usr/local/share/clawdie-iso/mcp-examples/.
Colibri vs. Clawdie service names
Live USB image
rc.conf: colibri_daemon_enable="YES"
service: colibri_daemon
purpose: lightweight live control plane for the operator desktop
Installed/deployed system
rc.conf: clawdie_enable="YES"
service: clawdie
purpose: persistent host agent/facade for health, inventory, watchdog,
credentials, and Colibri-backed orchestration
No current ISO build flag stages service clawdie. That name is reserved for
installed/deployed systems until the real persistent host service is chosen. The
baseline live USB ships Colibri directly and excludes the old mini-binary
intended for the final deployed service.
Quick Start
# 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:
tmp/output/clawdie-quindecim-0.11.0.img
Published/downloaded artifacts are compressed as .img.xz. Stream the
compressed image directly into dd:
xz -dc clawdie-quindecim-0.11.0.img.xz | dd of=/dev/daX bs=1M status=progress conv=fsync && sync
sync
For Linux or FreeBSD downloads from the published HTTPS path, prefer resumable
curl with retries before flashing:
curl -fL --continue-at - --retry 5 --retry-delay 5 --progress-bar -O \
https://osa.smilepowered.org/downloads/iso/clawdie-quindecim-0.11.0.img.xz
curl -fL --retry 5 --retry-delay 5 -O \
https://osa.smilepowered.org/downloads/iso/clawdie-quindecim-0.11.0.img.xz.sha256
For a build-local uncompressed image, plain dd is also fine:
dd if=tmp/output/clawdie-quindecim-0.11.0.img of=/dev/daX bs=1M status=progress conv=fsync && sync
sync
Target the whole USB device (/dev/daX); flashing to a partition is unsafe and
unsupported. See FLASHING.md
for Linux commands, checksum verification, and stale-label cleanup.
Useful Build Modes
# 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:
ls -l ~/.ssh/*.pub
cat ~/.ssh/id_ed25519.pub
Create a dedicated key for distributable images:
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:
sudo ./build.sh \
--live-default-password \
--ssh-key "$(cat ~/.ssh/clawdie-live-usb.pub)" \
--tailscale-auth-key "$TAILSCALE_AUTH_KEY"
Current implementation:
--ssh-keyprepopulates/home/clawdie/.ssh/authorized_keys(mode 0600,.ssh/mode 0700, ownedclawdie: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_configincluding that directory. Contents:PubkeyAuthentication yes,PasswordAuthentication no,KbdInteractiveAuthentication no,ChallengeResponseAuthentication no,PermitRootLogin no. build-manifest.jsonrecords 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, omit --ssh-key entirely; 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:
# 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) andnss_mdns. avahi_daemon_enable="YES"in live rc.conf.dbus_enable="YES"is already set; Avahi depends on it./etc/nsswitch.confhosts:line set tofiles mdns_minimal [NOTFOUND=return] dns mdnsso.localnames resolve from the live USB itself.- No config changes to the packaged Avahi
ssh.service— its_ssh._tcpadvertisement 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). Keep internal service names under home.arpa;
reserve .local exclusively for mDNS — 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.
PF logging stack is opt-in by default. pflog_enable
and pflogd_enable remain at their NO defaults. The ruleset
has no log keywords, so nothing is captured by default. When
debugging from a booted live USB, the operator enables the stack by
hand:
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:
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:
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:
ISO : <iso-version>-dev
FreeBSD : 15.x-RELEASE amd64
Clawdie : main
Clawdie commit: <resolved-sha>
The image contains:
/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 modified 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:
ISO_VERSION="${ISO_VERSION:-0.11.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=latestis 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:
-
Desktop-spin leftovers (telegram-desktop, mpv, abiword, 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.
-
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), and Blender's bundledbpyPython 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. Classify it as roadmap-essential in all future audits, not desktop-spin cruft.
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 |
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. The qt6-*/kf6-* entries below are transitive package
dependencies from desktop applications, not an installer direction:
| 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 |
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 bundledpinpm globals).codexis included as a FreeBSD pkg, not via npm.claude-codeis 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,screen,mc,zip,unzip,7-zip,python312,dejavu,py311-pillow.Python:
python3is 3.11 (FreeBSD'sPYTHON_DEFAULT).python312is also installed and available aspython3.12for anything that needs newer. The image-render / screenshot skill usespy311-pillow(works onpython3);clawdie-join-hive.shadvertisesimage-renderwhen Pillow imports andscreenshotwhen a display is present. -
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
- Fetch FreeBSD memstick and verify checksum.
- Fetch all package archives for offline install.
- Build local pkg repository metadata.
- Fetch Clawdie-AI by resolved ref and prepare offline
node_modulestarball. - Create/attach the working image.
- Inject firstboot scripts, packages, XFCE live-session assets, bundled npm globals, Clawdie-AI tarball, build config, and manifest.
- 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.
Build-host disk policy
When OSA gets tight on disk, prefer deleting repo-local build artifacts
(tmp/packages, tmp/cache/work.img, cached FreeBSD memstick images, old output
artifacts) over pruning host packages.
Preserve the remaining host-side graphics/media stack; it supports current operator tooling and validation lanes, not an old X11 desktop:
ffmpeg+py311-pillowback the Hermes/image-render and screenshot path.gtk3,cairo,pango,fontconfig,libX11,mesa-*,wayland,wlroots, and related libs are shared by Codex-adjacent tooling, VNC/screenshot helpers, media/rendering packages, and Python operator tools.- Dry-run package removal on OSA showed that reclaiming this subtree would remove
far more than a stale desktop:
Codex,ffmpeg,py311-pillow, ImageMagick, graphviz, aider-adjacent Python packages, and remote-display helpers all rode on the same dependency closure.
In practice: if you need gigabytes quickly, clear build caches first:
sudo rm -rf tmp/packages tmp/cache tmp/output
These caches typically reclaim 3-8 GB. If host pkg pressure becomes chronic,
that is a signal to move more build/package work onto
mother-build (PLANNED — see docs/POUDRIERE-BUILD-SERVER.md), not to hollow
out the FreeBSD operator host.
Note: OSA's /tmp is a ZFS dataset separate from build caches. If shell
commands fail with "No space left on device" during agent operations, clear
/tmp independently:
sudo rm -rf /tmp/*
Boot Flow Produced by the Image
- USB boots to XFCE live session.
/usr/local/etc/rc.d/clawdie_live_gpuruns before SDDM and selects a conservative GPU path.- SDDM presents the greeter; the operator logs in as
clawdieto launch the Clawdie XFCE session. - A desktop launcher opens the static Clawdie bootstrap page.
- The operator verifies browser,
pi, Tailscale, and local networking. - Later phases will add persistence, disk deployment, and upgrade/rescue flows.
Testing
Default verification order for the operator USB is now:
- static artifact verification on the build host
- bhyve boot verification on the ML350p virtualization lane
- physical hardware validation for the final acceptance pass
Use bhyve before writing to hardware whenever the lane is available:
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 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:
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