Update ISO installer tiers and backend deployment
This commit is contained in:
1 parent
79784a6743
commit
56668f7bdc
38 files changed
+7206
-175
No files matched your search
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env bash
|
||||
# Nexus One AI feasibility checker.
|
||||
# Runs before installation to classify existing hardware for software-only or appliance deployments.
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:-/opt/cezen/feasibility.json}"
|
||||
mkdir -p "$(dirname "$OUT")" 2>/dev/null || true
|
||||
|
||||
tmp_json="$(mktemp /tmp/cezen-feasibility.XXXXXX.json)"
|
||||
|
||||
python3 - "$tmp_json" <<'PY'
|
||||
import json, os, platform, shutil, socket, subprocess, sys
|
||||
from pathlib import Path
|
||||
|
||||
out = Path(sys.argv[1])
|
||||
|
||||
def run(cmd, timeout=5):
|
||||
try:
|
||||
return subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True, timeout=timeout).strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def read(path, default=""):
|
||||
try:
|
||||
return Path(path).read_text(errors="replace").strip()
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def gb(n):
|
||||
return round(n / (1024 ** 3), 1)
|
||||
|
||||
def os_info():
|
||||
data = {}
|
||||
for line in read("/etc/os-release").splitlines():
|
||||
if "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
data[k] = v.strip('"')
|
||||
return {
|
||||
"name": data.get("PRETTY_NAME") or platform.platform(),
|
||||
"id": data.get("ID", ""),
|
||||
"version_id": data.get("VERSION_ID", ""),
|
||||
"kernel": platform.release(),
|
||||
}
|
||||
|
||||
def cpu_info():
|
||||
model = ""
|
||||
for line in read("/proc/cpuinfo").splitlines():
|
||||
if line.lower().startswith("model name"):
|
||||
model = line.split(":", 1)[1].strip()
|
||||
break
|
||||
return {"model": model or platform.processor(), "cores": os.cpu_count() or 0}
|
||||
|
||||
def mem_gb():
|
||||
for line in read("/proc/meminfo").splitlines():
|
||||
if line.startswith("MemTotal:"):
|
||||
return round(int(line.split()[1]) / 1024 / 1024, 1)
|
||||
out = run(["sysctl", "-n", "hw.memsize"])
|
||||
if out.isdigit():
|
||||
return gb(int(out))
|
||||
try:
|
||||
pages = os.sysconf("SC_PHYS_PAGES")
|
||||
page_size = os.sysconf("SC_PAGE_SIZE")
|
||||
if pages and page_size:
|
||||
return gb(int(pages) * int(page_size))
|
||||
except Exception:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def disk_info():
|
||||
usage = shutil.disk_usage("/")
|
||||
return {"total_gb": gb(usage.total), "free_gb": gb(usage.free), "used_gb": gb(usage.used)}
|
||||
|
||||
def gpu_info():
|
||||
gpus = []
|
||||
if shutil.which("nvidia-smi"):
|
||||
q = "name,memory.total,driver_version"
|
||||
out = run(["nvidia-smi", f"--query-gpu={q}", "--format=csv,noheader,nounits"])
|
||||
for row in out.splitlines():
|
||||
parts = [p.strip() for p in row.split(",")]
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
vram = round(float(parts[1]) / 1024, 1)
|
||||
except Exception:
|
||||
vram = 0
|
||||
gpus.append({"name": parts[0], "vram_gb": vram, "driver": parts[2] if len(parts) > 2 else ""})
|
||||
if not gpus:
|
||||
for vendor in Path("/sys/bus/pci/devices").glob("*/vendor"):
|
||||
if read(vendor).lower() == "0x10de":
|
||||
gpus.append({"name": "NVIDIA GPU detected (driver not ready)", "vram_gb": 0, "driver": ""})
|
||||
break
|
||||
return gpus
|
||||
|
||||
def port_open(host, port):
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=1):
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def tool_state():
|
||||
return {
|
||||
"docker": bool(shutil.which("docker")),
|
||||
"docker_running": bool(run(["docker", "info"], timeout=3)) if shutil.which("docker") else False,
|
||||
"ansible": bool(shutil.which("ansible-playbook")),
|
||||
"python3": bool(shutil.which("python3")),
|
||||
"curl": bool(shutil.which("curl")),
|
||||
"git": bool(shutil.which("git")),
|
||||
}
|
||||
|
||||
def recommend(mem, disk, gpus, cpu_cores):
|
||||
max_vram = max([g.get("vram_gb", 0) for g in gpus] or [0])
|
||||
gpu_count = len([g for g in gpus if g.get("vram_gb", 0) > 0])
|
||||
profile = "core"
|
||||
tier = "starter"
|
||||
concurrency = "1-3"
|
||||
notes = []
|
||||
|
||||
if max_vram >= 120 and gpu_count >= 4:
|
||||
tier, profile, concurrency = "max", "gpu-max", "100+"
|
||||
elif max_vram >= 80 and gpu_count >= 2:
|
||||
tier, profile, concurrency = "pro", "gpu-pro", "20-100"
|
||||
elif max_vram >= 48:
|
||||
tier, profile, concurrency = "basic", "gpu-standard", "5-20"
|
||||
elif max_vram >= 24:
|
||||
tier, profile, concurrency = "starter", "gpu-starter", "1-10"
|
||||
elif max_vram >= 8:
|
||||
tier, profile, concurrency = "starter", "gpu-lite", "1-5"
|
||||
notes.append("GPU is suitable for small quantized models only.")
|
||||
elif mem >= 32 and cpu_cores >= 8:
|
||||
tier, profile, concurrency = "starter", "cpu-ai", "1-3"
|
||||
notes.append("No usable NVIDIA VRAM found; local CPU inference is limited. Use cloud/external model fallback for better UX.")
|
||||
else:
|
||||
tier, profile, concurrency = "starter", "core", "1-2"
|
||||
notes.append("Hardware is best for portal, RAG management, workflows, and external/cloud model routing.")
|
||||
|
||||
if mem < 16:
|
||||
notes.append("RAM below 16 GB; avoid local model serving.")
|
||||
if disk < 100:
|
||||
notes.append("Less than 100 GB free disk; model storage and document indexing will be constrained.")
|
||||
|
||||
return {
|
||||
"recommended_tier": tier,
|
||||
"recommended_profile": profile,
|
||||
"estimated_concurrent_users": concurrency,
|
||||
"notes": notes,
|
||||
}
|
||||
|
||||
mem = mem_gb()
|
||||
disk = disk_info()
|
||||
gpus = gpu_info()
|
||||
cpu = cpu_info()
|
||||
tools = tool_state()
|
||||
rec = recommend(mem, disk["free_gb"], gpus, cpu["cores"])
|
||||
|
||||
features = {
|
||||
"portal": True,
|
||||
"users_auth": True,
|
||||
"document_intelligence": mem >= 8,
|
||||
"rag_chromadb": mem >= 16 and disk["free_gb"] >= 50,
|
||||
"ollama_cpu": mem >= 32,
|
||||
"ollama_gpu": any(g.get("vram_gb", 0) >= 8 for g in gpus),
|
||||
"vllm": any(g.get("vram_gb", 0) >= 24 for g in gpus),
|
||||
"fine_tuning_qlora": any(g.get("vram_gb", 0) >= 24 for g in gpus),
|
||||
"distributed_training": len([g for g in gpus if g.get("vram_gb", 0) >= 48]) >= 2,
|
||||
"monitoring": True,
|
||||
"software_only": True,
|
||||
"air_gapped_ready": True,
|
||||
}
|
||||
|
||||
report = {
|
||||
"schema": "cezen.feasibility.v1",
|
||||
"generated_at": run(["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"]) or "",
|
||||
"host": socket.gethostname(),
|
||||
"os": os_info(),
|
||||
"cpu": cpu,
|
||||
"ram_gb": mem,
|
||||
"disk": disk,
|
||||
"gpus": gpus,
|
||||
"tools": tools,
|
||||
"ports_in_use": {str(p): port_open("127.0.0.1", p) for p in [80, 8080, 11434, 8000, 3000, 8888]},
|
||||
"recommendation": rec,
|
||||
"features": features,
|
||||
}
|
||||
out.write_text(json.dumps(report, indent=2))
|
||||
PY
|
||||
|
||||
if ! cp "$tmp_json" "$OUT" 2>/dev/null; then
|
||||
OUT="./feasibility.json"
|
||||
cp "$tmp_json" "$OUT"
|
||||
fi
|
||||
rm -f "$tmp_json"
|
||||
|
||||
python3 - "$OUT" <<'PY'
|
||||
import json, sys
|
||||
p = sys.argv[1]
|
||||
d = json.load(open(p))
|
||||
r = d["recommendation"]
|
||||
print("")
|
||||
print("Nexus One AI Feasibility Report")
|
||||
print("--------------------------------")
|
||||
print(f"Host: {d['host']}")
|
||||
print(f"OS: {d['os']['name']}")
|
||||
print(f"CPU: {d['cpu']['cores']} cores | RAM: {d['ram_gb']} GB | Free disk: {d['disk']['free_gb']} GB")
|
||||
if d["gpus"]:
|
||||
print("GPU: " + "; ".join(f"{g['name']} ({g.get('vram_gb', 0)} GB VRAM)" for g in d["gpus"]))
|
||||
else:
|
||||
print("GPU: none detected")
|
||||
print("")
|
||||
print(f"Recommended tier: {r['recommended_tier'].upper()}")
|
||||
print(f"Recommended profile: {r['recommended_profile']}")
|
||||
print(f"Estimated concurrency: {r['estimated_concurrent_users']} users")
|
||||
if r["notes"]:
|
||||
print("Notes:")
|
||||
for n in r["notes"]:
|
||||
print(f" - {n}")
|
||||
print("")
|
||||
print(f"JSON report: {p}")
|
||||
PY
|
||||
Reference in new issue
Block a user