729 lines
25 KiB
Markdown
729 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 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/)
|