clawdie-ai/CLAWDIE-ISO.md
Clawdie AI eb4ceac068 Align npm global paths and Aider install docs
---
Build: pass | Tests: pass — Tests  874 passed (874)
2026-04-12 06:19:36 +00:00

25 KiB
Raw Blame History

CLAWDIE-ISO — USB Installer Plan

Status: Active goal — bare-metal deployment path (complements cloud/VPS) — 17.Mar.2026 Target repo: codeberg.org/Clawdie/clawdie-iso (to be created)


Purpose

Create a bootable USB image that reduces a complete Clawdie-AI deployment to a single physical step: boot USB, answer a few screens, unplug USB, done.

The installed system is a plain FreeBSD + desktop + Clawdie-AI — nothing exotic, nothing persistent on the USB. The USB is a deployment vehicle only.

The install combines three procedures that currently run separately:

# Path Tool What it does
1 FreeBSD base bsdinstall Disk layout, ZFS, base OS, bootloader, root/user accounts
2 Desktop setup desktop-installer GPU detection, Xorg/Wayland, DE packages, login manager, audio
3 Clawdie-AI setup.sh → install Bastille jails, PostgreSQL, nginx/CMS, rc.d service, bot config

Current state: these three are completely separate. An operator must install FreeBSD, reboot, install desktop-installer, run it, reboot (sometimes), run setup.sh, configure. Minimum 2 reboots, typically 3+ operator interventions.

Target state: one ISO, one bsddialog wizard, one reboot.


Desktop-Installer Reference

outpaddling/desktop-installer is a FreeBSD ports package (pkg install desktop-installer) that automates the post-install desktop configuration step:

  • Detects GPU via pciconf -lv
  • Installs the right kld modules (i915kms, nvidia-modeset, drm, vboxguest, etc.)
  • Writes kld_list to /etc/rc.conf for persistent driver loading
  • Installs the chosen DE (Lumina — FreeBSD-native)
  • Configures dbus, CUPS, sound (OSS/PulseAudio), login manager (lightdm)
  • Handles VirtualBox, VMware, Parallels, bhyve guest additions automatically
  • Does NOT typically require a reboot — kld can be loaded live or on next boot
  • Respects quarterly vs latest pkg repository configured on the system

We borrow the detection and configuration logic from desktop-installer but do not run the interactive script. Our build process will vendor the GPU detection function and call it from our own bsddialog wizard.


Reboot Analysis — Can We Do 1 Reboot?

Three potential reboot triggers:

Reboot 1 (mandatory): bsdinstall completes bsdinstall always reboots after base install. Can't be avoided. The system must boot from HDD before userspace can run.

Reboot 2 (optional): GPU driver loading Most GPU drivers on FreeBSD are kernel modules (klds). desktop-installer writes them to kld_list in rc.conf, but they can also be kldloaded into a live system. The key question: can we detect the GPU and write correct config WITHOUT having the module loaded yet?

Answer: yes. GPU detection uses pciconf -lv (reads PCI device tree, no kld needed). The output gives us PCI device ID and vendor ID. We match against a known table to select the right kld and write it to rc.conf. On first boot from HDD, the kld loads automatically. X11/Wayland starts clean. No intermediate reboot is needed to make this work.

VirtualBox is explicitly out of scope for this installer. Target: real hardware and bhyve only. VBox guest packages are not bundled on the USB image.

Reboot 3 (not needed): Clawdie setup setup.sh and all jail provisioning steps run in userspace. No reboot needed. rc.d service starts without reboot via service clawdie onestart.

Verdict: 1 reboot is achievable for all common hardware. Two reboots may still be the safer default for first release if GPU detection reliability is uncertain. See Options section.


Pkg Branch Selection

FreeBSD ships two pkg repository branches:

Branch URL suffix Update cadence Best for
quarterly /quarterly/ Stable snapshots, security fixes only Servers, production
latest /latest/ Tracks ports HEAD, newest versions Desktops, GPU drivers, Node24

