#!/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 ""