fester/bootstrap.sh

226 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================
# Fester Bootstrap Installer
# ============================================================
# Creates a Python venv, installs all dependencies, sets up the
# config file, and initializes the SQLite database.
#
# Usage:
# ./bootstrap.sh # interactive
# ./bootstrap.sh --dev # dev mode (editable install)
# NONINTERACTIVE=1 ./bootstrap.sh # CI mode (defaults only)
# ============================================================
set -euo pipefail
# Resolve repo root
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO_ROOT"
NONINTERACTIVE="${NONINTERACTIVE:-0}"
DEV_MODE=0
if [[ "${1:-}" == "--dev" ]]; then
DEV_MODE=1
fi
echo "============================================================"
echo " 🧠 Fester Bootstrap Installer"
echo " Source: $REPO_ROOT"
echo " Mode: $([ "$DEV_MODE" = "1" ] && echo "dev (editable)" || echo "standard")"
echo " Non-interactive: $NONINTERACTIVE"
echo "============================================================"
echo ""
# ------------------------------------------------------------
# 1. Python version check
# ------------------------------------------------------------
echo "🔍 Checking Python..."
if ! command -v python3 >/dev/null 2>&1; then
echo " ✗ python3 not found"
echo " Install Python 3.12+ from https://www.python.org/downloads/"
exit 1
fi
PY_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
PY_MAJOR=$(echo "$PY_VERSION" | cut -d. -f1)
PY_MINOR=$(echo "$PY_VERSION" | cut -d. -f2)
if [ "$PY_MAJOR" -lt 3 ] || ([ "$PY_MAJOR" -eq 3 ] && [ "$PY_MINOR" -lt 12 ]); then
echo " ✗ Python $PY_VERSION found, but 3.12+ required"
exit 1
fi
echo " ✓ Python $PY_VERSION"
# ------------------------------------------------------------
# 2. Create virtualenv
# ------------------------------------------------------------
echo ""
echo "📦 Setting up virtualenv..."
VENV_DIR="$REPO_ROOT/.venv"
if [ ! -d "$VENV_DIR" ]; then
python3 -m venv "$VENV_DIR"
echo " ✓ Created $VENV_DIR"
else
echo " ✓ Using existing $VENV_DIR"
fi
# Activate
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
# Upgrade pip
python3 -m pip install --upgrade pip --quiet
echo " ✓ pip upgraded"
# ------------------------------------------------------------
# 3. Install Python dependencies
# ------------------------------------------------------------
echo ""
echo "📚 Installing Python dependencies..."
# Core deps (always installed)
CORE_DEPS=(
"fastapi>=0.128"
"uvicorn[standard]>=0.30"
"websockets>=12"
"pydantic>=2"
"aiohttp>=3.9"
"minio>=7.2"
"prometheus-client>=0.20"
"PyYAML>=6"
"requests>=2.31"
"websocket-client>=1.7"
)
for dep in "${CORE_DEPS[@]}"; do
echo " → installing $dep"
pip install "$dep" --quiet 2>&1 | tail -1 || \
echo " ⚠ warning: $dep install had issues (may already be installed)"
done
if [ "$DEV_MODE" = "1" ]; then
echo " → installing dev tools (ruff, mypy, pytest)..."
pip install ruff mypy pytest pytest-asyncio --quiet 2>&1 | tail -1 || true
fi
echo " ✓ All Python dependencies installed"
# ------------------------------------------------------------
# 4. Check optional system tools
# ------------------------------------------------------------
echo ""
echo "🔧 Checking optional system tools..."
check_tool() {
local tool=$1
local purpose=$2
if command -v "$tool" >/dev/null 2>&1; then
echo "$tool$purpose"
else
echo "$tool$purpose (optional, will be missed)"
fi
}
# Disable set -e for the optional tool checks (we want to continue even if missing)
set +e
check_tool tmux "live action output viewer"
check_tool mosh "shell-into-node button"
check_tool ssh "shell-into-node fallback"
check_tool qemu-img "qcow2 workspace snapshots"
check_tool qemu-nbd "qcow2 mount for freeze"
check_tool rsync "workspace sync for freeze"
check_tool cp "reflink copies (usually present)"
set -e
# ------------------------------------------------------------
# 5. Set up config
# ------------------------------------------------------------
echo ""
echo "⚙️ Setting up configuration..."
if [ ! -f "$REPO_ROOT/config.yaml" ]; then
cat > "$REPO_ROOT/config.yaml" <<'EOF'
master:
name: fester-master
role: control
nodes:
- name: localhost
host: 127.0.0.1
max_jobs: 4
projects:
- name: example
repo: local
targets:
default: "echo hello"
EOF
echo " ✓ Created default config.yaml (edit to add your cluster nodes)"
else
echo " ✓ config.yaml already exists"
fi
# Storage config
STORAGE_DIR="${FESTER_STORAGE_DIR:-$HOME/.fester}"
mkdir -p "$STORAGE_DIR" 2>/dev/null || true
mkdir -p "$STORAGE_DIR/cas" 2>/dev/null || true
mkdir -p "$STORAGE_DIR/snapshots" 2>/dev/null || true
mkdir -p "$STORAGE_DIR/cache" 2>/dev/null || true
echo " ✓ Storage dirs ready at $STORAGE_DIR"
# ------------------------------------------------------------
# 6. Initialize the database
# ------------------------------------------------------------
echo ""
echo "🗄️ Initializing SQLite database..."
DB_PATH="${FESTER_DB_PATH:-$STORAGE_DIR/fester.db}"
FESTER_DB_PATH="$DB_PATH" python3 -c "
import sys
sys.path.insert(0, '$REPO_ROOT')
from backend.storage.sqlite_db import Storage
s = Storage()
print(f' ✓ Database ready at {s.path}')
print(f' ✓ Tables: {len(s._conn.execute(\"SELECT name FROM sqlite_master WHERE type=\\\"table\\\"\").fetchall())}')
" 2>&1 || echo " ⚠ Database init had an issue (will be created on first run)"
# ------------------------------------------------------------
# 7. Make scripts executable
# ------------------------------------------------------------
echo ""
echo "📋 Making scripts executable..."
chmod +x "$REPO_ROOT/run.sh" 2>/dev/null || true
chmod +x "$REPO_ROOT/cli/fester.py" 2>/dev/null || true
echo " ✓ run.sh and cli/fester.py are executable"
# ------------------------------------------------------------
# 8. Print summary
# ------------------------------------------------------------
echo ""
echo "============================================================"
echo " ✅ Bootstrap complete!"
echo "============================================================"
echo ""
echo "Next steps:"
echo ""
echo " 1. Edit config.yaml to add your cluster nodes:"
echo " \$EDITOR $REPO_ROOT/config.yaml"
echo ""
echo " 2. Start the backend:"
echo " \$ ./run.sh"
echo ""
echo " 3. Open the UI:"
echo " http://localhost:8080"
echo ""
echo " 4. Use the CLI (with venv activated):"
echo " \$ source .venv/bin/activate"
echo " \$ fester health"
echo " \$ fester build --cmd 'echo hello' --dir /tmp --watch"
echo ""
echo " 5. Read the quickstart:"
echo " \$ less quickstart.md"
echo ""
echo "Environment variables you can set:"
echo " FESTER_CONFIG — path to YAML config (default: config.yaml)"
echo " FESTER_DB_PATH — SQLite database path (default: $STORAGE_DIR/fester.db)"
echo " FESTER_NO_DRIFT=1 — disable synthetic drift when agents unreachable"
echo " FESTER_AUTOBUILD=1 — auto-trigger a build every 60s for demo"
echo ""