Recommendation: default to latest. GPU drivers (drm-kmod, nvidia-driver), Node 24, and many Bastille-related packages are updated sooner in latest. Quarterly has caused GPU driver installation failures on new hardware.

How to implement the toggle:

During the install wizard (bsddialog), present a radiolist before any packages are installed:

Package repository branch:
  (*) Latest    — newest packages, best driver support [recommended]
  ( ) Quarterly — stable branch, security updates only

The selected branch sets /usr/local/etc/pkg/repos/FreeBSD.conf:

FreeBSD: {
  url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
  mirror_type: "srv",
  enabled: yes
}

For offline install from USB, the bundled pkg repo must match the selected branch. Build the USB with latest packages. Add a note: if the user selects quarterly, the offline install falls back to the USB-bundled packages (which are latest builds) and the system upgrades to quarterly on first internet connection. Simpler alternative: only support latest offline, add quarterly as a post-install option in the wizard.


Options

Option A — Pre-configured silent install (1 reboot, no interaction)

All choices are baked into a config file on the USB (clawdie.conf). The operator edits this file before booting:

PKG_BRANCH=latest
DESKTOP=lumina
AGENT_NAME=clawdie
ASSISTANT_NAME="Clawdie"
TZ=Europe/Ljubljana

bsdinstall runs from installerconfig. After base install, a post-install script reads clawdie.conf, installs everything offline, writes all config. Reboot once into a fully configured system.

Pro: Fully automated, reproducible, CI-friendly, no bsddialog needed Con: Requires USB preparation before boot. No interactive personalization. Reboots: 1 Best for: Reproducible deploys, CI testing, pre-provisioned hardware


Option B — Interactive bsddialog wizard in installerconfig (1 reboot, guided)

After bsdinstall's base install completes but before the system reboots, installerconfig runs a bsddialog wizard. The wizard covers:

  1. Pkg branch (latest / quarterly)
  2. Desktop: Lumina (fixed, no choice)
  3. Agent name + assistant name
  4. Telegram bot token (optional, can be skipped)
  5. Summary screen → confirm → install

GPU is detected from the live USB environment via pciconf -lv before chroot. Detection result is written into the target system's rc.conf. All packages are installed offline from the USB repo. Clawdie setup runs in chroot. Single reboot into configured system.

Pro: Best UX, matches existing bsddialog pattern, interactive but 1-step Con: bsddialog in post-install chroot requires careful path setup Reboots: 1 Best for: First install on new hardware, operator-configured deployments


Option C — rc.firstboot script (2 reboots, most reliable)

Standard bsdinstall base install. Minimal installerconfig just copies the wizard script and enables a firstboot rc.d service. On first real boot from HDD (reboot 1), the firstboot service runs the bsddialog wizard on the live system. Packages install, GPU loads live via kldload, DE and Clawdie come up. Optional second reboot (reboot 2) for clean DM start.

Pro: Runs on real hardware (not in chroot), GPU kldload works immediately, most reliable path, easier debugging Con: 2 reboots minimum. Wizard runs after first reboot, which can feel slow. Reboots: 2 Best for: Conservative first release, maximum hardware compatibility


Option D — Extended bsdinstall menus (1 reboot, most native)

Modify bsdinstall to add extra menu screens: pkg branch, DE selection, Clawdie agent config. The installer feels like a fully native FreeBSD install with Clawdie options integrated into the standard installer flow. Post-install runs silently based on selections.

Pro: Most seamless UX — everything happens inside one familiar installer Con: Requires forking/patching bsdinstall (sh scripts in /usr/libexec/bsdinstall). Fragile against FreeBSD updates. Significant maintenance burden. Reboots: 1 Best for: Polished v2.0+ release, not first iteration


Option E — Live USB + install-to-disk (2+ reboots, most impressive)

The USB boots as a live Clawdie desktop system. User can interact with a running agent demo before committing to install. An "Install Clawdie to Disk" button/command runs the installer. After install + reboot, the HDD system is fully configured.

