2026-03-14 14:55:47 +01:00
|
|
|
#!/usr/bin/env bash
|
2026-02-22 18:25:11 +02:00
|
|
|
set -euo pipefail
|
|
|
|
|
|
2026-03-11 11:33:36 +00:00
|
|
|
# setup.sh — Bootstrap script for Clawdie AI
|
2026-02-22 18:25:11 +02:00
|
|
|
# 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"
|
|
|
|
|
|
2026-03-14 00:07:55 +01:00
|
|
|
# shellcheck source=scripts/date-format.sh
|
|
|
|
|
. "$PROJECT_ROOT/scripts/date-format.sh"
|
|
|
|
|
|
2026-02-22 18:25:11 +02:00
|
|
|
mkdir -p "$PROJECT_ROOT/logs"
|
|
|
|
|
|
2026-03-14 00:07:55 +01:00
|
|
|
log() { echo "[$(format_display_timestamp_now)] [bootstrap] $*" >> "$LOG_FILE"; }
|
2026-02-22 18:25:11 +02:00
|
|
|
|
|
|
|
|
# --- Platform detection ---
|
|
|
|
|
|
|
|
|
|
detect_platform() {
|
|
|
|
|
local uname_s
|
|
|
|
|
uname_s=$(uname -s)
|
|
|
|
|
case "$uname_s" in
|
2026-03-11 11:33:36 +00:00
|
|
|
FreeBSD*) PLATFORM="freebsd" ;;
|
|
|
|
|
*)
|
|
|
|
|
echo "ERROR: Clawdie AI requires FreeBSD. Detected: $uname_s" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
2026-02-22 18:25:11 +02:00
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
IS_ROOT="false"
|
|
|
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
|
|
|
IS_ROOT="true"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-11 11:33:36 +00:00
|
|
|
log "Platform: $PLATFORM, Root: $IS_ROOT"
|
2026-02-22 18:25:11 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-14 14:55:47 +01:00
|
|
|
# --- 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=()
|
2026-03-14 15:26:46 +01:00
|
|
|
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")
|
|
|
|
|
;;
|
2026-06-17 14:57:19 +02:00
|
|
|
python312)
|
|
|
|
|
command -v python3.12 >/dev/null 2>&1 || missing_pkgs+=("$pkg")
|
2026-03-14 15:26:46 +01:00
|
|
|
;;
|
|
|
|
|
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")
|
|
|
|
|
;;
|
2026-04-18 09:12:48 +00:00
|
|
|
postgresql18-client)
|
2026-03-14 15:26:46 +01:00
|
|
|
command -v psql >/dev/null 2>&1 || missing_pkgs+=("$pkg")
|
|
|
|
|
;;
|
2026-04-04 14:17:04 +00:00
|
|
|
rust)
|
|
|
|
|
command -v rustc >/dev/null 2>&1 || missing_pkgs+=("$pkg")
|
|
|
|
|
;;
|
2026-03-14 15:26:46 +01:00
|
|
|
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"
|
2026-03-14 14:55:47 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 18:25:11 +02:00
|
|
|
# --- 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)
|
2026-03-14 00:07:55 +01:00
|
|
|
if [ "$major" -ge 24 ] 2>/dev/null; then
|
2026-02-22 18:25:11 +02:00
|
|
|
NODE_OK="true"
|
|
|
|
|
fi
|
|
|
|
|
log "Node $NODE_VERSION at $NODE_PATH_FOUND (major=$major, ok=$NODE_OK)"
|
|
|
|
|
else
|
|
|
|
|
log "Node not found"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 02:51:10 +01:00
|
|
|
# --- 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})"
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 18:25:11 +02:00
|
|
|
# --- 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"
|
|
|
|
|
|
2026-03-11 11:33:36 +00:00
|
|
|
if command -v cc >/dev/null 2>&1 && command -v make >/dev/null 2>&1; then
|
|
|
|
|
HAS_BUILD_TOOLS="true"
|
2026-02-22 18:25:11 +02:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log "Build tools: $HAS_BUILD_TOOLS"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- Main ---
|
|
|
|
|
|
|
|
|
|
log "=== Bootstrap started ==="
|
|
|
|
|
|
|
|
|
|
detect_platform
|
2026-03-14 14:55:47 +01:00
|
|
|
install_host_pkg_baseline
|
2026-02-22 18:25:11 +02:00
|
|
|
check_node
|
2026-03-14 02:51:10 +01:00
|
|
|
check_bsddialog
|
2026-02-22 18:25:11 +02:00
|
|
|
install_deps
|
|
|
|
|
check_build_tools
|
|
|
|
|
|
|
|
|
|
# Emit status block
|
|
|
|
|
STATUS="success"
|
2026-03-14 14:55:47 +01:00
|
|
|
if [ "$HOST_PKG_BASELINE_STATUS" = "missing_no_sudo" ] || [ "$HOST_PKG_BASELINE_STATUS" = "install_failed" ]; then
|
|
|
|
|
STATUS="host_packages_failed"
|
|
|
|
|
elif [ "$NODE_OK" = "false" ]; then
|
2026-02-22 18:25:11 +02:00
|
|
|
STATUS="node_missing"
|
|
|
|
|
elif [ "$DEPS_OK" = "false" ]; then
|
|
|
|
|
STATUS="deps_failed"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
cat <<EOF
|
2026-03-11 11:33:36 +00:00
|
|
|
=== CLAWDIE SETUP: BOOTSTRAP ===
|
2026-02-22 18:25:11 +02:00
|
|
|
PLATFORM: $PLATFORM
|
|
|
|
|
IS_ROOT: $IS_ROOT
|
2026-03-14 14:55:47 +01:00
|
|
|
HOST_PKG_BASELINE_STATUS: $HOST_PKG_BASELINE_STATUS
|
|
|
|
|
HOST_PKG_BASELINE_MISSING: ${HOST_PKG_BASELINE_MISSING:-none}
|
|
|
|
|
HOST_PKG_BASELINE_INSTALL_CMD: ${HOST_PKG_BASELINE_INSTALL_CMD:-none}
|
2026-02-22 18:25:11 +02:00
|
|
|
NODE_VERSION: $NODE_VERSION
|
|
|
|
|
NODE_OK: $NODE_OK
|
|
|
|
|
NODE_PATH: ${NODE_PATH_FOUND:-not_found}
|
|
|
|
|
DEPS_OK: $DEPS_OK
|
|
|
|
|
HAS_BUILD_TOOLS: $HAS_BUILD_TOOLS
|
2026-03-14 02:51:10 +01:00
|
|
|
BSDDIALOG_OK: $BSDDIALOG_OK
|
|
|
|
|
BSDDIALOG_PATH: ${BSDDIALOG_PATH:-not_found}
|
2026-02-22 18:25:11 +02:00
|
|
|
STATUS: $STATUS
|
|
|
|
|
LOG: logs/setup.log
|
|
|
|
|
=== END ===
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
log "=== Bootstrap completed: $STATUS ==="
|
|
|
|
|
|
2026-03-14 14:55:47 +01:00
|
|
|
if [ "$HOST_PKG_BASELINE_STATUS" = "missing_no_sudo" ] || [ "$HOST_PKG_BASELINE_STATUS" = "install_failed" ]; then
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-02-22 18:25:11 +02:00
|
|
|
if [ "$NODE_OK" = "false" ]; then
|
|
|
|
|
exit 2
|
|
|
|
|
fi
|
2026-04-11 14:47:18 +00:00
|
|
|
if [ "$DEPS_OK" = "false" ]; then
|
2026-02-22 18:25:11 +02:00
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-03-14 02:51:10 +01:00
|
|
|
|
2026-03-14 20:32:59 +00:00
|
|
|
# Wire tracked git hooks (idempotent)
|
|
|
|
|
git config core.hooksPath hooks/ 2>/dev/null || true
|
|
|
|
|
log "Git hooks path set to hooks/"
|
|
|
|
|
|
2026-03-14 08:14:04 +00:00
|
|
|
# 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
|
|
|
|
|
|
2026-03-14 02:51:10 +01:00
|
|
|
if [ "${CLAWDIE_SKIP_ONBOARDING:-0}" != "1" ] \
|
|
|
|
|
&& [ -t 0 ] \
|
|
|
|
|
&& [ -t 1 ]; then
|
2026-03-15 10:10:41 +01:00
|
|
|
log "Launching interactive onboarding"
|
2026-03-14 02:51:10 +01:00
|
|
|
echo
|
2026-03-15 10:10:41 +01:00
|
|
|
echo "Launching interactive onboarding..."
|
2026-03-14 02:51:10 +01:00
|
|
|
npm run wizard
|
|
|
|
|
fi
|