#!/usr/bin/env bash # ───────────────────────────────────────────────────────────────────────────── # Pudu Map GUI — THE one script. # # ./start.sh start it + open the browser # ./start.sh logs follow the server log # ./start.sh stop stop it (same as the dashboard's ⏻ Shutdown) # ./start.sh restart stop + start # ./start.sh build rebuild the docker image, then start # ./start.sh fg run attached in this terminal (debugging only) # # Everything runs in docker; the host needs only docker + an X server. # Host paths are mounted VERBATIM so every absolute path behaves the same in # and out of the container: # ~/Robotics_workspace stage dirs, map editor, relay, rviz configs # ~/Downloads Pudu exports # ~/.ssh (read-only) robot deploys (known_hosts redirected to /tmp) # X11 + DISPLAY RViz + the native file picker # --net=host web GUI on :8777, DDS for the local /map, rosbridge # # DETACHED BY DEFAULT: the dashboard is a service. Also, `docker run -it` can # hang forever on a host whose docker attach stream is wedged (seen live on this # workstation: create/start/stop fine, every attached run hung) — detached # sidesteps that. Use `fg` only when you need the raw attached output. # ───────────────────────────────────────────────────────────────────────────── set -euo pipefail CMD="${1:-start}" # not in the docker group in THIS shell? re-run the whole script under sg docker ps >/dev/null 2>&1 || exec sg docker -c "$0 $CMD" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" IMG=pudu-map-gui NAME=pudu-map-gui PORT=8777 # PUDU_BIND: which address the dashboard listens on. Default localhost-only. # PUDU_BIND=10.255.254.83 ./start.sh restart -> reachable from the LAN # PUDU_BIND=0.0.0.0 ./start.sh restart -> every interface # The container already runs --net=host, so this is the ONLY thing that decides # reachability. NO AUTH: anyone who can reach the port can drive the robots. PUDU_BIND="${PUDU_BIND:-127.0.0.1}" # health-check a REACHABLE address: 0.0.0.0 is a bind wildcard, not a destination case "$PUDU_BIND" in 0.0.0.0|::) HOSTADDR=127.0.0.1 ;; *) HOSTADDR="$PUDU_BIND" ;; esac URL="http://$HOSTADDR:$PORT" _stop() { # the GUI's own viewer processes live INSIDE the container, so removing it # takes them with it. Robots are never touched by this script. if docker rm -f "$NAME" >/dev/null 2>&1; then echo "stopped $NAME" else echo "$NAME was not running"; fi } case "$CMD" in stop) _stop; exit 0 ;; logs) exec docker logs -f "$NAME" ;; restart) _stop ;; esac if [ "$CMD" = build ] || ! docker image inspect $IMG >/dev/null 2>&1; then echo "== building $IMG (first run takes a few minutes) ==" docker build -t $IMG "$HERE" fi # let the container's root talk to the host X server (RViz / zenity picker) xhost +local: >/dev/null 2>&1 || true # a stray host-side instance would hold the port — hand over cleanly fuser -k "$PORT/tcp" >/dev/null 2>&1 || true docker rm -f "$NAME" >/dev/null 2>&1 || true RUN=(docker run --rm --net=host --name "$NAME" -e DISPLAY="${DISPLAY:-:0}" -e HOME="$HOME" -e PUDU_GUI_DIR="$HERE" -e PUDU_BIND="$PUDU_BIND" -e PUDU_TOKEN="${PUDU_TOKEN:-}" -e PUDU_OPEN="${PUDU_OPEN:-}" -v /tmp/.X11-unix:/tmp/.X11-unix -v "$HOME/Robotics_workspace:$HOME/Robotics_workspace" -v "$HOME/Downloads:$HOME/Downloads" -v "$HOME/.ssh:$HOME/.ssh:ro") if [ "$CMD" = fg ]; then # attached, for debugging TT=(-it); [ -t 0 ] || TT=() exec "${RUN[@]}" "${TT[@]}" $IMG fi "${RUN[@]}" -d $IMG >/dev/null # never claim success blindly — wait until the server actually answers for _ in $(seq 40); do if curl -fsS -m 2 -o /dev/null "$URL/" 2>/dev/null; then [ "${NOBROWSER:-0}" = 1 ] || (xdg-open "$URL" >/dev/null 2>&1 &) || true cat < $URL (opened in your browser; if the page looks old, reload with $URL/?v=1) logs: $0 logs stop: $0 stop restart: $0 restart or press ⏻ Shutdown in the dashboard header. EOT exit 0 fi docker ps --format '{{.Names}}' | grep -qx "$NAME" || { echo "container exited during startup — last log lines:" >&2 docker logs "$NAME" 2>&1 | tail -20 >&2 || true exit 1 } sleep 1 done echo "started, but $URL did not answer within 40s — check: $0 logs" >&2 exit 1