#!/usr/bin/env bash # Sanad Package 1 (Basic Communication) entrypoint. # 1) license gate 2) resolve P1 env (env > license > config) 3) launch. set -u PKG="P1" CFG="/app/pkg1_config/p1_config.json" # ── 1. license gate ────────────────────────────────────────────────────────── # license_check exits 0 only when entitled. If NOT entitled we exit the # CONTAINER cleanly (code 0) so a restart:on-failure policy won't crash-loop. if ! python3 -m sanad_pkg.license_check "$PKG"; then echo "[$PKG] not licensed for this robot — container exiting cleanly." exit 0 fi # ── 2. resolve config (env wins, then license feature, then config file) ────── read_cfg() { # read_cfg python3 - "$CFG" "$1" <<'PY' 2>/dev/null || true import json, sys try: print(json.load(open(sys.argv[1])).get(sys.argv[2], "") or "") except Exception: print("") PY } if [ -z "${SANAD_LANGUAGE:-}" ]; then SANAD_LANGUAGE="$(python3 -c 'from sanad_pkg import license as L; print(L.feature("language","") or "")' 2>/dev/null || true)" [ -z "$SANAD_LANGUAGE" ] && SANAD_LANGUAGE="$(read_cfg language_default)" [ -z "$SANAD_LANGUAGE" ] && SANAD_LANGUAGE="ar" fi export SANAD_LANGUAGE export SANAD_VOICE_BRAIN="${SANAD_VOICE_BRAIN:-gemini}" [ -z "${SANAD_AUDIO_PROFILE:-}" ] && SANAD_AUDIO_PROFILE="$(read_cfg audio_profile_default)" export SANAD_AUDIO_PROFILE="${SANAD_AUDIO_PROFILE:-builtin}" export SANAD_DASHBOARD_HOST="${SANAD_DASHBOARD_HOST:-0.0.0.0}" [ -z "${SANAD_DASHBOARD_PORT:-}" ] && SANAD_DASHBOARD_PORT="$(read_cfg port)" export SANAD_DASHBOARD_PORT="${SANAD_DASHBOARD_PORT:-8011}" export PYTHONUNBUFFERED=1 # Jetson + Unitree SDK OpenMP load-order fix (only if the lib exists; override-able). if [ -z "${LD_PRELOAD:-}" ] && [ -f /usr/lib/aarch64-linux-gnu/libgomp.so.1 ]; then export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1 fi echo "[$PKG] entitled — lang=$SANAD_LANGUAGE audio=$SANAD_AUDIO_PROFILE port=$SANAD_DASHBOARD_PORT brain=$SANAD_VOICE_BRAIN" # ── 3. first-run preflight (clear, obvious diagnostics) ────────────────────── python3 - "$SANAD_AUDIO_PROFILE" <<'PY' || true import importlib.util as u, sys profile = sys.argv[1] if len(sys.argv) > 1 else "builtin" def has(m): return u.find_spec(m) is not None print("[P1] preflight:") ok = sys.version_info >= (3, 9) print(" python : %s %s" % (".".join(map(str, sys.version_info[:3])), "OK" if ok else "TOO OLD — google-genai needs >=3.9; rebuild from python:3.10 base")) print(" google-genai : %s" % ("OK" if has("google.genai") else "MISSING — live Gemini conversation will NOT work (check the build)")) print(" pyaudio : %s" % ("OK" if has("pyaudio") else "missing — mic/speaker capture limited")) sdk = has("unitree_sdk2py") print(" unitree SDK : %s" % ("OK" if sdk else "absent")) if profile == "builtin" and not sdk: print(" >> NOTE: audio profile 'builtin' (G1 chest) needs the Unitree SDK, which") print(" is absent. Plug in a USB speaker/mic and set SANAD_AUDIO_PROFILE=plugged,") print(" or mount a prebuilt unitree_sdk2_python. (plugged works with no SDK.)") PY exec python3 /app/app_p1.py