9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
363 lines
9.4 KiB
Bash
Executable File
363 lines
9.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPORT_FILE="/tmp/ods-preflight-report.json"
|
|
TIER="${TIER:-1}"
|
|
RAM_GB="${RAM_GB:-0}"
|
|
DISK_GB="${DISK_GB:-0}"
|
|
GPU_BACKEND="${GPU_BACKEND:-nvidia}"
|
|
GPU_VRAM_MB="${GPU_VRAM_MB:-0}"
|
|
GPU_NAME="${GPU_NAME:-Unknown}"
|
|
PLATFORM_ID="${PLATFORM_ID:-linux}"
|
|
COMPOSE_OVERLAYS="${COMPOSE_OVERLAYS:-}"
|
|
SCRIPT_DIR="${SCRIPT_DIR:-$(pwd)}"
|
|
STRICT="false"
|
|
ENV_MODE="false"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--report)
|
|
REPORT_FILE="${2:-$REPORT_FILE}"
|
|
shift 2
|
|
;;
|
|
--tier)
|
|
TIER="${2:-$TIER}"
|
|
shift 2
|
|
;;
|
|
--ram-gb)
|
|
RAM_GB="${2:-$RAM_GB}"
|
|
shift 2
|
|
;;
|
|
--disk-gb)
|
|
DISK_GB="${2:-$DISK_GB}"
|
|
shift 2
|
|
;;
|
|
--gpu-backend)
|
|
GPU_BACKEND="${2:-$GPU_BACKEND}"
|
|
shift 2
|
|
;;
|
|
--gpu-vram-mb)
|
|
GPU_VRAM_MB="${2:-$GPU_VRAM_MB}"
|
|
shift 2
|
|
;;
|
|
--gpu-name)
|
|
GPU_NAME="${2:-$GPU_NAME}"
|
|
shift 2
|
|
;;
|
|
--platform-id)
|
|
PLATFORM_ID="${2:-$PLATFORM_ID}"
|
|
shift 2
|
|
;;
|
|
--compose-overlays)
|
|
COMPOSE_OVERLAYS="${2:-$COMPOSE_OVERLAYS}"
|
|
shift 2
|
|
;;
|
|
--script-dir)
|
|
SCRIPT_DIR="${2:-$SCRIPT_DIR}"
|
|
shift 2
|
|
;;
|
|
--strict)
|
|
STRICT="true"
|
|
shift
|
|
;;
|
|
--env)
|
|
ENV_MODE="true"
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
PYTHON_CMD="python3"
|
|
if [[ -f "$ROOT_DIR/lib/python-cmd.sh" ]]; then
|
|
. "$ROOT_DIR/lib/python-cmd.sh"
|
|
PYTHON_CMD="$(ods_detect_python_cmd)"
|
|
elif command -v python >/dev/null 2>&1; then
|
|
PYTHON_CMD="python"
|
|
fi
|
|
|
|
"$PYTHON_CMD" - "$REPORT_FILE" "$TIER" "$RAM_GB" "$DISK_GB" "$GPU_BACKEND" "$GPU_VRAM_MB" "$GPU_NAME" "$PLATFORM_ID" "$COMPOSE_OVERLAYS" "$SCRIPT_DIR" "$ENV_MODE" "$STRICT" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
(
|
|
report_file,
|
|
tier,
|
|
ram_gb,
|
|
disk_gb,
|
|
gpu_backend,
|
|
gpu_vram_mb,
|
|
gpu_name,
|
|
platform_id,
|
|
compose_overlays,
|
|
script_dir,
|
|
env_mode,
|
|
strict_mode,
|
|
) = sys.argv[1:]
|
|
|
|
env_mode = env_mode == "true"
|
|
strict_mode = strict_mode == "true"
|
|
|
|
try:
|
|
ram_gb = int(float(ram_gb))
|
|
except Exception:
|
|
ram_gb = 0
|
|
try:
|
|
disk_gb = int(float(disk_gb))
|
|
except Exception:
|
|
disk_gb = 0
|
|
try:
|
|
gpu_vram_mb = int(float(gpu_vram_mb))
|
|
except Exception:
|
|
gpu_vram_mb = 0
|
|
|
|
tier_key = str(tier).upper()
|
|
tier_rank_map = {
|
|
"0": 0,
|
|
"CLOUD": 0,
|
|
"1": 1,
|
|
"2": 2,
|
|
"3": 3,
|
|
"4": 4,
|
|
"T0": 0,
|
|
"T1": 1,
|
|
"T2": 2,
|
|
"T3": 3,
|
|
"T4": 4,
|
|
"SH_COMPACT": 3,
|
|
"SH_LARGE": 4,
|
|
}
|
|
tier_rank = tier_rank_map.get(tier_key, 1)
|
|
|
|
min_ram_map = {
|
|
"0": 4,
|
|
"CLOUD": 4,
|
|
"T0": 4,
|
|
"1": 16,
|
|
"2": 32,
|
|
"3": 48,
|
|
"4": 64,
|
|
"SH_COMPACT": 64,
|
|
"SH_LARGE": 96,
|
|
}
|
|
min_disk_map = {
|
|
"0": 15,
|
|
# Cloud mode skips local GGUF downloads and local llama-server model
|
|
# storage. It still needs room for core Docker images, configs, logs, and
|
|
# optional services, but it should not inherit the 50GB local-model default.
|
|
"CLOUD": 25,
|
|
"T0": 15,
|
|
"1": 30,
|
|
"2": 50,
|
|
"3": 80,
|
|
"4": 150,
|
|
"SH_COMPACT": 80,
|
|
"SH_LARGE": 120,
|
|
}
|
|
min_ram = min_ram_map.get(tier_key, 16)
|
|
min_disk = min_disk_map.get(tier_key, 50)
|
|
|
|
checks = []
|
|
|
|
def add_check(check_id, status, message, action):
|
|
checks.append(
|
|
{
|
|
"id": check_id,
|
|
"status": status,
|
|
"message": message,
|
|
"action": action,
|
|
}
|
|
)
|
|
|
|
# Platform support check
|
|
if platform_id in {"linux", "wsl"}:
|
|
add_check(
|
|
"platform-support",
|
|
"pass",
|
|
f"Platform '{platform_id}' is currently supported by install-core.sh.",
|
|
"",
|
|
)
|
|
elif platform_id in {"macos", "windows"}:
|
|
add_check(
|
|
"platform-support",
|
|
"warn",
|
|
f"Platform '{platform_id}' is supported via installer MVP path (not full parity yet).",
|
|
"Continue with platform installer and follow generated doctor report recommendations.",
|
|
)
|
|
else:
|
|
add_check(
|
|
"platform-support",
|
|
"blocker",
|
|
f"Platform '{platform_id}' is not yet supported by install-core.sh.",
|
|
"Use Linux/WSL path for now or run platform-specific installer once implemented.",
|
|
)
|
|
|
|
# Compose overlay existence check
|
|
overlays = [o.strip() for o in compose_overlays.split(",") if o.strip()]
|
|
if overlays:
|
|
missing = [o for o in overlays if not (pathlib.Path(script_dir) / o).exists()]
|
|
if missing:
|
|
add_check(
|
|
"compose-overlays",
|
|
"blocker",
|
|
f"Compose overlays are missing: {', '.join(missing)}.",
|
|
"Restore missing compose files or update capability profile overlay mapping.",
|
|
)
|
|
else:
|
|
add_check(
|
|
"compose-overlays",
|
|
"pass",
|
|
f"Compose overlays resolved: {', '.join(overlays)}.",
|
|
"",
|
|
)
|
|
else:
|
|
add_check(
|
|
"compose-overlays",
|
|
"warn",
|
|
"No compose overlays supplied from capability profile.",
|
|
"Ensure CAP_COMPOSE_OVERLAYS is populated; installer will use legacy fallback.",
|
|
)
|
|
|
|
# RAM and disk checks
|
|
if ram_gb >= min_ram:
|
|
add_check(
|
|
"memory",
|
|
"pass",
|
|
f"RAM {ram_gb}GB meets tier {tier_key} recommendation ({min_ram}GB).",
|
|
"",
|
|
)
|
|
else:
|
|
add_check(
|
|
"memory",
|
|
"warn",
|
|
f"RAM {ram_gb}GB is below tier {tier_key} recommendation ({min_ram}GB).",
|
|
f"Use a lower tier or increase memory to at least {min_ram}GB.",
|
|
)
|
|
|
|
if disk_gb >= min_disk:
|
|
add_check(
|
|
"disk",
|
|
"pass",
|
|
f"Disk {disk_gb}GB meets tier {tier_key} recommendation ({min_disk}GB).",
|
|
"",
|
|
)
|
|
else:
|
|
add_check(
|
|
"disk",
|
|
"blocker",
|
|
f"Disk {disk_gb}GB is below required minimum for tier {tier_key} ({min_disk}GB).",
|
|
f"Free at least {min_disk - disk_gb}GB or choose a smaller tier.",
|
|
)
|
|
|
|
# GPU checks
|
|
gpu_backend = (gpu_backend or "").lower()
|
|
if gpu_backend == "amd":
|
|
add_check(
|
|
"gpu-backend",
|
|
"pass",
|
|
f"AMD backend selected ({gpu_name}).",
|
|
"",
|
|
)
|
|
elif gpu_backend == "nvidia":
|
|
if gpu_name.strip().lower() in {"none", ""} or gpu_vram_mb <= 0:
|
|
add_check(
|
|
"gpu-vram",
|
|
"warn",
|
|
"NVIDIA backend selected but no NVIDIA GPU VRAM was detected.",
|
|
"Install/verify NVIDIA drivers or switch to a supported AMD path.",
|
|
)
|
|
elif tier_rank >= 2 and gpu_vram_mb < 10000:
|
|
add_check(
|
|
"gpu-vram",
|
|
"warn",
|
|
f"NVIDIA VRAM {gpu_vram_mb}MB is below recommended floor for tier {tier_key}.",
|
|
"Use tier 1 or a GPU with at least 12GB VRAM for better performance.",
|
|
)
|
|
else:
|
|
add_check(
|
|
"gpu-vram",
|
|
"pass",
|
|
f"NVIDIA backend selected ({gpu_name}, {gpu_vram_mb}MB VRAM).",
|
|
"",
|
|
)
|
|
elif gpu_backend == "apple":
|
|
add_check(
|
|
"gpu-backend",
|
|
"warn",
|
|
"Apple backend selected (experimental path).",
|
|
"Use macOS installer preflight + doctor and run reduced profile set until Tier A parity is complete.",
|
|
)
|
|
elif gpu_backend == "cpu":
|
|
if platform_id in {"windows", "macos"}:
|
|
add_check(
|
|
"gpu-backend",
|
|
"warn",
|
|
"CPU fallback selected on non-Linux platform.",
|
|
"Use reduced model/profile defaults; expect slower inference.",
|
|
)
|
|
else:
|
|
add_check(
|
|
"gpu-backend",
|
|
"warn",
|
|
"CPU fallback selected.",
|
|
"Install/verify GPU drivers for best performance or continue with small models.",
|
|
)
|
|
else:
|
|
add_check(
|
|
"gpu-backend",
|
|
"warn",
|
|
f"Unknown backend '{gpu_backend}'.",
|
|
"Verify capability profile and hardware detection output.",
|
|
)
|
|
|
|
blockers = [c for c in checks if c["status"] == "blocker"]
|
|
warnings = [c for c in checks if c["status"] == "warn"]
|
|
|
|
report = {
|
|
"version": "1",
|
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
"inputs": {
|
|
"tier": tier_key,
|
|
"ram_gb": ram_gb,
|
|
"disk_gb": disk_gb,
|
|
"gpu_backend": gpu_backend,
|
|
"gpu_vram_mb": gpu_vram_mb,
|
|
"gpu_name": gpu_name,
|
|
"platform_id": platform_id,
|
|
"compose_overlays": overlays,
|
|
"script_dir": script_dir,
|
|
},
|
|
"summary": {
|
|
"checks": len(checks),
|
|
"blockers": len(blockers),
|
|
"warnings": len(warnings),
|
|
"can_proceed": len(blockers) == 0,
|
|
},
|
|
"checks": checks,
|
|
}
|
|
|
|
report_path = pathlib.Path(report_file)
|
|
report_path.parent.mkdir(parents=True, exist_ok=True)
|
|
report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
|
|
|
if env_mode:
|
|
def out(key, value):
|
|
safe = str(value).replace("\\", "\\\\").replace('"', '\\"')
|
|
print(f'{key}="{safe}"')
|
|
|
|
out("PREFLIGHT_REPORT_FILE", str(report_path))
|
|
out("PREFLIGHT_CHECK_COUNT", report["summary"]["checks"])
|
|
out("PREFLIGHT_BLOCKERS", report["summary"]["blockers"])
|
|
out("PREFLIGHT_WARNINGS", report["summary"]["warnings"])
|
|
out("PREFLIGHT_CAN_PROCEED", str(report["summary"]["can_proceed"]).lower())
|
|
|
|
if strict_mode and blockers:
|
|
raise SystemExit(1)
|
|
PY
|