diff --git a/README.md b/README.md
index 8c55675..db7ed52 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,58 @@
# Lucy ROS 2 workspace (Jazzy)
-Workspace bringup for the Lucy / InMoov humanoid. Everything (ROS 2 Jazzy, Gazebo, RViz, the web control panel) runs inside a single Docker container — you only need **Docker**, **Git** and **Python 3** on the host (plus **`xhost`** on Linux for GUI forwarding; on macOS the GUI is viewed over VNC with no extra software — see [GUI: RViz and Gazebo](#gui-rviz-and-gazebo)).
+Workspace bringup for the Lucy / InMoov humanoid. ROS 2 Jazzy, Gazebo, RViz, and the web control panel run **natively** via [Pixi](https://pixi.prefix.dev/) and [RoboStack](https://robostack.github.io/) — no Docker required for day-to-day development.
## Requirements
-- [Python3](https://www.python.org/downloads/)
-- [Docker](https://docs.docker.com/engine/install/)
-- [Git](https://git-scm.com/downloads)
+| Requirement | Linux | macOS | Windows |
+|-------------|-------|-------|---------|
+| [Git](https://git-scm.com/downloads) | ✓ | ✓ | ✓ ([Git for Windows](https://git-scm.com/install/windows)) |
+| [Python 3](https://www.python.org/downloads/) (for `Lucy.py`) | ✓ | ✓ | ✓ |
+| [Pixi](https://pixi.prefix.dev/latest/installation/) **≥ 0.78** | ✓ | ✓ | ✓ |
+| **tmux** (multi-window launcher) | ✓ | ✓ (`brew install tmux`) | — (launcher runs directly) |
-Linux GUI forwarding uses `xhost` (preinstalled). On Wayland run `xhost +local:docker` if windows don't open — see [GUI](#gui-rviz-and-gazebo).
+ROS packages are installed by Pixi into `.pixi/`; you do not need a system ROS install.
-> **Windows users:** see the [Windows README](windows/README.md) — **`Lucy-Setup.exe`** to install/update, **`Lucy.exe`** to launch.
+GUI apps (RViz, Gazebo, rqt) use your **native display**. Platform-specific notes (Wayland, AirPlay port conflicts) are in the [developer guide](docs/developer_lucy_packages.md#platform-setup).
## Get the repository
-Install the [requirements](#requirements) first, then grab the repo. You can clone it with Git (recommended — makes updates a `git pull`) or download a ZIP.
-
-**Option A — Clone with Git (recommended):**
+**Clone (recommended):**
```bash
git clone https://github.com/Sentience-Robotics/lucy_ws.git
+cd lucy_ws
```
-**Option B — Download the ZIP:**
+**Or** download the ZIP from GitHub (**Code → Download ZIP**), extract, and `cd lucy_ws`.
-- Open the repository page on GitHub, click the green **Code** button, then **Download ZIP**, and extract it.
+Run `Lucy.py` and the install scripts **from the repository root** — they read `config/` and paths relative to that directory.
-Then open a terminal and move into the project folder before running anything:
+## Install
+
+### Linux / macOS
```bash
-cd lucy_ws
+curl -fsSL https://pixi.sh/install.sh | bash # if Pixi is not installed
+export PATH="$HOME/.pixi/bin:$PATH" # if needed
+./install.sh
```
-> The manager (`Lucy.py`) must be run **from the repository root** — it reads `config/`, `docker/Dockerfile.jazzy` and mounts the workspace relative to that directory.
+### Windows
+
+**End users:** download **`Lucy-Setup.exe`** from [GitHub Releases](https://github.com/Sentience-Robotics/lucy_ws/releases). It installs Lucy, clones sub-repos, runs `pixi install`, and builds the workspace. See the [Windows README](windows/README.md).
+
+**Developers (from source):**
+
+```powershell
+python windows\Lucy.py --cli install --repos-branch master
+```
+
+Or from Git Bash: `./install.sh`
## Quick start
-A Python-based Text User Interface (TUI) manages the whole workspace — installing, rebuilding, and launching the environment. From the repository root, run the manager for your platform:
+After install, launch the **Lucy manager** TUI from the repository root.
### Linux / macOS
@@ -44,53 +60,50 @@ A Python-based Text User Interface (TUI) manages the whole workspace — install
python3 Lucy.py
```
-> On UNIX systems, if you ever run the scripts by hand, `chmod +x install.sh launch_lucy.sh` first. `./install.sh` is only a fallback to `Lucy.py`.
-
### Windows
-**Installer (recommended):** download `Lucy-Setup.exe` from [GitHub Releases](https://github.com/Sentience-Robotics/lucy_ws/releases), then see the [Windows README](windows/README.md).
+**End users:** open **Lucy** from the Start Menu (runs `Lucy.exe` → Control Center).
-**From source (developers):**
+**Developers:**
-```bash
-python windows/Lucy.py --cli install # first time
-python windows/Lucy.py # launch
+```powershell
+python windows\Lucy.py
```
-> **Windows** additionally needs a third-party X Server for RViz/Gazebo — see the [Windows README](windows/README.md). End users should use **`Lucy-Setup.exe`** instead of manual install.
+Or Git Bash: `./launch_lucy.sh`
-### Opening the Control Panel
+## Using the Lucy launcher
After **`Launch`**, enable **Core + Control Panel** in the launcher. Once it is running, the **Lucy Control Panel is accessible in your browser at [http://localhost:4004](http://localhost:4004)** (or the next free port if 4004 is already taken). The launcher also shows the exact URL next to the Control Panel entry once it's up.
+| Key | Action |
+|-----|--------|
+| **Up/Down** | Navigate |
+| **Space** | Toggle a package or tool |
+| **Enter** | Apply changes / Start / Restart |
+| **X** | Stop all processes and exit |
-## Using the workspace
+**Components you can enable:**
-Selecting **Launch** from the manager starts everything inside a single **tmux** session in the Docker container. You first land on the **Lucy Control Center** TUI:
+- **Core** — base robot stack (`lucy_bringup`)
+- **Modifiers** — Simulator (Gazebo), **… headless** (server-only sim, under Simulator), Visualizer (RViz), Real Hardware
+- **Interfaces** — Control Panel (web UI), Lucy CLI
+- **Tools** — Console, rqt
-- **Up/Down arrows** — navigate
-- **Space** — toggle a package or tool on/off
-- **Enter** — apply your changes (new tools open in their own background windows)
-- **X** — stop all processes and exit the container
+> **Recommended starting point:** **Core + Control Panel** — the web 3D viewer is enough for most work without heavy GUI apps.
-### Launch options
+**Control panel:** after Launch, enable **Core + Control Panel**. Open [http://localhost:5000](http://localhost:5000) (or the URL shown in the launcher if another port was chosen).
-What you toggle in the launcher falls into a few groups:
+**tmux windows** (Linux/macOS):
-- **Core** — the base robot software stack (*lucy_bringup*), everything else builds on it.
-- **Modifiers** (extend Core):
- - **Simulator (Gazebo)** — physics simulation
- - **Visualizer (RViz)** — ROS 3D visualizer
- - **Real Hardware** — connect to the physical robot
-- **Interfaces:**
- - **Control Panel** — web UI with a built-in 3D viewer
- - **Lucy CLI** — terminal control interface
-- **Tools** — Console, rqt, and VNC viewers.
+- **`Ctrl+B` then `W`** — window list
+- **`Ctrl+B` then `N`** / **`P`** — next / previous window
-Gazebo, RViz and rqt are native GUI apps and need a display — native X11 or the VNC desktop (see [GUI: RViz and Gazebo](#gui-rviz-and-gazebo)). Each interface/viewer that exposes a URL shows it right in the launcher once it's running.
+On Windows, the Control Center runs without tmux (one process tree). Gazebo, RViz, and rqt still open as native GUI apps when enabled.
-> **Recommended starting point:** enable **Core + Control Panel**. The control panel's web 3D viewer is usually enough to get going and avoids the heavier GUI apps (Gazebo/RViz) entirely.
+## Developer setup
+<<<<<<< HEAD
### Managing tmux windows
Tools (the console, CLI, viewers…) run in background windows, so a few `tmux` basics help you move between them:
@@ -159,3 +172,6 @@ the address it shows. Defaults:
- [`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.
- [`docs/launcher_packages.md`](docs/launcher_packages.md) — launcher guide: how to add new packages to the launcher UI and understand the configuration fields.
+=======
+For developer mode, Pixi component tasks (`pixi run core`, `sim-headless`, …), debug shell, SSH clones, local repo overrides, ports, and advanced launch options, see the **[developer guide](docs/developer_lucy_packages.md)**.
+>>>>>>> 41e84e8 (evol(windows,docs): update Windows support and documentation)
diff --git a/docs/developer_lucy_packages.md b/docs/developer_lucy_packages.md
index d8db69b..4ac92d7 100644
--- a/docs/developer_lucy_packages.md
+++ b/docs/developer_lucy_packages.md
@@ -1,54 +1,29 @@
-# Lucy `lucy_ws` developer guide
+# Lucy developer guide
-ROS 2 **Jazzy** on Ubuntu 24.04 Noble. This document covers everything beyond the basic install/launch flow in the top-level [`README.md`](../README.md): per-repository docs, all install/launch flags, dev mode, ports, environment overrides, and an overview of the packages dropped under `src/`.
+ROS 2 **Jazzy** workspace for Lucy / InMoov. This guide covers developer mode, platform-specific setup, launch workflows, and pointers to deeper documentation. Basic install and launcher usage are in the top-level [`README.md`](../README.md).
-## Cross-repository docs
+## Developer mode
-Maintainer documentation for each Lucy sub-repository is **owned per repository**:
+Developer mode is toggled in **`Lucy.py`** (Install menu) or by setting **`DEV=true`** in `.env` (copy from [`.env.example`](../.env.example)).
-| Repository | Developer documentation |
-|------------|-------------------------|
-| **lucy_ros_packages** | [`lucy_ros_packages/docs/DEVELOPER.md`](../src/lucy_ros_packages/docs/DEVELOPER.md) — bringup, `lucy_ros2_control`, `camera_ros`, CI; [**ros2_control on Lucy**](../src/lucy_ros_packages/doc/ROS2_CONTROL.md) |
-| **inmoov_urdf** | [`inmoov_urdf/docs/DEVELOPER.md`](../src/inmoov_urdf/docs/DEVELOPER.md) — URDF/xacro, meshes, launches, RViz |
+When enabled:
-Repository-level READMEs: [`lucy_ros_packages`](../src/lucy_ros_packages/README.md), [`inmoov_urdf`](../src/inmoov_urdf/README.md).
+| Behavior | Effect |
+|----------|--------|
+| **SSH clones** | `install.sh` uses `url_ssh` from [`config/repos.json`](../config/repos.json) instead of HTTPS |
+| **No auto-launch** | Core and Control Panel are not started automatically on Launch |
-## Packages dropped under `src/` by `install.sh`
+SSH keys must be configured for GitHub on your host before running `./install.sh` with `DEV=true`.
-- **inmoov_urdf** — InMoov URDF, RViz config, `control.launch.py`, `gazebo.launch.py`, `rviz_standalone.launch.py` (robot + viz; the web stack lives in `lucy_bringup`).
-- **lucy_ros_packages** — `lucy_bringup`, `lucy_ros2_control`, `camera_ros`, etc.
-- **lucy_control_panel** — Vite web app exposing the robot state and controls.
+### Local overrides (gitignored)
-The exact set of repositories, branches and clone URLs is in [`config/repos.json`](../config/repos.json).
+| File | Purpose |
+|------|---------|
+| [`config/repos.json.local`](../config/repos.json.local) | Forks, feature branches, skip optional repos |
+| [`config/launcher_config.json.local`](../config/launcher_config.json.local) | Custom Control Center package list (e.g. multi-robot) |
+| [`config/install.profile.json`](../config/install.profile.json) | Windows installer choices (written by `Lucy-Setup.exe`) |
-## `install.sh`
-
-The first run clones missing sub-repositories, builds the Docker image (`lucy_ros2:jazzy`), and runs `rosdep` + `colcon build --symlink-install` + `yarn install` inside the container.
-
-`--symlink-install` keeps `install/share//config/controllers.yaml` pointing at the **source tree** paths that `lucy_config_pipeline` writes (`src/inmoov_urdf/config/controllers.yaml`), so launch files and the pipeline stay aligned during iterative hardware edits.
-
-Subsequent runs fast-forward each clone to the branch declared in `config/repos.json` and rebuild the workspace.
-
-| Command | What it does |
-|---------|--------------|
-| `./install.sh` | Clone missing repos, pull existing ones, rebuild the workspace |
-| `./install.sh --repair` | Wipe each repo under `src/` then re-clone and rebuild |
-| `./install.sh --build-only` | Skip git; just rebuild the workspace inside the container |
-| `./install.sh --arm[...]` | Build / run the image as `linux/arm64` (Apple Silicon under Docker Desktop). Persists in `.lucy-docker-platform`; combine with any other flag |
-
-### Apple Silicon notes
-
-Docker Desktop on Apple Silicon defaults to `linux/amd64` when no platform is pinned, which runs the container under emulation and can make `apt` / `rosdep` unreliable. Use `./install.sh --arm` to build and run a native `linux/arm64` image on `ubuntu:24.04` with `ros-jazzy-*` packages from apt (recorded in `.lucy-docker-platform`).
-
-### SSH vs HTTPS clones (`DEV=true`)
-
-`config/repos.json` carries both `url_https` (default) and `url_ssh` for each repo. To clone over SSH, copy `.env.example` to `.env` and set `DEV=true` before running `install.sh`. SSH keys must be configured for the relevant host.
-
-### Local repo overrides (`config/repos.json.local`)
-
-To point a repo at your own fork or a feature branch without editing the tracked `config/repos.json`, create **`config/repos.json.local`**. When present it is used instead of `repos.json` by both `install.sh` and the launcher (`windows/Lucy.py`), and it is gitignored so overrides are never committed.
-
-Use the same structure as `repos.json` — list only the repos you want to override (or all of them). Each entry needs `name` (the folder under `src/`), `branch`, and both `url_https` and `url_ssh` (Developer Mode selects SSH, otherwise HTTPS):
+Example for a fork — same structure as `repos.json`:
```json
{
@@ -63,69 +38,189 @@ Use the same structure as `repos.json` — list only the repos you want to overr
}
```
-Delete the file to fall back to the tracked `repos.json`.
+For a multi-robot dev setup, copy [`config/launcher_config.json.local.example`](../config/launcher_config.json.local.example) to `launcher_config.json.local`.
-### Windows install profile (`config/install.profile.json`)
+## Platform setup
-On Windows, **`Lucy-Setup.exe`** (or `Lucy.exe --cli …`) writes **`config/install.profile.json`** (gitignored) to record install choices: `lucy_ws` version, `repos_branch` (default `master`), `fetch_method` (`git` or `zip`), and whether **developer install** was selected. The file is created automatically on first install.
+### Linux
-| Windows | Linux/macOS equivalent |
-|---------|------------------------|
+Standard path: `./install.sh` then `python3 Lucy.py`.
+
+**Wayland:** RViz/Gazebo may need `xhost +local:` or an X11 session.
+
+**NixOS:** Pixi/RoboStack needs host GL libraries prepended **and** Mesa EGL — both are applied by [`scripts/nix_gl_env.sh`](../scripts/nix_gl_env.sh) (launcher and `pixi run sim*`). Install **`nixGLIntel`** (or another nixGL wrapper) on PATH, or rely on the `/run/opengl-driver/lib` fallback. **Do not set `LUCY_NIX_GL=0`** — EGL/`GZ_IP` alone is not enough; sim will hang on "requesting world names". Optional overrides: `LUCY_NIX_GL_WRAPPER`, `__EGL_VENDOR_LIBRARY_FILENAMES`, `GZ_IP` — see `.env.example`.
+
+### macOS
+
+- Install **tmux** (`brew install tmux`) for the multi-window launcher.
+- **Port 5000** is often used by AirPlay Receiver. Disable it in **System Settings → General → AirDrop & Handoff**, or set `PORT_CONTROL_PANEL=5001` in `.env`.
+- Pixi uses Cyclone DDS on macOS (`RMW_IMPLEMENTATION=rmw_cyclonedds_cpp` in `pixi.toml`).
+
+### Windows
+
+End-user install: **`Lucy-Setup.exe`** → **`Lucy.exe`**. Full details: [`windows/README.md`](../windows/README.md).
+
+Developer CLI equivalents:
+
+| Windows | Linux/macOS |
+|---------|-------------|
| `Lucy-Setup.exe` → Fresh install | `./install.sh` |
-| `Lucy-Setup.exe` → Update | `./install.sh` / `./install.sh --update` |
+| `Lucy-Setup.exe` → Update | `./install.sh` |
| `Lucy-Setup.exe` → Repair | `./install.sh --repair` |
-| `Lucy.exe` (no args) | `./launch_lucy.sh` / **Launch** in `Lucy.py` |
+| `Lucy.exe` | `./launch_lucy.sh` |
| `Lucy.exe --cli build-only` | `./install.sh --build-only` |
-## `launch_lucy.sh`
+Launch runs via Git Bash (`bash launch_lucy.sh`). Without tmux, the Control Center runs directly (`pixi run -- python launcher.py`).
-Builds the Docker image if needed, mounts the workspace at `/workspace`, sources the built ROS overlay, then:
+### Workspace install (`install.sh`)
-- **Normal mode (default)** — starts the control panel (Vite) in the background and runs `ros2 launch lucy_bringup lucy.launch.py gazebo:=true rviz:=true` in the foreground. GUI / X11 forwarded automatically when available.
-- **Dev mode (`DEV=true` in env or `.env`)** — same control panel in the background, but drops you into an interactive Jazzy shell so you can run any `ros2 launch` yourself (the script prints typical commands).
+Pixi installs RoboStack Jazzy; `colcon build --symlink-install` builds `src/`; `yarn install` sets up the control panel.
| Command | What it does |
|---------|--------------|
-| `./launch_lucy.sh` | Default launch (Control Panel + RViz + Gazebo, or dev shell when `DEV=true`) |
-| `./launch_lucy.sh --headless` | Same flow without GUI / X11 (Gazebo runs headless, RViz is disabled) |
-| `./launch_lucy.sh ` | Run a single command in the container — no control panel, no auto-launch |
-| `DEV=true ./launch_lucy.sh` | Force dev mode for one run |
+| `./install.sh` | Clone missing repos, pull existing ones, `pixi install`, colcon build |
+| `./install.sh --repair` | Wipe each repo under `src/` then re-clone and rebuild |
+| `./install.sh --build-only` | Skip git; `pixi install` + colcon + panel yarn |
+| `./install.sh --skip-build` | Clone/pull only (CI) |
-### `ros2 launch` cheat sheet (dev mode)
+**Do not use `rosdep`** — it bypasses Pixi/RoboStack. Add deps via `pixi.toml` or clone into `src/`. See [`docs/pixi_setup.md`](pixi_setup.md).
-Run these inside the dev-mode shell — `lucy_bringup` already brings up `rosbridge` and the `/config/*` services via `web_ros_api`:
+**RealSense** (optional, not in Pixi): after a normal build, run `./scripts/build_local_realsense.sh` or `LUCY_BUILD_REALSENSE=1 ./install.sh`. Primary target is Linux; see script for aarch64 notes.
-| What you want | Command |
-|---------------|---------|
-| Default Jetson stack (no RViz / no Gazebo) | `ros2 launch lucy_bringup lucy.launch.py real:=true` |
-| Jetson + RViz | `ros2 launch lucy_bringup lucy.launch.py real:=true rviz:=true` |
-| Dev + panel + RViz (no micro-ROS / cameras) | `ros2 launch lucy_bringup lucy.launch.py rviz:=true` |
-| Gazebo sim + panel (`rviz:=false` = headless Gazebo) | `ros2 launch lucy_bringup lucy.launch.py gazebo:=true` |
+**Packages under `src/`** (from [`config/repos.json`](../config/repos.json)):
-`gazebo:=true` cannot be combined with `real:=true` (the launch aborts). With Gazebo, `rviz` maps to `start_rviz` in `inmoov_urdf/gazebo.launch.py`.
+| Repo | Role |
+|------|------|
+| **inmoov_urdf** | URDF, Gazebo/RViz launches |
+| **lucy_ros_packages** | `lucy_bringup`, ros2_control, cameras |
+| **lucy_control_panel** | Web UI |
+| **micro_ros_agent**, **audio_common** | Optional (`optional: true` in repos.json) |
-### SIMULATION ONLY + RELOAD (control panel)
+## Launch
-From **Configuration → ACTIVATE**, enable **SIMULATION ONLY** to run **VALIDATE → ACTIVATE → RELOAD** without BUILD/FLASH. The pipeline generates a single mock `ros2_control` block and `lucy_sim_controller`, installs `inmoov_ros2_control.xacro` + `controllers.yaml` into the source robot tree, then calls **`/lucy_control/restart`** (`lucy_control_supervisor`) to restart `robot_state_publisher`, `ros2_control_node` (RViz-only), and controller spawners.
+Two supported workflows.
-Hardware mode runs the same **RELOAD** step after BUILD/FLASH once ros2_control artifacts are regenerated.
+### 1. Control Center (recommended)
-**Gazebo caveat:** spawners and RSP can be restarted without relaunching the world; if you add/remove joints in the URDF hardware blocks, `gz_ros2_control` may require a full Gazebo restart — the supervisor response notes this when `use_gazebo_sim:=true`.
+Day-to-day use: one tmux session (Linux/macOS), toggle components in the TUI.
-## Ports and environment overrides
+| Entry | Command |
+|-------|---------|
+| TUI manager | `python3 Lucy.py` → **Launch** |
+| Direct | `./launch_lucy.sh` |
+| Windows | `Lucy.exe` |
-| Env var | Default | Purpose |
-|---------|---------|---------|
-| `DEV` | unset | `true` → use `url_ssh` in `repos.json` (install) and the interactive dev shell (launch) |
-| `PORT_CONTROL_PANEL` | matches container port | Host port the control panel is published on |
-| `PORT_CONTROL_PANEL_CONTAINER` | `VITE_PORT` from `src/lucy_control_panel/.env`, else `4004` | Port the Vite dev server listens on inside the container |
-| `DOCKER_GUI_DISPLAY` | host `$DISPLAY` | X display string passed to the container (use when the host `DISPLAY` doesn't reach Docker, e.g. Docker Desktop) |
-| `DOCKER_GUI_USE_HOST_NETWORK` | unset | Run with `--network=host` and `DISPLAY=:0` (alternative GUI path) |
-| `LUCY_DOCKER_PLATFORM` | content of `.lucy-docker-platform`, else host CPU | Docker `--platform` to build/run with (e.g. `linux/arm64`) |
-| `LUCY_INSTALL_SKIP_XHOST` | unset | Skip the `xhost` requirement in `install.sh` (set automatically when `CI=true`) |
+| `launch_lucy.sh` flag | Purpose |
+|-----------------------|---------|
+| *(none)* | tmux + Control Center |
+| `--headless ` | One-shot command (default: `ros2 doctor --report`) |
+| `--shell` | Interactive dev shell (`pixi run shell`) |
+
+The launcher wraps panes in `pixi run`, forwards display/GL env from `.env`, and sources `nix_gl_env.sh` for `ros2 launch`. Package list: [`docs/launcher_packages.md`](launcher_packages.md).
+
+### 2. Pixi component tasks
-Inside the container, Vite proxies `/rosbridge` to `ws://127.0.0.1:9090`, and rosbridge is published on host port `9090`.
+Run stacks or tools in **separate terminals** (debug / scripting). ROS tasks use [`scripts/pixi_lucy_launch.sh`](../scripts/pixi_lucy_launch.sh).
-## Docker image rebuilds
+| Task | Command | What it starts |
+|------|---------|----------------|
+| Core | `pixi run core` | `lucy_bringup` (rosbridge, config pipeline, mock stack) |
+| Gazebo (GUI) | `pixi run sim` | Core + Gazebo window |
+| Gazebo headless | `pixi run sim-headless` | Core + Gazebo server only |
+| Sim + RViz | `pixi run sim-rviz` | Headless Gazebo + RViz |
+| RViz only | `pixi run rviz` | Core + RViz |
+| Control panel | `pixi run control-panel` | Vite dev server |
+| rqt | `pixi run rqt` | rqt (needs core running) |
+| Lucy CLI | `pixi run lucy-cli` | `ros2 run lucy_cli tui` (needs core) |
-`docker/ensure_image.sh` stamps each built image with `LABEL lucy.dockerfile.sha256="|"`. Both `install.sh` and `launch_lucy.sh` rebuild the image when the label no longer matches the current `Dockerfile.jazzy` + target platform.
+**Robot package** (default `inmoov_urdf`):
+
+```bash
+LUCY_ROBOT_PACKAGE=thais_urdf pixi run sim-headless
+```
+
+Example multi-terminal sim:
+
+```bash
+pixi run sim-headless # terminal 1
+pixi run control-panel # terminal 2
+```
+
+### Debug shell
+
+Interactive RoboStack + colcon overlay for ROS CLI:
+
+```bash
+pixi run shell
+# or: ./launch_lucy.sh --shell
+```
+
+```bash
+ros2 topic list
+ros2 service list
+ros2 launch lucy_bringup lucy.launch.py rviz:=true
+```
+
+For manual Gazebo launches outside `pixi run sim*`, source GL env when needed (e.g. NixOS with `nixGLIntel`):
+
+```bash
+source scripts/nix_gl_env.sh
+ros2 launch lucy_bringup lucy.launch.py gazebo:=true
+```
+
+### TUI ↔ Pixi CLI
+
+| Action | Command |
+|--------|---------|
+| Install / update | `./install.sh` |
+| Rebuild | `pixi run build` then `pixi run panel-install` |
+| Launch | `./launch_lucy.sh` |
+| Dev shell | `pixi run shell` |
+| Headless one-shot | `./launch_lucy.sh --headless ros2 doctor --report` |
+| Clean build dirs | `pixi run clean` |
+| Workspace tests | `pixi run workspace-test` |
+
+### `ros2 launch` modifiers
+
+`lucy_bringup` brings up rosbridge and `/config/*` via `web_ros_api`:
+
+| Goal | Command |
+|------|---------|
+| Default dev stack (mock hardware) | `ros2 launch lucy_bringup lucy.launch.py` |
+| Jetson / real hardware | `… real:=true` |
+| Real + RViz | `… real:=true rviz:=true` |
+| Gazebo headless | `pixi run sim-headless` or `… gazebo:=true headless:=true` |
+
+`gazebo:=true` cannot be combined with `real:=true`. With Gazebo, `rviz` is forwarded to the robot package's `gazebo.launch.py`.
+
+### Control panel: SIMULATION ONLY + RELOAD
+
+From **Configuration → ACTIVATE**, enable **SIMULATION ONLY** to run **VALIDATE → ACTIVATE → RELOAD** without BUILD/FLASH. The pipeline writes mock ros2_control artifacts and calls **`/lucy_control/restart`**. Hardware mode runs the same **RELOAD** after BUILD/FLASH.
+
+**Gazebo caveat:** joint changes in URDF hardware blocks may require a full Gazebo restart when `use_gazebo_sim:=true`.
+
+## Ports and environment
+
+| Env var | Default | Purpose |
+|---------|---------|---------|
+| `DEV` | unset | `true` → SSH clones during `install.sh` |
+| `PORT_CONTROL_PANEL` | auto | Host port for control panel URL |
+| `PORT_CONTROL_PANEL_CONTAINER` | `VITE_PORT` from `src/lucy_control_panel/.env`, else `4004` | Port the Vite dev server listens on inside the container |
+| `PORT_ROSBRIDGE` | `9090` | rosbridge WebSocket |
+| `LUCY_ROBOT_PACKAGE` | `inmoov_urdf` | Robot package for `pixi run core` / `sim-*` |
+| `LUCY_NIX_GL` | `auto` | Set `0` to skip host GL prepend (**breaks NixOS Gazebo sim**) |
+| `LUCY_NIX_GL_WRAPPER` | auto | `nixGLIntel`, `nixGLDefault`, or `nixGL` |
+
+Vite proxies `/rosbridge` to `ws://127.0.0.1:9090`. Launcher sets `LUCY_LCP_*` vars for panel URLs — see [`launch_lucy.sh`](../launch_lucy.sh).
+
+## More
+
+| Document | Contents |
+|----------|----------|
+| [`docs/launcher_packages.md`](launcher_packages.md) | Adding packages to the Control Center |
+| [`docs/pixi_setup.md`](pixi_setup.md) | Pixi/RoboStack deps, lock workflow, component tasks |
+| [`docs/pixi_release.md`](pixi_release.md) | Release packaging (pixi-build-ros) |
+| [`windows/README.md`](../windows/README.md) | Windows installer and `Lucy.exe` |
+| [`src/lucy_ros_packages/docs/DEVELOPER.md`](../src/lucy_ros_packages/docs/DEVELOPER.md) | bringup, ros2_control, CI |
+| [`src/lucy_ros_packages/doc/ROS2_CONTROL.md`](../src/lucy_ros_packages/doc/ROS2_CONTROL.md) | ros2_control on Lucy |
+| [`src/inmoov_urdf/docs/DEVELOPER.md`](../src/inmoov_urdf/docs/DEVELOPER.md) | URDF, meshes, sim launches |
diff --git a/docs/launcher_packages.md b/docs/launcher_packages.md
index 94a1911..937982d 100644
--- a/docs/launcher_packages.md
+++ b/docs/launcher_packages.md
@@ -44,10 +44,12 @@ Every package entry in `config/launcher_config.json` uses the following fields t
When you run `./launch_lucy.sh`:
-1. It builds and enters the Docker container.
-2. It drops you into a **tmux** session named `lucy_ws`.
-3. It automatically runs `launcher.py` (the TUI) in the main window.
+1. It ensures the workspace is built (`install/setup.bash` exists).
+2. On Linux/macOS with **host tmux**, it starts or attaches to a **tmux** session named `lucy_ws`.
+3. The main window runs `pixi run -- python launcher.py` (Control Center TUI).
+
+Package commands in other tmux windows are wrapped in `pixi run` so each pane gets RoboStack and the colcon overlay. Display and OpenGL-related variables from your shell and `.env` are forwarded into those panes.
When you apply changes in `launcher.py`:
- **Core + Modifiers:** The script takes the core command, appends all active modifier commands, and spins up a dedicated `core` tmux window.
-- **Interfaces / Tools:** The script spins up a new tmux window named after the package's `id` and executes its command. Alternatively, if a complex command object is provided, it executes the explicit `start` and `stop` shell commands in the background.
+- **Interfaces / Tools:** The script spins up a new tmux window named after the package's `id` and executes its command via Pixi. Legacy complex `{start, stop, is_running}` objects are still supported for local overrides.
diff --git a/docs/pixi_release.md b/docs/pixi_release.md
new file mode 100644
index 0000000..698b053
--- /dev/null
+++ b/docs/pixi_release.md
@@ -0,0 +1,30 @@
+# Pixi release infrastructure (follow-up PR)
+
+This document outlines the **deferred** end-user release path. The PoC branch keeps day-to-day development on **colcon + Pixi activation**; release packaging is a separate PR after validation gates are green.
+
+## Scope
+
+Full-stack artifact (not `lucy_bringup` alone):
+
+- All `lucy_ros_packages/*`, `inmoov_urdf`, optional `thais_urdf`
+- External clones built in CI (`micro_ros_agent`, `audio_common`)
+- Launcher assets + control panel packaging strategy
+- Release CI manifest with `preview = ["pixi-build"]` only in the release workflow
+
+## Distribution options
+
+| Option | Best for |
+|--------|----------|
+| Private conda channel (prefix.dev) | Networked workstations |
+| Pixi Pack bundles | Jetson / air-gapped |
+
+## Suggested follow-up commits
+
+1. `chore(pixi): add per-package pixi.toml stubs for release builds`
+2. `ci(release): add tag-triggered workflow with pixi-build-ros`
+3. `docs(pixi): document end-user install from channel or Pixi Pack`
+
+## References
+
+- [pixi-build-ros](https://github.com/RoboStack/pixi-build-ros)
+- Dev workflow: [`docs/pixi_setup.md`](pixi_setup.md)
diff --git a/docs/pixi_setup.md b/docs/pixi_setup.md
new file mode 100644
index 0000000..1efc3f7
--- /dev/null
+++ b/docs/pixi_setup.md
@@ -0,0 +1,100 @@
+# Pixi + RoboStack setup
+
+Lucy uses [Pixi](https://pixi.prefix.dev/) with the [RoboStack Jazzy](https://robostack.github.io/) channel for ROS 2 Jazzy dependencies. Workspace packages under `src/` are built with **colcon** into `install/`; Pixi provides the base ROS environment and activation scripts.
+
+## Do not use rosdep
+
+**Do not run `rosdep install`** in this workspace. rosdep resolves system packages via apt and bypasses Pixi, which breaks the reproducible RoboStack environment.
+
+Instead:
+
+- Add missing ROS packages with `pixi add --feature ros ros-jazzy-` (or edit `[feature.ros.dependencies]` in `pixi.toml`).
+- Clone packages that are not on RoboStack into `src/` via [`config/repos.json`](../config/repos.json).
+
+## Dependency audit workflow
+
+When adding or changing workspace packages:
+
+1. Read `package.xml` / `CMakeLists.txt` for new `depend` / `find_package` entries.
+2. Check whether RoboStack provides `ros-jazzy-` on [prefix.dev/robostack-jazzy](https://prefix.dev/robostack-jazzy).
+3. If available, add to `pixi.toml` under `[feature.ros.dependencies]`.
+4. If not available (e.g. `micro_ros_agent`, `audio_common`), add a clone entry to `config/repos.json` (use `"optional": true` when the stack works without it).
+5. Regenerate the lock and install:
+
+```bash
+pixi install # updates pixi.lock for every platform in pixi.toml
+```
+
+`install.sh` runs `pixi lock` automatically if `pixi.lock` is missing, then `pixi install`.
+
+**Pixi ≥ 0.78** is recommended for multi-platform lock resolution (`curl -fsSL https://pixi.sh/install.sh | bash`).
+
+## RoboStack vs source clones
+
+| Approach | When to use |
+|----------|-------------|
+| **Pixi / RoboStack** | Standard ROS Jazzy packages (`ros-jazzy-desktop`, `ros-jazzy-ros-gz`, controllers, rosbridge, etc.) |
+| **Clone to `src/`** | Packages not on RoboStack, forks, or workspace-specific repos (`lucy_ros_packages`, `inmoov_urdf`) |
+| **Optional `src/` clones** | `micro_ros_agent`, `audio_common` — not on RoboStack; cloned by default for release/CI parity but **not required** for core bringup or the control panel. Entries carry `"optional": true` in `repos.json`; omit from `repos.json.local` if you do not need them |
+| **Local build** | RealSense — not in Pixi; run `./scripts/build_local_realsense.sh` **after** a normal `pixi run build` (or `LUCY_BUILD_REALSENSE=1 ./install.sh` at end of install) |
+
+### Mutex pin
+
+`ros2-distro-mutex = "0.15.*"` in `pixi.toml` keeps all RoboStack packages on the same rebuild cycle ([RoboStack #125](https://github.com/RoboStack/ros-jazzy/issues/125)).
+
+### Platform-specific deps
+
+Use Pixi target tables in `pixi.toml`:
+
+- `[target.linux]` — `gstreamer`, `libgl-devel`
+- **tmux** — host package (apt, Homebrew); not in Pixi
+- `[feature.ros.target.osx-*]` — `pygraphviz`, Cyclone DDS RMW
+
+## Build, launch, and activation
+
+| Task | Command |
+|------|---------|
+| Install env | `pixi install` |
+| Build workspace | `pixi run build` |
+| Control panel deps | `pixi run panel-install` |
+| Tests | `pixi run test` |
+| ROS doctor | `pixi run doctor` |
+| Dev shell (ROS CLI) | `pixi run shell` |
+
+### Launch component tasks
+
+Run in **separate terminals** (or use `./launch_lucy.sh` Control Center instead). ROS stacks use [`scripts/pixi_lucy_launch.sh`](../scripts/pixi_lucy_launch.sh).
+
+| Task | Command |
+|------|---------|
+| Core | `pixi run core` |
+| Gazebo GUI | `pixi run sim` |
+| Gazebo headless | `pixi run sim-headless` |
+| Sim + RViz | `pixi run sim-rviz` |
+| RViz | `pixi run rviz` |
+| Control panel | `pixi run control-panel` |
+| rqt | `pixi run rqt` |
+| Lucy CLI | `pixi run lucy-cli` |
+
+Robot package: `LUCY_ROBOT_PACKAGE=thais_urdf pixi run sim-headless` (default `inmoov_urdf`).
+
+See [`docs/developer_lucy_packages.md`](developer_lucy_packages.md#launch) for Control Center vs Pixi workflows and the debug shell.
+
+Colcon uses `--symlink-install` on Linux/macOS and `--merge-install` on Windows (`pixi.toml` `[feature.build]` tasks).
+
+Activation scripts (`install/setup.bash` or `install/setup.bat`) are wired via `[target.unix.activation]` / `[target.win.activation]` in `pixi.toml`, so `pixi run` and `pixi shell` automatically overlay the workspace. `GZ_SIM_SYSTEM_PLUGIN_PATH` points at conda Gazebo plugins (`lib` on Unix, `Library/bin` on Windows).
+
+## Lock refresh checklist
+
+After editing `pixi.toml`:
+
+```bash
+pixi install
+git add pixi.toml pixi.lock
+```
+
+Commit both files together so CI and other platforms stay in sync.
+
+## Release builds (follow-up)
+
+End-user **pre-built** packages via `pixi-build-ros` and conda channels are planned as a separate release workflow. See [`docs/pixi_release.md`](pixi_release.md).
diff --git a/scripts/build_local_realsense.sh b/scripts/build_local_realsense.sh
new file mode 100755
index 0000000..f1d6148
--- /dev/null
+++ b/scripts/build_local_realsense.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+# Build Intel librealsense and realsense-ros locally (not via Pixi/RoboStack).
+#
+# RoboStack does not publish ros-jazzy-realsense2-camera reliably across platforms
+# (especially linux-aarch64). Use this script when you need RealSense
+# hardware support. camera_ros (MJPEG/GStreamer) does not require this.
+#
+# Usage:
+# ./scripts/build_local_realsense.sh # default prefix: .local/realsense
+# LUCY_REALSENSE_PREFIX=/opt/realsense ./scripts/build_local_realsense.sh
+#
+# Run after a normal workspace build (install/setup.bash must exist). Does not
+# replace pixi run build — it adds librealsense + realsense-ros to install/.
+# install.sh runs this when LUCY_BUILD_REALSENSE=1 (after colcon + panel-install).
+#
+# Uses a portable CPU count for cmake -j (nproc on Linux, sysctl on macOS).
+
+set -euo pipefail
+
+parallel_jobs() {
+ nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4
+}
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+PREFIX="${LUCY_REALSENSE_PREFIX:-${WORKSPACE_ROOT}/.local/realsense}"
+LIBRS_TAG="${LUCY_LIBRS_TAG:-v2.56.2}"
+REALSENSE_ROS_BRANCH="${LUCY_REALSENSE_ROS_BRANCH:-ros2-master}"
+BUILD_DIR="${WORKSPACE_ROOT}/.local/build/realsense"
+SRC_DIR="${WORKSPACE_ROOT}/.local/src"
+
+mkdir -p "$BUILD_DIR" "$SRC_DIR" "$PREFIX"
+
+build_librealsense() {
+ local librs_dir="${SRC_DIR}/librealsense"
+ if [ ! -d "$librs_dir/.git" ]; then
+ echo "Cloning librealsense (${LIBRS_TAG}) ..."
+ git clone --depth 1 --branch "$LIBRS_TAG" https://github.com/IntelRealSense/librealsense.git "$librs_dir"
+ else
+ echo "Using existing librealsense clone at $librs_dir"
+ fi
+
+ cmake -S "$librs_dir" -B "${BUILD_DIR}/librealsense" \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$PREFIX" \
+ -DBUILD_EXAMPLES=OFF \
+ -DBUILD_GRAPHICAL_EXAMPLES=OFF \
+ -DBUILD_WITH_OPENMP=OFF
+
+ cmake --build "${BUILD_DIR}/librealsense" -j"$(parallel_jobs)"
+ cmake --install "${BUILD_DIR}/librealsense"
+}
+
+build_realsense_ros() {
+ local rs_ros_dir="${SRC_DIR}/realsense-ros"
+ if [ ! -d "$rs_ros_dir/.git" ]; then
+ echo "Cloning realsense-ros (${REALSENSE_ROS_BRANCH}) ..."
+ git clone --depth 1 --branch "$REALSENSE_ROS_BRANCH" https://github.com/IntelRealSense/realsense-ros.git "$rs_ros_dir"
+ else
+ echo "Using existing realsense-ros clone at $rs_ros_dir"
+ fi
+
+ export CMAKE_PREFIX_PATH="${PREFIX}:${CMAKE_PREFIX_PATH:-}"
+ export LD_LIBRARY_PATH="${PREFIX}/lib:${LD_LIBRARY_PATH:-}"
+
+ # Build into workspace overlay (requires ROS env from pixi shell / install/setup.bash).
+ if [ -f "${WORKSPACE_ROOT}/install/setup.bash" ]; then
+ # shellcheck disable=SC1091
+ source "${WORKSPACE_ROOT}/install/setup.bash"
+ elif [ -n "${ROS_DISTRO:-}" ]; then
+ # shellcheck disable=SC1091
+ source "/opt/ros/${ROS_DISTRO}/setup.bash"
+ else
+ echo "ROS environment not found. Run from pixi shell or after colcon build." >&2
+ exit 1
+ fi
+
+ colcon build --symlink-install \
+ --paths "$rs_ros_dir/realsense2_camera_msgs" "$rs_ros_dir/realsense2_camera" \
+ --cmake-args -DCMAKE_PREFIX_PATH="$PREFIX"
+}
+
+echo "Local RealSense build — install prefix: $PREFIX"
+build_librealsense
+build_realsense_ros
+echo "Done. Prefix: $PREFIX"
diff --git a/windows/Lucy.py b/windows/Lucy.py
index 3962d31..59b7dd6 100644
--- a/windows/Lucy.py
+++ b/windows/Lucy.py
@@ -1,14 +1,16 @@
# Windows launcher for the Lucy workspace.
#
-# Compiled to Lucy.exe via PyInstaller. Default behaviour: start the workspace
-# (Docker + tmux + launcher). Install/update/repair is handled by Lucy-Setup.exe
-# via the hidden --cli mode (see windows/install_runner.py).
+# Compiled to Lucy.exe via PyInstaller. Default behaviour: launch via Pixi
+# (native RoboStack + Control Center launcher). Install/update/repair is handled
+# by Lucy-Setup.exe via the hidden --cli mode (see windows/install_runner.py).
#
# PREREQUISITES:
-# 1. Docker Desktop for Windows (must be running)
-# 2. Workspace must be installed (run Lucy-Setup.exe first)
+# 1. Pixi — https://pixi.prefix.dev/latest/installation/
+# 2. Git Bash (runs launch_lucy.sh) — https://git-scm.com/install/windows
+# 3. Workspace installed (run Lucy-Setup.exe first)
import os
+import shutil
import subprocess
import sys
@@ -20,20 +22,12 @@
if _WINDOWS_DIR not in sys.path:
sys.path.insert(0, _WINDOWS_DIR)
-import install_ops # noqa: E402
-
-if getattr(sys, 'frozen', False):
+if getattr(sys, "frozen", False):
PROJECT_ROOT = os.path.dirname(sys.executable)
else:
PROJECT_ROOT = os.path.dirname(_WINDOWS_DIR)
-IMAGE_NAME = install_ops.IMAGE_NAME
-WORKSPACE_DIR_HOST = PROJECT_ROOT
-WORKSPACE_DIR_CONTAINER = install_ops.WORKSPACE_CONTAINER
-
-LCP_DEFAULT_PORT = 4004
-
-_CLI_MODES = frozenset(('install', 'update', 'repair', 'build-only', 'check-prereqs'))
+_CLI_MODES = frozenset(("install", "update", "repair", "build-only", "check-prereqs"))
def run_command(command, check=True, interactive=False):
@@ -41,9 +35,15 @@ def run_command(command, check=True, interactive=False):
print(f"--- Running: {' '.join(command)} ---")
try:
if interactive:
- return subprocess.run(command, check=check).returncode
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
- for line in iter(process.stdout.readline, ''):
+ return subprocess.run(command, check=check, cwd=PROJECT_ROOT).returncode
+ process = subprocess.Popen(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ cwd=PROJECT_ROOT,
+ )
+ for line in iter(process.stdout.readline, ""):
print(line.rstrip())
process.wait()
if check and process.returncode != 0:
@@ -57,170 +57,56 @@ def run_command(command, check=True, interactive=False):
return e.returncode
-def _read_env_value(env_path, key):
- if not os.path.exists(env_path):
- return None
- value = None
- try:
- with open(env_path, 'r') as f:
- for line in f:
- stripped = line.strip()
- if stripped.startswith('#') or '=' not in stripped:
- continue
- if stripped.startswith(f"{key}="):
- value = stripped.split('=', 1)[1].strip().strip('"').strip("'")
- except OSError:
- return None
- return value
-
-
-def _read_lcp_env_value(key):
- return _read_env_value(os.path.join(PROJECT_ROOT, 'src', 'lucy_control_panel', '.env'), key)
-
-
-def _read_root_env_value(key):
- return _read_env_value(os.path.join(PROJECT_ROOT, '.env'), key)
-
-
-def _lcp_container_port():
- val = _read_lcp_env_value('VITE_PORT')
- if val and val.isdigit():
- return int(val)
- return LCP_DEFAULT_PORT
-
-
-def _lcp_scheme():
- val = _read_lcp_env_value('VITE_HTTPS')
- if val and val.strip().lower() == 'true':
- return 'https'
- return 'http'
-
-
-def _docker_gui_args():
- gui_display = os.environ.get('DOCKER_GUI_DISPLAY', os.environ.get('DISPLAY', '')).strip()
- if sys.platform == 'win32' and not gui_display:
- gui_display = 'host.docker.internal:0'
-
- if not gui_display:
- return []
-
- args = ['-e', f'DISPLAY={gui_display}', '-e', 'QT_X11_NO_MITSHM=1']
-
- if os.environ.get('DOCKER_GUI_USE_HOST_NETWORK'):
- if sys.platform == 'win32':
- print("WARNING: DOCKER_GUI_USE_HOST_NETWORK is not supported on Windows; using DISPLAY only.")
- return args
- return ['--network=host', '-e', 'DISPLAY=:0', '-e', 'QT_X11_NO_MITSHM=1']
-
- if sys.platform == 'win32':
- if 'host.docker.internal' in gui_display:
- args.extend(['--add-host', 'host.docker.internal:host-gateway'])
- return args
-
- return args + ['-v', '/tmp/.X11-unix:/tmp/.X11-unix:rw']
-
-
-def _parse_display_host_port(display_value):
- if display_value.startswith(':'):
- return 'localhost', 6000 + int(display_value[1:].split('.')[0])
- host, _, display_str = display_value.rpartition(':')
- if not host:
- host = 'localhost'
- try:
- display_num = int(display_str)
- except ValueError:
- display_num = 0
- return host, 6000 + display_num
-
-
-def _docker_gui_diagnostics(gui_display, gui_args):
- print("--- GUI diagnostics ---")
- print(f"DISPLAY value used inside the container: {gui_display}")
- host, port = _parse_display_host_port(gui_display)
- print(f"Checking TCP connectivity to X server at {host}:{port}...")
-
- python_check = (
- "import os, socket, sys\n"
- "display = os.environ.get('DISPLAY', '')\n"
- "print('container DISPLAY=' + display)\n"
- f"host = '{host}'\n"
- f"port = {port}\n"
- "try:\n"
- " s = socket.create_connection((host, port), timeout=3)\n"
- " print('OK: connected to', host, port)\n"
- " s.close()\n"
- "except Exception as e:\n"
- " print('FAIL: could not connect to', host, port, e)\n"
- " sys.exit(1)\n"
+def _workspace_built():
+ install_dir = os.path.join(PROJECT_ROOT, "install")
+ return (
+ os.path.isfile(os.path.join(install_dir, "setup.bat"))
+ or os.path.isfile(os.path.join(install_dir, "setup.bash"))
)
- docker_cmd = ['docker', 'run', '--rm'] + install_ops.docker_run_platform_args(PROJECT_ROOT) + gui_args + [IMAGE_NAME, '-c', f'python3 -c "{python_check}"']
- run_command(docker_cmd, check=False)
-
def launch_workspace():
- """Start Docker, attach to the Lucy Control Center launcher inside tmux."""
- if not os.path.isfile(os.path.join(PROJECT_ROOT, 'install', 'setup.bash')):
- print("Workspace not built. Run Lucy-Setup.exe to install or update first.", file=sys.stderr)
+ """Start Pixi and attach to the Lucy Control Center launcher."""
+ if not _workspace_built():
+ print(
+ "Workspace not built. Run Lucy-Setup.exe to install or update first.",
+ file=sys.stderr,
+ )
sys.exit(1)
- print("Launching workspace...")
+ if shutil.which("pixi") is None:
+ print(
+ "Missing pixi. Install: https://pixi.prefix.dev/latest/installation/",
+ file=sys.stderr,
+ )
+ sys.exit(1)
- container_script = (
- "source /opt/ros/jazzy/setup.bash && "
- "cd /workspace && source install/setup.bash && "
- "tmux start-server && "
- "if ! tmux has-session -t lucy_ws 2>/dev/null; then "
- "tmux new-session -d -s lucy_ws -n 'Lucy Workspace' 'python3 /workspace/launcher.py'; "
- "fi && "
- "tmux attach-session -t lucy_ws"
- )
+ bash = shutil.which("bash")
+ launch_script = os.path.join(PROJECT_ROOT, "launch_lucy.sh")
+ if not bash:
+ print(
+ "Git Bash (bash) is required to run launch_lucy.sh on Windows.",
+ file=sys.stderr,
+ )
+ print("Install Git for Windows: https://git-scm.com/install/windows", file=sys.stderr)
+ sys.exit(1)
+ if not os.path.isfile(launch_script):
+ print(f"Missing launch script: {launch_script}", file=sys.stderr)
+ sys.exit(1)
- volume_mapping = install_ops.format_volume_mapping(WORKSPACE_DIR_HOST, WORKSPACE_DIR_CONTAINER)
- gui_args = _docker_gui_args()
- display_value = os.environ.get('DOCKER_GUI_DISPLAY', os.environ.get('DISPLAY', ''))
- if sys.platform == 'win32' and not display_value:
- display_value = 'host.docker.internal:0'
-
- if gui_args:
- print(f"Enabling GUI forwarding with DISPLAY={display_value}")
- _docker_gui_diagnostics(display_value, gui_args)
- else:
- print("No DISPLAY configured; running without GUI.")
-
- lcp_container_port = _lcp_container_port()
- lcp_host_port = lcp_container_port
- lcp_scheme = _lcp_scheme()
-
- val = _read_root_env_value('PORT_ROSBRIDGE')
- rosbridge_host_port = int(val) if val and val.isdigit() else 9090
-
- docker_cmd = [
- 'docker', 'run', '-it', '--rm',
- *install_ops.docker_run_platform_args(PROJECT_ROOT),
- '--name', 'lucy_dev_win',
- '-p', f'{rosbridge_host_port}:9090',
- '-p', f'{lcp_host_port}:{lcp_container_port}',
- '-v', volume_mapping,
- '-e', f'LUCY_LCP_PUBLISHED_HOST_PORT={lcp_host_port}',
- '-e', f'LUCY_LCP_CONTAINER_PORT={lcp_container_port}',
- '-e', f'LUCY_LCP_SCHEME={lcp_scheme}',
- ] + gui_args + [
- IMAGE_NAME,
- '-c', container_script
- ]
-
- run_command(docker_cmd, interactive=True)
+ print("Launching workspace...")
+ run_command([bash, launch_script], interactive=True)
def _is_cli_invocation():
- return len(sys.argv) > 1 and (sys.argv[1] == '--cli' or sys.argv[1] in _CLI_MODES)
+ return len(sys.argv) > 1 and (sys.argv[1] == "--cli" or sys.argv[1] in _CLI_MODES)
def _run_cli():
"""Install/update/repair — used by Lucy-Setup.exe, not exposed in the default UX."""
from install_runner import main as install_main
- argv = [a for a in sys.argv[1:] if a != '--cli']
+
+ argv = [a for a in sys.argv[1:] if a != "--cli"]
return install_main(argv)
diff --git a/windows/README.md b/windows/README.md
index 179a41a..dbc0f4e 100644
--- a/windows/README.md
+++ b/windows/README.md
@@ -5,7 +5,7 @@ On Windows, Lucy is split into two programs:
| Program | Purpose |
|---------|---------|
| **`Lucy-Setup.exe`** | Install, update, repair, pick version, developer mode |
-| **`Lucy.exe`** | Launch the workspace (Docker → Lucy Control Center) |
+| **`Lucy.exe`** | Launch the workspace (Pixi → Control Center) |
`windows/Lucy.py` is the PyInstaller source for `Lucy.exe`. It launches the workspace directly — there is no install menu. Use **`Lucy-Setup.exe`** for all install lifecycle tasks.
@@ -13,22 +13,17 @@ On Windows, Lucy is split into two programs:
Install the following before running the project. After each installation, close and reopen any terminal so the updated `PATH` is picked up.
-1. **Docker Desktop**: Download from [docs.docker.com/desktop/setup/install/windows-install](https://docs.docker.com/desktop/setup/install/windows-install/).
- - During installation, **uncheck "Use WSL 2 instead of Hyper-V"** unless you are an advanced/dev user who specifically needs the WSL 2 backend.
- - After install, **start Docker Desktop** and wait until it reports "running" before launching Lucy.
-2. **Git for Windows** (optional but recommended): Download from [git-scm.com/install/windows](https://git-scm.com/install/windows).
- - Without Git, the installer downloads sub-repositories as ZIP archives.
-3. **Python 3** (manual dev workflow only): Download from [python.org/downloads](https://www.python.org/downloads/).
-4. **Windows X server** (optional): Required for GUI apps such as `rqt` inside the Docker container.
- - We recommend [VcXsrv](https://github.com/marchaesen/vcxsrv/releases).
- - Start VcXsrv on display `0`, allow TCP connections, and disable access control if needed.
- - Make sure Windows Firewall allows port `6000`.
+1. **Pixi** — [pixi.prefix.dev/latest/installation](https://pixi.prefix.dev/latest/installation/) (≥ 0.78 recommended).
+2. **Git for Windows** — [git-scm.com/install/windows](https://git-scm.com/install/windows).
+ - Required for `bash launch_lucy.sh` (default launch path).
+ - Without Git, the installer downloads sub-repositories as ZIP archives.
+3. **Python 3** (manual dev workflow only) — [python.org/downloads](https://www.python.org/downloads/).
-> If you intend to solely use the control panel visualizer alongside command line tools, you can skip the installation of a third-party Windows X Server.
+GUI apps (RViz, Gazebo, rqt) run **natively** on Windows via RoboStack when OpenGL/display support is available. The control panel web viewer does not require a separate X server.
### CPU architecture (x64 / ARM64)
-The installer detects the host CPU automatically and builds the matching Docker image — `linux/amd64` on Intel/AMD PCs, `linux/arm64` on Windows-on-ARM devices. Native ARM detection works even though `Lucy.exe` itself is an x64 build running under emulation (it reads the true arch from `PROCESSOR_ARCHITEW6432`). To force a platform, set `LUCY_DOCKER_PLATFORM` (e.g. `linux/amd64`) before running, or drop a `.lucy-docker-platform` file in the install folder.
+Pixi resolves **`win-64`** from `pixi.lock` on Intel/AMD and Windows-on-ARM hosts. Colcon uses `--merge-install` on Windows per RoboStack guidance.
## Installation (end users)
@@ -40,33 +35,41 @@ The installer:
- Creates a **Start Menu** shortcut to `Lucy.exe`
- Lets you choose **Fresh install**, **Update**, or **Repair**
- Lets you pick a **lucy_ws version** (latest `master` or a release tag)
-- **Always runs Install/Update** after setup (opens a console — clones sub-repos on `master`, builds Docker image and workspace, then launches Lucy)
+- Runs install/update after setup (clones sub-repos, `pixi install`, colcon build)
- Offers **Developer install** (off by default): requires Git, uses SSH clones and `DEV=true`
-After setup, open **Lucy** from the Start Menu — it launches the workspace directly.
+After setup, open **Lucy** from the Start Menu — it runs `bash launch_lucy.sh` and opens the Control Center.
To **update** or **repair**, run **`Lucy-Setup.exe`** again and pick the matching install mode.
### Control Panel
-In the **Lucy Control Center**, enable **Core + Control Panel**. Once it is running, the **Lucy Control Panel is accessible in your browser at [http://localhost:4004](http://localhost:4004)** (or the next free port if 4004 is already in use). The launcher also prints the exact URL next to the Control Panel entry once it is up.
+In the **Lucy Control Center**, enable **Core + Control Panel**. The panel is at [http://localhost:4004](http://localhost:4004). The launcher prints the exact URL when the panel is running.
## Manual install (developers)
-Clone the repo, then run install via CLI (same logic as the installer):
+Clone the repo, then install via CLI (same logic as the installer):
```powershell
cd C:\Users\\lucy_ws
-python windows/Lucy.py --cli install --repos-branch master
+python windows\Lucy.py --cli install --repos-branch master
```
-Then launch:
+Or use Pixi directly from Git Bash / WSL:
+
+```bash
+./install.sh
+pixi run build
+pixi run panel-install
+```
+
+Launch:
```powershell
-python windows/Lucy.py
+python windows\Lucy.py
```
-Or use the root Linux manager if you prefer WSL/Git Bash: `python3 Lucy.py` (full menu — see the main [README](../README.md)).
+Or from Git Bash: `./launch_lucy.sh` or `python3 Lucy.py` (full TUI — see the main [README](../README.md)).
### Advanced CLI (installer internals)
@@ -101,5 +104,5 @@ python -c "from PIL import Image; Image.open('path\to\lucy-logo.jpg').save('wind
## Terminal choice
-- **Native Windows:** use `Lucy.exe` (installed) or `python windows/Lucy.py` (from a clone).
-- **WSL / Git Bash:** use the root `Lucy.py` and `install.sh` / `launch_lucy.sh` instead.
+- **Native Windows:** `Lucy.exe` (installed) or `python windows/Lucy.py` (from a clone) — uses Git Bash for launch.
+- **Git Bash / WSL:** root `Lucy.py`, `install.sh`, and `launch_lucy.sh` (recommended for developers).
diff --git a/windows/install_ops.py b/windows/install_ops.py
index 4dbdcf7..ec5996d 100644
--- a/windows/install_ops.py
+++ b/windows/install_ops.py
@@ -6,7 +6,6 @@
from __future__ import annotations
-import hashlib
import json
import os
import platform
@@ -23,16 +22,12 @@
REQUIREMENT_DOCS = {
"python": ("Python 3", "https://www.python.org/downloads/"),
"git": ("Git for Windows", "https://git-scm.com/install/windows"),
- "docker": ("Docker Desktop", "https://docs.docker.com/desktop/setup/install/windows-install/"),
- "xserver": ("Windows X server (optional)", "https://github.com/marchaesen/vcxsrv/releases"),
+ "pixi": ("Pixi", "https://pixi.prefix.dev/latest/installation/"),
}
LUCY_WS_GITHUB = "Sentience-Robotics/lucy_ws"
DEFAULT_REPOS_BRANCH = "master"
-IMAGE_NAME = "lucy_ros2:jazzy"
-WORKSPACE_CONTAINER = "/workspace"
-DOCKER_PLATFORM_FILE = ".lucy-docker-platform"
-DOCKER_IMAGE_LABEL = "lucy.dockerfile.sha256"
+MIN_PIXI_VERSION = "0.78.0"
InstallMode = str # "install" | "update" | "repair" | "build-only"
@@ -117,12 +112,46 @@ def git_available() -> bool:
return False
-def docker_available() -> bool:
+def pixi_available() -> bool:
+ return shutil.which("pixi") is not None
+
+
+def _pixi_version_ok() -> bool:
+ if not pixi_available():
+ return False
try:
- result = _run_quiet(["docker", "version"])
- return result.returncode == 0
+ result = _run_quiet(["pixi", "--version"])
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
return False
+ if result.returncode != 0:
+ return False
+ parts = result.stdout.strip().split()
+ version = parts[1] if len(parts) >= 2 else ""
+ if not version:
+ return False
+ return _version_at_least(version, MIN_PIXI_VERSION)
+
+
+def _version_at_least(version: str, minimum: str) -> bool:
+ def parse(v: str) -> list[int]:
+ out: list[int] = []
+ for piece in v.split("."):
+ try:
+ out.append(int(piece))
+ except ValueError:
+ break
+ return out
+
+ cur = parse(version)
+ minv = parse(minimum)
+ for i in range(max(len(cur), len(minv))):
+ c = cur[i] if i < len(cur) else 0
+ m = minv[i] if i < len(minv) else 0
+ if c > m:
+ return True
+ if c < m:
+ return False
+ return True
def python_available() -> bool:
@@ -157,13 +186,21 @@ def check_prerequisites(
name, url = REQUIREMENT_DOCS["python"]
issues.append({"id": "python", "name": name, "url": url, "detail": "python not found"})
- if not docker_available():
- name, url = REQUIREMENT_DOCS["docker"]
+ if not pixi_available():
+ name, url = REQUIREMENT_DOCS["pixi"]
+ issues.append({
+ "id": "pixi",
+ "name": name,
+ "url": url,
+ "detail": "pixi not found in PATH",
+ })
+ elif not _pixi_version_ok():
+ name, url = REQUIREMENT_DOCS["pixi"]
issues.append({
- "id": "docker",
+ "id": "pixi",
"name": name,
"url": url,
- "detail": "docker not found or Docker Desktop is not running",
+ "detail": f"pixi must be >= {MIN_PIXI_VERSION} for multi-platform lock support",
})
if developer or not git_available():
@@ -206,12 +243,12 @@ def parse_repos(project_root: str, developer: bool, repos_branch: Optional[str]
repos = []
for repo in data.get("repos", []):
- name = repo.get("name", "").strip()
+ name = repo.get("name", "").strip().strip("\r\n")
if not name:
continue
- branch = repos_branch or repo.get("branch", DEFAULT_REPOS_BRANCH)
- url_https = (repo.get("url_https") or repo.get("url") or "").strip()
- url_ssh = (repo.get("url_ssh") or "").strip()
+ branch = (repos_branch or repo.get("branch", DEFAULT_REPOS_BRANCH)).strip().strip("\r\n")
+ url_https = (repo.get("url_https") or repo.get("url") or "").strip().strip("\r\n")
+ url_ssh = (repo.get("url_ssh") or "").strip().strip("\r\n")
url = (url_ssh or url_https) if developer else (url_https or url_ssh)
if url:
repos.append({"name": name, "branch": branch, "url": url})
@@ -242,18 +279,7 @@ def _safe_rmtree(path: str) -> None:
def remove_workspace_src_repo(project_root: str, name: str, run_command: Callable) -> None:
- src_path = os.path.join(project_root, "src", name)
- _safe_rmtree(src_path)
- volume = format_volume_mapping(project_root, WORKSPACE_CONTAINER)
- run_command(
- [
- "docker", "run", "--rm",
- "-v", volume,
- IMAGE_NAME,
- "-c", f"rm -rf {WORKSPACE_CONTAINER}/src/{name}",
- ],
- check=False,
- )
+ _safe_rmtree(os.path.join(project_root, "src", name))
def _extract_zip_to_dest(zip_path: str, dest: str, repo_name: str) -> None:
@@ -401,11 +427,6 @@ def install_repos(
return effective_method
-def format_volume_mapping(host_path: str, container_path: str) -> str:
- host_abs = os.path.abspath(host_path)
- return host_abs.replace("\\", "/") + ":" + container_path
-
-
def _native_machine() -> str:
"""Best-effort *native* CPU arch, seeing through Windows x64 emulation.
@@ -421,130 +442,43 @@ def _native_machine() -> str:
return platform.machine().lower()
-def host_container_platform() -> str:
- """Map the host CPU to a Docker Linux platform (mirrors docker/ensure_image.sh)."""
+def host_pixi_platform() -> str:
+ """Map the native host to a Pixi platform id (pixi.toml / pixi.lock)."""
machine = _native_machine()
- if machine in ("x86_64", "amd64", "x86"):
- return "linux/amd64"
+ if sys.platform == "win32":
+ return "win-64"
+ if sys.platform == "darwin":
+ if machine in ("aarch64", "arm64"):
+ return "osx-arm64"
+ return "osx-64"
if machine in ("aarch64", "arm64"):
- return "linux/arm64"
- return f"linux/{machine}"
-
-
-def workspace_target_platform(project_root: str) -> str:
- """Target platform: LUCY_DOCKER_PLATFORM, then .lucy-docker-platform, then host arch."""
- override = os.environ.get("LUCY_DOCKER_PLATFORM", "").strip()
- if override:
- return override
- marker = os.path.join(project_root, DOCKER_PLATFORM_FILE)
- if os.path.isfile(marker):
- try:
- with open(marker, "r", encoding="utf-8") as f:
- value = f.readline().strip()
- if value:
- return value
- except OSError:
- pass
- return host_container_platform()
-
-
-def _platform_build_settings(target_platform: str) -> tuple[str, int]:
- """Return (base_image, install_vnc) for a target platform.
-
- The Jazzy image is built on ubuntu:24.04 (Noble); arm64 also enables the
- optional VNC desktop tooling in Dockerfile.jazzy.
- """
- base_image = "ubuntu:24.04"
- install_vnc = 1 if target_platform == "linux/arm64" else 0
- if os.environ.get("LUCY_FORCE_VNC", "").strip().lower() in ("1", "true", "yes"):
- install_vnc = 1
- return base_image, install_vnc
-
-
-def _dockerfile_build_hash(dockerfile: str) -> str:
- """sha256 of the Dockerfile ignoring comments/blank lines (mirrors ensure_image.sh)."""
- kept = []
- with open(dockerfile, "r", encoding="utf-8") as f:
- for line in f:
- line = line.rstrip("\n")
- stripped = line.strip()
- if not stripped or stripped.startswith("#"):
- continue
- kept.append(line.rstrip())
- payload = ("\n".join(kept) + "\n").encode("utf-8")
- return hashlib.sha256(payload).hexdigest()
+ return "linux-aarch64"
+ return "linux-64"
-def _current_image_label(image_name: str) -> Optional[str]:
- try:
- result = subprocess.run(
- ["docker", "image", "inspect", image_name,
- "--format", '{{index .Config.Labels "' + DOCKER_IMAGE_LABEL + '"}}'],
- capture_output=True, text=True,
- )
- except FileNotFoundError:
- return None
- if result.returncode != 0:
- return None
- return result.stdout.strip()
+def host_container_platform() -> str:
+ """Legacy alias used by Windows CI — returns Pixi platform, not Docker."""
+ return host_pixi_platform()
-def docker_run_platform_args(project_root: str) -> list[str]:
- """`--platform ` so the daemon never guesses the run architecture."""
- return ["--platform", workspace_target_platform(project_root)]
+def pixi_install(project_root: str, run_command: Callable, log: Callable[[str], None] = print) -> None:
+ lock_path = os.path.join(project_root, "pixi.lock")
+ if not os.path.isfile(lock_path):
+ log("No pixi.lock — running pixi lock...")
+ run_command(["pixi", "lock"])
+ log("Installing Pixi environment (RoboStack Jazzy)...")
+ run_command(["pixi", "install"])
-def build_docker_image(
+def build_workspace(
project_root: str,
run_command: Callable,
log: Callable[[str], None] = print,
- force_rebuild: bool = False,
) -> None:
- dockerfile = os.path.join(project_root, "docker", "Dockerfile.jazzy")
- target_platform = workspace_target_platform(project_root)
- base_image, install_vnc = _platform_build_settings(target_platform)
- build_hash = _dockerfile_build_hash(dockerfile)
- want_label = f"{build_hash}|{target_platform}|vnc={install_vnc}"
-
- if not force_rebuild and _current_image_label(IMAGE_NAME) == want_label:
- log(f"Docker image {IMAGE_NAME} is up to date ({target_platform}); skipping build.")
- return
-
- log(f"Building Docker image for {target_platform} (base: {base_image})...")
- run_command([
- "docker", "build",
- "--platform", target_platform,
- "-f", dockerfile,
- "--build-arg", f"LUCY_FROM_PLATFORM={target_platform}",
- "--build-arg", f"LUCY_BASE_IMAGE={base_image}",
- "--build-arg", f"LUCY_INSTALL_VNC={install_vnc}",
- "--build-arg", f"DOCKERFILE_SHA256={build_hash}",
- "--build-arg", f"LUCY_DOCKER_BUILD_PLATFORM={target_platform}",
- "-t", IMAGE_NAME,
- project_root,
- ])
-
-
-def build_workspace(project_root: str, run_command: Callable, log: Callable[[str], None] = print) -> None:
- log("Building workspace inside the container...")
- inner_cmd = (
- "source /opt/ros/jazzy/setup.bash && "
- "cd /workspace && "
- 'rosdep install --from-paths src --ignore-src -r -y --skip-keys="audio_common thais_urdf" && '
- "rm -rf build/camera_ros install/camera_ros && "
- "colcon build --symlink-install && "
- 'if [ -f src/lucy_control_panel/package.json ]; then '
- "(cd src/lucy_control_panel && yarn install); "
- "fi"
- )
- volume = format_volume_mapping(project_root, WORKSPACE_CONTAINER)
- run_command([
- "docker", "run", "--rm",
- *docker_run_platform_args(project_root),
- "-v", volume,
- IMAGE_NAME,
- "-c", inner_cmd,
- ])
+ log("Building workspace (colcon via Pixi)...")
+ run_command(["pixi", "run", "build"])
+ log("Installing control panel dependencies (yarn)...")
+ run_command(["pixi", "run", "panel-install"])
def set_dev_mode(project_root: str, enabled: bool) -> None:
@@ -600,7 +534,7 @@ def run_install_flow(
profile["fetch_method"] = effective
set_dev_mode(project_root, dev)
- build_docker_image(project_root, run_command, log, force_rebuild=(mode == "repair"))
+ pixi_install(project_root, run_command, log)
if mode in ("install", "update", "repair", "build-only"):
build_workspace(project_root, run_command, log)
diff --git a/windows/installer/Lucy.nsi b/windows/installer/Lucy.nsi
index 519d4cb..7d55b0a 100644
--- a/windows/installer/Lucy.nsi
+++ b/windows/installer/Lucy.nsi
@@ -1,6 +1,6 @@
; Lucy Windows installer (NSIS) — bundles the workspace + Lucy.exe, then runs the
; install via "Lucy.exe --cli ...". NSIS is used (instead of Inno Setup) because
-; nsExec::ExecToLog streams the long docker/colcon build output live into the
+; nsExec::ExecToLog streams the long pixi/colcon build output live into the
; installer's details log.
;
; Build: windows/build_installer.ps1 (requires PyInstaller + NSIS / makensis)
@@ -16,8 +16,7 @@ Unicode true
!define MyAppURL "https://github.com/Sentience-Robotics/lucy_ws"
!define MyAppExeName "Lucy.exe"
-!define DOC_DOCKER "https://docs.docker.com/desktop/setup/install/windows-install/"
-!define DOC_VCXSRV "https://github.com/marchaesen/vcxsrv/releases"
+!define DOC_PIXI "https://pixi.prefix.dev/latest/installation/"
!define DOC_GIT "https://git-scm.com/install/windows"
!define DOC_PYTHON "https://www.python.org/downloads/"
@@ -56,7 +55,7 @@ Var ModeCombo
Var VersionCombo
Var DevCheck
Var RefreshCheck
-Var DockerCheck
+Var PixiCheck
Var PrereqText
; ----------------------------------------------------------------------------
@@ -214,23 +213,23 @@ Function PrereqPageCreate
Abort
${EndIf}
- StrCpy $PrereqText "Required:$\r$\n Docker Desktop$\r$\n ${DOC_DOCKER}$\r$\n Uncheck $\"Use WSL 2$\" unless you need it for advanced development.$\r$\n Configure Docker resources to use at least 5 GB RAM.$\r$\n$\r$\nOptional (GUI apps):$\r$\n VcXsrv$\r$\n ${DOC_VCXSRV}$\r$\n$\r$\nOptional (developers):$\r$\n Git for Windows$\r$\n ${DOC_GIT}$\r$\n Without Git, repositories are downloaded as ZIP archives.$\r$\n Python 3$\r$\n ${DOC_PYTHON}"
+ StrCpy $PrereqText "Required:$\r$\n Pixi$\r$\n ${DOC_PIXI}$\r$\n Git for Windows (launch via bash launch_lucy.sh)$\r$\n ${DOC_GIT}$\r$\n$\r$\nOptional (developers):$\r$\n Python 3$\r$\n ${DOC_PYTHON}$\r$\n Without Git, repositories are downloaded as ZIP archives."
; Read-only multiline edit; reliably displays/wraps and scrolls if needed.
${NSD_CreateMLText} 0 0 100% -22u $PrereqText
Pop $1
${NSD_Edit_SetReadOnly} $1 1
- ${NSD_CreateCheckbox} 0 -18u 100% 12u "Docker Desktop is installed"
- Pop $DockerCheck
+ ${NSD_CreateCheckbox} 0 -18u 100% 12u "Pixi is installed"
+ Pop $PixiCheck
nsDialogs::Show
FunctionEnd
Function PrereqPageLeave
- ${NSD_GetState} $DockerCheck $0
+ ${NSD_GetState} $PixiCheck $0
${If} $0 != ${BST_CHECKED}
- MessageBox MB_OK|MB_ICONEXCLAMATION "Please confirm that Docker Desktop is installed before continuing."
+ MessageBox MB_OK|MB_ICONEXCLAMATION "Please confirm that Pixi is installed before continuing."
Abort
${EndIf}
FunctionEnd
@@ -239,13 +238,13 @@ FunctionEnd
; Prerequisite failure dialog (mirrors the old Inno ShowPrerequisitesFailed)
; ----------------------------------------------------------------------------
Function ShowPrerequisitesFailed
- StrCpy $0 "Required software is missing or not running, so the workspace install was not started.$\r$\n$\r$\nRequired:$\r$\n Docker Desktop must be installed and running.$\r$\n"
+ StrCpy $0 "Required software is missing or not available, so the workspace install was not started.$\r$\n$\r$\nRequired:$\r$\n Pixi must be installed and on PATH.$\r$\n"
${If} $DeveloperInstall == "1"
StrCpy $0 "$0$\r$\nDeveloper install also requires Git for Windows.$\r$\n"
${EndIf}
- StrCpy $0 "$0$\r$\nLucy was copied to your PC. After fixing the items above, run Lucy-Setup.exe again and choose Update.$\r$\n$\r$\nOpen the Docker Desktop install page now?"
+ StrCpy $0 "$0$\r$\nLucy was copied to your PC. After fixing the items above, run Lucy-Setup.exe again and choose Update.$\r$\n$\r$\nOpen the Pixi install page now?"
MessageBox MB_YESNO|MB_ICONEXCLAMATION "$0" IDNO +2
- ExecShell "open" "${DOC_DOCKER}"
+ ExecShell "open" "${DOC_PIXI}"
FunctionEnd
; ----------------------------------------------------------------------------
@@ -254,7 +253,8 @@ FunctionEnd
Section "Install"
SetOutPath "$INSTDIR"
File "..\..\dist\${MyAppExeName}"
- File "..\..\docker\Dockerfile.jazzy"
+ File "..\..\pixi.toml"
+ File "..\..\pixi.lock"
File "..\..\install.sh"
File "..\..\launch_lucy.sh"
File "..\..\Lucy.py"
@@ -265,9 +265,6 @@ Section "Install"
SetOutPath "$INSTDIR\config"
File /r "..\..\config\*"
- SetOutPath "$INSTDIR\docker"
- File /r "..\..\docker\*"
-
SetOutPath "$INSTDIR\windows"
File /r /x "installer" /x "build_installer.ps1" "..\..\windows\*"
@@ -313,7 +310,7 @@ Section "Install"
StrCpy $2 "$2 --refresh-workspace"
${EndIf}
- DetailPrint "Running $InstallMode (cloning repos + building Docker image + workspace; this can take a while)..."
+ DetailPrint "Running $InstallMode (cloning repos + pixi install + colcon build; this can take a while)..."
nsExec::ExecToLog $2
Pop $3
${If} $3 == 0