# 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 clawdie module jls -N # list jails service clawdie status ``` --- ## 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/)