Keeps service clawdie as a deployed-system contract only, removes the old mini-binary FEATURE_CLAWDIE staging lane from the ISO build, and adds explicit Linux-vs-FreeBSD proof boundaries for provider/runtime claims.\n\nChecks: ./scripts/check-format.sh; git diff --check; sh -n over scripts/ firstboot/ live/operator-session/ executables
728 lines
25 KiB
Markdown
728 lines
25 KiB
Markdown
# Firstboot — Modular Shell Pipeline
|
||
|
||
**Audience:** developers, operators, contributors maintaining the installed-system firstboot path.
|
||
|
||
**Scope:** this document describes the **installed-system firstboot shell
|
||
pipeline** in `firstboot/`. The current live operator USB graphical boot path
|
||
runs a separate pre-SDDM rc.d service (`/usr/local/etc/rc.d/clawdie_live_gpu`)
|
||
and does not exercise this pipeline. The `shell-gpu.sh` examples below describe
|
||
the installed-target firstboot/module flow.
|
||
|
||
This file consolidates and supersedes the earlier `SHELL-ARCHITECTURE.md`,
|
||
`SHELL-MODULES.md`, and `firstboot/MODULE-MANIFEST.md`.
|
||
|
||
---
|
||
|
||
## Design Philosophy
|
||
|
||
Traditional installers do one of two things:
|
||
|
||
1. **Monolithic:** single script that does everything — hard to test, debug, or reuse.
|
||
2. **Heavy framework:** external runtime (Python, Perl, Ansible) with VM overhead and dependencies.
|
||
|
||
**Clawdie Shell chooses a third path:** modular POSIX-shell functions
|
||
organised by concern, sourceable independently, with no runtime dependencies
|
||
beyond `/bin/sh`.
|
||
|
||
Each module is:
|
||
|
||
- **Standalone:** can be sourced without others.
|
||
- **Idempotent:** safe to run twice — checks state first.
|
||
- **Testable:** functions have clear inputs/outputs and can be invoked in isolation.
|
||
- **Debuggable:** `clawdie_shell_gpu_detect` can be run from a shell to reproduce a step.
|
||
|
||
### Why POSIX over Bash
|
||
|
||
| Aspect | POSIX `/bin/sh` | Bash |
|
||
| ----------------------- | --------------- | --------------------------- |
|
||
| Default on FreeBSD | yes | requires `pkg install bash` |
|
||
| First-boot availability | always | needs install first |
|
||
| Dependencies | none | libreadline, ncurses |
|
||
| Startup overhead | ~5 ms | ~50 ms |
|
||
|
||
All modules use POSIX shell only. No `[[ ]]`, no `${var#pattern}` outside POSIX-defined cases, no `function` keyword, no `local` (use `_`-prefixed plain variables instead), no `set -o pipefail`.
|
||
|
||
### Why shell over Python or Node
|
||
|
||
| Aspect | Shell | Python | Node |
|
||
| ------------------- | ---------- | --------- | ----------- |
|
||
| Startup time | 5 ms | 100 ms | 300 ms |
|
||
| Footprint | one file | ~50 MB | ~100 MB |
|
||
| External deps | none | venv, pip | npm modules |
|
||
| First-boot overhead | negligible | 100+ ms | 300+ ms |
|
||
|
||
For a one-time installer, shell wins on speed and simplicity.
|
||
|
||
---
|
||
|
||
## Architecture Overview
|
||
|
||
```
|
||
firstboot.sh
|
||
├─ Load GUI config (/tmp/clawdie-install.conf) or wizard input
|
||
├─ Source: shell-zfs.sh
|
||
├─ Source: shell-gpu.sh
|
||
├─ Source: shell-nvidia.sh
|
||
├─ Source: shell-pkg.sh
|
||
├─ Source: shell-ssh.sh
|
||
├─ Source: shell-env.sh
|
||
├─ Source: shell-system.sh
|
||
├─ Source: shell-desktop.sh
|
||
├─ Source: shell-pf.sh
|
||
├─ Source: shell-tailscale.sh
|
||
├─ Source: shell-npm-globals.sh
|
||
├─ Source: shell-deploy.sh
|
||
└─ Call functions in sequence:
|
||
├─ clawdie_shell_zfs_detect (baremetal only)
|
||
├─ clawdie_shell_gpu_detect
|
||
├─ clawdie_shell_nvidia_detect (only if GPU=nvidia)
|
||
├─ clawdie_shell_pkg_setup
|
||
├─ clawdie_shell_ssh_setup
|
||
├─ clawdie_shell_env_generate
|
||
├─ clawdie_shell_system_config
|
||
├─ clawdie_shell_desktop_detect
|
||
├─ clawdie_shell_pf
|
||
├─ clawdie_shell_tailscale_setup (optional)
|
||
├─ clawdie_shell_npm_globals_install
|
||
└─ clawdie_shell_deploy
|
||
```
|
||
|
||
---
|
||
|
||
## Dependency Graph
|
||
|
||
```
|
||
[0.0 setup-import] (optional) → setup.txt + system.env import
|
||
↓
|
||
[0.1 zfs] (baremetal only) → CLAWDIE_BOOT_MODE
|
||
↓
|
||
[User Input]
|
||
↓
|
||
[1.1 env] → ASSISTANT_NAME, AGENT_DOMAIN, TZ, LLM_PROVIDER
|
||
↓
|
||
├→ [1.2 pkg] (optional: can run any time)
|
||
├→ [1.3 gpu] (optional: hardware detection)
|
||
├→ [1.3b nvidia] ← REQUIRES: DETECTED_GPU from [1.3]
|
||
├→ [1.3c ssh] ← REQUIRES: ASSISTANT_NAME from [1.1]
|
||
├→ [1.4 system] ← REQUIRES: TZ, AGENT_DOMAIN from [1.1]
|
||
├→ [1.4.1 pf] ← REQUIRES: ASSISTANT_NAME from [1.1]
|
||
├→ [1.4.2 desktop] ← PREFERS: GPU config from [1.3]
|
||
├→ [1.5 tailscale] (optional) ← PREFERS: repos from [1.2], pf from [1.4.1]
|
||
├→ [1.5.1 npm-globals] ← PREFERS: repos from [1.2]
|
||
└→ [1.6 deploy] ← REQUIRES: .env from [1.1], repos from [1.2]
|
||
← PREFERS: gpu config from [1.3]
|
||
```
|
||
|
||
### Hard dependencies
|
||
|
||
- **1.1 must complete before 1.6** — deploy sources `.env`.
|
||
- **1.3 must complete before 1.3b** — nvidia module checks `DETECTED_GPU`.
|
||
- **1.4 should run before 1.4.2 and 1.4.1** — system config writes rc.conf that affects desktop + PF.
|
||
- **1.4.1 should run before 1.5** — Tailscale updates PF rules.
|
||
|
||
---
|
||
|
||
## Module Reference
|
||
|
||
### 0.0 — `setup-import.sh`
|
||
|
||
Read `setup.txt` and `system.env` from the writable USB config partition
|
||
before the wizard, for unattended installs.
|
||
|
||
**Wizard inputs:** none.
|
||
**Outputs (exports):** `ASSISTANT_NAME`, `HOSTNAME`, `AGENT_DOMAIN`, `TZ`,
|
||
optional pre-baked provider/channel values, `ZFS_POOL`, `ZFS_LAYOUT`,
|
||
`ZFS_DATA_DISKS`, `ZFS_HOT_SPARES`, `ZFS_PREFIX`, hardware hints.
|
||
**Checkpoint:** `setup-import`.
|
||
**Skip:** no `CLAWDIE` FAT32 partition, or `setup.txt` missing, or values
|
||
incomplete for non-interactive path.
|
||
**Error handling:** safe-fail back to wizard; never blocks an interactive
|
||
install on a missing partition.
|
||
|
||
---
|
||
|
||
### 0.1 — `shell-zfs.sh`
|
||
|
||
Detect existing ZFS pools and select boot mode.
|
||
|
||
**Main function:** `clawdie_shell_zfs_detect()`.
|
||
**Wizard inputs:** none — automatic detection.
|
||
**Outputs (exports):**
|
||
|
||
- `CLAWDIE_BOOT_MODE` — `install` | `upgrade` | `maintenance`.
|
||
- `POOL_NAME` — detected pool name when present.
|
||
|
||
**Checkpoint:** `[ZFS]`.
|
||
**Skip:** `TARGET=vps` (ZFS not used).
|
||
**Error handling:** safe-fail to `install` if no pools found. If maintenance
|
||
mode selected, execs `maintenance-mode.sh` and exits.
|
||
|
||
---
|
||
|
||
### 1.1 — `shell-env.sh`
|
||
|
||
Generate a minimal `.env` seed (identity + feature flags).
|
||
|
||
**Main function:** `clawdie_shell_env_generate()`.
|
||
|
||
**Wizard inputs (Tier 1, required):**
|
||
|
||
- `ASSISTANT_NAME` — human name (e.g., "Clawdie Smith").
|
||
- `AGENT_DOMAIN` — public or local domain zone (default: `${agentname}.home.arpa`).
|
||
- `TZ` — timezone (e.g., `Europe/Ljubljana`).
|
||
|
||
**Wizard inputs (Tier 2, optional):** provider/model values, provider API keys,
|
||
Telegram credentials — normally configured after first boot in `/setup`.
|
||
|
||
**Outputs:**
|
||
|
||
- `$ENV_FILE` — `/home/clawdie/.env` (mode 0600), identity + feature flags.
|
||
- Copied into `/home/clawdie/clawdie-ai/.env` by shell-deploy (1.6).
|
||
- Completed by Clawdie-AI onboarding (secrets + derived defaults).
|
||
|
||
**Exports for downstream:** `AGENT_NAME` (derived from `ASSISTANT_NAME`),
|
||
`DETECTED_ABI` (optional, from `pkg config abi`).
|
||
|
||
**Functions:**
|
||
|
||
```sh
|
||
clawdie_shell_env_generate # Main entry point
|
||
clawdie_shell_env_gen_secret # openssl rand -base64 32 (with /dev/urandom fallback)
|
||
clawdie_shell_env_derive_names # ASSISTANT_NAME → AGENT_NAME
|
||
clawdie_shell_env_derive_ips # Allocate jail IPs from AGENT_SUBNET_BASE
|
||
clawdie_shell_env_validate # Verify .env has required vars
|
||
```
|
||
|
||
**Jail IP allocation** (from `AGENT_SUBNET_BASE`, default `10.0.0`):
|
||
|
||
| Slot | Service | Default IP |
|
||
| ----- | ------------------------- | ----------- |
|
||
| .1 | Gateway (host bridge) | 10.0.0.1 |
|
||
| .2 | Management jail | 10.0.0.2 |
|
||
| .3 | Database (PostgreSQL) | 10.0.0.3 |
|
||
| .4 | Code service (git mirror) | 10.0.0.4 |
|
||
| .5 | Web service (nginx) | 10.0.0.5 |
|
||
| .101+ | Worker jails (dynamic) | 10.0.0.101+ |
|
||
|
||
**Checkpoint:** `[ENV]`.
|
||
**Skip:** `.env` exists AND contains `AGENT_DOMAIN`.
|
||
**Error handling:** fails if `ASSISTANT_NAME`, `AGENT_DOMAIN`, or `TZ` missing.
|
||
Validates `.env` has minimal required vars before marking complete.
|
||
**Recovery:** `clawdie-firstboot --resume` skips if `[ENV] COMPLETE` in progress
|
||
file. Re-runnable (idempotent via sysrc pattern).
|
||
|
||
---
|
||
|
||
### 1.2 — `shell-pkg.sh`
|
||
|
||
Configure FreeBSD package repositories (online + offline USB) and seed Bastille jail cache.
|
||
|
||
**Main function:** `clawdie_shell_pkg_setup()`.
|
||
**Wizard inputs:** none — automatic from `[1.1]`.
|
||
**Inputs from `[1.1]`:** `DETECTED_ABI` (optional, auto-detected if missing).
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/pkg/repos/FreeBSD.conf` — online fallback, priority 10.
|
||
- URL: `pkg+https://pkg.FreeBSD.org/${ABI}/latest`.
|
||
- `/etc/pkg/repos/Clawdie-USB.conf` — offline USB repo, priority 100 (preferred).
|
||
- URL: `file:///mnt/media/packages`.
|
||
- `/var/cache/pkg/bastille/` — seeded with .pkg files from USB for offline jail provisioning.
|
||
|
||
**Functions:**
|
||
|
||
```sh
|
||
clawdie_shell_pkg_setup
|
||
clawdie_shell_pkg_detect_abi # pkg config abi or derive from uname
|
||
clawdie_shell_pkg_write_freebsd_conf # writes FreeBSD.conf
|
||
clawdie_shell_pkg_write_clawdie_conf # writes Clawdie-USB.conf
|
||
clawdie_shell_pkg_seed_cache # copies USB pkgs to bastille cache
|
||
```
|
||
|
||
**Checkpoint:** `[PKG]`.
|
||
**Skip:** if both `FreeBSD.conf` AND `Clawdie-USB.conf` exist.
|
||
**Error handling:** safe-fail — missing USB packages → continues online only;
|
||
`pkg update` may fail in chroot (expected, logged). Cache seeding errors are non-fatal.
|
||
**Recovery:** re-runnable to refresh cache or update repo configs.
|
||
|
||
---
|
||
|
||
### 1.3 — `shell-gpu.sh`
|
||
|
||
Detect GPU hardware and select driver kernel module.
|
||
|
||
**Scope:** installed-target firstboot path. The live USB boot path uses
|
||
`clawdie_live_gpu` (an rc.d service) instead.
|
||
|
||
**Main function:** `clawdie_shell_gpu_detect()`.
|
||
**Wizard inputs:** none.
|
||
|
||
**Hardware detection (via `pciconf -lv`, class `0x030000`):**
|
||
|
||
| Vendor | Module(s) | Note |
|
||
| --------- | ---------------------------------- | ---------------------------------- |
|
||
| Intel | `i915kms` | Intel integrated (iGPU) |
|
||
| AMD | `amdgpu` (or `radeon` on older HW) | Radeon / RDNA |
|
||
| NVIDIA | `nvidia-modeset nvidia` | branch chosen by `shell-nvidia.sh` |
|
||
| VMware | `vmwgfx` | VMware SVGA 3D |
|
||
| (unknown) | (empty) | VESA software fallback |
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/rc.conf` — `kld_list="..."` (idempotent; replaces existing line).
|
||
|
||
**Exports for downstream:** `DETECTED_GPU` — `intel` | `amd` | `nvidia` | `vmware` | `vesa`. Used by `[1.3b]` to decide run or skip.
|
||
|
||
**Functions:**
|
||
|
||
```sh
|
||
clawdie_shell_gpu_detect
|
||
clawdie_shell_gpu_detect_pci # pciconf -lv → vendor:device
|
||
clawdie_shell_gpu_match_driver # vendor → kld list
|
||
clawdie_shell_gpu_write_rcconf # rc.conf kld_list
|
||
clawdie_shell_gpu_load_live # optional kldload (may fail in chroot)
|
||
clawdie_shell_gpu_validate
|
||
```
|
||
|
||
**Checkpoint:** `[GPU]`.
|
||
**Skip:** `kld_list=` already in rc.conf.
|
||
**Error handling:** `pciconf` may fail outside chroot (expected). Falls back
|
||
silently to `vesa` if detection fails. `kldload` live fails gracefully in chroot.
|
||
**Recovery:** re-runnable. Must run before `[1.4 system]` (which reads rc.conf)
|
||
and is required before `[1.3b nvidia]`.
|
||
|
||
---
|
||
|
||
### 1.3b — `shell-nvidia.sh`
|
||
|
||
NVIDIA driver version selection (PC-BSD heritage).
|
||
|
||
**Main function:** `clawdie_shell_nvidia_detect()`.
|
||
**Skip if:** `DETECTED_GPU != "nvidia"`.
|
||
|
||
**Wizard inputs (Tier 2, optional):**
|
||
|
||
- `NVIDIA_DRIVER_VERSION` — `590` | `470` | `390` (env var for unattended), or interactive `bsddialog` menu.
|
||
|
||
**Device ranges → driver:**
|
||
|
||
| Range | Era | Driver |
|
||
| ------------- | ----------------------------- | ------------------ |
|
||
| ≥0x2700 | Ada (RTX 40xx) | 590 |
|
||
| 0x2060–0x2500 | Turing/Ampere (RTX 20xx/30xx) | 590 |
|
||
| 0x1340–0x2186 | Maxwell (GTX 750–980 Ti) | 470 |
|
||
| 0x1180–0x139F | Kepler (GTX 650–780 Ti) | 390 |
|
||
| (unknown) | — | 590 (safe default) |
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/rc.conf` — `nvidia_driver_version="590"` (or `"470"`/`"390"`), idempotent.
|
||
- `/var/run/nvidia_driver_version.txt` — optional marker file (safe-fail).
|
||
|
||
**Exports:** `NVIDIA_DRIVER_VERSION`, used during package selection in `[1.6]`.
|
||
|
||
**Checkpoint:** `[NVIDIA]`.
|
||
**Error handling:** invalid version → warning + default `590`. `bsddialog`
|
||
unavailable → silent default. `/var/run` write failure → non-fatal.
|
||
|
||
---
|
||
|
||
### 1.3c — `shell-ssh.sh`
|
||
|
||
Configure SSH access and system passwords.
|
||
|
||
**Main function:** `clawdie_shell_ssh_setup()`.
|
||
|
||
**Wizard inputs (Tier 1, required):**
|
||
|
||
- `ASSISTANT_NAME` — used for default user naming.
|
||
- `ASSISTANT_PASSWORD` — operator password.
|
||
- `ROOT_PASSWORD` — root password (optional if disabled).
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/master.passwd` updates via `pw`.
|
||
- `/home/clawdie/.ssh/authorized_keys` if SSH key provided.
|
||
- `/etc/ssh/sshd_config` — hardened defaults.
|
||
|
||
**Checkpoint:** `[SSH]`.
|
||
**Error handling:** password prompts must succeed; missing SSH key is
|
||
non-fatal (password login allowed).
|
||
|
||
---
|
||
|
||
### 1.4 — `shell-system.sh`
|
||
|
||
System configuration (hostname, timezone, rc.conf, services).
|
||
|
||
**Main function:** `clawdie_shell_system_config()`.
|
||
|
||
**Inputs (from `[1.1]`):**
|
||
|
||
- `TZ`, `AGENT_DOMAIN`.
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/rc.conf` — `timezone`, `dbus_enable=YES`, `display_manager=sddm`, `sddm_enable=YES`, `linux_enable`, `zfs_enable`, `kld_list` (idempotent via sysrc).
|
||
- `/etc/hostname` — single line: `home.arpa` (or `AGENT_DOMAIN`).
|
||
- `/etc/profile.d/clawdie.sh` — npm `PATH`, `npm_config_prefix`.
|
||
|
||
**Functions:**
|
||
|
||
```sh
|
||
clawdie_shell_system_config # orchestrator
|
||
clawdie_shell_system_write_rcconf
|
||
clawdie_shell_system_set_hostname
|
||
clawdie_shell_system_setup_env
|
||
clawdie_shell_system_enable_services
|
||
```
|
||
|
||
**Checkpoint:** `[SYSTEM]`.
|
||
**Skip:** `timezone=` AND `dbus_enable=` already in rc.conf.
|
||
**Error handling:** `hostname` may fail in chroot (safe-fail). `service`
|
||
onestart fails gracefully in chroot (expected).
|
||
|
||
---
|
||
|
||
### 1.4.2 — `shell-desktop.sh`
|
||
|
||
Detect display hardware and enable the desktop stack when appropriate.
|
||
|
||
**Main function:** `clawdie_shell_desktop_detect()`.
|
||
**Inputs from `[1.3]`:** `DETECTED_GPU` (used to decide which desktop bits to enable).
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/rc.conf` — desktop service flags (e.g., `sddm_enable=YES` when display
|
||
detected; `sddm_enable=NO` for headless VPS/cloud).
|
||
|
||
**Checkpoint:** `[DESKTOP]`.
|
||
**Skip:** headless/VPS scenario detected, or desktop already enabled.
|
||
**Error handling:** detection failures fall back to headless mode.
|
||
|
||
---
|
||
|
||
### 1.4.1 — `shell-pf.sh`
|
||
|
||
Configure PF firewall with block-all default, SSH protection, jail NAT, glasspane VNC.
|
||
|
||
**Main function:** `clawdie_shell_pf()`.
|
||
**Wizard inputs:** none.
|
||
**Inputs from `[1.1]`:** `ASSISTANT_NAME`; `AGENT_NET` (optional, default `192.168.100.0/24`).
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/pf.conf`:
|
||
- block-all default
|
||
- SSH brute-force protection (`max-src-conn-rate 3/60`)
|
||
- NAT for agent jails (`192.168.0.0/16` supernet)
|
||
- HTTP/HTTPS pass rules
|
||
- commented Tailscale + glasspane VNC block (agent uncomments later)
|
||
- `/etc/rc.conf` appends:
|
||
- `cloned_interfaces="bridge0"`
|
||
- `ifconfig_bridge0_name="warden0"`
|
||
- `ifconfig_${BRIDGE}="inet 192.168.100.1/24 up"`
|
||
- `gateway_enable="YES"`
|
||
- `pf_enable="YES"`
|
||
- `pf_reload_enable="YES"`
|
||
- `/usr/local/etc/rc.d/pf_reload`:
|
||
- `REQUIRE: tailscaled`
|
||
- Fixes PF/Tailscale cold-boot race (resolves `tailscale0` interface index).
|
||
|
||
**Glasspane VNC:** port 5900 (`wayvnc` → `cage` → browser), blocked on
|
||
`ext_if`, passed on `tailscale0`. Uncommented by agent when Tailscale enabled.
|
||
|
||
**Checkpoint:** `[PF]`.
|
||
**Skip:** `pf_enable="YES"` already in rc.conf.
|
||
**Error handling:** fails if external interface cannot be detected (no
|
||
default route). Bridge creation and PF load may fail in chroot (expected,
|
||
non-fatal; loads on reboot).
|
||
**Recovery:** `pf_reload` pre-installed even without Tailscale — harmless
|
||
now, essential later. Must run before `[1.5 tailscale]`.
|
||
|
||
---
|
||
|
||
### 1.5 — `shell-tailscale.sh`
|
||
|
||
Optional Tailscale enablement for secure remote access.
|
||
|
||
**Main function:** `clawdie_shell_tailscale_setup()`.
|
||
**Wizard inputs (Tier 2):** `FEATURE_TAILSCALE` — `YES`/`NO` (default `NO`).
|
||
|
||
**Outputs:**
|
||
|
||
- `/etc/rc.conf` — `tailscaled_enable="YES"`.
|
||
- Tailscale left unauthenticated; operator runs `tailscale up` from the running system.
|
||
|
||
**Checkpoint:** `[TAILSCALE]`.
|
||
**Skip:** `FEATURE_TAILSCALE != YES`.
|
||
**Error handling:** safe-fail — network or daemon issues are logged and do
|
||
not abort install. `pkg install tailscale` failure → warning + continue.
|
||
**Recovery:** `service tailscaled start && tailscale up`.
|
||
|
||
---
|
||
|
||
### 1.5.1 — `shell-npm-globals.sh`
|
||
|
||
Install bundled npm CLI tools (`claude`, `gemini`, `pi`) from ISO cache.
|
||
|
||
**Main function:** `clawdie_shell_npm_globals_install()`.
|
||
|
||
**Inputs preferred:** offline repo cache from `[1.2]` (fully offline install).
|
||
|
||
**Outputs:**
|
||
|
||
- `/opt/clawdie/npm-global/bin/*` — bundled CLI binaries.
|
||
- `/etc/profile.d/clawdie.sh` — `PATH` update if missing.
|
||
|
||
**Checkpoint:** `[NPM-GLOBALS]`.
|
||
**Skip:** npm globals already installed in `/opt/clawdie/npm-global`.
|
||
**Error handling:** missing cache → warning + skip (non-fatal).
|
||
|
||
---
|
||
|
||
### 1.6 — `shell-deploy.sh`
|
||
|
||
Extract Clawdie-AI (offline tarball incl. `node_modules`), run installer, start services.
|
||
|
||
**Main function:** `clawdie_shell_deploy()`.
|
||
|
||
**Hard inputs from `[1.1]`:** `.env` at `$ENV_FILE` — sourced for
|
||
`ASSISTANT_NAME`, `AGENT_DOMAIN`, jail IPs, DB config. Missing `.env` →
|
||
**hard exit**.
|
||
|
||
**Preferred (not required) inputs:**
|
||
|
||
- `[1.2]` — repos + pkg cache (speeds offline provisioning).
|
||
- `[1.3]`/`[1.3b]` — `kld_list`, `nvidia_driver_version` (used for jail pkg selection).
|
||
|
||
**Outputs:**
|
||
|
||
- `/home/clawdie/clawdie-ai/` — extracted from `/usr/local/share/clawdie-iso/clawdie-ai.tar.gz`.
|
||
- `/home/clawdie/clawdie-ai/.env` — seeded from `[1.1]`.
|
||
- `node_modules/` bundled into the ISO tarball at build time.
|
||
- Bastille jails created: worker, db, cms (optional mgmt).
|
||
- `just install` runs as the `clawdie` user.
|
||
- PostgreSQL seeded in db jail; nginx configured in cms jail; `rc.d` `clawdie` service installed and enabled.
|
||
|
||
**Functions:**
|
||
|
||
```sh
|
||
clawdie_shell_deploy
|
||
clawdie_shell_deploy_extract_tarball # tar xzf → /home/clawdie, chown
|
||
clawdie_shell_deploy_run_install_all # just install
|
||
clawdie_shell_deploy_verify # jails up, service present, .env ok
|
||
```
|
||
|
||
**Checkpoint:** `[DEPLOY]`.
|
||
**Skip:** `package.json` AND `node_modules/` present → skip extraction; always run `just install`.
|
||
**Error handling:** missing `.env` → hard exit. Missing tarball → assume
|
||
already extracted (warning). Missing `node_modules` → fall back to `npm ci`
|
||
(needs network), fails hard if deps cannot install. Jail creation failures →
|
||
warning + continue. DB connectivity → deferred to runtime.
|
||
**Recovery:** idempotent re-run; failures block full system setup.
|
||
|
||
---
|
||
|
||
## Execution Order
|
||
|
||
```
|
||
User input (wizard Tier 1 + optional Tier 2)
|
||
↓
|
||
1. zfs CONDITIONAL — baremetal only, sets boot mode
|
||
2. gpu RECOMMENDED — hardware detection
|
||
3. nvidia CONDITIONAL — only if GPU=nvidia
|
||
4. pkg OPTIONAL — early for offline support
|
||
5. ssh REQUIRED — user + password provisioning
|
||
6. env REQUIRED — creates .env with identity
|
||
7. system REQUIRED — system-level rc.conf
|
||
8. desktop CONDITIONAL — display detection + enablement
|
||
9. pf REQUIRED — firewall + jail NAT
|
||
10. tailscale OPTIONAL — remote access, modifies PF rules
|
||
11. npm-globals OPTIONAL — offline npm CLIs
|
||
12. deploy REQUIRED — depends on .env from env
|
||
↓
|
||
SUCCESS: Clawdie-AI ready for first boot
|
||
```
|
||
|
||
---
|
||
|
||
## Error Handling and Recovery
|
||
|
||
### Module pattern
|
||
|
||
```sh
|
||
#!/bin/sh
|
||
set -eu # exit on error and undefined vars (POSIX)
|
||
trap 'echo "ERROR in clawdie_shell_FUNCTION at line $LINENO" >&2; exit 1' ERR
|
||
|
||
clawdie_shell_FUNCTION() {
|
||
_status_file="/var/log/clawdie-firstboot.progress"
|
||
echo "[MODULE] FUNCTION_START" >> "$_status_file"
|
||
|
||
# validate inputs
|
||
if [ -z "${VAR:-}" ]; then
|
||
log_msg "ERROR: VAR not set"
|
||
return 1
|
||
fi
|
||
|
||
# do work
|
||
if ! some_command; then
|
||
log_msg "ERROR: some_command failed"
|
||
return 1
|
||
fi
|
||
|
||
echo "[MODULE] FUNCTION_COMPLETE" >> "$_status_file"
|
||
return 0
|
||
}
|
||
```
|
||
|
||
### Logging
|
||
|
||
All errors land in `/var/log/clawdie-firstboot.log`:
|
||
|
||
```
|
||
[2026-03-23 15:34:12] [pkg] Starting package setup
|
||
[2026-03-23 15:34:15] [pkg] Detected ABI: FreeBSD:15:amd64
|
||
[2026-03-23 15:34:18] [pkg] ERROR at line 42: repo config failed
|
||
[2026-03-23 15:34:18] ERROR in clawdie_shell_pkg_setup at line 42
|
||
```
|
||
|
||
Operator can `tail -100 /var/log/clawdie-firstboot.log`, SSH in to debug, or
|
||
`clawdie-firstboot --resume` to skip completed checkpoints.
|
||
|
||
### Resume
|
||
|
||
`--resume` parses the progress file and skips modules that logged
|
||
`[MODULE] COMPLETE`. Skip logic (pseudo-code):
|
||
|
||
```sh
|
||
grep -q "\[ENV\] COMPLETE" "$PROGRESS_FILE" && skip_env=1
|
||
grep -q "\[PKG\] COMPLETE" "$PROGRESS_FILE" && skip_pkg=1
|
||
grep -q "\[GPU\] COMPLETE" "$PROGRESS_FILE" && skip_gpu=1
|
||
grep -q "\[NVIDIA\] COMPLETE" "$PROGRESS_FILE" || [ "$DETECTED_GPU" != nvidia ] && skip_nvidia=1
|
||
# ...
|
||
```
|
||
|
||
### Failure classes
|
||
|
||
- **Soft failure (warning):** module logs and continues. Examples: GPU
|
||
live-load fails, `pkg update` fails. Action on resume: re-run module (idempotent).
|
||
- **Hard failure (`exit 1`):** module exits shell. Examples: `.env` missing
|
||
in 1.6, `AGENT_DOMAIN` not set. Action on resume: fix input, re-run from
|
||
start.
|
||
|
||
---
|
||
|
||
## Testing
|
||
|
||
Each module is sourceable and runnable standalone:
|
||
|
||
```sh
|
||
# GPU detection
|
||
export RC_CONF=/tmp/test-rc.conf
|
||
. firstboot/shell-gpu.sh
|
||
clawdie_shell_gpu_detect
|
||
cat /tmp/test-rc.conf # should have kld_list
|
||
|
||
# Env generation
|
||
export ASSISTANT_NAME="Test"
|
||
export AGENT_DOMAIN=test.local
|
||
export TZ=UTC
|
||
export CLAWDIE_HOME=/tmp/test-clawdie
|
||
. firstboot/shell-env.sh
|
||
clawdie_shell_env_generate
|
||
cat /tmp/test-clawdie/.env # should have ~45 lines incl. JWT_SECRET
|
||
|
||
# Pkg setup
|
||
export PKG_CONF_DIR=/tmp/test-repos
|
||
export USB_PKG_PATH=/tmp/fake-usb/packages
|
||
mkdir -p "$USB_PKG_PATH" && touch "$USB_PKG_PATH/test.pkg"
|
||
. firstboot/shell-pkg.sh
|
||
clawdie_shell_pkg_setup
|
||
cat /tmp/test-repos/FreeBSD.conf
|
||
cat /tmp/test-repos/Clawdie-USB.conf
|
||
```
|
||
|
||
After install, the same modules can be re-sourced from
|
||
`/usr/local/share/clawdie-iso/firstboot/` to troubleshoot or rerun a phase
|
||
without full reinstall.
|
||
|
||
### Verifying module output
|
||
|
||
```sh
|
||
# After env module
|
||
grep ASSISTANT_NAME /home/clawdie/clawdie-ai/.env
|
||
|
||
# After pkg module
|
||
cat /etc/pkg/repos/Clawdie-USB.conf
|
||
|
||
# After gpu module
|
||
sysctl hw.pci.dump | grep -i vga
|
||
grep kld_list /etc/rc.conf
|
||
|
||
# After deploy module
|
||
jls -N # list jails
|
||
# service clawdie status # future deployed-system service acceptance
|
||
```
|
||
|
||
---
|
||
|
||
## Adding a New Module
|
||
|
||
To add a new setup phase (e.g., `shell-ollama.sh` for local AI):
|
||
|
||
1. Create `firstboot/shell-ollama.sh` with a `clawdie_shell_ollama_setup()` function and the standard error/log/progress scaffolding.
|
||
2. Add a corresponding entry to this manifest: purpose, inputs, outputs, checkpoint, skip condition, error handling, recovery.
|
||
3. Source and call it from `firstboot.sh` in the appropriate slot:
|
||
```sh
|
||
. /usr/local/share/clawdie-iso/firstboot/shell-ollama.sh
|
||
clawdie_shell_ollama_setup
|
||
```
|
||
4. Add a checkpoint to the resume table.
|
||
|
||
No framework changes needed.
|
||
|
||
---
|
||
|
||
## POSIX Compliance — Rules
|
||
|
||
```sh
|
||
# Required
|
||
set -eu # POSIX: exit on error + undefined vars
|
||
trap '...' ERR # POSIX: error trap
|
||
|
||
# Forbidden (bash-only)
|
||
set -o pipefail # bash-only
|
||
[[ -f foo ]] # bash-only; use [ -f foo ]
|
||
function name { ... } # bash-only; use name() { ... }
|
||
local var=... # bash-only; use plain _var (prefixed)
|
||
```
|
||
|
||
### FreeBSD vs Linux idioms
|
||
|
||
| Wrong (Linux) | Right (FreeBSD) |
|
||
| ------------------------- | ----------------------- |
|
||
| `systemctl start service` | `service clawdie start` |
|
||
| `apt-get install package` | `pkg install package` |
|
||
| `/dev/sda1` | `/dev/da0s1` |
|
||
|
||
---
|
||
|
||
## Version History
|
||
|
||
- **current dev ISO:** live XFCE operator USB via SDDM, interactive login, 12 shell modules, ZFS/desktop/npm-globals.
|
||
- **v0.9.0:** 8 modules, runtime GPU detection, no ZFS/desktop/npm-globals.
|
||
- **v0.5.0:** 6 modules, PF firewall, glasspane VNC support.
|
||
- **v1.1 (planned):** add `shell-gpu-passthrough.sh`, `shell-upgrade.sh`.
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- `firstboot/firstboot.sh` — orchestrator (sources all modules and runs the sequence).
|
||
- `TESTING.md` — bhyve + hardware validation procedures.
|
||
- `BUILD.md` — build flags and cache behaviour.
|
||
- [POSIX Shell Reference](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html)
|
||
- [FreeBSD rc.d Services](https://docs.freebsd.org/en/books/handbook/config-tuning/#config-daemons)
|
||
- [Bastille Jails Documentation](https://bastille.readthedocs.io/)
|