2026-05-24 23:21:02 +02:00
# 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
2026-06-13 12:12:34 +02:00
# After deploy module
2026-05-24 23:21:02 +02:00
jls -N # list jails
2026-06-13 12:12:34 +02:00
# service clawdie status # future deployed-system service acceptance
2026-05-24 23:21:02 +02:00
```
---
## 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/ )