# syntax=docker/dockerfile:1 # ───────────────────────────────────────────────────────────────────────────── # Sanad Package 3 — Recognition + Places + Memories. SELF-CONTAINED (vendors the # SanadV3 engine + Mask lib under ./vendor; FROM python:3.10-slim; no sanad-base). # docker build -t sanad-p3:latest . (Jetson without buildx: DOCKER_BUILDKIT=0) # ───────────────────────────────────────────────────────────────────────────── ARG BASE_OS_IMAGE=python:3.10-slim-bookworm FROM ${BASE_OS_IMAGE} ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/app WORKDIR /app # System deps: audio + PortAudio/toolchain (pyaudio) + BlueZ/D-Bus (mask) + # iproute2 (`ip`, chest-mic) + libGL/glib for opencv-headless V4L camera capture. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates libsndfile1 alsa-utils pulseaudio-utils iproute2 \ portaudio19-dev libportaudio2 build-essential python3-dev \ bluez libdbus-1-3 libglib2.0-0 libgl1 \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt /tmp/requirements.txt RUN python3 -m pip install --no-cache-dir --upgrade pip \ && python3 -m pip install --no-cache-dir -r /tmp/requirements.txt # Optional Unitree SDK — chest (builtin) audio over DDS (full CycloneDDS + idlc, # pinned binding). Wrapped so a failure never breaks the image. ARG WITH_UNITREE_SDK=1 ENV CYCLONEDDS_HOME=/usr/local LD_LIBRARY_PATH=/usr/local/lib RUN if [ "$WITH_UNITREE_SDK" = "1" ]; then \ ( set -eux; apt-get update; apt-get install -y --no-install-recommends git cmake build-essential; \ git clone --depth 1 -b releases/0.10.x https://github.com/eclipse-cyclonedds/cyclonedds /tmp/cyclonedds; \ cmake -S /tmp/cyclonedds -B /tmp/cyclonedds/build -DCMAKE_INSTALL_PREFIX=/usr/local; \ cmake --build /tmp/cyclonedds/build --target install -j"$(nproc)"; \ CYCLONEDDS_HOME=/usr/local CMAKE_PREFIX_PATH=/usr/local python3 -m pip install --no-cache-dir "cyclonedds==0.10.2"; \ git clone --depth 1 https://github.com/unitreerobotics/unitree_sdk2_python /opt/unitree_sdk2_python; \ python3 -m pip install --no-cache-dir -e /opt/unitree_sdk2_python; \ python3 -c "import unitree_sdk2py; print('unitree_sdk2py OK')"; \ rm -rf /tmp/cyclonedds /var/lib/apt/lists/*; ) \ || echo "WARN[P3]: Unitree SDK build failed — chest audio unavailable; use SANAD_AUDIO_PROFILE=plugged"; \ else echo "WITH_UNITREE_SDK=0 — skipping Unitree SDK"; fi COPY vendor/sanad_pkg /app/sanad_pkg RUN mkdir -p /etc/sanad && cp /app/sanad_pkg/pubkey.ed25519 /etc/sanad/pubkey.ed25519 COPY vendor/mask /app/mask COPY vendor/Sanad /app/Sanad # P3 launcher + package-local memory feature + convenience routes + entrypoint + config. COPY app_p3.py /app/app_p3.py COPY routes_p3.py /app/routes_p3.py COPY visitor_memory.py /app/visitor_memory.py COPY routes_memory.py /app/routes_memory.py COPY entrypoint.sh /app/entrypoint.sh COPY config /app/pkg3_config RUN chmod +x /app/entrypoint.sh COPY strip_key.py /tmp/strip_key.py RUN python3 /tmp/strip_key.py && rm -f /tmp/strip_key.py RUN python3 - <<'PY' import importlib.util as u, sys ok = all(u.find_spec(m) for m in ("sanad_pkg.license", "Sanad", "visitor_memory")) sys.path.insert(0, "/app/mask") print("P3 self-contained: modules importable:", ok, "| cv2:", u.find_spec("cv2") is not None) sys.exit(0 if ok else 1) PY ENV SANAD_PACKAGE=P3 \ SANAD_DASHBOARD_PORT=8013 \ SANAD_DASHBOARD_HOST=0.0.0.0 \ SANAD_MASK_DIR=/app/mask \ SANAD_MEMORIES_DIR=/app/Sanad/data/memories \ SANAD_LICENSE=/etc/sanad/sanad.lic \ SANAD_PUBKEY=/etc/sanad/pubkey.ed25519 EXPOSE 8013 ENTRYPOINT ["/app/entrypoint.sh"]