2026-03-26 14:03:08 +00:00
# Clawdie Shell Modules — Implementation Reference
**Version:** v0.9.0
**Implementation Date:** 26.mar.2026
2026-04-08 18:24:48 +00:00
**Status:** ✅ Complete (12/12 modules)
2026-03-26 14:03:08 +00:00
---
## Overview
2026-04-08 18:24:48 +00:00
The Clawdie Shell comprises 12 POSIX-compliant shell modules that orchestrate the complete installation and configuration of Clawdie-AI from ISO boot to running system.
2026-03-26 14:03:08 +00:00
Each module is independent and can be sourced individually for testing. Together, they form the firstboot pipeline that executes on first HDD boot.
---
## Module Index
| Module | Function | Lines | Purpose | Status |
|--------|----------|-------|---------|--------|
2026-04-08 18:24:48 +00:00
| shell-zfs.sh | `clawdie_shell_zfs_detect()` | 192 | ZFS pool detection | ✅ Complete |
| shell-gpu.sh | `clawdie_shell_gpu_detect()` | 184 | GPU detection + kld_list | ✅ Complete |
| shell-nvidia.sh | `clawdie_shell_nvidia_detect()` | 229 | NVIDIA driver versioning | ✅ Complete |
| shell-pkg.sh | `clawdie_shell_pkg_setup()` | 199 | Package repos + USB cache | ✅ Complete |
| shell-ssh.sh | `clawdie_shell_ssh_setup()` | 260 | SSH keys + system passwords | ✅ Complete |
| shell-env.sh | `clawdie_shell_env_generate()` | 192 | .env + secrets generation | ✅ Complete |
| shell-system.sh | `clawdie_shell_system_config()` | 233 | rc.conf + hostname + services | ✅ Complete |
| shell-desktop.sh | `clawdie_shell_desktop_detect()` | 101 | Desktop detection + enablement | ✅ Complete |
| shell-pf.sh | `clawdie_shell_pf()` | 148 | PF firewall + jail NAT | ✅ Complete |
| shell-tailscale.sh | `clawdie_shell_tailscale_setup()` | 121 | Tailscale remote access (optional) | ✅ Complete |
| shell-npm-globals.sh | `clawdie_shell_npm_globals_install()` | 101 | Bundled npm CLIs | ✅ Complete |
2026-04-14 18:53:06 +00:00
| shell-deploy.sh | `clawdie_shell_deploy()` | 424 | Tarball extract + just install | ✅ Complete |
2026-04-08 18:24:48 +00:00
**Total:** 2,384 lines of production code (POSIX shell, no bash-isms)
2026-03-26 14:03:08 +00:00
---
2026-04-08 18:24:48 +00:00
## Module: shell-gpu.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-gpu.sh`
**Main Function:** `clawdie_shell_gpu_detect()`
### Purpose
Detect the system's GPU (if any) and configure appropriate kernel modules for X11/Wayland rendering. Writes `kld_list` to `/etc/rc.conf` .
### Entry Point
```bash
. firstboot/shell-gpu.sh
clawdie_shell_gpu_detect
```
### Functions
#### `clawdie_shell_gpu_detect()`
Main orchestrator. Detects GPU vendor via `pciconf` , matches to driver, writes rc.conf.
**Sets:** `DETECTED_GPU` (intel, amd, nvidia, vmware, or vesa)
**Side effects:** Writes to rc.conf, progress file, log file
#### `clawdie_shell_gpu_detect_pci()`
Queries `pciconf -lv` to find VGA device (class 0x030000).
**Returns:** GPU vendor string (intel, amd, nvidia, vmware, or empty)
#### `clawdie_shell_gpu_match_driver(vendor)`
Maps vendor to FreeBSD kernel module list.
**Mappings:**
| Vendor | Module | Note |
|--------|--------|------|
| intel | i915kms | Intel integrated GPU (iGPU) |
| amd | amdgpu | AMD Radeon / RDNA |
| nvidia | nvidia-modeset nvidia | NVIDIA discrete GPU (see shell-nvidia.sh for version) |
| vmware | vmwgfx | VMware SVGA 3D |
| (other) | (empty) | VESA fallback, no kld needed |
#### `clawdie_shell_gpu_write_rcconf(kld_list)`
Writes `kld_list="..."` to `/etc/rc.conf` , replacing any existing kld_list.
**Side effects:**
- Removes existing kld_list (idempotent)
- Appends new kld_list entry
- Skips if kld_list is empty (VESA case)
#### `clawdie_shell_gpu_validate()`
Verifies rc.conf has valid kld_list.
### Environment Variables
**Input (from caller):**
- (none required; uses pciconf on system)
**Output (sets for downstream modules):**
- `DETECTED_GPU` — GPU vendor (used by shell-nvidia.sh)
**Configuration (can override for testing):**
- `RC_CONF` (default: `/etc/rc.conf` )
- `LOG_FILE` (default: `/var/log/clawdie-firstboot.log` )
- `PROGRESS_FILE` (default: `/var/log/clawdie-firstboot.progress` )
### Error Handling
Returns 0 on success. Returns 1 on critical failure (e.g., can't write rc.conf).
Gracefully handles:
- Missing pciconf output → defaults to VESA
- Unknown GPU vendor → defaults to VESA
- Read-only /etc/rc.conf → returns error
### Testing
```bash
# Quick test
export RC_CONF="/tmp/test-rc.conf"
export LOG_FILE="/tmp/test.log"
. firstboot/shell-gpu.sh
clawdie_shell_gpu_detect
cat /tmp/test-rc.conf # Should have kld_list
```
---
2026-04-08 18:24:48 +00:00
## Module: shell-nvidia.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-nvidia.sh`
**Main Function:** `clawdie_shell_nvidia_detect()`
### Purpose
For NVIDIA GPUs, detect the specific GPU model and select the correct driver version (590, 470, or 390). Modern GPUs use 590; Maxwell-era use 470; Kepler and older use 390.
### Entry Point
```bash
export DETECTED_GPU="nvidia" # Set by shell-gpu.sh
. firstboot/shell-nvidia.sh
clawdie_shell_nvidia_detect
```
### Functions
#### `clawdie_shell_nvidia_detect()`
Main orchestrator. Only executes if `DETECTED_GPU=nvidia` .
**Sets:** `NVIDIA_DRIVER` (590, 470, or 390)
**Side effects:** Writes updated kld_list to rc.conf
#### `clawdie_shell_nvidia_get_device_id()`
Queries pciconf for NVIDIA GPU device ID (PCI vendor 0x10de).
**Returns:** 4-digit hex device ID (e.g., "2388" for RTX 4090)
#### `clawdie_shell_nvidia_select_driver(device_id)`
Maps device ID to driver version using lookup table.
**Device ranges:**
| Range | GPU Era | Driver |
|-------|---------|--------|
| 0x2700+ (≥9984) | Ada (RTX 40xx) | 590 |
| 0x2060– 0x2500 (8288– 9472) | Turing/Ampere (RTX 20xx/30xx) | 590 |
| 0x1340– 0x2186 (4928– 8582) | Maxwell (GTX 750– 980 Ti) | 470 |
| 0x1180– 0x139F (4480– 5023) | Kepler (GTX 650– 780 Ti) | 390 |
| (unknown) | — | 590 (safe default) |
#### `clawdie_shell_nvidia_write_rcconf(nvidia_version)`
Updates rc.conf with correct nvidia-driver and nvidia-modeset modules.
**Side effects:**
- Removes any existing nvidia entries from kld_list
- Adds `nvidia-modeset nvidia` at front of kld_list
- Preserves other kld modules (e.g., i915kms from dual GPU)
#### `clawdie_shell_nvidia_validate()`
Checks that nvidia is in rc.conf.
### Environment Variables
**Input (required):**
- `DETECTED_GPU` — Must equal "nvidia" to execute
**Output (sets):**
- `NVIDIA_DRIVER` — Selected version (590, 470, or 390)
**Configuration (can override):**
- `RC_CONF` (default: `/etc/rc.conf` )
- `LOG_FILE` (default: `/var/log/clawdie-firstboot.log` )
- `PROGRESS_FILE` (default: `/var/log/clawdie-firstboot.progress` )
### Fallback Behavior
- No GPU detected → Silently returns 0 (NVIDIA module skipped)
- Unknown device ID → Defaults to driver 590
### Testing
```bash
# Test with simulated NVIDIA GPU
export DETECTED_GPU="nvidia"
export RC_CONF="/tmp/test-rc.conf"
. firstboot/shell-nvidia.sh
clawdie_shell_nvidia_detect
cat /tmp/test-rc.conf # Should have nvidia-modeset nvidia
```
---
2026-04-08 18:24:48 +00:00
## Module: shell-pkg.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-pkg.sh`
**Main Function:** `clawdie_shell_pkg_setup()`
### Purpose
Configure FreeBSD package repositories (online and offline USB) and seed Bastille jail cache with pre-downloaded packages for offline provisioning.
### Entry Point
```bash
. firstboot/shell-pkg.sh
clawdie_shell_pkg_setup
```
### Functions
#### `clawdie_shell_pkg_setup()`
Main orchestrator. Detects ABI, writes repo configs, seeds cache.
**Returns:** 0 on success, 1 on critical error
**Side effects:** Creates /etc/pkg/repos, /var/cache/pkg/bastille directories
#### `clawdie_shell_pkg_detect_abi()`
Queries `pkg config abi` or derives from `uname` .
**Returns:** ABI string like "FreeBSD:15:amd64"
#### `clawdie_shell_pkg_write_freebsd_conf(abi)`
Writes `/etc/pkg/repos/FreeBSD.conf` pointing to pkg.FreeBSD.org.
**Uses:** `DEFAULT_PKG_BRANCH` from build.cfg (latest or quarterly)
**Output file format:**
```
FreeBSD: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
enabled: yes
}
```
#### `clawdie_shell_pkg_write_clawdie_conf()`
Writes `/etc/pkg/repos/Clawdie-USB.conf` pointing to bundled USB packages.
**Output file format:**
```
Clawdie-USB: {
url: "file:///usr/local/share/clawdie-iso/packages",
mirror_type: "none",
enabled: yes,
priority: 100
}
```
Priority 100 ensures USB packages take precedence if online repo is also available.
#### `clawdie_shell_pkg_seed_cache()`
Copies all .pkg files from USB to `/var/cache/pkg/bastille` .
**Purpose:** Allows jails to install packages offline after first boot
**Side effects:**
- Creates /var/cache/pkg/bastille if missing
- Copies *.pkg files (non-fatal if USB path missing)
### Environment Variables
**Input (optional):**
- `DEFAULT_PKG_BRANCH` — "latest" or "quarterly" (from build.cfg)
**Configuration (can override):**
- `PKG_CONF_DIR` (default: `/etc/pkg/repos` )
- `FREEBSD_REPO_CONF` (default: `/etc/pkg/repos/FreeBSD.conf` )
- `CLAWDIE_USB_REPO_CONF` (default: `/etc/pkg/repos/Clawdie-USB.conf` )
- `USB_PKG_PATH` (default: `/usr/local/share/clawdie-iso/packages` )
- `BASTILLE_PKG_CACHE` (default: `/var/cache/pkg/bastille` )
- `LOG_FILE` , `PROGRESS_FILE`
### Error Handling
Non-fatal errors:
- Missing /etc/pkg/repos directory → created
- Missing USB packages → skipped with warning
- FreeBSD repo already exists → replaced
Returns 1 only if critical failure (e.g., can't write config).
### Testing
```bash
# Test with mock directories
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
# Verify
cat /tmp/test-repos/FreeBSD.conf
cat /tmp/test-repos/Clawdie-USB.conf
```
---
2026-04-08 18:24:48 +00:00
## Module: shell-env.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-env.sh`
**Main Function:** `clawdie_shell_env_generate()`
### Purpose
Generate `.env` configuration file with secrets, identity variables, and jail IP allocation. This file is used by Clawdie-AI at runtime.
### Entry Point
```bash
export ASSISTANT_NAME="MyAssistant"
export AGENT_DOMAIN="clawdie.local"
export TZ="Europe/Ljubljana"
. firstboot/shell-env.sh
clawdie_shell_env_generate
```
### Functions
#### `clawdie_shell_env_generate()`
Main orchestrator. Validates inputs, generates secrets, writes .env file.
**Requires:**
- `ASSISTANT_NAME` (from wizard)
- `AGENT_DOMAIN` (from wizard)
- `TZ` (from wizard, defaults to UTC)
**Side effects:**
- Creates /home/clawdie/.env
- Sets permissions 600
- Changes owner to clawdie:clawdie
#### `clawdie_shell_env_write_file()`
Generates complete .env file with:
- Identity (ASSISTANT_NAME, AGENT_DOMAIN, TZ)
- Auto-generated secrets (JWT_SECRET, API_KEY, DB_PASSWORD, REDIS_PASSWORD)
- Jail IP allocation (derived from AGENT_SUBNET_BASE)
- Feature flags (FEATURE_MANAGEMENT_JAIL, FEATURE_OLLAMA, FEATURE_TELEGRAM)
- LLM provider config
- System metadata
**File format:** Shell-compatible KEY="value" (can be sourced in other scripts)
**Variables generated:**
- Identity: ASSISTANT_NAME, AGENT_NAME, AGENT_DOMAIN, TZ
- Secrets: JWT_SECRET, ANTHROPIC_API_KEY, DB_PASSWORD, REDIS_PASSWORD (32-char base64 each)
- Network: AGENT_SUBNET_BASE, AGENT_GATEWAY_IP, MGMT_JAIL_IP, DB_JAIL_IP, GIT_JAIL_IP, CMS_JAIL_IP, WORKER_JAIL_IP_START
- Database: DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
- Features: FEATURE_MANAGEMENT_JAIL, FEATURE_OLLAMA, FEATURE_TELEGRAM
2026-04-27 11:56:49 +02:00
- Telegram: TELEGRAM_BOT_TOKEN, TELEGRAM_ADMIN_IDS (optional, blank initially)
2026-03-26 14:03:08 +00:00
- LLM: LLM_PROVIDER
- SSH: SSH_PUBLIC_KEY (if provided at install)
- System: FREEBSD_VERSION, INSTALL_DATE
#### `clawdie_shell_env_gen_secret()`
Generates random 32-character secrets suitable for JWT/API keys.
**Method:** `openssl rand -base64 32` with fallback to /dev/urandom
**Returns:** 32-char base64 string (alphanumeric + +/)
#### `clawdie_shell_env_validate()`
Verifies .env file has:
- Correct permissions (600)
- All required variables
- Non-empty secret values
### Environment Variables
**Input (required):**
- `ASSISTANT_NAME` — Display name for agent
- `AGENT_DOMAIN` — FQDN for internal network
- `TZ` — Timezone (e.g., Europe/Ljubljana)
**Input (optional):**
- `SSH_PUBLIC_KEY` — User's SSH public key (if provided at install)
**Configuration (can override):**
- `CLAWDIE_HOME` (default: `/home/clawdie` )
- `ENV_FILE` (default: `/home/clawdie/.env` )
- `AGENT_SUBNET_BASE` (default: `10.0.0` )
- `LOG_FILE` , `PROGRESS_FILE`
### Jail IP Allocation
IPs are auto-derived from AGENT_SUBNET_BASE:
| Slot | Service | 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+ |
### Testing
```bash
# Test .env generation
export ASSISTANT_NAME="TestAgent"
export AGENT_DOMAIN="test.local"
export TZ="UTC"
export CLAWDIE_HOME="/tmp/test-clawdie"
. firstboot/shell-env.sh
clawdie_shell_env_generate
# Verify
cat /tmp/test-clawdie/.env
wc -l /tmp/test-clawdie/.env # Should have ~45 lines
grep "JWT_SECRET" /tmp/test-clawdie/.env
```
---
2026-04-08 18:24:48 +00:00
## Module: shell-deploy.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-deploy.sh`
**Main Function:** `clawdie_shell_deploy()`
### Purpose
2026-04-14 18:53:06 +00:00
Extract Clawdie-AI tarball to /home/clawdie/ and run `just install` , which provisions jails, installs databases, configures services.
2026-03-26 14:03:08 +00:00
### Entry Point
```bash
. firstboot/shell-deploy.sh
clawdie_shell_deploy
```
### Functions
#### `clawdie_shell_deploy()`
2026-04-14 19:04:41 +00:00
Main orchestrator. Extracts tarball, ensures dependencies exist (offline tarball includes `node_modules` ), runs installer, verifies services.
2026-03-26 14:03:08 +00:00
**Requires:**
- `ASSISTANT_NAME` , `AGENT_DOMAIN` , `TZ` (from env module)
**Returns:** 0 on success, 1 on failure
**Side effects:** Creates /home/clawdie/clawdie-ai, provisions jails, starts services
#### `clawdie_shell_deploy_extract_tarball()`
Extracts clawdie-ai.tar.gz to /home/clawdie/.
**Tarball location:** `/usr/local/share/clawdie-iso/clawdie-ai.tar.gz`
**Side effects:**
- Creates /home/clawdie/clawdie-ai
- Renames Clawdie-AI/ → clawdie-ai/ if needed
- Fixes ownership to clawdie:clawdie
#### `clawdie_shell_deploy_run_install_all()`
2026-04-14 18:53:06 +00:00
Executes `just install` from /home/clawdie/clawdie-ai/.
2026-03-26 14:03:08 +00:00
**What it does (from Clawdie-AI's package.json):**
2026-04-14 19:04:41 +00:00
- Uses bundled `node_modules` (offline); if missing, deploy falls back to `npm ci` (network required)
2026-03-26 14:03:08 +00:00
- Creates and starts Bastille jails (worker, db, git, cms)
- Sets up PostgreSQL in db jail
- Configures nginx in cms jail
- Installs rc.d service script for clawdie agent
- Enables service
**Runs as:** clawdie user (drops from root if needed via `su - clawdie` )
#### `clawdie_shell_deploy_verify()`
Checks post-install state:
- At least one jail running (jls -N)
- clawdie service enabled in rc.conf
- .env file exists
**Warnings:** Non-fatal if any check fails (jails may still be starting)
### Environment Variables
**Input (optional):**
- `ASSISTANT_NAME` , `AGENT_DOMAIN` , `TZ` (from env module if available)
**Configuration (can override):**
- `CLAWDIE_HOME` (default: `/home/clawdie` )
- `CLAWDIE_AI_DIR` (default: `/home/clawdie/clawdie-ai` )
- `CLAWDIE_TARBALL` (default: `/usr/local/share/clawdie-iso/clawdie-ai.tar.gz` )
- `ENV_FILE` (default: `/home/clawdie/.env` )
- `LOG_FILE` , `PROGRESS_FILE`
### Error Handling
Returns 1 (fails) if:
- Tarball not found
- Extraction fails
- package.json not found after extraction
2026-04-14 18:53:06 +00:00
- just install returns error
2026-03-26 14:03:08 +00:00
Returns 0 (succeeds) even if:
- Verification finds missing jails (they may still be starting)
- .env file missing (it's created by env module, but this module doesn't require it)
### Testing
```bash
# Minimal test (mock)
export CLAWDIE_HOME="/tmp/test-clawdie"
export CLAWDIE_AI_DIR="$CLAWDIE_HOME/clawdie-ai"
mkdir -p "$CLAWDIE_AI_DIR"
# Create mock package.json
cat > "$CLAWDIE_AI_DIR/package.json" < < 'EOF'
2026-04-14 18:53:06 +00:00
{"name":"clawdie-ai","version":"0.9.0","scripts":{"install":"echo 'Mock install'"}}
2026-03-26 14:03:08 +00:00
EOF
. firstboot/shell-deploy.sh
clawdie_shell_deploy
# Should complete without errors
```
---
2026-04-08 18:24:48 +00:00
## Module: shell-system.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-system.sh`
**Main Function:** `clawdie_shell_system_config()`
### Purpose
Configure system-level settings: timezone, hostname, rc.conf services (dbus, hald, seatd, lightdm), and Lumina desktop environment.
### Functions
#### `clawdie_shell_system_config()`
Main orchestrator.
**Requires:**
- `TZ` (timezone from wizard)
- `AGENT_DOMAIN` (from wizard, used as hostname)
**Side effects:**
- Writes /etc/rc.conf
- Writes /etc/hostname
- Creates /etc/profile.d/clawdie.sh
- Enables services
#### `clawdie_shell_system_write_rcconf()`
Configures rc.conf with:
- Timezone
- Service enables (dbus, hald, seatd, lightdm)
- Lumina desktop settings
#### `clawdie_shell_system_set_hostname()`
Sets hostname from AGENT_DOMAIN
#### `clawdie_shell_system_setup_env()`
Creates /etc/profile.d/clawdie.sh with environment variables
#### `clawdie_shell_system_enable_services()`
Enables required services in rc.conf:
- dbus (message bus)
- hald (hardware abstraction)
- seatd (seat management for Wayland)
- lightdm (display manager for Lumina)
---
2026-04-08 18:24:48 +00:00
## Module: shell-ssh.sh
2026-03-26 14:03:08 +00:00
**File:** `firstboot/shell-ssh.sh`
**Main Function:** `clawdie_shell_ssh_setup()`
### Purpose
Configure SSH access and system user passwords. If SSH key is provided, disables password auth. Otherwise, generates secure password for clawdie user.
### Functions
#### `clawdie_shell_ssh_setup()`
Main orchestrator.
**Optional input:**
- `SSH_PUBLIC_KEY` (from install, if provided)
**Configures:**
- SSH keys for clawdie user
- System user passwords (root + clawdie)
- rc.conf for sshd
---
2026-04-08 18:24:48 +00:00
## Module: shell-zfs.sh
**Purpose:** Detect existing ZFS pools and choose boot mode
**Main Function:** `clawdie_shell_zfs_detect()`
**Outputs:**
- `CLAWDIE_BOOT_MODE` — install | upgrade | maintenance
- `POOL_NAME` — detected pool name (if present)
---
## Module: shell-desktop.sh
**Purpose:** Detect display hardware and enable desktop stack when appropriate
**Main Function:** `clawdie_shell_desktop_detect()`
**Outputs:**
- Desktop enablement flags in rc.conf (headless when skipped)
---
## Module: shell-pf.sh
**Purpose:** Configure PF firewall, NAT, and glasspane VNC rules
**Main Function:** `clawdie_shell_pf()`
**Outputs:**
- `/etc/pf.conf` , `/usr/local/etc/rc.d/pf_reload` , rc.conf PF settings
---
## Module: shell-tailscale.sh
**Purpose:** Optional Tailscale install + enablement for remote access
**Main Function:** `clawdie_shell_tailscale_setup()`
**Outputs:**
- tailscaled enablement in rc.conf, optional auth flow
---
## Module: shell-npm-globals.sh
**Purpose:** Install bundled npm CLI tools from ISO cache
**Main Function:** `clawdie_shell_npm_globals_install()`
**Outputs:**
2026-04-12 06:10:27 +00:00
- `/opt/clawdie/npm-global/bin` populated with CLI binaries
2026-04-08 18:24:48 +00:00
---
2026-03-26 14:03:08 +00:00
## Execution Order (firstboot.sh)
2026-04-08 18:24:48 +00:00
The modules execute in this sequence (see firstboot.sh run_step block):
2026-03-26 14:03:08 +00:00
```
2026-04-08 18:24:48 +00:00
1. clawdie_shell_zfs_detect → Sets CLAWDIE_BOOT_MODE (baremetal only)
2. clawdie_shell_gpu_detect → Sets DETECTED_GPU
3. clawdie_shell_nvidia_detect → Sets NVIDIA_DRIVER_VERSION
4. clawdie_shell_pkg_setup → Configures repos
5. clawdie_shell_ssh_setup → SSH + passwords
6. clawdie_shell_env_generate → Generates .env
7. clawdie_shell_system_config → Configures system
8. clawdie_shell_desktop_detect → Desktop enablement
9. clawdie_shell_pf → PF firewall + NAT
10. clawdie_shell_tailscale_setup → Tailscale remote access
11. clawdie_shell_npm_globals_install → Bundled npm CLIs
12. clawdie_shell_deploy → Extracts + installs
2026-03-26 14:03:08 +00:00
```
**State handoff:**
2026-04-08 18:24:48 +00:00
- ZFS module → sets CLAWDIE_BOOT_MODE + POOL_NAME
2026-03-26 14:03:08 +00:00
- GPU module → sets DETECTED_GPU
- env module → creates .env (used by deploy)
- deploy module → provisions jails + services
---
## POSIX Compliance
All modules use POSIX shell features only (no bash-isms):
- ✅ No `[[ ]]` (use `[ ]` )
- ✅ No `${var#pattern}` (avoid)
- ✅ No `function` keyword (use `name()` )
- ✅ No `local` (use plain variables in functions, prefix with `_` to avoid collisions)
- ✅ No `>&2` redirects without careful quoting
This ensures compatibility with FreeBSD /bin/sh.
---
## Error Handling Pattern
All modules follow this pattern:
```bash
function_name() {
# 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
# Log success
log_msg "Task complete"
return 0
}
```
Each module defines its own `log_msg()` function to write to `LOG_FILE` .
---
## Testing Individual Modules
Each module can be tested standalone:
```bash
# Test GPU detection
export RC_CONF="/tmp/rc.conf"
. firstboot/shell-gpu.sh
clawdie_shell_gpu_detect
# Test environment generation
export ASSISTANT_NAME="Test"
export AGENT_DOMAIN="test.local"
export TZ="UTC"
. firstboot/shell-env.sh
clawdie_shell_env_generate
```
---
## Integration with firstboot.sh
2026-04-08 18:24:48 +00:00
The orchestrator `firstboot.sh` (lines 1– 282):
2026-03-26 14:03:08 +00:00
2026-04-08 18:24:48 +00:00
1. Sources all 12 shell-*.sh modules
2026-03-26 14:03:08 +00:00
2. Collects wizard inputs (or uses pre-baked config)
3. Exports inputs as environment variables
4. Calls each module's main function in sequence
5. Checks progress file for [MODULE] COMPLETE entries
6. On success, disables clawdie-firstboot service
See [firstboot/firstboot.sh ](firstboot/firstboot.sh ) for full flow.
---
## References
- [TESTING.md ](TESTING.md ) — Testing procedures
- [IMPLEMENTATION-PLAN.md ](IMPLEMENTATION-PLAN.md ) — Task status
- [BUILD.md ](BUILD.md ) — Build process