clawdie-iso/BUILD.md

624 lines
28 KiB
Markdown
Raw Normal View History

2026-05-12 19:26:35 +02:00
# 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
2026-05-12 19:26:35 +02:00
The ISO version is independent from the bundled Clawdie-AI ref:
2026-05-12 19:26:35 +02:00
```sh
ISO_VERSION="0.1.0"
BUILD_CHANNEL="dev" # dev | release
CLAWDIE_REF="main" # validation default
```
2026-05-12 19:26:35 +02:00
Release builds must pin a Clawdie-AI tag with `--clawdie-version X.Y.Z`.
---
2026-05-12 19:26:35 +02:00
## Prerequisites
2026-05-12 19:26:35 +02:00
On the FreeBSD build host:
2026-05-12 19:26:35 +02:00
```sh
sudo pkg install -y curl node24 npm-node24 sudo
2026-05-12 19:26:35 +02:00
```
2026-05-12 19:26:35 +02:00
Also required:
2026-05-12 19:26:35 +02:00
- 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`.
2026-05-12 19:26:35 +02:00
### 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 ../colibri && cargo build --workspace --release)
sudo ./build.sh --skip-fetch
```
Override locations when needed:
```sh
COLIBRI_REPO=/path/to/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`.
2026-05-12 19:26:35 +02:00
---
## Quick Start
2026-05-12 19:26:35 +02:00
```sh
# Confirm you are building the intended branch/head first.
git status --short --branch
git log --oneline -5
2026-05-12 19:26:35 +02:00
# Full build: fetch + assemble.
sudo ./build.sh
```
2026-05-12 19:26:35 +02:00
Output:
2026-05-12 19:26:35 +02:00
```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:
2026-05-12 19:26:35 +02:00
```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.
2026-05-12 19:26:35 +02:00
---
## Useful Build Modes
2026-05-12 19:26:35 +02:00
```sh
# Download/cache FreeBSD, packages, npm globals, and Clawdie-AI only.
./build.sh --fetch-only
# Assemble using cached inputs.
Unify ISO and fix GPU installation gap (Sam & ZAI) BREAKING CHANGE: Removes --target and --gpu-driver flags, unified ISO for all use cases ## Phase 0: GPU Fix + Unified ISO ### Core Changes **GPU Package Installation (FIXES CRITICAL GAP):** - Add clawdie_shell_nvidia_install() function to shell-nvidia.sh - NVIDIA drivers now installed after detection (previously only configured) - Works offline (USB packages) or online (pkg install) - Resolves issue where rc.conf was set but driver not installed **Unified ISO Architecture:** - Remove --target flag from build.sh (no more vps/baremetal branching) - Remove --gpu-driver flag from build.sh (runtime detection instead) - All packages included on every ISO (desktop + all GPU drivers) - Single image works on VPS, baremetal, and cloud **Runtime Detection:** - Add shell-desktop.sh for display detection at firstboot - VPS/cloud: no display → lightdm disabled (headless) - Baremetal: display detected → lightdm enabled (Lumina desktop) - GPU detection always runs, installs correct driver version **Sudo Unification:** - Replace all doas references with sudo across entire codebase - Update AGENTS.md with system configuration guidelines - Update all documentation (BUILD.md, README.md, REQUIREMENTS.md, etc.) - Admin panel now uses sudo for privileged operations ### Files Modified **Core System:** - build.sh: Remove target/gpu-driver logic, unified package selection - firstboot/firstboot.sh: Add desktop detection module - firstboot/shell-nvidia.sh: Add package installation function (+33 lines) **New Files:** - firstboot/shell-desktop.sh: Display detection and desktop enablement - packages/pkg-list-nvidia-all.txt: All three NVIDIA driver versions (390/470/590) - .opencode/plans/phase0-gpu-fix-unified-iso.md: Implementation plan **Documentation:** - PLAN-UNIFY.md: Update Step 3 for unified approach - REQUIREMENTS.md: Simplify (no target choice), update for sudo - BUILD.md: Update for unified ISO, sudo commands - README.md: Update installation instructions - AGENTS.md: Add system configuration section (sudo standardization) - ADMIN-PANEL.md: Update privileged operations to use sudo - CLAWDIE-SHELL.md: Update example commands to sudo - CLAWDIE-ISO-REFACTORED.md: Update access paths to sudo - REFACTOR-SUMMARY.md: Update permissions section to sudo ### Benefits **Simplicity:** - One build command: ./build.sh (no flags needed) - One ISO to test and maintain - No wrong choices for users - No documentation explaining target differences **Flexibility:** - VPS can use GUI via VNC (wayvnc always available) - Baremetal can run headless (disable lightdm) - Repurpose hardware without reinstall - All GPU drivers available for any hardware **Technical:** - Fixes critical GPU driver installation gap - Runtime detection replaces build-time decisions - Disk overhead: ~650MB (1-2% of 50GB - acceptable) - No runtime overhead on VPS (services disabled by detection) ### Testing Required - [ ] Build unified ISO: ./build.sh - [ ] Test on VPS (no display): lightdm disabled, packages installed - [ ] Test on baremetal (display): lightdm enabled, Lumina boots - [ ] Test on NVIDIA hardware: driver installed and loaded - [ ] Test sudo commands work without password prompts - [ ] Verify all doas references removed
2026-04-06 13:28:56 +02:00
sudo ./build.sh --skip-fetch
2026-05-12 19:26:35 +02:00
# 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"
2026-05-12 19:26:35 +02:00
# 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
```
2026-05-12 19:26:35 +02:00
`--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.
2026-05-12 19:26:35 +02:00
---
2026-05-12 19:26:35 +02:00
## 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
```
2026-05-12 19:26:35 +02:00
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
2026-05-12 19:26:35 +02:00
Edit `build.cfg` for persistent defaults:
```sh
2026-05-12 19:26:35 +02:00
ISO_VERSION="0.1.0"
BUILD_CHANNEL="${BUILD_CHANNEL:-dev}"
IMAGE_SIZE="28G"
2026-05-12 19:26:35 +02:00
CLAWDIE_REF="${CLAWDIE_REF:-main}"
DEFAULT_PKG_BRANCH="latest"
FEATURE_TAILSCALE="${FEATURE_TAILSCALE:-YES}"
```
2026-05-12 19:26:35 +02:00
Notes:
2026-05-12 19:26:35 +02:00
- 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. |
2026-05-12 19:26:35 +02:00
---
2026-05-12 19:26:35 +02:00
## Build Process
2026-05-12 19:26:35 +02:00
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.
2026-05-12 19:26:35 +02:00
7. Copy the final sparse image into `tmp/output/`.
2026-05-12 19:26:35 +02:00
The build is intentionally cache-friendly. If in doubt before validation, run the
full `sudo ./build.sh` once after pulling current `main`.
2026-05-12 19:26:35 +02:00
---
## 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.
2026-05-12 19:26:35 +02:00
---
2026-05-12 19:26:35 +02:00
## Testing
2026-05-12 19:26:35 +02:00
Before writing to hardware, use bhyve when available:
2026-05-12 19:26:35 +02:00
```sh
sudo ./scripts/bhyve-pf-allow.sh
sudo ./scripts/bhyve-test.sh
2026-05-12 19:26:35 +02:00
```
See [TESTING.md](TESTING.md) for the full validation checklist.
---
## Troubleshooting
2026-05-12 19:26:35 +02:00
**`ERROR: missing package archive for <pkg>`**
2026-05-12 19:26:35 +02:00
A package was added after your cache was created. Run a full fetch/build:
2026-05-12 19:26:35 +02:00
```sh
sudo ./build.sh
```
2026-05-12 19:26:35 +02:00
**`pkg` not found during unprivileged fetch**
2026-05-12 19:26:35 +02:00
`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.
2026-05-12 19:26:35 +02:00
**`npm ci` falls back to `npm install`**
2026-05-12 19:26:35 +02:00
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.
2026-05-12 19:26:35 +02:00
**Image says small allocated size**
2026-05-12 19:26:35 +02:00
The image is sparse on the build host. Use the logical image size when choosing
a USB key.
---
**Last updated:** 16.maj.2026