Pro: Demo capability, validate hardware/agent before committing, impressive Con: Most complex build (~23GB image), live persistence layer, 2 reboots, significant extra build infrastructure Reboots: 2+ Best for: Demo/showcase events, evaluators who want to try before installing


Recommendation

Start with Option C (rc.firstboot, 2 reboots) for v1 of the repo.

Reason: GPU detection running on the real live system (not in chroot) eliminates the most common failure mode. The 2-reboot tradeoff is acceptable for a v1 that works reliably. The wizard runs on boot from HDD, using the same bsddialog infrastructure already in Clawdie's setup.sh — no new tooling needed.

Upgrade path to Option B (1 reboot) for v1.1:

Once we have Option C working and tested on multiple hardware configs, we port the wizard to run in the installerconfig post-install chroot. At that point we drop the firstboot service and the second reboot.

Skip Option D and E for now. Neither is worth the complexity at this stage. Option A can be added as a --unattended flag on top of Option B/C.


Three-Path Merge Flow (Option C)

USB boot
  └─ bsdinstall
       ├─ Disk partitioning (ZFS, GPT+EFI)
       ├─ Base system distribution
       ├─ Bootloader
       ├─ Root password
       ├─ User account (clawdie)
       └─ installerconfig:
            ├─ Copy /usr/local/share/clawdie-iso/firstboot.sh to HDD
            ├─ Install clawdie-iso rc.d firstboot service
            └─ Reboot from HDD

First boot from HDD (reboot 1)
  └─ rc.firstboot (clawdie-firstboot service)
       ├─ bsddialog: pkg branch (latest [default] / quarterly)
       ├─ bsddialog: agent name + assistant name
       ├─ bsddialog: Telegram bot token (optional)
       ├─ bsddialog: summary + confirm
       │
       ├─ [pkg] Write /usr/local/etc/pkg/repos/FreeBSD.conf (selected branch)
       ├─ [pkg] pkg install from USB offline repo (desktop-installer deps)
       │
       ├─ [gpu] pciconf -lv → detect GPU vendor/device ID
       ├─ [gpu] kldload appropriate driver(s)
       ├─ [gpu] Write kld_list to /etc/rc.conf
       ├─ [gpu] Write /etc/X11/xorg.conf.d/ if needed
       │
       ├─ [de] Install chosen desktop environment from USB repo
       ├─ [de] Enable dbus, hald, login manager in rc.conf
       ├─ [de] Configure .xinitrc / session for clawdie user
       │
       ├─ [clawdie] Extract clawdie-ai.tar.gz to /home/clawdie/clawdie-ai
       ├─ [clawdie] Set up .env defaults (AGENT_NAME, ASSISTANT_NAME, TZ)
       ├─ [clawdie] cd /home/clawdie/clawdie-ai && npm run install
       │    ├─ environment (host pkg baseline from USB)
       │    ├─ jails (worker + db + git + cms)
       │    ├─ db (PostgreSQL + pgvector)
       │    ├─ skills-memory (if artifacts/skills.db present on USB)
       │    ├─ cms (nginx + Astro)
       │    ├─ service (rc.d clawdie)
       │    └─ verify
       │
       └─ Disable clawdie-firstboot service (runs once only)

Running system (no reboot 2 needed for Option C baseline)
  └─ Login manager starts → desktop session → clawdie agent running

Repo Structure (clawdie-iso)

clawdie-iso/
├── build.sh              ← main build script
├── build.cfg             ← version pins, image size, pkg list
├── installerconfig       ← bsdinstall post-install hook
├── firstboot/
│   ├── firstboot.sh      ← the bsddialog wizard + setup runner
│   ├── rc.d/
│   │   └── clawdie-firstboot  ← FreeBSD rc.d service (runs once)
│   └── gpu-detect.sh     ← pciconf → kld table lookup
├── packages/             ← pre-fetched .pkg files (gitignored, fetched by build.sh)
│   ├── .repo/            ← generated by pkg repo
│   └── *.pkg
└── CLAWDIE-ISO.md        ← this document (copy)

