From 34075f3a5ecf45535a807be60e1ff77dbe538dd5 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Tue, 17 Mar 2026 10:20:23 +0000 Subject: [PATCH] feat: initial clawdie-iso skeleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USB installer for Clawdie-AI. Combines FreeBSD base install, desktop-installer GPU/DE setup, and Clawdie-AI deployment into a single rc.firstboot wizard flow. Skeleton includes: - build.cfg: FreeBSD 15.0-RELEASE-p4, amd64, XFCE default - build.sh: 7-step build outline (fetch → inject → repack), stubs - installerconfig: bsdinstall post-install hook, copies firstboot/ to HDD - firstboot/rc.d/clawdie-firstboot: runs once on first HDD boot - firstboot/firstboot.sh: tiered bsddialog wizard (identity, desktop, pi profile, auto-generated secrets, AGENTS.md seeding, npm prefix setup) - firstboot/gpu-detect.sh: pciconf PCI ID → kld/xorg driver mapping - CLAWDIE-ISO.md: full design doc (copied from clawdie-ai) VirtualBox excluded. pkg latest default. LLM keys deferred to pi first-run. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 9 + CLAWDIE-ISO.md | 594 +++++++++++++++++++++++++++++++ README.md | 31 ++ build.cfg | 20 ++ build.sh | 94 +++++ firstboot/firstboot.sh | 245 +++++++++++++ firstboot/gpu-detect.sh | 73 ++++ firstboot/rc.d/clawdie-firstboot | 42 +++ installerconfig | 39 ++ packages/.gitkeep | 0 10 files changed, 1147 insertions(+) create mode 100644 .gitignore create mode 100644 CLAWDIE-ISO.md create mode 100644 README.md create mode 100644 build.cfg create mode 100644 build.sh create mode 100644 firstboot/firstboot.sh create mode 100644 firstboot/gpu-detect.sh create mode 100644 firstboot/rc.d/clawdie-firstboot create mode 100644 installerconfig create mode 100644 packages/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1b53ed6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Fetched at build time — do not commit +cache/ +packages/*.pkg +packages/*.txz +packages/.repo/ + +# Build output +*.img +*.iso diff --git a/CLAWDIE-ISO.md b/CLAWDIE-ISO.md new file mode 100644 index 00000000..b08b180c --- /dev/null +++ b/CLAWDIE-ISO.md @@ -0,0 +1,594 @@ +# CLAWDIE-ISO — USB Installer Plan + +**Status:** Draft brainstorm — 2026-03-17 +**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-all | 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](https://github.com/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 (KDE Plasma, XFCE, MATE, GNOME, Lumina, etc.) +- Configures dbus, CUPS, sound (OSS/PulseAudio), login manager (SDDM/lightdm/slim) +- 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 `kldload`ed 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`: + +```ucl +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: + +```sh +PKG_BRANCH=latest +DESKTOP=xfce +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 environment (XFCE / KDE Plasma / MATE / Headless) +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 (~2–3GB 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: desktop environment (XFCE / KDE / MATE / Headless) + ├─ 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-all + │ ├─ 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` + +**Desktop-installer and Xorg baseline:** +`desktop-installer`, `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. + +**XFCE (default DE):** +`xfce`, `xfce4-goodies`, `lightdm`, `lightdm-gtk-greeter` + +**KDE Plasma (optional, large):** +`plasma5-plasma`, `kde-baseapps`, `sddm` + +**MATE (optional, lightweight alt):** +`mate`, `mate-extra`, `lightdm`, `lightdm-gtk-greeter` + +**NVIDIA support (optional):** +`nvidia-driver`, `nvidia-settings` + +Total estimated size (XFCE path, latest): ~2–3 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=/home/clawdie/.npm-global +``` + +**PATH addition** (must be in `/home/clawdie/.profile` or `.shrc`): +```sh +export PATH="$HOME/.npm-global/bin:$PATH" +``` + +**firstboot.sh must:** +1. Write `/home/clawdie/.npmrc` with `prefix=/home/clawdie/.npm-global` +2. Create `/home/clawdie/.npm-global/` (with correct ownership) +3. Append PATH export to `/home/clawdie/.profile` +4. 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. + +--- + +## 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-all` 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: `.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 API key" ...`, write result +to `.env`, emit restart. + +--- + +### .env Option 2 — Tiered wizard with credentials summary (recommended) + +**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: + +```sh +# 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=xfce # xfce, kde, mate, headless +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, XFCE 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 diff --git a/README.md b/README.md new file mode 100644 index 00000000..04fde887 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Clawdie-ISO + +Bootable USB installer for [Clawdie-AI](https://codeberg.org/Clawdie/Clawdie-AI). + +Installs FreeBSD + desktop + Clawdie-AI to a target hard drive in a single +guided flow. All packages are bundled on the USB — no internet required. + +## What it does + +1. Boot from USB +2. Standard FreeBSD install to HDD (bsdinstall) +3. On first HDD boot: bsddialog wizard asks name, timezone, domain, desktop, provider +4. All secrets auto-generated, packages installed offline, agent started + +See [CLAWDIE-ISO.md](CLAWDIE-ISO.md) for the full design. + +## Build + +```sh +# on a FreeBSD host +./build.sh +# output: clawdie-iso-YYYYMMDD.img + +# write to USB (replace daX with your device) +dd if=clawdie-iso-YYYYMMDD.img of=/dev/daX bs=1M status=progress +``` + +## Status + +Early skeleton — build.sh stubs are not yet functional. +See CLAWDIE-ISO.md for implementation plan and open questions. diff --git a/build.cfg b/build.cfg new file mode 100644 index 00000000..4bc4a015 --- /dev/null +++ b/build.cfg @@ -0,0 +1,20 @@ +#!/bin/sh +# clawdie-iso build configuration +# Sourced by build.sh — edit before building + +FREEBSD_VERSION="15.0-RELEASE-p4" +FREEBSD_ARCH="amd64" +FREEBSD_MEMSTICK_URL="https://download.freebsd.org/releases/${FREEBSD_ARCH}/${FREEBSD_VERSION}/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img" +FREEBSD_MEMSTICK_SHA256_URL="${FREEBSD_MEMSTICK_URL}.SHA256" + +# Output image +IMAGE_SIZE="8G" +IMAGE_NAME="clawdie-iso-$(date +%Y%m%d).img" + +# Clawdie-AI release to bundle (fetched from Codeberg) +CLAWDIE_VERSION="0.8.2" +CLAWDIE_TARBALL_URL="https://codeberg.org/Clawdie/Clawdie-AI/archive/v${CLAWDIE_VERSION}.tar.gz" + +# Default installer choices (can be overridden by clawdie.conf on USB) +DEFAULT_PKG_BRANCH="latest" # latest or quarterly +DEFAULT_DESKTOP="xfce" # xfce, kde, mate, headless diff --git a/build.sh b/build.sh new file mode 100644 index 00000000..31e882af --- /dev/null +++ b/build.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# clawdie-iso build script +# Produces a bootable FreeBSD memstick image with Clawdie-AI pre-bundled. +# +# Usage: +# ./build.sh # build with defaults from build.cfg +# ./build.sh --clawdie-version 0.9.0 # override Clawdie version +# ./build.sh --skip-fetch # skip pkg/tarball fetch (use existing) +# +# Requirements (run on FreeBSD host): +# pkg install curl gnupg2 md5 xorriso + +set -e + +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +. "${SCRIPT_DIR}/build.cfg" + +# --- argument parsing --- +SKIP_FETCH=0 +while [ "$#" -gt 0 ]; do + case "$1" in + --clawdie-version) CLAWDIE_VERSION="$2"; shift 2 ;; + --skip-fetch) SKIP_FETCH=1; shift ;; + *) echo "Unknown arg: $1"; exit 1 ;; + esac +done + +echo "==> clawdie-iso build" +echo " FreeBSD : ${FREEBSD_VERSION} ${FREEBSD_ARCH}" +echo " Clawdie : v${CLAWDIE_VERSION}" +echo " Desktop : ${DEFAULT_DESKTOP}" +echo " Pkg : ${DEFAULT_PKG_BRANCH}" +echo "" + +# --- step 1: fetch FreeBSD memstick --- +MEMSTICK="${SCRIPT_DIR}/cache/FreeBSD-${FREEBSD_VERSION}-${FREEBSD_ARCH}-memstick.img" +if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$MEMSTICK" ]; then + echo "==> [1/7] Fetching FreeBSD memstick..." + mkdir -p "${SCRIPT_DIR}/cache" + curl -L -o "$MEMSTICK" "$FREEBSD_MEMSTICK_URL" + curl -L -o "${MEMSTICK}.SHA256" "$FREEBSD_MEMSTICK_SHA256_URL" + sha256 -c "${MEMSTICK}.SHA256" || { echo "Checksum mismatch!"; exit 1; } +else + echo "==> [1/7] FreeBSD memstick already cached, skipping fetch." +fi + +# --- step 2: fetch pkg dependencies --- +if [ "$SKIP_FETCH" -eq 0 ]; then + echo "==> [2/7] Fetching packages to packages/..." + # TODO: read package list from packages/pkg-list.txt + # pkg fetch --yes --dependencies --output packages/ + echo " (stub — implement pkg fetch loop from packages/pkg-list.txt)" +else + echo "==> [2/7] Skipping package fetch." +fi + +# --- step 3: generate local pkg repo metadata --- +echo "==> [3/7] Generating offline pkg repo metadata..." +# TODO: pkg repo packages/ +echo " (stub — run: pkg repo packages/)" + +# --- step 4: fetch Clawdie-AI tarball --- +CLAWDIE_TARBALL="${SCRIPT_DIR}/cache/clawdie-ai-v${CLAWDIE_VERSION}.tar.gz" +if [ "$SKIP_FETCH" -eq 0 ] || [ ! -f "$CLAWDIE_TARBALL" ]; then + echo "==> [4/7] Fetching Clawdie-AI v${CLAWDIE_VERSION}..." + curl -L -o "$CLAWDIE_TARBALL" \ + "https://codeberg.org/Clawdie/Clawdie-AI/archive/v${CLAWDIE_VERSION}.tar.gz" +else + echo "==> [4/7] Clawdie-AI tarball already cached, skipping." +fi + +# --- step 5: unpack memstick image --- +echo "==> [5/7] Unpacking memstick image..." +# TODO: mdconfig, mount, prepare working copy +echo " (stub — mount memstick image via mdconfig)" +WORK_IMG="${SCRIPT_DIR}/cache/work.img" +cp "$MEMSTICK" "$WORK_IMG" + +# --- step 6: inject payload into image --- +echo "==> [6/7] Injecting payload into image..." +# TODO: mount work image, copy into it: +# - installerconfig → /etc/installerconfig +# - firstboot/ → /usr/local/share/clawdie-iso/firstboot/ +# - packages/ → /usr/local/share/clawdie-iso/packages/ +# - clawdie-ai tarball → /usr/local/share/clawdie-iso/clawdie-ai.tar.gz +# - build.cfg defaults → /usr/local/share/clawdie-iso/build.cfg +echo " (stub — mount + copy injection)" + +# --- step 7: finalize and output --- +echo "==> [7/7] Writing output image..." +cp "$WORK_IMG" "${SCRIPT_DIR}/${IMAGE_NAME}" +echo "" +echo " Done: ${SCRIPT_DIR}/${IMAGE_NAME}" +echo " Write to USB: dd if=${IMAGE_NAME} of=/dev/daX bs=1M status=progress" diff --git a/firstboot/firstboot.sh b/firstboot/firstboot.sh new file mode 100644 index 00000000..14fd72c4 --- /dev/null +++ b/firstboot/firstboot.sh @@ -0,0 +1,245 @@ +#!/bin/sh +# firstboot.sh — Clawdie-AI first-boot setup wizard +# +# Runs once on first HDD boot via clawdie-firstboot rc.d service. +# Uses bsddialog for all interactive prompts. +# +# Flow: +# Tier 1 (always): pkg branch → DE selection → agent identity → domain +# Tier 2 (optional): pi provider profile selection +# Tier 3 (advanced): subnet, feature flags (press 'A' on summary screen) +# Secrets: all DB/service passwords auto-generated +# LLM keys: deferred — entered via pi on first agent run +# Summary screen: shows generated credentials before install starts +# Then: install packages → GPU setup → desktop → Clawdie-AI + +set -e + +SHARE="/usr/local/share/clawdie-iso" +CLAWDIE_HOME="/home/clawdie" +CLAWDIE_AI="${CLAWDIE_HOME}/clawdie-ai" +LOG="/var/log/clawdie-firstboot.log" +ENV_FILE="${CLAWDIE_AI}/.env" + +. "${SHARE}/build.cfg" + +# --- helpers --- + +dialog() { bsddialog --backtitle "Clawdie-AI Setup" "$@" ; } + +die() { echo "ERROR: $1" >&2; exit 1; } + +gen_secret() { + openssl rand -base64 32 | tr -d '\n/+=' | head -c 32 +} + +write_env() { + KEY="$1"; VALUE="$2" + if grep -q "^${KEY}=" "$ENV_FILE" 2>/dev/null; then + sed -i '' "s|^${KEY}=.*|${KEY}=${VALUE}|" "$ENV_FILE" + else + echo "${KEY}=${VALUE}" >> "$ENV_FILE" + fi +} + +# --- screen: welcome --- + +dialog --msgbox \ + "\nWelcome to Clawdie-AI Setup.\n\nThis wizard will configure your system.\nAll packages install offline from this USB.\n\nPress Enter to begin." \ + 12 60 + +# --- tier 1: pkg branch --- + +PKG_BRANCH=$(dialog --radiolist \ + "Package repository branch:" 12 60 2 \ + "latest" "Newest packages — best GPU/driver support [recommended]" on \ + "quarterly" "Stable branch — security updates only" off \ + 3>&1 1>&2 2>&3) || die "Cancelled." + +# Write pkg repo config +mkdir -p /usr/local/etc/pkg/repos +ABI=$(pkg config abi 2>/dev/null || echo "FreeBSD:15:amd64") +cat > /usr/local/etc/pkg/repos/FreeBSD.conf < /usr/local/etc/pkg/repos/Clawdie-USB.conf <&1 1>&2 2>&3) || die "Cancelled." + +# --- tier 1: agent identity --- + +ASSISTANT_NAME=$(dialog --inputbox \ + "Assistant name (displayed to users):" 8 50 "Clawdie" \ + 3>&1 1>&2 2>&3) || die "Cancelled." + +# Derive AGENT_NAME: lowercase, alphanumeric + hyphen only +AGENT_NAME=$(echo "$ASSISTANT_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/-*$//') + +AGENT_DOMAIN=$(dialog --inputbox \ + "Domain name for this agent:" 8 50 "${AGENT_NAME}.local" \ + 3>&1 1>&2 2>&3) || die "Cancelled." + +TZ=$(dialog --inputbox \ + "Timezone (IANA format):" 8 50 "UTC" \ + 3>&1 1>&2 2>&3) || die "Cancelled." + +# --- tier 2: pi provider profile (optional) --- + +DO_PI=$(dialog --yesno \ + "Configure LLM provider profile now?\n\n(You can also set this up later — the agent will prompt you on first run.)" \ + 9 60 3>&1 1>&2 2>&3 && echo yes || echo no) + +PI_PROFILE="operator" +PI_PROVIDER="anthropic" +if [ "$DO_PI" = "yes" ]; then + PI_PROVIDER=$(dialog --radiolist \ + "LLM provider:" 16 60 6 \ + "anthropic" "Anthropic (Claude)" on \ + "openai" "OpenAI (GPT-4)" off \ + "openrouter" "OpenRouter" off \ + "groq" "Groq" off \ + "ollama" "Ollama (local)" off \ + "zai" "ZAI / GLM" off \ + 3>&1 1>&2 2>&3) || PI_PROVIDER="anthropic" + + # Note: key entry is deferred to pi first-run + dialog --msgbox \ + "\nProvider set to: ${PI_PROVIDER}\n\nYour API key will be requested the first time the agent runs.\nYou can also add it manually to:\n ${ENV_FILE}\n\nThen restart: service ${AGENT_NAME} restart" \ + 13 60 +fi + +# --- auto-generate secrets --- + +POSTGRES_ADMIN_PASSWORD=$(gen_secret) +SKILLS_DB_PASSWORD=$(gen_secret) +MEMORY_DB_PASSWORD=$(gen_secret) +STRAPI_DB_PASSWORD=$(gen_secret) +STRAPI_APP_KEYS="$(gen_secret),$(gen_secret)" +STRAPI_API_TOKEN_SALT=$(gen_secret) +STRAPI_ADMIN_JWT_SECRET=$(gen_secret) +STRAPI_TRANSFER_TOKEN_SALT=$(gen_secret) +STRAPI_JWT_SECRET=$(gen_secret) +SCREENSHOTS_PASSWORD=$(gen_secret) + +# --- summary screen --- + +dialog --msgbox \ + "\nReady to install. Summary:\n\ +\n Agent : ${ASSISTANT_NAME} (${AGENT_NAME})\ +\n Domain : ${AGENT_DOMAIN}\ +\n Timezone : ${TZ}\ +\n Desktop : ${DESKTOP}\ +\n Pkg : ${PKG_BRANCH}\ +\n Provider : ${PI_PROVIDER}\ +\n\nGenerated credentials written to:\n ${ENV_FILE}\n\nInstallation will take 15–30 minutes.\nPress Enter to begin." \ + 20 65 + +# --- write .env --- + +mkdir -p "$CLAWDIE_AI" +touch "$ENV_FILE" +chmod 600 "$ENV_FILE" + +write_env "AGENT_NAME" "$AGENT_NAME" +write_env "ASSISTANT_NAME" "$ASSISTANT_NAME" +write_env "AGENT_DOMAIN" "$AGENT_DOMAIN" +write_env "AGENT_INTERNAL_DOMAIN" "${AGENT_NAME}.home.arpa" +write_env "TZ" "$TZ" +write_env "SETUP_LOCALE" "en-US" +write_env "DISPLAY_LOCALE" "en-US" +write_env "ASSISTANT_LOCALE" "en-US" +write_env "SYSTEM_LOCALE" "en_US.UTF-8" +write_env "PI_TUI_PROVIDER" "$PI_PROVIDER" +write_env "PI_TUI_PROFILE" "$PI_PROFILE" +write_env "AGENT_SUBNET_BASE" "10.0.0" +write_env "WARDEN_SUBNET" "10.0.0.0/24" +write_env "WARDEN_GATEWAY" "10.0.0.1" +write_env "WARDEN_DB_IP" "10.0.0.3" +write_env "WARDEN_GIT_IP" "10.0.0.4" +write_env "POSTGRES_ADMIN_PASSWORD" "$POSTGRES_ADMIN_PASSWORD" +write_env "SKILLS_DB_PASSWORD" "$SKILLS_DB_PASSWORD" +write_env "MEMORY_DB_PASSWORD" "$MEMORY_DB_PASSWORD" +write_env "STRAPI_DB_PASSWORD" "$STRAPI_DB_PASSWORD" +write_env "STRAPI_APP_KEYS" "$STRAPI_APP_KEYS" +write_env "STRAPI_API_TOKEN_SALT" "$STRAPI_API_TOKEN_SALT" +write_env "STRAPI_ADMIN_JWT_SECRET" "$STRAPI_ADMIN_JWT_SECRET" +write_env "STRAPI_TRANSFER_TOKEN_SALT" "$STRAPI_TRANSFER_TOKEN_SALT" +write_env "STRAPI_JWT_SECRET" "$STRAPI_JWT_SECRET" +write_env "SCREENSHOTS_PASSWORD" "$SCREENSHOTS_PASSWORD" +write_env "SCREENSHOTS_USER" "$AGENT_NAME" + +# --- node.js: set up npm prefix for clawdie user --- +echo "prefix=${CLAWDIE_HOME}/.npm-global" > "${CLAWDIE_HOME}/.npmrc" +mkdir -p "${CLAWDIE_HOME}/.npm-global/bin" +if ! grep -q '\.npm-global' "${CLAWDIE_HOME}/.profile" 2>/dev/null; then + echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "${CLAWDIE_HOME}/.profile" +fi +chown -R clawdie:clawdie "${CLAWDIE_HOME}/.npmrc" "${CLAWDIE_HOME}/.npm-global" "${CLAWDIE_HOME}/.profile" + +# --- gpu detection --- +echo "Detecting GPU..." +eval $(sh "${SHARE}/firstboot/gpu-detect.sh") +echo " GPU: ${GPU_VENDOR} → kld: ${GPU_KLD}" +for kld in $GPU_KLD; do + kldload "$kld" 2>/dev/null || true +done +sysrc kld_list+="${GPU_KLD}" +[ -n "$GPU_NOTES" ] && echo " Note: ${GPU_NOTES}" + +# --- pkg install: desktop packages --- +echo "Installing packages offline..." +# TODO: install DE-specific package list +# pkg install -y $(cat "${SHARE}/packages/pkg-list-${DESKTOP}.txt") + +# --- extract clawdie-ai --- +echo "Extracting Clawdie-AI..." +tar -xzf "${SHARE}/clawdie-ai.tar.gz" -C "$CLAWDIE_HOME" +chown -R clawdie:clawdie "$CLAWDIE_AI" + +# --- seed AGENTS.md from template --- +AGENTS_TPL="${CLAWDIE_AI}/groups/global/AGENTS.md.tpl" +AGENTS_OUT="${CLAWDIE_AI}/groups/global/AGENTS.md" +if [ -f "$AGENTS_TPL" ]; then + sed \ + -e "s|{{ASSISTANT_NAME}}|${ASSISTANT_NAME}|g" \ + -e "s|{{AGENT_NAME}}|${AGENT_NAME}|g" \ + -e "s|{{AGENT_DOMAIN}}|${AGENT_DOMAIN}|g" \ + -e "s|{{ASSISTANT_LOCALE}}|en-US|g" \ + -e "s|{{TZ}}|${TZ}|g" \ + "$AGENTS_TPL" > "$AGENTS_OUT" + chown clawdie:clawdie "$AGENTS_OUT" +fi + +# --- run clawdie-ai setup --- +echo "Running Clawdie-AI install..." +cd "$CLAWDIE_AI" +su -m clawdie -c "npm install 2>&1" | tee -a "$LOG" +su -m clawdie -c "npm run install-all 2>&1" | tee -a "$LOG" + +echo "" +echo "============================================" +echo " Clawdie-AI setup complete." +echo " Agent : ${ASSISTANT_NAME}" +echo " Domain: ${AGENT_DOMAIN}" +echo " Logs : ${LOG}" +echo "============================================" diff --git a/firstboot/gpu-detect.sh b/firstboot/gpu-detect.sh new file mode 100644 index 00000000..9bf166df --- /dev/null +++ b/firstboot/gpu-detect.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# gpu-detect.sh — detect GPU via pciconf and return the appropriate kld module +# +# Usage: +# gpu-detect.sh +# +# Output: +# Prints shell variable assignments to stdout, to be eval'd by caller: +# GPU_VENDOR="Intel" +# GPU_KLD="i915kms" +# GPU_XORG_DRIVER="intel" +# GPU_NOTES="" +# +# Based on detection logic from outpaddling/desktop-installer (BSD 2-clause). +# Simplified to target hardware only — VirtualBox excluded. + +set -e + +# Default: vesa fallback +GPU_VENDOR="Unknown" +GPU_KLD="vesa" +GPU_XORG_DRIVER="vesa" +GPU_NOTES="" + +# Parse pciconf for display controllers (class 0x03) +PCI_DISPLAY=$(pciconf -lv 2>/dev/null | awk ' + /class=0x03/ { found=1 } + found && /vendor=/ { vendor=$0 } + found && /device=/ { device=$0; print vendor; print device; found=0 } +') + +VENDOR_ID=$(pciconf -l 2>/dev/null | awk -F'[@ :]' '/^vgapci|^drm/ { print $5 }' | head -1) + +case "$VENDOR_ID" in + # Intel + 8086) + GPU_VENDOR="Intel" + GPU_KLD="i915kms" + GPU_XORG_DRIVER="intel" + ;; + # NVIDIA + 10de) + GPU_VENDOR="NVIDIA" + GPU_KLD="nvidia-modeset nvidia" + GPU_XORG_DRIVER="nvidia" + GPU_NOTES="Requires nvidia-driver package" + ;; + # AMD / ATI + 1002) + GPU_VENDOR="AMD" + GPU_KLD="amdgpu" + GPU_XORG_DRIVER="amdgpu" + GPU_NOTES="amdgpu preferred; radeon fallback for older cards" + ;; + # VMware (kept for bhyve SVGA compatibility) + 15ad) + GPU_VENDOR="VMware/bhyve" + GPU_KLD="vmwgfx" + GPU_XORG_DRIVER="vmware" + ;; + *) + GPU_VENDOR="Unknown (${VENDOR_ID})" + GPU_KLD="vesa" + GPU_XORG_DRIVER="vesa" + GPU_NOTES="Falling back to VESA. Check pciconf -lv for GPU details." + ;; +esac + +# Output as eval-able shell vars +echo "GPU_VENDOR=\"${GPU_VENDOR}\"" +echo "GPU_KLD=\"${GPU_KLD}\"" +echo "GPU_XORG_DRIVER=\"${GPU_XORG_DRIVER}\"" +echo "GPU_NOTES=\"${GPU_NOTES}\"" diff --git a/firstboot/rc.d/clawdie-firstboot b/firstboot/rc.d/clawdie-firstboot new file mode 100644 index 00000000..87610ef6 --- /dev/null +++ b/firstboot/rc.d/clawdie-firstboot @@ -0,0 +1,42 @@ +#!/bin/sh +# +# PROVIDE: clawdie_firstboot +# REQUIRE: NETWORKING LOGIN +# BEFORE: clawdie +# KEYWORD: firstboot +# +# clawdie-firstboot — runs once on first HDD boot to complete Clawdie-AI setup. +# Self-disables on completion. + +. /etc/rc.subr + +name="clawdie_firstboot" +rcvar="${name}_enable" +start_cmd="${name}_start" +stop_cmd=":" + +SHARE="/usr/local/share/clawdie-iso" +LOG="/var/log/clawdie-firstboot.log" + +clawdie_firstboot_start() +{ + echo "Starting Clawdie first-boot setup..." + + # Run on ttyv0 so bsddialog has a real TTY for the wizard + /usr/bin/script -q -a "$LOG" \ + /bin/sh "${SHARE}/firstboot/firstboot.sh" + + RC=$? + + if [ "$RC" -eq 0 ]; then + echo "Clawdie first-boot setup complete. Disabling service." + sysrc -x clawdie_firstboot_enable + rm -rf "$SHARE" + else + echo "Clawdie first-boot setup failed (exit $RC). Check $LOG" + fi +} + +load_rc_config "$name" +: "${clawdie_firstboot_enable:=NO}" +run_rc_command "$1" diff --git a/installerconfig b/installerconfig new file mode 100644 index 00000000..dcecb1ab --- /dev/null +++ b/installerconfig @@ -0,0 +1,39 @@ +#!/bin/sh +# installerconfig — bsdinstall post-install hook +# +# bsdinstall sources this file automatically after base system installation +# completes. Runs in the context of the live USB environment, with the +# target HDD mounted at /mnt. +# +# Responsibilities: +# 1. Copy firstboot payload from USB to installed HDD +# 2. Enable the clawdie-firstboot rc.d service (runs once on first HDD boot) +# 3. That's it — all real work happens in firstboot.sh on first boot + +set -e + +USB_SHARE="/usr/local/share/clawdie-iso" +HDD_SHARE="/mnt/usr/local/share/clawdie-iso" +HDD_RCD="/mnt/usr/local/etc/rc.d" + +echo "clawdie-iso: injecting firstboot payload..." + +# Copy firstboot scripts +mkdir -p "$HDD_SHARE" +cp -r "${USB_SHARE}/firstboot" "${HDD_SHARE}/" +cp -r "${USB_SHARE}/packages" "${HDD_SHARE}/" +cp "${USB_SHARE}/clawdie-ai.tar.gz" "${HDD_SHARE}/" +cp "${USB_SHARE}/build.cfg" "${HDD_SHARE}/" + +chmod +x "${HDD_SHARE}/firstboot/firstboot.sh" +chmod +x "${HDD_SHARE}/firstboot/gpu-detect.sh" + +# Install firstboot rc.d service +mkdir -p "$HDD_RCD" +cp "${USB_SHARE}/firstboot/rc.d/clawdie-firstboot" "${HDD_RCD}/clawdie-firstboot" +chmod +x "${HDD_RCD}/clawdie-firstboot" + +# Enable service in rc.conf on HDD +echo 'clawdie_firstboot_enable="YES"' >> /mnt/etc/rc.conf + +echo "clawdie-iso: firstboot payload installed. Rebooting to HDD..." diff --git a/packages/.gitkeep b/packages/.gitkeep new file mode 100644 index 00000000..e69de29b