clawdie-iso/FIRSTBOOT.md
Sam & Claude 0e6d8cbe53 Drop unresolved Clawdie service staging from USB (Sam & Codex)
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
2026-06-13 12:12:34 +02:00

728 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |
| 0x20600x2500 | Turing/Ampere (RTX 20xx/30xx) | 590 |
| 0x13400x2186 | Maxwell (GTX 750980 Ti) | 470 |
| 0x11800x139F | Kepler (GTX 650780 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/)