build.sh responsibilities:

  1. Fetch FreeBSD memstick installer image (verified checksum)
  2. Fetch all required .pkg files to packages/ (node24, bastille, desktop-installer deps, etc.)
  3. Run pkg repo packages/ to generate repo metadata
  4. Unpack memstick image
  5. Inject into memstick: installerconfig, firstboot/ tree, packages/ repo, clawdie-ai.tar.gz
  6. Repack image
  7. Output: clawdie-iso-YYYYMMDD.img

USB Package Manifest

Derived from setup/environment.ts host baseline + desktop-installer + DE packages:

Clawdie host baseline: node24, npm, bsddialog, bastille, git, tmux, python311, uv, ripgrep, fd-find, rsync, postgresql17-client, py311-pillow, dejavu

Xorg baseline: xorg-minimal, xf86-video-intel, drm-kmod, xf86-input-libinput, dbus, hal

Note: xf86-video-vboxvideo and VirtualBox guest additions are excluded. Target hardware is real machines and bhyve only. VirtualBox is out of scope.

Lumina (sole supported DE): lumina-core, lumina-themes, lumina-calculator, lumina-archiver, lumina-filemanager, lumina-screenshot, lumina-open, openbox, libxcb, libxdg-basedir, lightdm, lightdm-gtk-greeter

NVIDIA support (optional): nvidia-driver, nvidia-settings

Total estimated size (Lumina path, latest): ~1.5 GB packages + ~600 MB FreeBSD base


Node.js Shared Modules Setup

A critical user-space tweak that must be reproduced exactly on the installed system: npm's global package prefix must be set to the clawdie user's home directory, not to /usr/local.

Why it matters: On FreeBSD, /usr/local is owned by root. Running npm install -g as the clawdie user fails without this. The prefix setting redirects globals to a user-owned path that is in PATH.

Current production setup (from /home/clawdie/.npmrc):

prefix=/opt/clawdie/npm-global

PATH addition (must be in /home/clawdie/.profile or .shrc):

export PATH="/opt/clawdie/npm-global/bin:$PATH"

firstboot.sh must:

  1. Write /home/clawdie/.npmrc with prefix=/opt/clawdie/npm-global
  2. Create /opt/clawdie/npm-global/ (with correct ownership)
  3. Symlink /home/clawdie/.npm-global/opt/clawdie/npm-global
  4. Append PATH export to /home/clawdie/.profile
  5. Pre-populate the global npm cache from USB-bundled packages so that npm install inside clawdie-ai runs fully offline

Build-time: build.sh must pre-fetch npm packages and bundle them as a local npm cache tarball (npm-cache.tar.gz) to be extracted to /home/clawdie/.npm during firstboot. This eliminates network calls to registry.npmjs.org during install.

Bundled Agent CLIs (npm-globals)

In addition to the offline npm cache, the ISO ships three pre-packed agent CLIs as standalone tarballs so the firstboot environment satisfies the setup onboard agent-CLI prereq gate without network access:

  • @anthropic-ai/claude-codeclaude
  • @google/gemini-cligemini
  • @mariozechner/pi-coding-agentpi

Build path:

  1. clawdie-iso/scripts/fetch-npm-globals.sh runs npm pack for each package into tmp/npm-globals/
  2. build.sh copies that directory to ${USB_SHARE}/npm-globals/
  3. firstboot/shell-npm-globals.sh (function clawdie_shell_npm_globals_install) runs npm install -g <tgz> as the clawdie user with npm_config_prefix=/opt/clawdie/npm-global so the binaries land in the user-owned prefix

codex is not in the npm bundle — it ships as the FreeBSD codex pkg and is installed via pkg install -y codex. End-to-end validation of all four CLIs in a fresh worker jail is in doc/AGENT-CLI-VALIDATION.md.


AGENTS.md Seeding (Planning)

AGENTS.md is the agent's identity and behavior file. It must be seeded during firstboot with values derived from the install wizard (assistant name, locale, agent domain, etc.).

