#!/usr/bin/env bash # Bring the X2 dashboard up ON THE ROBOT and keep it up. # # The dashboard is served by the robot itself, so the link people open is the # robot's own address - http://:8770. Nothing depends on any laptop # being switched on, and when the robot joins a different Wi-Fi the link simply # follows its new address. # # Idempotent by design: it starts only what is not already running, so it is # safe to call from cron every minute, from @reboot, and by hand. # # No `set -u` - the ROS setup scripts read undefined variables. set -e DIR="$(dirname "$(readlink -f "$0")")" LOG_DIR="/home/agi/x2_dashboard_logs" AGENT_PORT="${X2_AGENT_PORT:-8781}" WEB_PORT="${X2_WEB_PORT:-8770}" mkdir -p "$LOG_DIR" listening() { ss -ltn 2>/dev/null | grep -q ":$1 " } # The previous guard here was `pgrep -f "x2_dashboard/-m backend|backend.server"`, # and neither alternative can match the real command line, which is plain # `python3 -m backend`. That left the port check as the only guard, and the port # is not bound for the first ~10 s of startup - so the every-minute timer fired # again mid-boot and launched a second backend on top of the first. That is the # recurring "[Errno 98] address already in use" in dashboard.log. # # Matching on /proc rather than trusting pgrep alone: `pgrep -f` also matches any # shell whose command line merely mentions the pattern, so require python argv[0]. backend_running() { local p cmd for p in $(pgrep -f 'python3 -m backend' 2>/dev/null); do cmd=$(tr '\0' ' ' < "/proc/$p/cmdline" 2>/dev/null) case "$cmd" in python*|*/python*) return 0 ;; esac done return 1 } trim() { # keep the logs from growing without bound [ -f "$1" ] || return 0 if [ "$(stat -c%s "$1" 2>/dev/null || echo 0)" -gt 2000000 ]; then tail -n 300 "$1" > "$1.tmp" && mv "$1.tmp" "$1" fi } # 1. the ROS bridge ------------------------------------------------------- if ! listening "$AGENT_PORT" && ! pgrep -f "x2_agent.py" >/dev/null 2>&1; then trim "$LOG_DIR/agent.log" echo "[$(date -Is)] starting agent" >> "$LOG_DIR/agent.log" setsid nohup "$DIR/run_agent.sh" >> "$LOG_DIR/agent.log" 2>&1 < /dev/null & sleep 3 fi # 2. the web dashboard ---------------------------------------------------- if ! listening "$WEB_PORT" && ! backend_running; then trim "$LOG_DIR/dashboard.log" echo "[$(date -Is)] starting dashboard" >> "$LOG_DIR/dashboard.log" cd /home/agi/x2_dashboard # The dashboard talks to the agent over localhost and needs no ROS itself, # but sourcing costs nothing and keeps one environment for both. setsid nohup env PYTHONUNBUFFERED=1 python3 -m backend \ >> "$LOG_DIR/dashboard.log" 2>&1 < /dev/null & fi exit 0