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
293 lines
10 KiB
Bash
Executable File
293 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
OUT_DIR="${1:-${ROOT_DIR}/artifacts/installer-sim}"
|
|
mkdir -p "$OUT_DIR"
|
|
|
|
LINUX_LOG="${OUT_DIR}/linux-dryrun.log"
|
|
LINUX_SUMMARY_JSON="${OUT_DIR}/linux-install-summary.json"
|
|
MACOS_LOG="${OUT_DIR}/macos-installer.log"
|
|
WINDOWS_SIM_JSON="${OUT_DIR}/windows-preflight-sim.json"
|
|
MACOS_PREFLIGHT_JSON="${OUT_DIR}/macos-preflight.json"
|
|
MACOS_DOCTOR_JSON="${OUT_DIR}/macos-doctor.json"
|
|
DOCTOR_JSON="${OUT_DIR}/doctor.json"
|
|
SUMMARY_JSON="${OUT_DIR}/summary.json"
|
|
SUMMARY_MD="${OUT_DIR}/SUMMARY.md"
|
|
GOLDEN_CONTRACT_JSON="${ROOT_DIR}/config/golden-paths.json"
|
|
GOLDEN_EVIDENCE_JSON="${OUT_DIR}/golden-paths.json"
|
|
|
|
FAKEBIN="$(mktemp -d)"
|
|
trap 'rm -rf "$FAKEBIN"' EXIT
|
|
cat > "${FAKEBIN}/curl" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
exit 0
|
|
EOF
|
|
chmod +x "${FAKEBIN}/curl"
|
|
|
|
cd "$ROOT_DIR"
|
|
|
|
# 1) Linux installer dry-run simulation
|
|
LINUX_EXIT=0
|
|
if ! PATH="${FAKEBIN}:$PATH" bash install-core.sh --dry-run --non-interactive --skip-docker --force --summary-json "$LINUX_SUMMARY_JSON" >"$LINUX_LOG" 2>&1; then
|
|
LINUX_EXIT=$?
|
|
fi
|
|
|
|
# 2) macOS installer MVP simulation
|
|
MACOS_EXIT=0
|
|
if ! bash installers/macos.sh --no-delegate --report "$MACOS_PREFLIGHT_JSON" --doctor-report "$MACOS_DOCTOR_JSON" >"$MACOS_LOG" 2>&1; then
|
|
MACOS_EXIT=$?
|
|
fi
|
|
|
|
# 3) Windows scenario simulation via preflight engine (since pwsh may be unavailable in CI/sandbox)
|
|
scripts/preflight-engine.sh \
|
|
--report "$WINDOWS_SIM_JSON" \
|
|
--tier T1 \
|
|
--ram-gb 16 \
|
|
--disk-gb 120 \
|
|
--gpu-backend nvidia \
|
|
--gpu-vram-mb 12288 \
|
|
--gpu-name "RTX 3060" \
|
|
--platform-id windows \
|
|
--compose-overlays docker-compose.base.yml,docker-compose.nvidia.yml \
|
|
--script-dir "$ROOT_DIR" \
|
|
--env >/dev/null
|
|
|
|
# 4) Doctor snapshot for current machine context
|
|
DOCTOR_EXIT=0
|
|
if ! scripts/ods-doctor.sh "$DOCTOR_JSON" >/dev/null 2>&1; then
|
|
DOCTOR_EXIT=$?
|
|
fi
|
|
|
|
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" - "$SUMMARY_JSON" "$SUMMARY_MD" "$GOLDEN_EVIDENCE_JSON" "$GOLDEN_CONTRACT_JSON" "$LINUX_LOG" "$MACOS_LOG" "$WINDOWS_SIM_JSON" "$MACOS_PREFLIGHT_JSON" "$MACOS_DOCTOR_JSON" "$DOCTOR_JSON" "$LINUX_SUMMARY_JSON" "$LINUX_EXIT" "$MACOS_EXIT" "$DOCTOR_EXIT" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
(
|
|
summary_json_path,
|
|
summary_md_path,
|
|
golden_evidence_json_path,
|
|
golden_contract_json_path,
|
|
linux_log,
|
|
macos_log,
|
|
windows_sim_json,
|
|
macos_preflight_json,
|
|
macos_doctor_json,
|
|
doctor_json,
|
|
linux_install_summary_json,
|
|
linux_exit,
|
|
macos_exit,
|
|
doctor_exit,
|
|
) = sys.argv[1:]
|
|
|
|
root_dir = pathlib.Path.cwd()
|
|
|
|
def load_json(path):
|
|
p = pathlib.Path(path)
|
|
if not p.exists():
|
|
return None
|
|
try:
|
|
return json.loads(p.read_text(encoding="utf-8"))
|
|
except Exception:
|
|
return None
|
|
|
|
linux_text = pathlib.Path(linux_log).read_text(encoding="utf-8", errors="replace") if pathlib.Path(linux_log).exists() else ""
|
|
macos_text = pathlib.Path(macos_log).read_text(encoding="utf-8", errors="replace") if pathlib.Path(macos_log).exists() else ""
|
|
|
|
linux_signals = {
|
|
"capability_loaded": bool(re.search(r"Capability profile loaded", linux_text)),
|
|
"hardware_class_logged": bool(re.search(r"Hardware class:", linux_text)),
|
|
"backend_contract_loaded": bool(re.search(r"Backend contract loaded", linux_text)),
|
|
"preflight_report_logged": bool(re.search(r"Preflight report:", linux_text)),
|
|
"compose_selection_logged": bool(re.search(r"Compose selection:", linux_text)),
|
|
}
|
|
|
|
summary = {
|
|
"version": "1",
|
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
"runs": {
|
|
"linux_dryrun": {
|
|
"exit_code": int(linux_exit),
|
|
"signals": linux_signals,
|
|
"log": linux_log,
|
|
"install_summary": load_json(linux_install_summary_json) or {},
|
|
},
|
|
"macos_installer_mvp": {
|
|
"exit_code": int(macos_exit),
|
|
"log": macos_log,
|
|
"preflight": load_json(macos_preflight_json),
|
|
"doctor": load_json(macos_doctor_json),
|
|
},
|
|
"windows_scenario_preflight": {
|
|
"report": load_json(windows_sim_json),
|
|
},
|
|
"doctor_snapshot": {
|
|
"exit_code": int(doctor_exit),
|
|
"report": load_json(doctor_json),
|
|
},
|
|
},
|
|
}
|
|
|
|
def run_status(run_name, run):
|
|
if run_name == "linux_dryrun":
|
|
signals = run.get("signals") or {}
|
|
return {
|
|
"ok": run.get("exit_code") == 0 and all(signals.values()),
|
|
"exit_code": run.get("exit_code"),
|
|
"required_signals": signals,
|
|
}
|
|
if run_name == "macos_installer_mvp":
|
|
summary_block = ((run.get("preflight") or {}).get("summary") or {})
|
|
blockers = summary_block.get("blockers")
|
|
return {
|
|
"ok": run.get("exit_code") == 0 and (blockers in (0, None)),
|
|
"exit_code": run.get("exit_code"),
|
|
"blockers": blockers,
|
|
"warnings": summary_block.get("warnings"),
|
|
}
|
|
if run_name == "windows_scenario_preflight":
|
|
summary_block = ((run.get("report") or {}).get("summary") or {})
|
|
blockers = summary_block.get("blockers")
|
|
return {
|
|
"ok": blockers == 0,
|
|
"blockers": blockers,
|
|
"warnings": summary_block.get("warnings"),
|
|
}
|
|
return {"ok": run is not None}
|
|
|
|
def build_golden_evidence():
|
|
contract = load_json(golden_contract_json_path) or {"scenarios": []}
|
|
evidence = {
|
|
"version": "1",
|
|
"generated_at": summary["generated_at"],
|
|
"contract": golden_contract_json_path,
|
|
"scenarios": [],
|
|
}
|
|
|
|
runs = summary.get("runs") or {}
|
|
for scenario in contract.get("scenarios", []):
|
|
installer = scenario.get("installer") or {}
|
|
expected = scenario.get("expected") or {}
|
|
run_name = installer.get("ci_simulation")
|
|
run = runs.get(run_name) if run_name else None
|
|
compose_files = expected.get("compose_files") or []
|
|
generated_configs = expected.get("generated_configs") or []
|
|
health_checks = expected.get("health_checks") or []
|
|
missing_compose = [
|
|
path for path in compose_files
|
|
if not (root_dir / path).exists()
|
|
]
|
|
|
|
status = run_status(run_name, run or {})
|
|
scenario_ok = bool(status.get("ok")) and not missing_compose
|
|
evidence["scenarios"].append({
|
|
"id": scenario.get("id"),
|
|
"label": scenario.get("label"),
|
|
"status": "pass" if scenario_ok else "fail",
|
|
"simulation_run": run_name,
|
|
"simulation_status": status,
|
|
"expected": {
|
|
"ods_mode": expected.get("ods_mode"),
|
|
"llm_backend": expected.get("llm_backend"),
|
|
"llm_host_port": expected.get("llm_host_port"),
|
|
"llm_container_port": expected.get("llm_container_port"),
|
|
"model_route": expected.get("model_route") or {},
|
|
"compose_files": compose_files,
|
|
"generated_config_surfaces": [
|
|
item.get("surface") for item in generated_configs
|
|
if isinstance(item, dict)
|
|
],
|
|
"health_check_services": [
|
|
item.get("service") for item in health_checks
|
|
if isinstance(item, dict)
|
|
],
|
|
},
|
|
"checks": {
|
|
"compose_files_exist": not missing_compose,
|
|
"missing_compose_files": missing_compose,
|
|
"generated_config_count": len(generated_configs),
|
|
"health_check_count": len(health_checks),
|
|
},
|
|
})
|
|
return evidence
|
|
|
|
golden_evidence = build_golden_evidence()
|
|
summary["golden_paths"] = {
|
|
"contract": golden_contract_json_path,
|
|
"evidence": golden_evidence_json_path,
|
|
"scenario_count": len(golden_evidence.get("scenarios", [])),
|
|
"pass_count": sum(1 for item in golden_evidence.get("scenarios", []) if item.get("status") == "pass"),
|
|
}
|
|
|
|
pathlib.Path(summary_json_path).write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
|
pathlib.Path(golden_evidence_json_path).write_text(json.dumps(golden_evidence, indent=2) + "\n", encoding="utf-8")
|
|
|
|
lines = []
|
|
lines.append("# Installer Simulation Summary")
|
|
lines.append("")
|
|
lines.append(f"Generated: {summary['generated_at']}")
|
|
lines.append("")
|
|
lines.append("## Linux Dry-Run")
|
|
lines.append(f"- Exit code: {linux_exit}")
|
|
for k, v in linux_signals.items():
|
|
lines.append(f"- {k}: {'yes' if v else 'no'}")
|
|
lines.append(f"- Log: `{linux_log}`")
|
|
lines.append("")
|
|
|
|
mp = summary["runs"]["macos_installer_mvp"].get("preflight") or {}
|
|
ms = (mp.get("summary") or {})
|
|
lines.append("## macOS Installer MVP")
|
|
lines.append(f"- Exit code: {macos_exit}")
|
|
lines.append(f"- Preflight blockers: {ms.get('blockers', 'n/a')}")
|
|
lines.append(f"- Preflight warnings: {ms.get('warnings', 'n/a')}")
|
|
lines.append(f"- Log: `{macos_log}`")
|
|
lines.append(f"- Preflight JSON: `{macos_preflight_json}`")
|
|
lines.append(f"- Doctor JSON: `{macos_doctor_json}`")
|
|
lines.append("")
|
|
|
|
wp = summary["runs"]["windows_scenario_preflight"].get("report") or {}
|
|
ws = (wp.get("summary") or {})
|
|
lines.append("## Windows Scenario (Simulated)")
|
|
lines.append(f"- Preflight blockers: {ws.get('blockers', 'n/a')}")
|
|
lines.append(f"- Preflight warnings: {ws.get('warnings', 'n/a')}")
|
|
lines.append(f"- Report: `{windows_sim_json}`")
|
|
lines.append("")
|
|
|
|
dr = summary["runs"]["doctor_snapshot"].get("report") or {}
|
|
dsum = dr.get("summary") or {}
|
|
lines.append("## Doctor Snapshot")
|
|
lines.append(f"- Exit code: {doctor_exit}")
|
|
lines.append(f"- Runtime ready: {dsum.get('runtime_ready', 'n/a')}")
|
|
lines.append(f"- Report: `{doctor_json}`")
|
|
lines.append("")
|
|
lines.append("## Golden Paths")
|
|
for item in golden_evidence.get("scenarios", []):
|
|
status = item.get("status", "unknown")
|
|
label = item.get("label") or item.get("id")
|
|
run_name = item.get("simulation_run") or "n/a"
|
|
lines.append(f"- {label}: {status} via `{run_name}`")
|
|
lines.append(f"- Evidence JSON: `{golden_evidence_json_path}`")
|
|
|
|
pathlib.Path(summary_md_path).write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
PY
|
|
|
|
if [[ -x "${ROOT_DIR}/scripts/validate-sim-summary.py" ]]; then
|
|
"${ROOT_DIR}/scripts/validate-sim-summary.py" "$SUMMARY_JSON"
|
|
fi
|
|
|
|
echo "Installer simulation complete."
|
|
echo " JSON: $SUMMARY_JSON"
|
|
echo " MD: $SUMMARY_MD"
|
|
echo " Golden paths: $GOLDEN_EVIDENCE_JSON"
|