Plan (not yet implemented):

  • A template AGENTS.md.tpl is bundled on the USB inside the clawdie-ai tarball at groups/global/AGENTS.md.tpl (or groups/main/AGENTS.md.tpl)
  • During firstboot, after .env is written, firstboot.sh renders the template by substituting:
    • {{ASSISTANT_NAME}} → value from wizard
    • {{AGENT_NAME}} → derived system name
    • {{AGENT_DOMAIN}} → configured domain
    • {{ASSISTANT_LOCALE}} → selected locale
    • {{TZ}} → selected timezone
  • Output written to groups/global/AGENTS.md before npm run install runs
  • This ensures the agent has a fully personalized identity from first boot

Scope: Template design and rendering are handled in the clawdie-ai repo. The clawdie-iso repo only needs to call the render step during firstboot.


.env Generation Strategy

The installed system requires 65 environment variables to be set before Clawdie-AI can start. They fall into four categories:

Category Count Source
Identity / locale 8 Human provides via wizard
API keys / tokens 14 Human provides (all optional / deferrable)
Auto-generated secrets 9 openssl rand -base64 32 at install time
Derived structural 34 Calculated from identity (jail IPs, names, etc.)

Three options for how the wizard handles this:

.env Option 1 — Minimal wizard (5 questions, everything else silent)

The wizard asks only the irreducible human questions:

  1. Assistant name → derives AGENT_NAME automatically
  2. Timezone
  3. Domain name (default: <agentname>.local)
  4. Primary LLM provider + key (Anthropic / OpenAI / skip)
  5. Telegram bot token (or skip)

All 9 secrets: auto-generated silently with openssl rand. All 34 structural vars: derived from identity choices (fixed subnet, fixed jail IPs, names from AGENT_NAME, etc.). API keys not asked: left blank in .env, operator fills post-install.

Pro: Fastest path. 5 screens. Least friction. Works completely offline. Con: No visibility into generated credentials during install. Operator must read .env to find database passwords. No way to customize subnet or features. Best for: First-time users, fast re-deploys, CI/automated testing.

LLM Key Entry — Deferred to pi first-run (design decision)

LLM provider keys are not collected by the ISO installer wizard. This is intentional. The installer is decoupled from provider choice — keys can change, providers can rotate, and keys should not be baked into USB media.

Instead the wizard collects only the pi profile (which provider class: Anthropic / OpenAI / Ollama / skip). The key itself is entered the first time pi is used:

Path A — pi first-run prompt (TTY detected): On the operator's first interaction with the agent (or on firstboot post-install if a TTY is present), pi-config checks for the provider key. If missing and a TTY is available: bsddialog password input → written to .env → service restarted automatically.

Path B — manual .env edit (headless / operator preference): Operator edits /home/clawdie/clawdie-ai/.env directly, sets the appropriate key var (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.), then runs service clawdie restart. pi-config detects the key is present and skips prompting on all future runs.

Detection logic (to be implemented in clawdie-ai):

pi-config runs →
  selected provider key var present in .env? → skip, continue
  not present AND TTY available? → bsddialog password input → write .env
  not present AND no TTY? → log instruction, continue (non-fatal)

This is a gap in the current codebase — the non-blocking warning exists but the TTY-triggered bsddialog prompt does not. This is a scoped task for the clawdie-ai repo, not the clawdie-iso repo.

Task (clawdie-ai): Add TTY-aware key prompt to setup/pi-config.ts: check for TTY (process.stdin.isTTY), if true and key missing, run bsddialog --passwordbox "Enter <PROVIDER> API key" ..., write result to .env, emit restart.


Tier 1 (always shown, 4 screens):

  • Name + timezone + domain → derive/generate everything → write .env

Tier 2 (shown with "Configure now? [Y/n]"):

  • LLM provider selection radiolist → key input
  • Telegram bot token input

Tier 3 (shown only if operator presses "Advanced"):

  • Custom subnet base (default 10.0.0.0/24)
  • Feature flags: Ollama (local inference), Gitea (self-hosted git), Management jail
  • ZFS dataset layout (default vs Clawdie-optimized)

