33#
44# Prerequisite: ./install.sh — clones src/, builds the Docker image, builds the workspace.
55#
6- # Usage:
7- # ./launch_lucy.sh start control panel + `ros2 launch lucy_bringup lucy.launch.py gazebo:=true rviz:=true`
8- # ./launch_lucy.sh --headless same but without GUI / X11 (Gazebo runs headless, RViz disabled)
9- # ./launch_lucy.sh <command> run a single command in the container (no control panel, no auto-launch)
6+ # Usage for local development :
7+ # ./launch_lucy.sh Attach to the interactive launcher TUI.
8+ # ./launch_lucy.sh --headless Same but without GUI / X11.
9+ # ./launch_lucy.sh <command> Run a single command in the container.
1010#
11- # Dev mode (DEV=true in env or .env): drop into an interactive Humble shell with the control
12- # panel running in background; you launch the ROS stack yourself (handy commands are printed).
11+ # Usage for servers (with PM2):
12+ # ./launch_lucy.sh --daemon Start the container as a background daemon.
13+ # ./launch_lucy.sh --attach Attach to the interactive launcher in the running daemon.
1314#
14- # Ports published on the host: rosbridge 9090, control panel PORT_CONTROL_PANEL (defaults to
15- # VITE_PORT from src/lucy_control_panel/.env, else 5000). Vite proxies /rosbridge to the bridge.
16- #
17- # Docker platform follows the last ./install.sh run (.lucy-docker-platform; override with LUCY_DOCKER_PLATFORM).
15+ # Dev mode (DEV=true): Drops into an interactive shell instead of the launcher TUI.
1816
1917set -e
2018SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
3432IMAGE_NAME=" lucy_ros2:humble"
3533DOCKERFILE_PATH=" $SCRIPT_DIR /Dockerfile.humble"
3634WORKSPACE=" /workspace"
35+ DAEMON_CONTAINER_NAME=" lucy_daemon"
3736
3837# shellcheck disable=SC1091
3938source " $SCRIPT_DIR /docker/ensure_image.sh"
@@ -42,13 +41,17 @@ ensure_docker_image() {
4241 ensure_lucy_docker_image " $SCRIPT_DIR " " $IMAGE_NAME " " $DOCKERFILE_PATH "
4342}
4443
45- # ----------------------------------------------------------------------------
46- # GUI / X11 forwarding
47- # ----------------------------------------------------------------------------
48- # GUI on by default. `--headless` disables it. Override the display with
49- # DOCKER_GUI_DISPLAY (e.g. Docker Desktop) or use DOCKER_GUI_USE_HOST_NETWORK=1
50- # to share the host network namespace (DISPLAY=:0 inside the container).
44+ # --- Argument Parsing ---
45+ MODE=" interactive"
46+ if [ " ${1:- } " = " --daemon" ]; then
47+ MODE=" daemon"
48+ shift
49+ elif [ " ${1:- } " = " --attach" ]; then
50+ MODE=" attach"
51+ shift
52+ fi
5153
54+ # --- GUI / X11 Forwarding ---
5255X11_ARGS=()
5356if [ " ${1:- } " = " --headless" ]; then
5457 shift
@@ -59,127 +62,74 @@ else
5962 if command -v xhost & > /dev/null; then
6063 xhost +local:docker 2> /dev/null || true
6164 fi
62- if [ -n " ${DOCKER_GUI_USE_HOST_NETWORK:- } " ]; then
63- X11_ARGS=(-e " DISPLAY=:0" --network=host)
64- echo " GUI: DISPLAY=:0 (host network)."
65- else
66- X11_ARGS=(-e DISPLAY=" $GUI_DISPLAY " -v /tmp/.X11-unix:/tmp/.X11-unix:rw)
67- echo " GUI: DISPLAY=$GUI_DISPLAY "
68- fi
65+ X11_ARGS=(-e DISPLAY=" $GUI_DISPLAY " -v /tmp/.X11-unix:/tmp/.X11-unix:rw)
66+ echo " GUI: DISPLAY=$GUI_DISPLAY "
6967 else
70- echo " GUI: DISPLAY not set; Gazebo will run headless ( RViz disabled) ."
68+ echo " GUI: No DISPLAY set. Gazebo will run headless and RViz will be disabled."
7169 fi
7270fi
7371
7472ensure_docker_image
7573docker_run_platform_flags " $SCRIPT_DIR "
7674docker_run_it_flags
7775
78- # ----------------------------------------------------------------------------
79- # Port mapping (control panel + rosbridge)
80- # ----------------------------------------------------------------------------
81-
82- # Vite reads VITE_PORT from src/lucy_control_panel/.env. We must publish that
83- # exact container port, otherwise the printed host URL silently lies.
76+ # --- Port Mapping ---
8477vite_listen_port_from_envfile () {
8578 local f=" $SCRIPT_DIR /src/lucy_control_panel/.env"
86- local raw val
8779 [[ -f " $f " ]] || return 1
88- raw=$( grep -E ' ^[[:space:]]*VITE_PORT[[:space:]]*=' " $f " | tail -1 2> /dev/null || true)
89- [[ -n " $raw " ]] || return 1
90- val=" ${raw#* =} "
91- val=" ${val# " ${val%% [![:space:]]* } " } "
92- val=" ${val% " ${val##* [![:space:]]} " } "
93- val=" ${val// \" / } "
94- val=" ${val// \' / } "
95- [[ " $val " =~ ^[0-9]+$ ]] || return 1
96- echo " $val "
80+ grep -E ' ^[[:space:]]*VITE_PORT[[:space:]]*=' " $f " | tail -1 | sed -e ' s/.*=//' -e ' s/[[:space:]]//g' -e ' s/"//g' -e " s/'//g"
9781}
98-
9982PORT_ROSBRIDGE=9090
100- if [[ -z " ${PORT_CONTROL_PANEL_CONTAINER:- } " ]]; then
101- if v=" $( vite_listen_port_from_envfile) " ; then
102- PORT_CONTROL_PANEL_CONTAINER=" $v "
103- else
104- PORT_CONTROL_PANEL_CONTAINER=5000
105- fi
106- fi
83+ PORT_CONTROL_PANEL_CONTAINER=$( vite_listen_port_from_envfile || echo 5000)
10784PORT_CONTROL_PANEL=" ${PORT_CONTROL_PANEL:- $PORT_CONTROL_PANEL_CONTAINER } "
85+ DOCKER_PORT_ARGS=(-p " ${PORT_ROSBRIDGE} :9090" -p " ${PORT_CONTROL_PANEL} :${PORT_CONTROL_PANEL_CONTAINER} " )
10886
109- DOCKER_PORT_ARGS=(
110- -p " ${PORT_ROSBRIDGE} :9090 "
111- -p " ${PORT_CONTROL_PANEL} : ${PORT_CONTROL_PANEL_CONTAINER} "
112- )
87+ # --- Container Scripts ---
88+ CONTAINER_PREAMBLE= ' set -e; source /opt/ros/humble/setup.bash; cd /workspace; if [[ ! -f install/setup.bash ]]; then echo "Workspace not built. Run ./install.sh first." >&2; exit 1; fi; source install/setup.bash '
89+ TMUX_SCRIPT= ' if [ -z "$TMUX" ]; then tmux start-server; if ! tmux has-session -t lucy_ws 2>/dev/null; then tmux new-session -d -s lucy_ws -n "Lucy Workspace" "launcher"; fi; tmux attach-session -t lucy_ws; else bash -i; fi '
90+ INTERACTIVE_CONTAINER_SCRIPT= " ${CONTAINER_PREAMBLE} ; ${TMUX_SCRIPT} "
11391
114- # ----------------------------------------------------------------------------
115- # Container scripts
116- # ----------------------------------------------------------------------------
92+ # --- Dispatch ---
11793
118- SETUP=" source /opt/ros/humble/setup.bash"
119- SOURCE_WORKSPACE=" cd $WORKSPACE && source install/setup.bash"
120- LAUNCH_GAZEBO_RVIZ_BRIDGE_CP=" ros2 launch lucy_bringup lucy.launch.py gazebo:=true rviz:=true"
121- LAUNCH_RVIZ_BRIDGE_CP=" ros2 launch lucy_bringup lucy.launch.py rviz:=true"
122-
123- # Preamble run inside the container: source ROS + overlay.
124- read -r -d ' ' CONTAINER_PREAMBLE << 'EOS ' || true
125- set -e
126- source /opt/ros/humble/setup.bash
127- cd /workspace
128- if [[ ! -f install/setup.bash ]]; then
129- echo "Workspace not built. Run ./install.sh (or ./install.sh --build-only) on the host first." >&2
130- exit 1
131- fi
132- source install/setup.bash
133- EOS
134-
135- # In DEV mode, attach to a tmux session. Exiting the last tmux window will exit the container.
136- read -r -d ' ' TMUX_SCRIPT << 'EOS ' || true
137- if [ -z "$TMUX" ]; then
138- # Start tmux server and create session if it doesn't exist
139- tmux start-server
140- if ! tmux has-session -t lucy_ws 2>/dev/null; then
141- tmux new-session -d -s lucy_ws -n 'Lucy Workspace'
94+ if [ " $MODE " = " daemon" ]; then
95+ echo " Starting container '$DAEMON_CONTAINER_NAME ' in daemon mode..."
96+ if [ " $( docker ps -q -f name=^/${DAEMON_CONTAINER_NAME} $) " ]; then
97+ echo " Container is already running. To restart, run 'docker stop $DAEMON_CONTAINER_NAME ' first."
98+ exit 0
14299 fi
100+ docker run -d --rm " ${DOCKER_RUN_PLATFORM_ARGS[@]} " \
101+ --name " $DAEMON_CONTAINER_NAME " \
102+ " ${DOCKER_PORT_ARGS[@]} " \
103+ -v " $SCRIPT_DIR :$WORKSPACE " \
104+ " ${X11_ARGS[@]} " \
105+ -e LUCY_CP_PUBLISHED_HOST_PORT=" $PORT_CONTROL_PANEL " \
106+ -e LUCY_CP_CONTAINER_PORT=" $PORT_CONTROL_PANEL_CONTAINER " \
107+ " $IMAGE_NAME " tail -f /dev/null # Keep container alive
108+ echo " Container started. Attach to the launcher with './launch_lucy.sh --attach'"
109+
110+ elif [ " $MODE " = " attach" ]; then
111+ echo " Attaching to container '$DAEMON_CONTAINER_NAME '..."
112+ if ! [ " $( docker ps -q -f name=^/${DAEMON_CONTAINER_NAME} $) " ]; then
113+ echo " Error: Container '$DAEMON_CONTAINER_NAME ' is not running. Start it with './launch_lucy.sh --daemon' first." >&2
114+ exit 1
115+ fi
116+ docker exec -it " $DAEMON_CONTAINER_NAME " bash -c " $INTERACTIVE_CONTAINER_SCRIPT "
143117
144- # Send the command
145- tmux send-keys -t lucy_ws "launcher" C-m
146-
147- # Attach to session.
148- # When the last window is closed, the server exits, the script ends, and the container stops.
149- tmux attach-session -t lucy_ws
150- else
151- # Already inside tmux, do nothing special.
152- bash -i
153- fi
154- EOS
155-
156- INTERACTIVE_CONTAINER_SCRIPT=" ${CONTAINER_PREAMBLE}
157- ${TMUX_SCRIPT}
158- "
159-
160- NORMAL_CONTAINER_SCRIPT=" ${CONTAINER_PREAMBLE}
161- ${LAUNCH_GAZEBO_RVIZ_BRIDGE_CP}
162- "
163-
164- # ----------------------------------------------------------------------------
165- # Dispatch
166- # ----------------------------------------------------------------------------
167-
168- if [ $# -eq 0 ]; then
169- CONTAINER_SCRIPT=" $INTERACTIVE_CONTAINER_SCRIPT "
118+ elif [ $# -gt 0 ]; then # Run a single command
170119 docker run " ${DOCKER_RUN_PLATFORM_ARGS[@]} " " ${DOCKER_RUN_IT[@]} " --rm \
171120 --name lucy_dev \
172121 " ${DOCKER_PORT_ARGS[@]} " \
173122 -v " $SCRIPT_DIR :$WORKSPACE " \
174123 " ${X11_ARGS[@]} " \
175- -e LUCY_CP_PUBLISHED_HOST_PORT=" $PORT_CONTROL_PANEL " \
176- -e LUCY_CP_CONTAINER_PORT=" $PORT_CONTROL_PANEL_CONTAINER " \
177- " $IMAGE_NAME " -c " $CONTAINER_SCRIPT "
178- else
124+ " $IMAGE_NAME " -c " ${CONTAINER_PREAMBLE} && $* "
125+
126+ else # Default interactive mode
179127 docker run " ${DOCKER_RUN_PLATFORM_ARGS[@]} " " ${DOCKER_RUN_IT[@]} " --rm \
180128 --name lucy_dev \
181129 " ${DOCKER_PORT_ARGS[@]} " \
182130 -v " $SCRIPT_DIR :$WORKSPACE " \
183131 " ${X11_ARGS[@]} " \
184- " $IMAGE_NAME " -c " ${SETUP} && ${SOURCE_WORKSPACE} && $* "
132+ -e LUCY_CP_PUBLISHED_HOST_PORT=" $PORT_CONTROL_PANEL " \
133+ -e LUCY_CP_CONTAINER_PORT=" $PORT_CONTROL_PANEL_CONTAINER " \
134+ " $IMAGE_name " -c " $INTERACTIVE_CONTAINER_SCRIPT "
185135fi
0 commit comments