226 lines
7.1 KiB
Bash
Executable File
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 ""
|