ai-lsc/bootstrap.sh

348 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
# ──────────────────────────────────────────────────────────────
# AI Local Stack Control v3.0 — Ankh of Jah
# Bootstrap Script
#
# Fully portable: works wherever the tarball lands.
# Resolves the base directory from cwd or env var.
# Creates venv in-project. On Arch, uses pacman's
# pre-built PySide6 to avoid pip compile hell.
# ──────────────────────────────────────────────────────────────
set -euo pipefail
BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
# ── Resolve paths (current-path aware) ────────────────────────
# SCRIPT_DIR = wherever bootstrap.sh lives (the project root)
# AI_BASE = the managed working directory for all AI tools
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="${SCRIPT_DIR}/.venv"
VENV_PYTHON_STAMP="${VENV_DIR}/.python-version-stamp"
# Base directory: env var > parent of SCRIPT_DIR if named "tools" > /mnt/AI
# This lets you extract to /mnt/AI/tools/ai_lsc-v3/ and have it detect
# /mnt/AI as the base. If extracted elsewhere, defaults to /mnt/AI or
# whatever AI_LSC_BASE_DIR says.
_PARENT_DIR="$(dirname "$SCRIPT_DIR")"
_PARENT_NAME="$(basename "$_PARENT_DIR")"
if [ -n "${AI_LSC_BASE_DIR:-}" ]; then
AI_BASE="$AI_LSC_BASE_DIR"
elif [ "$_PARENT_NAME" = "tools" ] && [ -d "$(dirname "$_PARENT_DIR")/models" ]; then
# We're inside .../tools/ai_lsc-v3/ — base is the parent of tools/
AI_BASE="$(dirname "$_PARENT_DIR")"
else
AI_BASE="${AI_LSC_BASE_DIR:-/mnt/AI}"
fi
export AI_LSC_BASE_DIR="$AI_BASE"
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ AI Local Stack Control v3.0 — Ankh of Jah ║${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${CYAN} Project root : ${SCRIPT_DIR}${NC}"
echo -e "${CYAN} Base dir : ${AI_BASE}${NC}"
echo -e "${CYAN} Venv : ${VENV_DIR}${NC}"
echo ""
# ── Clean stale ~/.local/bin/ai-lsc from old pip/pipx installs ──
STALE_BIN="${HOME}/.local/bin/ai-lsc"
if [ -f "$STALE_BIN" ]; then
warn "Found stale entry-point: ${STALE_BIN}"
warn "Removing — everything runs via the project venv now"
rm -f "$STALE_BIN"
fi
if command -v pipx &>/dev/null && pipx list 2>/dev/null | grep -q "ai-lsc"; then
warn "Found pipx-installed ai-lsc — uninstalling"
pipx uninstall ai-lsc 2>/dev/null || true
fi
# ── Ensure AI_BASE exists ─────────────────────────────────────
if [ ! -d "$AI_BASE" ]; then
if [ "$(id -u)" -eq 0 ]; then
mkdir -p "$AI_BASE"
info "Created ${AI_BASE} (running as root)"
else
echo ""
echo -e "${YELLOW}${AI_BASE} does not exist.${NC}"
echo " This is the managed working directory for all AI tools."
echo ""
read -p "Create ${AI_BASE} now? [Y/n] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
if command -v sudo &>/dev/null; then
sudo mkdir -p "$AI_BASE"
sudo chown "$(id -u):$(id -g)" "$AI_BASE"
info "Created ${AI_BASE}"
else
error "Need sudo/root to create ${AI_BASE}. Create it manually and re-run."
fi
else
echo ""
read -p "Alternative base directory? [${SCRIPT_DIR}/ai-stack] " ALT_BASE
ALT_BASE="${ALT_BASE:-${SCRIPT_DIR}/ai-stack}"
mkdir -p "$ALT_BASE"
warn "Using ${ALT_BASE}"
AI_BASE="$ALT_BASE"
export AI_LSC_BASE_DIR="$AI_BASE"
fi
fi
fi
# ── Helper: get system Python version string ──────────────────
_sys_python_version() {
python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"
}
# ── Helper: check if venv is stale (Python version mismatch) ──
_venv_is_stale() {
if [ ! -f "$VENV_PYTHON_STAMP" ]; then
return 0 # no stamp → treat as stale
fi
local stamp_version
stamp_version="$(cat "$VENV_PYTHON_STAMP")"
local sys_version
sys_version="$(_sys_python_version)"
if [ "$stamp_version" != "$sys_version" ]; then
return 0 # version mismatch → stale
fi
return 1 # versions match → fresh
}
# ── Detect OS ──────────────────────────────────────────────────
if command -v pacman &>/dev/null; then
PKG_MANAGER="pacman"
info "Detected Arch Linux (pacman)"
elif command -v apt-get &>/dev/null; then
PKG_MANAGER="apt"
warn "Detected Debian/Ubuntu — some packages may differ from Arch names"
elif command -v dnf &>/dev/null; then
PKG_MANAGER="dnf"
warn "Detected Fedora/RHEL — some packages may differ from Arch names"
else
warn "Unknown package manager. You may need to install dependencies manually."
PKG_MANAGER="manual"
fi
# ── System Dependencies ───────────────────────────────────────
echo ""
info "Installing system dependencies..."
if [ "$PKG_MANAGER" = "pacman" ]; then
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
if command -v sudo &>/dev/null; then
SUDO="sudo"
fi
fi
# Core system packages (python-pyside6 is NOT in official repos —
# we attempt it, then fall back to pip inside the venv below)
$SUDO pacman -Sy --noconfirm --needed \
python \
python-pip \
git \
tmux \
ripgrep \
fd \
tree-sitter \
sqlite \
redis \
base-devel \
|| warn "Some system packages failed to install (non-critical)"
# Try python-pyside6 from pacman if available (AUR / community)
if $SUDO pacman -Si python-pyside6 &>/dev/null; then
$SUDO pacman -Sy --noconfirm --needed python-pyside6 \
|| warn "python-pyside6 pacman install failed (will try pip fallback)"
else
warn "python-pyside6 not available in repos — will install via pip"
fi
info "Core system packages installed."
echo ""
read -p "Install NVIDIA CUDA support? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
$SUDO pacman -Sy --noconfirm --needed cuda || warn "CUDA install failed"
info "CUDA toolkit installed."
fi
read -p "Install container runtimes (podman, docker)? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
$SUDO pacman -Sy --noconfirm --needed podman docker || warn "Container runtimes install failed"
info "Container runtimes installed."
fi
read -p "Install LXC support? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
$SUDO pacman -Sy --noconfirm --needed lxc lxcfs || warn "LXC install failed"
info "LXC support installed."
fi
elif [ "$PKG_MANAGER" = "apt" ]; then
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv python3-pyside6.qt6 \
git tmux ripgrep fd-find sqlite3 redis-server \
build-essential \
|| warn "Some system packages failed to install"
info "Core system packages installed."
elif [ "$PKG_MANAGER" = "dnf" ]; then
sudo dnf install -y \
python3 python3-pip git tmux ripgrep fd-find sqlite redis \
|| warn "Some system packages failed to install"
info "Core system packages installed."
fi
# ── Python Virtual Environment ─────────────────────────────────
echo ""
_create_venv() {
if [ "$PKG_MANAGER" = "pacman" ]; then
# Arch: --system-site-packages so venv sees pacman-installed PySide6
python3 -m venv --system-site-packages "$VENV_DIR"
else
python3 -m venv "$VENV_DIR"
fi
_sys_python_version > "$VENV_PYTHON_STAMP"
}
if [ ! -d "$VENV_DIR" ]; then
info "Creating Python virtual environment at ${VENV_DIR}..."
_create_venv
else
if _venv_is_stale; then
old_ver=""
if [ -f "$VENV_PYTHON_STAMP" ]; then
old_ver="$(cat "$VENV_PYTHON_STAMP")"
fi
warn "Virtual environment is stale (was Python ${old_ver}, system is now $(_sys_python_version))"
warn "Removing old venv and recreating..."
rm -rf "$VENV_DIR"
_create_venv
info "Virtual environment recreated with Python $(_sys_python_version)"
else
info "Virtual environment already exists and is up-to-date at ${VENV_DIR}"
fi
fi
info "Activating virtual environment..."
source "$VENV_DIR/bin/activate"
# ── Verify venv activation (critical on Arch) ───────────────────
if [ ! -f "${VENV_DIR}/bin/pip" ]; then
error "Virtual environment pip not found at ${VENV_DIR}/bin/pip — venv may be broken. Delete ${VENV_DIR} and re-run."
fi
VENV_PIP="${VENV_DIR}/bin/pip"
# ── Python Dependencies (inside venv — safe from EXTERNALLY-MANAGED) ──
echo ""
info "Installing Python dependencies into venv..."
$VENV_PIP install --upgrade pip setuptools wheel --quiet
# PySide6 — on Arch this comes from pacman (already installed above).
if [ "$PKG_MANAGER" = "pacman" ]; then
if "${VENV_DIR}/bin/python3" -c "import PySide6" 2>/dev/null; then
info "PySide6 (system) — OK"
else
warn "PySide6 (system) not visible in venv — attempting pip install..."
$VENV_PIP install PySide6 --quiet 2>/dev/null || {
warn "PySide6 installation failed. The app will run in headless mode."
}
fi
else
$VENV_PIP install PySide6 --quiet 2>/dev/null || {
warn "PySide6 installation failed. The app will run in headless mode."
}
fi
# uv — fast Python package manager
$VENV_PIP install uv --quiet 2>/dev/null || warn "uv installation failed (non-critical)"
# ── Verify Installation ──────────────────────────────────────
echo ""
info "Verifying installation..."
ERRORS=0
PY_VER="${VENV_DIR}/bin/python3"
PY_VER_STR=$("$PY_VER" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
PY_MAJOR=$("$PY_VER" -c "import sys; print(sys.version_info.major)")
PY_MINOR=$("$PY_VER" -c "import sys; print(sys.version_info.minor)")
if [ "$PY_MAJOR" -ge 3 ] && [ "$PY_MINOR" -ge 11 ]; then
info "Python ${PY_VER_STR} (venv) — OK"
else
warn "Python ${PY_VER_STR} (venv) — recommend 3.11+"
fi
if "$PY_VER" -c "import PySide6" 2>/dev/null; then
info "PySide6 — OK"
else
warn "PySide6 — NOT FOUND (UI will be unavailable)"
ERRORS=$((ERRORS + 1))
fi
# Check registry loads (pass AI_LSC_BASE_DIR so it resolves correctly)
cd "$SCRIPT_DIR"
if AI_LSC_BASE_DIR="$AI_BASE" "$PY_VER" -c "
import sys
sys.path.insert(0, 'src')
from ai_lsc.registry.loader import load_merged_registry
reg = load_merged_registry()
print(f' Registry: {len(reg)} tools loaded')
" 2>/dev/null; then
info "Registry — OK"
else
warn "Registry — could not load (check file structure)"
ERRORS=$((ERRORS + 1))
fi
for cmd in ollama podman docker tmux ripgrep fd tree-sitter; do
if command -v "$cmd" &>/dev/null; then
info "${cmd} — found"
else
warn "${cmd} — not found (optional)"
fi
done
# ── Summary ───────────────────────────────────────────────────
echo ""
if [ "$ERRORS" -eq 0 ]; then
echo -e "${GREEN}${BOLD}Bootstrap complete! AI Local Stack Control is ready.${NC}"
echo ""
echo -e " ${CYAN}Base dir: ${AI_BASE}${NC}"
echo ""
echo " Launch the application:"
echo " cd ${SCRIPT_DIR}"
echo " python ai_lsc.py"
echo ""
echo " Or use the convenience launcher:"
echo " bash run.sh"
echo ""
else
echo -e "${YELLOW}Bootstrap complete with ${ERRORS} warning(s).${NC}"
echo "The application may run in limited mode. Review warnings above."
fi
# ── Write env file so run.sh and ai_lsc.py can pick it up ────
cat > "${SCRIPT_DIR}/.env" <<ENVEOF
AI_LSC_BASE_DIR=${AI_BASE}
ENVEOF