Skip to content

Commit 93efda9

Browse files
committed
evol(launcher): detached mode for vps
Signed-off-by: Mael-RABOT <mael.rabot@gmail.com>
1 parent 057c158 commit 93efda9

3 files changed

Lines changed: 235 additions & 110 deletions

File tree

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,41 @@ Because all tools (like the console or the control panel) run in background wind
4545

4646
Open the control panel at **http://localhost:5000/**.
4747

48+
### Deploying on a Server (VPS) with PM2
49+
50+
For running the workspace on a headless server, we recommend using **PM2** to manage the application as a persistent background service.
51+
52+
**Prerequisites on the VPS:**
53+
1. **Node.js & PM2**:
54+
```bash
55+
sudo apt update && sudo apt install nodejs npm
56+
sudo npm install -g pm2
57+
```
58+
2. **Docker & Git**: Must be installed.
59+
60+
**Workflow:**
61+
62+
1. **Start the Service:**
63+
Use PM2 to start the launch script in daemon mode. This will create a background Docker container named `lucy_daemon`.
64+
```bash
65+
pm2 start ./launch_lucy.sh --name lucy-workspace -- --daemon
66+
```
67+
68+
2. **Attach to the Console:**
69+
To interact with the running workspace, use the `--attach` flag.
70+
```bash
71+
./launch_lucy.sh --attach
72+
```
73+
This will connect you to the `tmux` session inside the container where the **Lucy Control Center** is running.
74+
75+
3. **Detach and Keep it Running:**
76+
To leave the console without stopping the workspace, detach from the `tmux` session by pressing **`Ctrl+B`** then **`D`**.
77+
78+
4. **Manage with PM2:**
79+
- **View Logs:** `pm2 logs lucy-workspace`
80+
- **Stop Service:** `pm2 stop lucy-workspace` (This stops the script, but you may also need to run `docker stop lucy_daemon`)
81+
- **Enable Auto-Restart on Boot:** Run `pm2 startup` and follow the on-screen instructions.
82+
4883
## More
4984

5085
- [`docs/developer_lucy_packages.md`](docs/developer_lucy_packages.md) — developer guide: per-repo docs, all `install.sh` / `launch_lucy.sh` flags, dev mode, ports, environment overrides, packages overview.

launch_lucy.sh

Lines changed: 60 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
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

1917
set -e
2018
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -34,6 +32,7 @@ esac
3432
IMAGE_NAME="lucy_ros2:humble"
3533
DOCKERFILE_PATH="$SCRIPT_DIR/Dockerfile.humble"
3634
WORKSPACE="/workspace"
35+
DAEMON_CONTAINER_NAME="lucy_daemon"
3736

3837
# shellcheck disable=SC1091
3938
source "$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 ---
5255
X11_ARGS=()
5356
if [ "${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
7270
fi
7371

7472
ensure_docker_image
7573
docker_run_platform_flags "$SCRIPT_DIR"
7674
docker_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 ---
8477
vite_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-
9982
PORT_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)
10784
PORT_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"
185135
fi

0 commit comments

Comments
 (0)