End screen — Credentials Summary:

┌─ Clawdie Installation Complete ──────────────────────────┐
│                                                           │
│  Agent:       Clawdie  (clawdie)                         │
│  Domain:      clawdie.local                               │
│  Timezone:    Europe/Ljubljana                            │
│                                                           │
│  DB password: Xy7qP2mR...  (see /home/clawdie/.env)      │
│  Telegram:    ✓ configured                                │
│  LLM:         Anthropic ✓                                 │
│                                                           │
│  Press Enter to start installation.                       │
└───────────────────────────────────────────────────────────┘

Pro: Best balance. Guided but not overwhelming. Summary screen prevents lost credentials. Advanced tier serves power users without cluttering the default path. Con: More wizard screens to implement. Summary screen needs careful design. Best for: Standard production deployments, first installs on new hardware.

.env Option 3 — Pre-boot config file on USB (clawdie.conf)

The USB image contains a clawdie.conf template file on a FAT32 partition that is readable/writable from any OS (Windows, macOS, Linux, FreeBSD). Operator edits it before booting:

# Edit this file on the USB before booting.
# Leave a field blank to be prompted during install.
ASSISTANT_NAME=Clawdie
TZ=Europe/Ljubljana
AGENT_DOMAIN=clawdie.local
PKG_BRANCH=latest              # latest or quarterly
DESKTOP=lumina                 # lumina only
TELEGRAM_BOT_TOKEN=            # optional, leave blank to skip
ANTHROPIC_API_KEY=             # optional

Firstboot reads clawdie.conf first. Blank required fields trigger bsddialog prompts. Blank optional fields are silently skipped. All secrets still auto-generated regardless. A fully pre-filled clawdie.conf = zero-interaction install.

Pro: Supports fully automated reproducible deploys without modifying ISO. Familiar to sysadmins (edit a config file → boot). Works offline. Can be scripted or templated externally. Con: Operator must mount USB on another machine before install (FAT32 partition is accessible but requires extra step). Blank fields still need wizard. Best for: Reproducible testing, staging/production fleet deployments, CI.


Open Questions

  1. GPU detection table: desktop-installer uses a shell function with a PCI ID lookup table. We should vendor gpu-detect.sh from desktop-installer source rather than reimplementing it. Verify license (BSD 2-clause — fine).

  2. pkg offline vs online: If target machine has internet, pkg install will upgrade from online repo after we've pointed at USB. That's fine and expected. The USB repo is only for air-gapped or offline installs.

  3. ZFS layout: Should we mirror bsdinstall's default ZFS layout or enforce a Clawdie-specific layout (separate datasets for bastille jails, var/db)? Decision needed before writing installerconfig.

  4. ARM64 / Raspberry Pi: build.sh should detect host arch and fetch the matching FreeBSD image. For Pi, memstick is replaced with a different image type. Scope for v1: amd64 only, Pi deferred.

  5. clawdie user UID: bsdinstall creates the user interactively. Should we override/enforce UID 1001 for clawdie? Or let operator choose during bsdinstall and then reference $CLAWDIE_USER in firstboot.sh?

  6. Clawdie-AI source bundling: Bundle a tagged release tarball (clawdie-ai vX.Y.Z) or pull from Codeberg during firstboot? Offline-first → bundle. build.sh should accept --clawdie-version X.Y.Z and fetch the tarball.


Next Steps

  1. Create codeberg.org/Clawdie/clawdie-iso repo (empty, README only)
  2. Copy this document as the repo's CLAWDIE-ISO.md
  3. Write build.cfg with FreeBSD 15.0-RELEASE-p4, amd64, Lumina default
  4. Write gpu-detect.sh (vendor from desktop-installer, simplify)
  5. Write firstboot.sh (bsddialog wizard + exec setup.sh)
  6. Write installerconfig (minimal, just copies firstboot/ tree)
  7. Write build.sh skeleton (fetch → inject → repack)
  8. Test: boot in bhyve VM on this host, validate firstboot flow