# Inspire hand service + web dashboard, self-contained (aarch64 / Jetson).
# Build context is assembled by build_image.sh (pulls unitree_sdk2 + CycloneDDS
# from the robot). Run with: --network host --privileged -v /dev:/dev
FROM arm64v8/ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential cmake \
        libboost-all-dev libspdlog-dev libeigen3-dev \
        python3 python3-flask \
        iproute2 procps ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# CycloneDDS (ddscxx + iceoryx) headers & runtime libs from the robot's /usr/local
COPY deps/usr_local_include/ /usr/local/include/
COPY deps/usr_local_lib/     /usr/local/lib/
RUN ldconfig

# unitree_sdk2 headers + prebuilt static lib (for build.sh linking)
COPY deps/unitree_sdk2/ /opt/unitree_sdk2/

# Record/replay panel: the recorder is a python child process that speaks DDS itself, so
# the image needs the bindings as well as the script. Skipped cleanly when build_image.sh
# could not find the sources (empty dirs) — the panel then reports "recorder not found".
# NOTE: this layer pip-installs, so the robot needs network access at BUILD time. Offline
# alternative: copy the robot's own site-packages in the same way the C libs are copied.
COPY deps/recorder/ /opt/recorder/
COPY deps/unitree_sdk2_python/ /opt/unitree_sdk2_python/
# cyclonedds is pinned to what unitree_sdk2py demands (0.10.2) — installing anything else
# just gets downgraded when the SDK lands. It is built from sdist here, so it compiles
# against the robot's own CycloneDDS headers (0.10.5) and links its libddsc.
ARG CYCLONEDDS_PY=0.10.2
ENV CYCLONEDDS_HOME=/usr/local
# The SDK is COPIED into dist-packages rather than pip-installed, and both alternatives
# were tried on the robot first:
#   * a wheel install loses four subpackages — b2, comm, g1, h1 ship with no __init__.py,
#     so find_packages() drops them and the package's own
#     `from . import idl, utils, core, rpc, go2, b2` dies with "cannot import name 'b2'";
#   * `pip install -e` reports success (runs setup.py develop) but never lands on sys.path
#     in this image — the very next line still gets "No module named 'unitree_sdk2py'".
# A copy is deterministic and keeps every subdirectory, including the ones that resolve as
# implicit namespace packages. numpy comes from apt; opencv-python (its other declared
# dependency, ~100MB+) is skipped because nothing in the SDK imports cv2 — verified.
# The destination is derived from the freshly-installed cyclonedds rather than from
# sysconfig: on Debian/Ubuntu sysconfig's purelib is /usr/lib/python3.8/site-packages,
# which is not even on sys.path (it is dist-packages here), so the copy landed nowhere.
RUN if [ -d /opt/unitree_sdk2_python/unitree_sdk2py ]; then \
      apt-get update && apt-get install -y --no-install-recommends \
          python3-pip python3-dev python3-numpy && \
      pip3 install --no-cache-dir "cyclonedds==${CYCLONEDDS_PY}" && \
      cp -r /opt/unitree_sdk2_python/unitree_sdk2py \
            "$(python3 -c 'import cyclonedds,os;print(os.path.dirname(os.path.dirname(cyclonedds.__file__)))')/" && \
      python3 -c "import unitree_sdk2py, cyclonedds; print('python DDS ok')" && \
      rm -rf /var/lib/apt/lists/* ; \
    else echo "no unitree_sdk2_python in context — record/replay disabled"; fi
ENV RECORDER_DIR=/opt/recorder RECORDER_PY=python3

# the service sources (everything except web/), then build the binaries in-image
COPY app/ /opt/hand/
WORKDIR /opt/hand
RUN chmod +x build.sh docker/start.sh manage.sh 2>/dev/null; \
    UNITREE_SDK2_DIR=/opt/unitree_sdk2 ./build.sh

# The dashboard goes in LAST, on purpose: it is the part that changes every other day, and
# from here a redeploy is a seconds-long layer copy instead of a full C++ recompile.
COPY app_web/ /opt/hand/web/

ENV HAND_REPO=/opt/hand
EXPOSE 8088
CMD ["/opt/hand/docker/start.sh"]
