#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" profile="detection-cpu" cmvr_es_root="${PROJECT_ROOT}/../cmvr-es" skip_codegen=false skip_check=false verify_model_artifact() { local model_id="$1" local relative_path="$2" local expected_size="$3" local expected_sha256="$4" local artifact_path="${PROJECT_ROOT}/${relative_path}" if [[ ! -f "${artifact_path}" ]]; then echo "error: model artifact is missing for ${model_id}: ${relative_path}" >&2 echo "restore the model file before using the ${profile} profile" >&2 exit 1 fi if LC_ALL=C grep -q -F "version https://git-lfs.github.com/spec/v1" "${artifact_path}"; then echo "error: model artifact is an unexpected Git LFS pointer: ${relative_path}" >&2 echo "recommit the complete weight as a regular Git file" >&2 exit 1 fi local actual_size actual_size="$(wc -c < "${artifact_path}")" actual_size="${actual_size//[[:space:]]/}" if [[ "${actual_size}" != "${expected_size}" ]]; then echo "error: model artifact size mismatch for ${model_id}: ${relative_path}" >&2 echo "expected ${expected_size} bytes, got ${actual_size} bytes" >&2 exit 1 fi local actual_sha256 actual_sha256="$(sha256sum "${artifact_path}" | awk '{print $1}')" if [[ "${actual_sha256}" != "${expected_sha256}" ]]; then echo "error: model artifact checksum mismatch for ${model_id}: ${relative_path}" >&2 echo "expected SHA256 ${expected_sha256}, got ${actual_sha256}" >&2 exit 1 fi } usage() { cat <<'EOF' Usage: bash scripts/bootstrap.sh [options] Create the locked uv environment, optionally generate cmvr-es Python bindings, and run basic health checks. Options: --profile PROFILE core, detection-cpu (default), or dev --cmvr-es-root PATH cmvr-es checkout (default: sibling ../cmvr-es) --skip-codegen do not generate cmvr-es protobuf/gRPC bindings --skip-check install only; skip validation and tests -h, --help show this help Profiles: core framework and minimal fixture only detection-cpu gRPC + HTTP server/client + PyAV + locked CPU ONNX runtime dev detection-cpu plus locked ONNX export and test toolchains EOF } while (($#)); do case "$1" in --profile) if (($# < 2)); then echo "error: --profile requires a value" >&2 exit 2 fi profile="$2" shift 2 ;; --cmvr-es-root) if (($# < 2)); then echo "error: --cmvr-es-root requires a path" >&2 exit 2 fi cmvr_es_root="$2" shift 2 ;; --skip-codegen) skip_codegen=true shift ;; --skip-check) skip_check=true shift ;; -h|--help) usage exit 0 ;; *) echo "error: unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done case "${profile}" in core) sync_args=(--locked --no-default-groups) needs_cmvr_bindings=false ;; detection-cpu) sync_args=( --locked --no-default-groups --extra grpc --extra http --extra video --extra image --extra server --extra onnx-cpu ) needs_cmvr_bindings=true ;; dev) sync_args=( --locked --no-default-groups --group dev --extra grpc --extra http --extra video --extra image --extra server --extra onnx-export-cpu ) needs_cmvr_bindings=true ;; *) echo "error: unsupported profile '${profile}'" >&2 echo "expected one of: core, detection-cpu, dev" >&2 exit 2 ;; esac if ! command -v uv >/dev/null 2>&1; then echo "error: uv is not installed or not available on PATH" >&2 echo "see https://docs.astral.sh/uv/getting-started/installation/" >&2 exit 1 fi cd "${PROJECT_ROOT}" echo "==> project: ${PROJECT_ROOT}" echo "==> profile: ${profile}" if [[ "${needs_cmvr_bindings}" == true && "${skip_codegen}" == false ]]; then if [[ ! -d "${cmvr_es_root}/protos/cmvr" ]]; then echo "error: cmvr-es protos were not found under: ${cmvr_es_root}" >&2 echo "pass --cmvr-es-root PATH or use --skip-codegen with existing bindings" >&2 exit 1 fi echo "==> installing the locked protobuf codegen environment" uv sync --locked --only-group codegen echo "==> generating cmvr-es Python bindings from ${cmvr_es_root}" .venv/bin/python scripts/generate_cmvr_stubs.py \ --cmvr-es-root "${cmvr_es_root}" \ --portable fi echo "==> installing the locked ${profile} environment" uv sync "${sync_args[@]}" if [[ "${skip_check}" == true ]]; then echo "==> installation complete (checks skipped)" exit 0 fi echo "==> validating the minimal framework fixture" .venv/bin/cmvr-edge-ai validate \ --config tests/fixtures/minimal_pipeline.yaml \ --pipeline minimal if [[ "${needs_cmvr_bindings}" == true ]]; then echo "==> verifying detection model artifacts" verify_model_artifact \ "construction-ppe-yolov8@2" \ "models/detection/construction-ppe-yolov8/v2/model.onnx" \ "44774179" \ "b8e5d116a964d0e7091e14b5382ab9301c196ae33b363516df04fddfb0d0b57a" verify_model_artifact \ "ppe-6classes-yolov8n@2" \ "models/detection/ppe-6classes-yolov8n/v2/model.onnx" \ "10993196" \ "6cb0e567b4fbf353aeb61c271ab33c45260d6ddddbe26eb3b64c245c2683c03b" verify_model_artifact \ "people-talking-yolov8x@2" \ "models/detection/people-talking-yolov8x/v2/model.onnx" \ "272787930" \ "b4387307dbdf1083c0229a12e833ccdc4a3ad0063d5bdb7908765072470e840b" verify_model_artifact \ "yolov8n-mobile-phone@2" \ "models/detection/yolov8n-mobile-phone/v2/model.onnx" \ "12265264" \ "df170d9bc86984894797da6f2101519617669c3f589d75289acb0ba1937f857f" echo "==> checking detection runtime imports" .venv/bin/python -c \ "import av, fastapi, grpc, httpx, numpy, onnxruntime, PIL, uvicorn; import cmvr.api.camera_service_pb2_grpc; print(f'onnxruntime={onnxruntime.__version__} numpy={numpy.__version__} pillow={PIL.__version__} fastapi={fastapi.__version__} uvicorn={uvicorn.__version__}')" echo "==> validating the PPE and phone-use detection pipeline" .venv/bin/cmvr-edge-ai validate \ --config configs/active_detection.yaml \ --pipeline detection echo "==> validating the passive Detect Server pipelines" .venv/bin/cmvr-edge-ai validate \ --config configs/server_detect.yaml .venv/bin/python server/detect/show_detections.py \ --config configs/detection_viewer.yaml \ --validate-only .venv/bin/cmvr-edge-ai models fi if [[ "${profile}" == dev ]]; then echo "==> checking ONNX exporter imports" .venv/bin/python -c \ "import onnx, onnxslim, torch, ultralytics; print(f'onnx={onnx.__version__} onnxslim={onnxslim.__version__} torch={torch.__version__} ultralytics={ultralytics.__version__}')" echo "==> running tests" .venv/bin/pytest fi echo "==> bootstrap complete" echo "run commands with: uv run --no-sync cmvr-edge-ai ..."