fester/install-fester.sh

251 lines
7.8 KiB
Bash
Executable File

#!/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" <<EOF
cluster:
name: $CLUSTER_NAME
master:
name: fester-master
role: control
nodes:
- name: node-1
host: 192.168.1.10
max_jobs: 8
- name: node-2
host: 192.168.1.11
max_jobs: 8
integrations:
libvirt: $LIBVIRT
lxc: $LXC
distcc: $DISTCC
cache:
backend: $([ "$MINIO" = "y" ] && echo "minio" || echo "local")
endpoint: localhost:9000
observability:
prometheus: true
grafana: true
scheduler:
mode: weighted-thermal-aware
projects: []
EOF
echo " ✓ Wrote $BASE_DIR/config.yaml"
# ------------------------------------------------------------
# Role DB location
# ------------------------------------------------------------
mkdir -p /etc/fester 2>/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 <<EOF
[Unit]
Description=Fester Distributed Build System
After=network.target
[Service]
Type=simple
WorkingDirectory=$BASE_DIR
ExecStart=$(which python3) -m uvicorn backend.main:app --host 0.0.0.0 --port 8080
Restart=on-failure
User=$USER
Environment=PYTHONPATH=$BASE_DIR
[Install]
WantedBy=multi-user.target
EOF
echo ""
echo "📋 Systemd unit written to /tmp/fester.service"
echo " Install with: sudo mv /tmp/fester.service /etc/systemd/system/ && sudo systemctl enable --now fester"
fi
# ------------------------------------------------------------
# Done
# ------------------------------------------------------------
echo ""
echo "============================================================"
echo " ✅ Fester installed at $BASE_DIR"
echo "============================================================"
echo ""
echo "Next steps:"
echo " 1. Start the server:"
echo " cd $BASE_DIR && python3 -m uvicorn backend.main:app --host 0.0.0.0 --port 8080"
echo ""
echo " 2. Open the UI:"
echo " http://localhost:8080/"
echo ""
echo " 3. Use the CLI:"
echo " $BASE_DIR/cli/fester.py status"
echo ""
echo " 4. (Optional) Install systemd unit (see above)"
echo ""