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
199 lines
7.2 KiB
Bash
Executable File
199 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
OUTPUT_FILE=""
|
|
ENV_MODE="false"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--output)
|
|
OUTPUT_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--env)
|
|
ENV_MODE="true"
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$OUTPUT_FILE" ]]; then
|
|
OUTPUT_FILE="${ROOT_DIR}/.capabilities.json"
|
|
fi
|
|
|
|
if [[ ! -x "${SCRIPT_DIR}/detect-hardware.sh" ]]; then
|
|
echo "detect-hardware.sh not found or not executable" >&2
|
|
exit 1
|
|
fi
|
|
|
|
[[ -f "$ROOT_DIR/lib/safe-env.sh" ]] && . "$ROOT_DIR/lib/safe-env.sh"
|
|
|
|
HARDWARE_JSON="$("${SCRIPT_DIR}/detect-hardware.sh" --json)"
|
|
|
|
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
|
|
|
|
CLASS_ENV="$("${SCRIPT_DIR}/classify-hardware.sh" \
|
|
--platform-id "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('os','unknown'))" "$HARDWARE_JSON")" \
|
|
--gpu-vendor "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('gpu',{}).get('type','unknown'))" "$HARDWARE_JSON")" \
|
|
--memory-type "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('gpu',{}).get('memory_type','unknown'))" "$HARDWARE_JSON")" \
|
|
--vram-mb "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('gpu',{}).get('vram_mb',0))" "$HARDWARE_JSON")" \
|
|
--device-id "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('gpu',{}).get('device_id',''))" "$HARDWARE_JSON")" \
|
|
--gpu-name "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('gpu',{}).get('name',''))" "$HARDWARE_JSON")" \
|
|
--cpu-name "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('cpu',''))" "$HARDWARE_JSON")" \
|
|
--ram-mb "$("$PYTHON_CMD" -c "import json,sys; print(json.loads(sys.argv[1]).get('ram_gb',0) * 1024)" "$HARDWARE_JSON")" \
|
|
--env)"
|
|
load_env_from_output <<< "$CLASS_ENV"
|
|
|
|
# Source service registry for LLM port
|
|
if [[ -f "$ROOT_DIR/lib/service-registry.sh" ]]; then
|
|
export SCRIPT_DIR="$ROOT_DIR"
|
|
. "$ROOT_DIR/lib/service-registry.sh"
|
|
sr_load
|
|
[[ -f "$ROOT_DIR/lib/safe-env.sh" ]] && . "$ROOT_DIR/lib/safe-env.sh"
|
|
[[ -f "$ROOT_DIR/.env" ]] && load_env_file "$ROOT_DIR/.env" && sr_resolve_ports
|
|
fi
|
|
_LLM_PORT="${SERVICE_PORTS[llama-server]:-11434}"
|
|
_LLM_HEALTH="${SERVICE_HEALTH[llama-server]:-/health}"
|
|
|
|
"$PYTHON_CMD" - "$HARDWARE_JSON" "$OUTPUT_FILE" "$ENV_MODE" "${HW_CLASS_ID:-unknown}" "${HW_CLASS_LABEL:-Unknown}" "${HW_REC_BACKEND:-cpu}" "${HW_REC_TIER:-T1}" "${HW_REC_COMPOSE_OVERLAYS:-}" "$_LLM_PORT" "$_LLM_HEALTH" <<'PY'
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
|
|
hardware = json.loads(sys.argv[1])
|
|
output_path = pathlib.Path(sys.argv[2])
|
|
env_mode = sys.argv[3] == "true"
|
|
hw_class_id = sys.argv[4]
|
|
hw_class_label = sys.argv[5]
|
|
hw_rec_backend = sys.argv[6]
|
|
hw_rec_tier = sys.argv[7]
|
|
hw_rec_overlays = [x for x in sys.argv[8].split(",") if x]
|
|
llm_port = int(sys.argv[9]) if len(sys.argv) > 9 else 11434
|
|
llm_health = sys.argv[10] if len(sys.argv) > 10 else "/health"
|
|
|
|
os_name = (hardware.get("os") or "unknown").lower()
|
|
if os_name in {"linux", "wsl"}:
|
|
family = "linux"
|
|
elif os_name == "macos":
|
|
family = "darwin"
|
|
elif os_name == "windows":
|
|
family = "windows"
|
|
else:
|
|
family = "unknown"
|
|
|
|
gpu = hardware.get("gpu", {})
|
|
gpu_type = (gpu.get("type") or "none").lower()
|
|
gpu_name = gpu.get("name") or "None"
|
|
memory_type = (gpu.get("memory_type") or "none").lower()
|
|
vram_mb = int(gpu.get("vram_mb") or 0)
|
|
gpu_count = int(gpu.get("count") or (1 if gpu_type not in {"none", ""} else 0))
|
|
experimental_jetson = os.environ.get("ODS_ENABLE_EXPERIMENTAL_JETSON") == "1"
|
|
|
|
llm_health_url = f"http://localhost:{llm_port}{llm_health}"
|
|
llm_api_port = llm_port
|
|
|
|
if gpu_type == "amd" and memory_type == "unified":
|
|
llm_backend = "amd"
|
|
overlays = ["docker-compose.base.yml", "docker-compose.amd.yml"]
|
|
elif gpu_type == "nvidia":
|
|
llm_backend = "nvidia"
|
|
overlays = ["docker-compose.base.yml", "docker-compose.nvidia.yml"]
|
|
elif gpu_type == "jetson" and experimental_jetson:
|
|
# Jetson is Tegra (arm64 + iGPU + unified memory). The dedicated overlay
|
|
# docker-compose.jetson.yml lands in milestone 1 phase 3; until then fall
|
|
# back to the cpu overlay so the installer pipeline stays valid on Jetson
|
|
# hosts without claiming a runtime path that doesn't exist yet.
|
|
llm_backend = "jetson"
|
|
overlays = ["docker-compose.base.yml", "docker-compose.cpu.yml"]
|
|
elif gpu_type == "apple":
|
|
llm_backend = "apple"
|
|
overlays = ["docker-compose.base.yml", "docker-compose.amd.yml"]
|
|
else:
|
|
llm_backend = "cpu"
|
|
overlays = ["docker-compose.base.yml", "docker-compose.cpu.yml"]
|
|
|
|
tier = (hardware.get("tier") or "T1").upper()
|
|
if tier in {"T1", "T2", "T3", "T4"}:
|
|
recommended = tier
|
|
elif tier in {"SH_COMPACT", "SH_LARGE"}:
|
|
recommended = tier
|
|
else:
|
|
recommended = "T1"
|
|
|
|
if hw_rec_tier:
|
|
recommended = hw_rec_tier
|
|
if hw_rec_backend:
|
|
llm_backend = hw_rec_backend
|
|
if hw_rec_overlays:
|
|
overlays = hw_rec_overlays
|
|
|
|
profile = {
|
|
"version": "1",
|
|
"platform": {
|
|
"id": os_name,
|
|
"family": family,
|
|
},
|
|
"gpu": {
|
|
"vendor": gpu_type if gpu_type in {"nvidia", "amd", "apple", "none"} else "unknown",
|
|
"name": gpu_name,
|
|
"memory_type": memory_type if memory_type in {"discrete", "unified", "none"} else "unknown",
|
|
"count": gpu_count,
|
|
"vram_mb": vram_mb,
|
|
},
|
|
"runtime": {
|
|
"llm_backend": llm_backend,
|
|
"llm_health_url": llm_health_url,
|
|
"llm_api_port": llm_api_port,
|
|
},
|
|
"compose": {
|
|
"overlays": overlays,
|
|
},
|
|
"tier": {
|
|
"recommended": recommended,
|
|
},
|
|
"hardware_class": {
|
|
"id": hw_class_id,
|
|
"label": hw_class_label,
|
|
}
|
|
}
|
|
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
output_path.write_text(json.dumps(profile, indent=2) + "\n", encoding="utf-8")
|
|
|
|
if env_mode:
|
|
env = {
|
|
"CAP_PROFILE_VERSION": profile["version"],
|
|
"CAP_PLATFORM_ID": profile["platform"]["id"],
|
|
"CAP_PLATFORM_FAMILY": profile["platform"]["family"],
|
|
"CAP_GPU_VENDOR": profile["gpu"]["vendor"],
|
|
"CAP_GPU_NAME": profile["gpu"]["name"],
|
|
"CAP_GPU_MEMORY_TYPE": profile["gpu"]["memory_type"],
|
|
"CAP_GPU_COUNT": str(profile["gpu"]["count"]),
|
|
"CAP_GPU_VRAM_MB": str(profile["gpu"]["vram_mb"]),
|
|
"CAP_LLM_BACKEND": profile["runtime"]["llm_backend"],
|
|
"CAP_LLM_HEALTH_URL": profile["runtime"]["llm_health_url"],
|
|
"CAP_LLM_API_PORT": str(profile["runtime"]["llm_api_port"]),
|
|
"CAP_RECOMMENDED_TIER": profile["tier"]["recommended"],
|
|
"CAP_COMPOSE_OVERLAYS": ",".join(profile["compose"]["overlays"]),
|
|
"CAP_HARDWARE_CLASS_ID": profile["hardware_class"]["id"],
|
|
"CAP_HARDWARE_CLASS_LABEL": profile["hardware_class"]["label"],
|
|
"CAP_PROFILE_FILE": str(output_path),
|
|
}
|
|
for key, value in env.items():
|
|
safe = str(value).replace("\\", "\\\\").replace('"', '\\"')
|
|
print(f'{key}="{safe}"')
|
|
PY
|