#!/usr/bin/env bash # ============================================================ # Fester Bootstrap Installer # ============================================================ # Deploys the Fester codebase to a target directory, runs the # first-run config wizard, and writes the systemd unit + cockpit # manifest. Does NOT overwrite the real CLI with a stub. # # Usage: # ./install-fester.sh # interactive # BASE_DIR=/opt/fester ./install-fester.sh # custom target # NONINTERACTIVE=1 ./install-fester.sh # CI mode (defaults) # ============================================================ set -euo pipefail # Resolve the repo root (directory containing this script) REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_DIR="${BASE_DIR:-/usr/share/cockpit/fester}" NONINTERACTIVE="${NONINTERACTIVE:-0}" echo "============================================================" echo " Fester Bootstrap Installer" echo " Source: $REPO_ROOT" echo " Target: $BASE_DIR" echo "============================================================" # ------------------------------------------------------------ # Dependency checks # ------------------------------------------------------------ echo "" echo "🔍 Checking dependencies..." check_dep() { if command -v "$1" >/dev/null 2>&1; then echo " ✓ $1" return 0 else echo " ✗ $1 NOT FOUND" return 1 fi } MISSING=0 check_dep python3 || MISSING=1 # Python packages PYTHON_OK=1 python3 -c "import fastapi" 2>/dev/null && echo " ✓ python: fastapi" || { echo " ✗ python: fastapi missing"; PYTHON_OK=0; } python3 -c "import uvicorn" 2>/dev/null && echo " ✓ python: uvicorn" || { echo " ✗ python: uvicorn missing"; PYTHON_OK=0; } python3 -c "import websockets" 2>/dev/null && echo " ✓ python: websockets" || { echo " ✗ python: websockets missing"; PYTHON_OK=0; } python3 -c "import prometheus_client" 2>/dev/null && echo " ✓ python: prometheus_client" || { echo " ✗ python: prometheus_client missing"; PYTHON_OK=0; } if [ "$MISSING" -eq 1 ]; then echo "" echo "❌ Required system tools missing. Install them first." exit 1 fi if [ "$PYTHON_OK" -eq 0 ]; then echo "" echo "⚠️ Some Python packages missing. Installing..." pip3 install --break-system-packages fastapi uvicorn websockets prometheus_client minio pydantic 2>/dev/null || true fi # ------------------------------------------------------------ # Create directory structure # ------------------------------------------------------------ echo "" echo "📁 Creating directory structure at $BASE_DIR..." sudo mkdir -p "$BASE_DIR" sudo chown -R "$USER":"$USER" "$BASE_DIR" 2>/dev/null || true mkdir -p "$BASE_DIR"/{backend,ui,cockpit/fester-module,cli,docs,config} # ------------------------------------------------------------ # Copy code from repo # ------------------------------------------------------------ echo "📦 Copying code from repo..." # Backend cp -r "$REPO_ROOT/backend/"* "$BASE_DIR/backend/" # UI cp "$REPO_ROOT/style.css" "$BASE_DIR/" cp "$REPO_ROOT/index.html" "$BASE_DIR/" cp -r "$REPO_ROOT/ui/"* "$BASE_DIR/ui/" # Cockpit module cp -r "$REPO_ROOT/cockpit/"* "$BASE_DIR/cockpit/" # CLI (the REAL one, not a stub) cp "$REPO_ROOT/cli/fester.py" "$BASE_DIR/cli/fester.py" chmod +x "$BASE_DIR/cli/fester.py" # Top-level files cp "$REPO_ROOT/manifest.json" "$BASE_DIR/" cp "$REPO_ROOT/project.schema.json" "$BASE_DIR/" cp "$REPO_ROOT/CHEATSHEET.md" "$BASE_DIR/docs/" cp "$REPO_ROOT/README" "$BASE_DIR/LICENSE" 2>/dev/null || true # ------------------------------------------------------------ # Config wizard # ------------------------------------------------------------ echo "" echo "⚙️ Configuration" if [ "$NONINTERACTIVE" = "1" ]; then CLUSTER_NAME="fester-cluster" LIBVIRT="y" LXC="y" DISTCC="y" MINIO="y" else read -rp "Cluster name [fester-cluster]: " CLUSTER_NAME CLUSTER_NAME=${CLUSTER_NAME:-fester-cluster} read -rp "Enable libvirt integration? (y/n) [y]: " LIBVIRT LIBVIRT=${LIBVIRT:-y} read -rp "Enable LXC integration? (y/n) [y]: " LXC LXC=${LXC:-y} read -rp "Enable distcc integration? (y/n) [y]: " DISTCC DISTCC=${DISTCC:-y} read -rp "Enable MinIO cache backend? (y/n) [y]: " MINIO MINIO=${MINIO:-y} fi # Write the canonical config.yaml — aligned with the project.schema.json cat > "$BASE_DIR/config.yaml" </dev/null && sudo chown "$USER":"$USER" /etc/fester 2>/dev/null || true echo '{}' > /etc/fester/node_roles.json 2>/dev/null || true # ------------------------------------------------------------ # Cache dir # ------------------------------------------------------------ mkdir -p /var/lib/fester/cache 2>/dev/null && sudo chown "$USER":"$USER" /var/lib/fester 2>/dev/null || true # ------------------------------------------------------------ # Prometheus scrape config (drop-in) # ------------------------------------------------------------ cat > "$BASE_DIR/add-to-prometheus-config.yml" <<'EOF' scrape_configs: - job_name: 'fester' static_configs: - targets: ['localhost:8080'] metrics_path: /metrics EOF # ------------------------------------------------------------ # Grafana dashboard stub # ------------------------------------------------------------ cat > "$BASE_DIR/add-to-grafana-config.json" <<'EOF' { "dashboard": { "title": "Fester Cluster Overview", "panels": [ { "type": "stat", "title": "Nodes Online", "targets": [{ "expr": "fester_nodes_online" }] }, { "type": "stat", "title": "Active Jobs", "targets": [{ "expr": "fester_cluster_active_jobs" }] }, { "type": "graph", "title": "Avg Heat", "targets": [{ "expr": "fester_cluster_avg_heat" }] } ] } } EOF # ------------------------------------------------------------ # Systemd unit (optional) # ------------------------------------------------------------ if command -v systemctl >/dev/null 2>&1; then cat > /tmp/fester.service <