#!/usr/bin/env bash # Container entrypoint for SanadR1. Waits for the robot link (eth10) to be UP with # its 192.168.123.x address before launching, so the single per-process DDS init # (arm.init → ChannelFactoryInitialize) binds the right interface. Mirrors the # conda start_sanad.sh eth10 wait; without it, on a cold robot boot the process # would init DDS on a not-yet-ready iface and silently run hardware-dead. set -u IFACE="${SANAD_DDS_INTERFACE:-eth10}" PORT="${PORT:-8001}" echo "[entrypoint] waiting up to 25s for ${IFACE} to be UP with a 192.168.123.x IP…" for i in $(seq 1 25); do if ip link show "$IFACE" 2>/dev/null | grep -q "state UP" \ && ip -4 -o addr show "$IFACE" 2>/dev/null | grep -q "192.168.123."; then echo "[entrypoint] ${IFACE} ready: $(ip -4 -o addr show "$IFACE" | awk '{print $4}')" break fi sleep 1 done if ! ip link show "$IFACE" 2>/dev/null | grep -q "state UP"; then echo "[entrypoint] WARNING: ${IFACE} not UP after 25s — launching anyway; robot DDS (arm/loco/audio) may be unavailable this session" >&2 fi # ── audio path ─────────────────────────────────────────────────────────────── # If a USB audio device was passed in (--device /dev/snd), run as the non-root # 'sanad' user with a per-user PulseAudio so the JBL/Anker-style device profiles # work (Sanad refuses to touch audio as root). Otherwise keep the original root # path — the R1's DDS chest speaker + UDP mic need no local sound stack. if [ -e /dev/snd ]; then export XDG_RUNTIME_DIR=/run/user/1000 # Volumes mount root-owned; hand them + the runtime dir to the app user. chown -R sanad:sanad /opt/SanadR1/data /opt/SanadR1/logs "$XDG_RUNTIME_DIR" 2>/dev/null || true [ -s /etc/machine-id ] || tr -d - < /proc/sys/kernel/random/uuid > /etc/machine-id 2>/dev/null || true echo "[entrypoint] /dev/snd present — starting PulseAudio (USB audio mode)" setpriv --reuid sanad --regid sanad --init-groups \ env XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" HOME=/home/sanad \ pulseaudio --start --exit-idle-time=-1 >/dev/null 2>&1 \ || echo "[entrypoint] WARNING: PulseAudio failed to start" >&2 # udev doesn't run in the container, so PulseAudio's udev-detect finds no # cards. Load each USB ALSA card BY NAME (skip the Jetson built-in HDA/APE) so # its sink/source are named for the device — that's what the Anker/JBL-style # device profiles match on (e.g. alsa_output.PowerConf.analog-stereo). sleep 1 for _card in $(aplay -l 2>/dev/null | sed -n 's/^card [0-9][0-9]*: \([A-Za-z0-9_]*\) .*/\1/p' | sort -u); do case "$_card" in HDA|APE|"") : ;; *) echo "[entrypoint] loading USB audio card: ${_card}" setpriv --reuid sanad --regid sanad --init-groups \ env XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \ pactl load-module module-alsa-card device_id="${_card}" >/dev/null 2>&1 || true ;; esac done echo "[entrypoint] launching main.py --port ${PORT} as user 'sanad'" exec setpriv --reuid sanad --regid sanad --init-groups \ env XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" HOME=/home/sanad \ python3 main.py --port "${PORT}" else echo "[entrypoint] no /dev/snd — DDS audio only; launching main.py --port ${PORT} as root" exec python3 main.py --port "${PORT}" fi