74 lines
3.7 KiB
Bash
Executable File
74 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Sanad Package 4 (Guide Tour) entrypoint.
|
|
# 1) license gate 2) resolve P2 env (env > license > config) 3) preflight 4) launch.
|
|
set -u
|
|
PKG="P4"
|
|
CFG="/app/pkg4_config/p4_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 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 <key>
|
|
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
|
|
}
|
|
|
|
# Language: empty = MULTILINGUAL auto-detect (P2's headline feature). Only set a
|
|
# fixed language if the operator/license/config explicitly pins one.
|
|
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)"
|
|
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:-8014}"
|
|
export SANAD_MASK_DIR="${SANAD_MASK_DIR:-/app/mask}"
|
|
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:-<multilingual>} audio=$SANAD_AUDIO_PROFILE port=$SANAD_DASHBOARD_PORT brain=$SANAD_VOICE_BRAIN mask_dir=$SANAD_MASK_DIR"
|
|
|
|
# ── 3. preflight (clear diagnostics) ─────────────────────────────────────────
|
|
python3 - "$SANAD_AUDIO_PROFILE" "$SANAD_MASK_DIR" <<'PY' || true
|
|
import importlib.util as u, sys
|
|
profile = sys.argv[1] if len(sys.argv) > 1 else "builtin"
|
|
mask_dir = sys.argv[2] if len(sys.argv) > 2 else "/app/mask"
|
|
def has(m): return u.find_spec(m) is not None
|
|
print("[P4] 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"))
|
|
print(" google-genai : %s" % ("OK" if has("google.genai") else "MISSING — live conversation will NOT work"))
|
|
print(" pyaudio : %s" % ("OK" if has("pyaudio") else "missing — mic/speaker capture limited"))
|
|
print(" bleak (mask) : %s" % ("OK" if has("bleak") else "MISSING — LED mask will NOT connect"))
|
|
print(" Pillow (face) : %s" % ("OK" if has("PIL") else "missing — LifelikeFace falls back to FaceAnimator"))
|
|
sys.path.insert(0, mask_dir)
|
|
print(" mask lib : %s (%s)" % ("OK" if has("mask") else "MISSING", mask_dir))
|
|
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 is")
|
|
print(" absent. Plug a USB speaker/mic and set SANAD_AUDIO_PROFILE=plugged.")
|
|
PY
|
|
|
|
exec python3 /app/app_p4.py
|