#!/usr/bin/env bash set -euo pipefail # setup.sh — Bootstrap script for Clawdie AI # Handles Node.js/npm setup, then hands off to the Node.js setup modules. # This is the only bash script in the setup flow. PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="$PROJECT_ROOT/logs/setup.log" # shellcheck source=scripts/date-format.sh . "$PROJECT_ROOT/scripts/date-format.sh" mkdir -p "$PROJECT_ROOT/logs" log() { echo "[$(format_display_timestamp_now)] [bootstrap] $*" >> "$LOG_FILE"; } # --- Platform detection --- detect_platform() { local uname_s uname_s=$(uname -s) case "$uname_s" in FreeBSD*) PLATFORM="freebsd" ;; *) echo "ERROR: Clawdie AI requires FreeBSD. Detected: $uname_s" >&2 exit 1 ;; esac IS_ROOT="false" if [ "$(id -u)" -eq 0 ]; then IS_ROOT="true" fi log "Platform: $PLATFORM, Root: $IS_ROOT" } # --- Host package baseline --- install_host_pkg_baseline() { HOST_PKG_BASELINE_STATUS="skipped" HOST_PKG_BASELINE_MISSING="" HOST_PKG_BASELINE_INSTALL_CMD="none" [ "$PLATFORM" = "freebsd" ] || return local -a missing_pkgs=() local package_file="$PROJECT_ROOT/infra/packages/host-baseline.txt" local pkg while IFS= read -r pkg; do [ -n "$pkg" ] || continue case "$pkg" in \#*) continue ;; node24) command -v node >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; npm) command -v npm >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; bsddialog) command -v bsddialog >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; bastille) command -v bastille >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; git) command -v git >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; tmux) command -v tmux >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; python312) command -v python3.12 >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; uv) command -v uv >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; ripgrep) command -v rg >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; fd) command -v fd >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; rsync) command -v rsync >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; postgresql18-client) command -v psql >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; rust) command -v rustc >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; dejavu) [ -f /usr/local/share/fonts/dejavu/DejaVuSansMono.ttf ] || missing_pkgs+=("$pkg") ;; *) command -v "$pkg" >/dev/null 2>&1 || missing_pkgs+=("$pkg") ;; esac done < "$package_file" if [ "${#missing_pkgs[@]}" -eq 0 ]; then HOST_PKG_BASELINE_STATUS="already_present" log "Host package baseline already present" return fi HOST_PKG_BASELINE_MISSING="${missing_pkgs[*]}" if [ "$IS_ROOT" = "true" ]; then HOST_PKG_BASELINE_INSTALL_CMD="pkg install -y ${HOST_PKG_BASELINE_MISSING}" elif command -v sudo >/dev/null 2>&1; then HOST_PKG_BASELINE_INSTALL_CMD="sudo pkg install -y ${HOST_PKG_BASELINE_MISSING}" else HOST_PKG_BASELINE_STATUS="missing_no_sudo" log "Host package baseline missing and sudo unavailable: ${HOST_PKG_BASELINE_MISSING}" return fi log "Installing missing host package baseline: ${HOST_PKG_BASELINE_MISSING}" if $HOST_PKG_BASELINE_INSTALL_CMD >> "$LOG_FILE" 2>&1; then HOST_PKG_BASELINE_STATUS="installed" log "Host package baseline installed successfully" else HOST_PKG_BASELINE_STATUS="install_failed" log "Host package baseline installation failed" fi } # --- Node.js check --- check_node() { NODE_OK="false" NODE_VERSION="not_found" NODE_PATH_FOUND="" if command -v node >/dev/null 2>&1; then NODE_VERSION=$(node --version 2>/dev/null | sed 's/^v//') NODE_PATH_FOUND=$(command -v node) local major major=$(echo "$NODE_VERSION" | cut -d. -f1) if [ "$major" -ge 24 ] 2>/dev/null; then NODE_OK="true" fi log "Node $NODE_VERSION at $NODE_PATH_FOUND (major=$major, ok=$NODE_OK)" else log "Node not found" fi } # --- bsddialog check --- check_bsddialog() { BSDDIALOG_OK="false" BSDDIALOG_PATH="not_found" if command -v bsddialog >/dev/null 2>&1; then BSDDIALOG_OK="true" BSDDIALOG_PATH=$(command -v bsddialog) fi log "bsddialog: $BSDDIALOG_OK (${BSDDIALOG_PATH:-not_found})" } # --- npm install --- install_deps() { DEPS_OK="false" if [ "$NODE_OK" = "false" ]; then log "Skipping npm install — Node not available" return fi cd "$PROJECT_ROOT" # npm install with --unsafe-perm if root (needed for native modules) local npm_flags="" if [ "$IS_ROOT" = "true" ]; then npm_flags="--unsafe-perm" log "Running as root, using --unsafe-perm" fi log "Running npm install $npm_flags" if npm install $npm_flags >> "$LOG_FILE" 2>&1; then DEPS_OK="true" log "npm install succeeded" else log "npm install failed" return fi } # --- Build tools check --- check_build_tools() { HAS_BUILD_TOOLS="false" if command -v cc >/dev/null 2>&1 && command -v make >/dev/null 2>&1; then HAS_BUILD_TOOLS="true" fi log "Build tools: $HAS_BUILD_TOOLS" } # --- Main --- log "=== Bootstrap started ===" detect_platform install_host_pkg_baseline check_node check_bsddialog install_deps check_build_tools # Emit status block STATUS="success" if [ "$HOST_PKG_BASELINE_STATUS" = "missing_no_sudo" ] || [ "$HOST_PKG_BASELINE_STATUS" = "install_failed" ]; then STATUS="host_packages_failed" elif [ "$NODE_OK" = "false" ]; then STATUS="node_missing" elif [ "$DEPS_OK" = "false" ]; then STATUS="deps_failed" fi cat </dev/null || true log "Git hooks path set to hooks/" # Detect host locale silently — reads $LANG / locale / ~/.login_conf from the FreeBSD install # Falls back to en-US on neutral/cloud environments. No prompt needed. log "Detecting host locale" npx tsx setup/index.ts --step profile --accept-detected >> "$LOG_FILE" 2>&1 || true if [ "${CLAWDIE_SKIP_ONBOARDING:-0}" != "1" ] \ && [ -t 0 ] \ && [ -t 1 ]; then log "Launching interactive onboarding" echo echo "Launching interactive onboarding..." npm run wizard fi