clawdie-iso/BUILD.md
Sam & Claude c45360864a feat(iso): stage colibri test agent and provider key help (Sam & Codex)
Switch ISO staging/docs from colibri-smoke-agent to colibri-test-agent, include rust/pkgconf for live Colibri rebuilds, stage provider.env.sample, wire the provider env rc.conf path, and document LLM key setup on the Firefox bootstrap page.\n\nChecks: npx --yes prettier@3 --check docs/LIVE-COLIBRI-REBUILD.md live/operator-session/bootstrap.html BUILD.md TESTING.md README.md; sh -n scripts/stage-colibri-iso.sh; sh -n build.sh; fake Colibri staging + sh -n staged rc.d script; git diff --check.
2026-06-15 07:49:43 +02:00

30 KiB

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 for the live USB
  • deployed-system clawdie service lane for future disk/server installs

The ISO version is independent from the bundled Clawdie-AI ref:

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:

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 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; it does not compile Rust while the image is mounted.

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

Do not use that for operator-USB validation builds; the accepted USB runtime is Colibri-backed. The image includes:

/usr/local/bin/colibri-daemon
/usr/local/bin/colibri
/usr/local/bin/colibri-test-agent
/usr/local/bin/colibri-mcp              # MCP bridge for Zed/Claude Code/Cursor
/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 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 uses Colibri directly and must not ship the old mini-binary as if it were 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.2.29.img

Published/downloaded artifacts are compressed as .img.xz. Stream the compressed image directly into dd:

xz -dc clawdie-quindecim-0.2.29.img.xz | 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:

curl -fL --continue-at - --retry 5 --retry-delay 5 --progress-bar -O \
  https://osa.smilepowered.org/downloads/iso/clawdie-quindecim-0.2.29.img.xz
curl -fL --retry 5 --retry-delay 5 -O \
  https://osa.smilepowered.org/downloads/iso/clawdie-quindecim-0.2.29.img.xz.sha256

For a build-local uncompressed image, plain dd is also fine:

sudo dd if=tmp/output/clawdie-quindecim-0.2.29.img of=/dev/daX bs=1M status=progress conv=fsync
sync

Use the whole USB device (/dev/daX), not a partition. 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-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:

# 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:

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     : 0.1.0-dev
FreeBSD : 15.0-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 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:

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), 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. 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
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-dispatcherpulseaudio 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-dispatcherpulseaudio 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:

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