#!/usr/bin/env bash # Refresh the vendored SanadV3 engine + sanad_pkg + flat BLE Mask lib from a full # monorepo checkout. P2 ships SELF-CONTAINED copies under ./vendor so the repo # builds standalone. When SanadV3 or Project/Mask change upstream, run this from a # checkout that has Project/Sanadv3 + Project/Mask + Project/Packages, then commit # the updated ./vendor + ./data + ./license. # # ./sync_vendor.sh [/path/to/Project] # default: ../../ (Packages/.. = Project/) # # Excludes runtime data, Logs, caches, the temp3d 3D viewer, and tests. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" PROJECT="${1:-$(cd "$HERE/../.." && pwd)}" # = Project/G1 (packages live under G1/) SRC_V3="$PROJECT/Sanadv3" SRC_PKG="$PROJECT/Packages/sanad_pkg" SRC_LIC="$PROJECT/Packages/licensing" # The flat Mask lib moved OUT of the robot tree in the G1/ reorg (now Project/Other/Mask). # Try a few locations; override with SANAD_MASK_SRC=/abs/path if it moves again. SRC_MASK="${SANAD_MASK_SRC:-}" if [ -z "$SRC_MASK" ]; then for c in "$PROJECT/Mask" "$PROJECT/../Other/Mask" "$PROJECT/Other/Mask" "$(dirname "$PROJECT")/Other/Mask"; do [ -d "$c" ] && { SRC_MASK="$(cd "$c" && pwd)"; break; } done fi [ -d "$SRC_V3" ] || { echo "ERROR: no Sanadv3/ at $SRC_V3"; exit 1; } [ -d "$SRC_MASK" ] || { echo "ERROR: Mask lib not found (set SANAD_MASK_SRC=/path/to/Mask)"; exit 1; } echo ">> using Mask lib: $SRC_MASK" [ -d "$SRC_PKG" ] || { echo "ERROR: no sanad_pkg at $SRC_PKG"; exit 1; } echo ">> vendoring SanadV3 engine from $SRC_V3" rm -rf "$HERE/vendor"; mkdir -p "$HERE/vendor" rsync -a \ --exclude 'data/' --exclude 'Logs/' --exclude '__pycache__/' --exclude '*.pyc' \ --exclude '.git/' --exclude 'dashboard/static/temp3d/' --exclude 'tests/' \ "$SRC_V3/" "$HERE/vendor/Sanad/" echo ">> seeding minimal data/" mkdir -p "$HERE/vendor/Sanad/data/motions" cp "$SRC_V3/data/motions/config.json" "$HERE/vendor/Sanad/data/motions/config.json" for j in audio_device.json camera_device.json wake_phrases.json; do [ -f "$SRC_V3/data/$j" ] && cp "$SRC_V3/data/$j" "$HERE/vendor/Sanad/data/$j" || true done for d in recordings audio faces photos zones memories; do mkdir -p "$HERE/vendor/Sanad/data/$d"; touch "$HERE/vendor/Sanad/data/$d/.gitkeep"; done echo ">> vendoring sanad_pkg + public key" rm -rf "$HERE/vendor/sanad_pkg"; cp -r "$SRC_PKG" "$HERE/vendor/sanad_pkg" find "$HERE/vendor/sanad_pkg" -name __pycache__ -type d -prune -exec rm -rf {} + 2>/dev/null || true mkdir -p "$HERE/license"; cp "$SRC_LIC/pubkey.ed25519" "$HERE/license/pubkey.ed25519" echo ">> vendoring flat BLE Mask lib (own path)" rm -rf "$HERE/vendor/mask" rsync -a --exclude '__pycache__/' --exclude '*.pyc' --exclude '.git/' \ --exclude 'test_*.py' --exclude 'selftest.py' "$SRC_MASK/" "$HERE/vendor/mask/" echo ">> ship keyless (blank any baked Gemini key)" python3 - "$HERE" <<'PY' import json, sys h = sys.argv[1] for p, sec in ((h+"/vendor/Sanad/config/core_config.json", "gemini_defaults"), (h+"/vendor/Sanad/data/motions/config.json", "gemini")): try: d = json.load(open(p)) except Exception: continue s = d.get(sec) if isinstance(s, dict) and s.get("api_key"): s["api_key"] = "" json.dump(d, open(p, "w"), ensure_ascii=False, indent=2) print(" blanked", sec, "in", p) PY echo ">> refresh ./data seed mirror (keep structure, drop runtime media)" rsync -a --delete \ --exclude 'recordings/*' --exclude 'audio/*' --exclude 'faces/*' --exclude 'photos/*' \ "$HERE/vendor/Sanad/data/" "$HERE/data/" for d in recordings audio faces photos zones memories; do mkdir -p "$HERE/data/$d"; touch "$HERE/data/$d/.gitkeep"; done echo ">> refresh host mask_config.json seed (the mask-color persistence mount)" # Only this one file — do NOT touch the hand-written config/p2_config.json. cp "$HERE/vendor/Sanad/config/mask_config.json" "$HERE/config/mask_config.json" echo ">> done. vendor: $(du -sh "$HERE/vendor" | cut -f1) — review & commit ./vendor ./data ./config ./license"