# 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 carries its own product version, independent of any component (zot, Colibri, Clawdie-AI). Component versions are recorded in `build-manifest.json`. ```sh ISO_VERSION="0.10.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: ```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 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: ```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 ``` Temporary debugging escape hatch while the flag still exists: ```sh 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: ```text /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_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 ```text 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 ```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-quindecim-0.10.0.img ``` Published/downloaded artifacts are compressed as `.img.xz`. Stream the compressed image directly into `dd`: ```sh xz -dc clawdie-quindecim-0.10.0.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: ```sh curl -fL --continue-at - --retry 5 --retry-delay 5 --progress-bar -O \ https://osa.smilepowered.org/downloads/iso/clawdie-quindecim-0.10.0.img.xz curl -fL --retry 5 --retry-delay 5 -O \ https://osa.smilepowered.org/downloads/iso/clawdie-quindecim-0.10.0.img.xz.sha256 ``` For a build-local uncompressed image, plain `dd` is also fine: ```sh sudo dd if=tmp/output/clawdie-quindecim-0.10.0.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 `) 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@ ``` 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`, `.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 : -dev FreeBSD : 15.0-RELEASE amd64 Clawdie : main Clawdie commit: ``` 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 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: ```sh ISO_VERSION="${ISO_VERSION:-0.10.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, 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 | | `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 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`, `screen`, `mc`, `zip`, `unzip`, `7-zip`, `python312`, `dejavu`. Python package-flavored extras such as Pillow stay out of pkg lists until FreeBSD quarterly publishes Python 3.12 flavors. - **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 `** 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