@rainbow-robotics/muscat-plugin-kit 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/docker/Dockerfile.cpp +22 -5
- package/cli/docker/Dockerfile.dev +25 -5
- package/cli/docker/Dockerfile.python +19 -0
- package/cli/src/bridge.mjs +19 -2
- package/cli/src/build.mjs +31 -79
- package/cli/src/cpp.mjs +10 -6
- package/cli/src/dev.mjs +7 -2
- package/cli/src/devBack.mjs +44 -34
- package/cli/src/host_agent.py +143 -83
- package/cli/src/mcp.mjs +75 -16
- package/cli/src/new.mjs +5 -2
- package/cli/src/ports.mjs +21 -7
- package/cli/src/python.mjs +16 -20
- package/cli/src/ros2.mjs +13 -8
- package/cli/src/sdk.mjs +23 -21
- package/cli/src/templates-embedded.mjs +1 -1
- package/cli/src/type.mjs +42 -8
- package/cli/src/util.mjs +53 -5
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-CRNvRzJD.js → Color-YHDXOIA2-DPI4Is-F.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-C4S_Kfa9.js → Docs-CKTHz6Db.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DurNQvJo.js → Docs-CQnf2HJu.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BV0FgAF_.js → Docs-CbCty_Qg.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-axk94PIl.js → Docs-DDYksosp.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-80l_9VTs.js → Docs-Dow4_Rnl.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BgDx9SSn.js → Docs-TMe-1-I2.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-CEdKXElc.js → DocsRenderer-CFRXHY34-DSTzrCAK.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-CyZ4AE2b.js → NxAvatar.stories-B3OxXBgc.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-Bx8UtGNd.js → NxButton.stories-UTqX8ZkA.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-skizAdd5.js → NxNumberInput.stories-GoLEe-uJ.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-gtIxt2u0.js → NxPagination.stories-Jj6dt6eG.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-DSHMk3N5.js → NxPointerHoldButton.stories-tLN9u4nC.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-C1YtrFZ1.js → NxRangeInput.stories-C-MK73xb.js} +1 -1
- package/cli/storybook-dist/assets/{NxSelectBox.stories-DCySAtBp.js → NxSelectBox.stories-CBcFcOIX.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-gSaMOWo7.js → NxSlideRange.stories-oNQkoW66.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-DF0DBiyG.js → NxSwitch.stories-CqBkO5GC.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-Dill0M6z.js → NxTextArea.stories-DtysSyl4.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-Bax14r48.js → NxTextInput.stories-BOLDbSgF.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-CCwz_pIV.js → RBAccordion.stories-D9-wu9LY.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BjDxxdPV.js → RBAccordionGroup.stories-BPfnIKm6.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-5z6GasgP.js → RBAlert.stories-CGn-c2ac.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-BwcM7i7w.js → RBBadge.stories-CBLmeoq1.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-DOeQH3rK.js → RBBattery.stories-oo47rbDh.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-DcmiOXAV.js → RBCard.stories-SiD2VfQ4.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-BYru87fX.js → RBCheckBox.stories-sEEVrjSg.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CXUjtR1a.js → RBCheckIconButton.stories-C5GQUjZf.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-BSOQV7Du.js → RBCheckLabelButton.stories-DTaTqBvJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-ByBmeukU.js → RBChipTab.stories-3AZr4kOz.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-GtJ01RTr.js → RBChipsBoxTab.stories-hCJnBYQl.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-C9saET1u.js → RBColorIcons.stories-BX4RJygu.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-CcvnWlWZ.js → RBCommand.stories-BidZ4H8f.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-hdBVSdBS.js → RBConfirm.stories-D3Txw1kT.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-BE9JQl-E.js → RBDateInput.stories-CLxRaN6T.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-BaWuDgl_.js → RBDialog.stories-CEhmUGp-.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-D3INKm_Z.js → RBDivider.stories-peXP2KG9.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-BZ8V0J6-.js → RBDrawer.stories-38iDf6uC.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-DxLi8HRw.js → RBDropdownMenu.stories-Dsi8tqSy.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-DQcvDt2S.js → RBEmptyView.stories-VMxWhzLP.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-DJfuTpQ0.js → RBIconButton.stories-BPLhceGO.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-BSsyb0BK.js → RBIcons.stories-DuYdO951.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-Di3th--x.js → RBInlineLabelButton.stories-CGNYhQyl.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-DfGazm4n.js → RBJoyStick.stories-CCmn-LMy.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-CLbxzW56.js → RBLabelButton.stories-IkKjthGk.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-DeiuDm_P.js → RBLight.stories-eJFQOjPH.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-BZQTV9e5.js → RBMenubar.stories-kg-xXGrW.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-DDNmdi_n.js → RBMultiRangeInput.stories-Czr1r3gd.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-CLBCEyCU.js → RBPagination.stories-Dyu6Khnl.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-DRI5Kfa-.js → RBPopover.stories-CAM6hpf2.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-DIAmypoJ.js → RBPrompt.stories-B4M-veaf.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-BS_N4aLf.js → RBRadioBox.stories-DeAOKvil.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-BTm8cYvm.js → RBRadioButton.stories-DjXwYXFy.js} +1 -1
- package/cli/storybook-dist/assets/{RBRangeDateInput.stories-D5hBsSyB.js → RBRangeDateInput.stories-9pzKhXGf.js} +1 -1
- package/cli/storybook-dist/assets/{RBResizable.stories-CxPEkbXQ.js → RBResizable.stories-B4Hi4DUD.js} +1 -1
- package/cli/storybook-dist/assets/{RBSelectBox.stories-FUQ7AcUi.js → RBSelectBox.stories-tVVv4c6z.js} +1 -1
- package/cli/storybook-dist/assets/{RBSheet.stories-mhVHH_71.js → RBSheet.stories-BwOCmaNN.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-5FMkLUim.js → RBSideNumberInput.stories-BbGOIitU.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-Bnhcp9pz.js → RBSlider.stories-yIDdawYQ.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-Bf4416mP.js → RBSpinner.stories-CIJvpbgK.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-BNhFjRaV.js → RBSwitch.stories-Bo8hrSiq.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-Bsr7jlfb.js → RBTab.stories-Cq3N4Dgb.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-BkA6pKzs.js → RBTable.stories-DDeZf-Wb.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-D07Z7D5-.js → RBTableRow.stories-BeKVMSo3.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-DHnZiV21.js → RBTabs.stories-DovB7PPB.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-CtLMLuV_.js → RBTextArea.stories-D7H4RvP8.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-Chz0i1-u.js → RBTextField.stories-CmkNXeEk.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-Dk5cZfUF.js → RBTextInput.stories-hnxbQJsb.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-tZxbjmr8.js → RBToast.stories-BklGGkNh.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-VTe7a4xZ.js → RBToggle.stories-C56AXyxq.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-WiFnGGYx.js → RBToggleGroup.stories-CpLPdAF0.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-B_L4W7Mn.js → RBTooltip.stories-BdOnCwGk.js} +1 -1
- package/cli/storybook-dist/assets/{SideNumberInput-os5wKKf7.js → SideNumberInput-DLmM2_Dy.js} +80 -80
- package/cli/storybook-dist/assets/{entry-preview-COJgWzHb.js → entry-preview-tBizwvut.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-DQEjJuXA.js → iframe-BVXraKDc.js} +2 -2
- package/cli/storybook-dist/assets/{index-DEgQqQ_-.js → index-CI3FxSvF.js} +3 -3
- package/cli/storybook-dist/assets/{preview-BZ5PpEp_.js → preview-BUOha-H6.js} +2 -2
- package/cli/storybook-dist/assets/preview-DGIRF99V.js +3 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +65 -4
- package/cli/templates/common/py/main.py +5 -1
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +1 -2
- package/cli/templates/cpp/cpp/main.cpp +48 -8
- package/cli/templates/readme-cpp/README.md +969 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +1 -1
- package/package.json +3 -3
- package/cli/storybook-dist/assets/preview-CmCb8Xea.js +0 -3
|
@@ -10,18 +10,35 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
10
10
|
cmake \
|
|
11
11
|
&& rm -rf /var/lib/apt/lists/*
|
|
12
12
|
|
|
13
|
-
# Rainbow Robotics C++ SDK (
|
|
14
|
-
# find_package(rb_sdk). Pinned to the robot's SDK version — the binary links
|
|
15
|
-
# librb_sdk.so.<ver> by SONAME and the robot provides that exact .so at runtime.
|
|
13
|
+
# Rainbow Robotics C++ SDK for find_package(rb_sdk).
|
|
16
14
|
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
17
|
-
ARG RB_SDK_VERSION=0.1.
|
|
15
|
+
ARG RB_SDK_VERSION=0.1.3
|
|
18
16
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
19
17
|
&& install -d /etc/apt/keyrings \
|
|
20
18
|
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
21
|
-
&&
|
|
19
|
+
&& printf 'deb [signed-by=/etc/apt/keyrings/rb-sdk.asc] https://rainbowrobotics.github.io/rb-sdk %s main\n' stable dev > /etc/apt/sources.list.d/rb-sdk.list \
|
|
22
20
|
&& apt-get update && apt-get install -y --no-install-recommends "rainbow-rb-sdk-dev=${RB_SDK_VERSION}" \
|
|
23
21
|
&& rm -rf /var/lib/apt/lists/*
|
|
24
22
|
|
|
23
|
+
# MediaMTX — same version/ports as the Muscat runtime (rby2 scripts/backend/runtime/mediamtx.yml),
|
|
24
|
+
# plus webrtcAdditionalHosts so a browser on the dev host reaches the WebRTC media port.
|
|
25
|
+
ARG MEDIAMTX_VERSION=1.12.3
|
|
26
|
+
RUN set -eux; \
|
|
27
|
+
apt-get update; apt-get install -y --no-install-recommends ca-certificates curl; rm -rf /var/lib/apt/lists/*; \
|
|
28
|
+
case "$(uname -m)" in x86_64) a=amd64 ;; aarch64|arm64) a=arm64 ;; *) exit 1 ;; esac; \
|
|
29
|
+
curl -fsSL "https://github.com/bluenviron/mediamtx/releases/download/v${MEDIAMTX_VERSION}/mediamtx_v${MEDIAMTX_VERSION}_linux_${a}.tar.gz" \
|
|
30
|
+
| tar -xz -C /usr/local/bin mediamtx; \
|
|
31
|
+
mkdir -p /etc/mediamtx; \
|
|
32
|
+
printf '%s\n' \
|
|
33
|
+
'logLevel: warn' \
|
|
34
|
+
'rtsp: yes' 'rtspAddress: :8554' \
|
|
35
|
+
'webrtc: yes' 'webrtcAddress: :8889' \
|
|
36
|
+
'webrtcLocalUDPAddress: :8189' 'webrtcLocalTCPAddress: :8189' \
|
|
37
|
+
'webrtcAdditionalHosts: [127.0.0.1]' 'webrtcAllowOrigin: "*"' \
|
|
38
|
+
'hls: no' 'rtmp: no' 'srt: no' 'api: no' \
|
|
39
|
+
'paths:' ' all_others:' ' source: publisher' \
|
|
40
|
+
> /etc/mediamtx/mediamtx.yml
|
|
41
|
+
|
|
25
42
|
WORKDIR /plugin
|
|
26
43
|
|
|
27
44
|
CMD ["bash"]
|
|
@@ -9,18 +9,38 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
9
9
|
python3-colcon-common-extensions \
|
|
10
10
|
&& rm -rf /var/lib/apt/lists/*
|
|
11
11
|
|
|
12
|
-
# Rainbow Robotics C++ SDK (
|
|
13
|
-
# find_package(rb_sdk). Pinned to the robot's SDK version — the binary links
|
|
14
|
-
# librb_sdk.so.<ver> by SONAME and the robot provides that exact .so at runtime.
|
|
12
|
+
# Rainbow Robotics C++ SDK for find_package(rb_sdk).
|
|
15
13
|
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
16
|
-
ARG RB_SDK_VERSION=0.1.
|
|
14
|
+
ARG RB_SDK_VERSION=0.1.3
|
|
17
15
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
18
16
|
&& install -d /etc/apt/keyrings \
|
|
19
17
|
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
20
|
-
&&
|
|
18
|
+
&& printf 'deb [signed-by=/etc/apt/keyrings/rb-sdk.asc] https://rainbowrobotics.github.io/rb-sdk %s main\n' stable dev > /etc/apt/sources.list.d/rb-sdk.list \
|
|
21
19
|
&& apt-get update && apt-get install -y --no-install-recommends "rainbow-rb-sdk-dev=${RB_SDK_VERSION}" \
|
|
22
20
|
&& rm -rf /var/lib/apt/lists/*
|
|
23
21
|
|
|
22
|
+
# MediaMTX — same version/ports as the Muscat runtime (rby2 scripts/backend/runtime/mediamtx.yml),
|
|
23
|
+
# plus webrtcAdditionalHosts so a browser on the dev host reaches the WebRTC media port.
|
|
24
|
+
ARG MEDIAMTX_VERSION=1.12.3
|
|
25
|
+
RUN set -eux; \
|
|
26
|
+
apt-get update; apt-get install -y --no-install-recommends ca-certificates curl; rm -rf /var/lib/apt/lists/*; \
|
|
27
|
+
case "$(uname -m)" in x86_64) a=amd64 ;; aarch64|arm64) a=arm64 ;; *) exit 1 ;; esac; \
|
|
28
|
+
curl -fsSL "https://github.com/bluenviron/mediamtx/releases/download/v${MEDIAMTX_VERSION}/mediamtx_v${MEDIAMTX_VERSION}_linux_${a}.tar.gz" \
|
|
29
|
+
| tar -xz -C /usr/local/bin mediamtx; \
|
|
30
|
+
mkdir -p /etc/mediamtx; \
|
|
31
|
+
printf '%s\n' \
|
|
32
|
+
'logLevel: warn' \
|
|
33
|
+
'rtsp: yes' 'rtspAddress: :8554' \
|
|
34
|
+
'webrtc: yes' 'webrtcAddress: :8889' \
|
|
35
|
+
'webrtcLocalUDPAddress: :8189' 'webrtcLocalTCPAddress: :8189' \
|
|
36
|
+
'webrtcAdditionalHosts: [127.0.0.1]' 'webrtcAllowOrigin: "*"' \
|
|
37
|
+
'hls: no' 'rtmp: no' 'srt: no' 'api: no' \
|
|
38
|
+
'paths:' ' all_others:' ' source: publisher' \
|
|
39
|
+
> /etc/mediamtx/mediamtx.yml
|
|
40
|
+
|
|
41
|
+
# `docker exec` skips /ros_entrypoint.sh — source ROS for the login shells dev:back runs.
|
|
42
|
+
RUN echo ". /opt/ros/${ROS_DISTRO}/setup.sh" > /etc/profile.d/ros.sh
|
|
43
|
+
|
|
24
44
|
WORKDIR /plugin
|
|
25
45
|
|
|
26
46
|
CMD ["bash"]
|
|
@@ -15,6 +15,25 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
15
15
|
# cpp SDK shared lib must be present for the python module to load. Install the
|
|
16
16
|
# rainbow cpp SDK (apt, pinned to the robot's version) here once it exists.
|
|
17
17
|
|
|
18
|
+
# MediaMTX — same version/ports as the Muscat runtime (rby2 scripts/backend/runtime/mediamtx.yml),
|
|
19
|
+
# plus webrtcAdditionalHosts so a browser on the dev host reaches the WebRTC media port.
|
|
20
|
+
ARG MEDIAMTX_VERSION=1.12.3
|
|
21
|
+
RUN set -eux; \
|
|
22
|
+
apt-get update; apt-get install -y --no-install-recommends ca-certificates curl; rm -rf /var/lib/apt/lists/*; \
|
|
23
|
+
case "$(uname -m)" in x86_64) a=amd64 ;; aarch64|arm64) a=arm64 ;; *) exit 1 ;; esac; \
|
|
24
|
+
curl -fsSL "https://github.com/bluenviron/mediamtx/releases/download/v${MEDIAMTX_VERSION}/mediamtx_v${MEDIAMTX_VERSION}_linux_${a}.tar.gz" \
|
|
25
|
+
| tar -xz -C /usr/local/bin mediamtx; \
|
|
26
|
+
mkdir -p /etc/mediamtx; \
|
|
27
|
+
printf '%s\n' \
|
|
28
|
+
'logLevel: warn' \
|
|
29
|
+
'rtsp: yes' 'rtspAddress: :8554' \
|
|
30
|
+
'webrtc: yes' 'webrtcAddress: :8889' \
|
|
31
|
+
'webrtcLocalUDPAddress: :8189' 'webrtcLocalTCPAddress: :8189' \
|
|
32
|
+
'webrtcAdditionalHosts: [127.0.0.1]' 'webrtcAllowOrigin: "*"' \
|
|
33
|
+
'hls: no' 'rtmp: no' 'srt: no' 'api: no' \
|
|
34
|
+
'paths:' ' all_others:' ' source: publisher' \
|
|
35
|
+
> /etc/mediamtx/mediamtx.yml
|
|
36
|
+
|
|
18
37
|
WORKDIR /plugin
|
|
19
38
|
|
|
20
39
|
CMD ["bash"]
|
package/cli/src/bridge.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import { createServer, connect } from "node:net";
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
10
|
import { canSkipImageBuild, hasBin, kitRoot, log, pruneMuskatImages } from "./util.mjs";
|
|
11
|
+
import { MEDIA_PORTS, mediaPortFlags } from "./ports.mjs";
|
|
12
|
+
import { usesHostNetwork } from "./zenohDev.mjs";
|
|
11
13
|
|
|
12
14
|
export const BRIDGE_IMAGE = "muscat-pk-bridge";
|
|
13
15
|
export const BRIDGE_CONTAINER = "muscat-pk-bridge-run";
|
|
@@ -63,9 +65,16 @@ function ensureImage() {
|
|
|
63
65
|
pruneMuskatImages();
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
/** A running Muscat container (e.g. muscat-dev) that owns host port 10000, or null. */
|
|
69
|
+
export function localMuscatContainer() {
|
|
70
|
+
const r = spawnSync("docker", ["ps", "--filter", `publish=${BRIDGE_PORT}`, "--format", "{{.Names}}"], { encoding: "utf8" });
|
|
71
|
+
return (r.stdout || "").split("\n").map((n) => n.trim()).find((n) => n && n !== BRIDGE_CONTAINER) ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
66
74
|
/**
|
|
67
75
|
* Build the image, preflight the port, and start the bridge container.
|
|
68
|
-
* Returns
|
|
76
|
+
* Returns false if port 10000 was already in use (caller decides), else { media }:
|
|
77
|
+
* whether the MediaMTX ports are free for the backend's own MediaMTX.
|
|
69
78
|
*/
|
|
70
79
|
export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
71
80
|
assertDockerReady();
|
|
@@ -88,6 +97,13 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
88
97
|
}
|
|
89
98
|
const publishable = pluginPorts.filter((port) => !busy.includes(port));
|
|
90
99
|
|
|
100
|
+
const mediaBusy = [];
|
|
101
|
+
for (const port of MEDIA_PORTS) {
|
|
102
|
+
if (await isPortInUse(port)) mediaBusy.push(port);
|
|
103
|
+
}
|
|
104
|
+
const media = mediaBusy.length === 0;
|
|
105
|
+
if (!media) log.warn(`MediaMTX port(s) already in use: ${mediaBusy.join(", ")} — video streaming disabled in dev.`);
|
|
106
|
+
|
|
91
107
|
ensureImage();
|
|
92
108
|
|
|
93
109
|
log.step("starting Socket.IO bridge container…");
|
|
@@ -98,6 +114,7 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
98
114
|
"-p", `127.0.0.1:${BRIDGE_PORT}:${BRIDGE_PORT}`,
|
|
99
115
|
...(zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : []),
|
|
100
116
|
...publishable.flatMap((port) => ["-p", `127.0.0.1:${port}:${port}`]),
|
|
117
|
+
...(media && !usesHostNetwork() ? mediaPortFlags() : []),
|
|
101
118
|
];
|
|
102
119
|
const up = spawnSync(
|
|
103
120
|
"docker",
|
|
@@ -111,7 +128,7 @@ export async function startBridge({ zenohPort = null, pluginPorts = [] } = {}) {
|
|
|
111
128
|
if (zenohPort) log.step(`Zenoh dev peer port published on :${zenohPort}`);
|
|
112
129
|
if (publishable.length > 0)
|
|
113
130
|
log.step(`plugin backend ports published on 127.0.0.1: ${publishable.join(", ")}`);
|
|
114
|
-
return
|
|
131
|
+
return { media };
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
export function stopBridge() {
|
package/cli/src/build.mjs
CHANGED
|
@@ -75,33 +75,19 @@ function runViteBuild(pluginRoot, { mode = "production" } = {}) {
|
|
|
75
75
|
// rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
|
|
76
76
|
// rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
|
|
77
77
|
export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
78
|
-
if (manifestRuntime(root) !== "ros2") return
|
|
78
|
+
if (manifestRuntime(root) !== "ros2") return;
|
|
79
79
|
const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
|
|
80
|
-
|
|
81
|
-
assertDockerReady();
|
|
82
|
-
} catch (e) {
|
|
83
|
-
log.warn(`ros2 build skipped: ${e.message}`);
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
80
|
+
assertDockerReady();
|
|
86
81
|
|
|
87
82
|
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const skipped = [];
|
|
91
|
-
if (rosClient === "rclcpp") {
|
|
83
|
+
if (manifest.runtime?.rosClient === "rclcpp") {
|
|
92
84
|
// C++ ROS2: compile per arch so each robot picks the right install-{arch}/
|
|
93
85
|
for (const arch of archs) {
|
|
94
86
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
95
|
-
if (!dockerPlatform)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
colconBuild(root, { symlink: false, arch });
|
|
100
|
-
log.ok(`dist/ros2_ws/install-${arch}/ ready`);
|
|
101
|
-
} catch {
|
|
102
|
-
log.warn(`${arch} ros2 build skipped — QEMU not available`);
|
|
103
|
-
skipped.push(arch);
|
|
104
|
-
}
|
|
87
|
+
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
88
|
+
await ensureImage(`${ROS2_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
89
|
+
colconBuild(root, { symlink: false, arch });
|
|
90
|
+
log.ok(`dist/ros2_ws/install-${arch}/ ready`);
|
|
105
91
|
}
|
|
106
92
|
} else {
|
|
107
93
|
// rclpy / pure Python: arch-agnostic, build once
|
|
@@ -109,54 +95,37 @@ export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
|
109
95
|
colconBuild(root, { symlink: false });
|
|
110
96
|
log.ok("dist/ros2_ws/install/ ready");
|
|
111
97
|
}
|
|
112
|
-
return skipped;
|
|
113
98
|
}
|
|
114
99
|
|
|
115
100
|
// For cpp plugins, compile cpp/ inside the Ubuntu container (Release) per arch
|
|
116
101
|
// and copy each binary into dist/{arch}/{entry} so the ZIP carries relocatable
|
|
117
|
-
// robot-compatible executables.
|
|
118
|
-
// to dist/{entry} (legacy). Skipped (with a warning) if Docker is unavailable.
|
|
102
|
+
// robot-compatible executables.
|
|
119
103
|
export async function exportCppBuild(root, distDir, archs = ["amd64", "arm64"]) {
|
|
120
|
-
if (manifestRuntime(root) !== "cpp") return
|
|
104
|
+
if (manifestRuntime(root) !== "cpp") return;
|
|
121
105
|
const { assertDockerReady, ensureImage, cmakeBuild, CPP_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./cpp.mjs");
|
|
122
|
-
|
|
123
|
-
assertDockerReady();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
log.warn(`cpp build skipped: ${e.message}`);
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
106
|
+
assertDockerReady();
|
|
128
107
|
|
|
129
108
|
const entry = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.entry;
|
|
130
|
-
if (!entry)
|
|
131
|
-
log.warn("manifest.json runtime.entry not set — cpp build skipped");
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
109
|
+
if (!entry) throw new Error("manifest.json runtime.entry is required for cpp (the built executable name)");
|
|
134
110
|
|
|
135
|
-
const skipped = [];
|
|
136
111
|
for (const arch of archs) {
|
|
137
112
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
138
|
-
if (!dockerPlatform)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (existsSync(built)) {
|
|
145
|
-
const archDir = resolve(distDir, arch);
|
|
146
|
-
mkdirSync(archDir, { recursive: true });
|
|
147
|
-
const dest = resolve(archDir, entry);
|
|
148
|
-
copyFileSync(built, dest);
|
|
149
|
-
chmodSync(dest, 0o755);
|
|
150
|
-
log.ok(`cpp binary → dist/${arch}/${entry}`);
|
|
151
|
-
} else {
|
|
152
|
-
log.warn(`cpp binary not found at cpp/build-${arch}/${entry}`);
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
log.warn(`${arch} cpp build skipped — QEMU not available`);
|
|
156
|
-
skipped.push(arch);
|
|
113
|
+
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
114
|
+
await ensureImage(`${CPP_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
115
|
+
cmakeBuild(root, { buildType: "Release", arch });
|
|
116
|
+
const built = resolve(root, `cpp/build-${arch}`, entry);
|
|
117
|
+
if (!existsSync(built)) {
|
|
118
|
+
throw new Error(`cpp/build-${arch}/${entry} not found — the CMake target name must equal runtime.entry`);
|
|
157
119
|
}
|
|
120
|
+
const archDir = resolve(distDir, arch);
|
|
121
|
+
mkdirSync(archDir, { recursive: true });
|
|
122
|
+
const dest = resolve(archDir, entry);
|
|
123
|
+
copyFileSync(built, dest);
|
|
124
|
+
chmodSync(dest, 0o755);
|
|
125
|
+
const libDir = resolve(root, `cpp/build-${arch}`, "lib");
|
|
126
|
+
if (existsSync(libDir)) cpSync(libDir, resolve(archDir, "lib"), { recursive: true, verbatimSymlinks: true });
|
|
127
|
+
log.ok(`cpp binary → dist/${arch}/${entry}${existsSync(libDir) ? " (+ lib/)" : ""}`);
|
|
158
128
|
}
|
|
159
|
-
return skipped;
|
|
160
129
|
}
|
|
161
130
|
|
|
162
131
|
const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
|
|
@@ -426,11 +395,12 @@ export async function runBuild(args) {
|
|
|
426
395
|
|
|
427
396
|
await promptVersion(root);
|
|
428
397
|
const archs = await promptArch(args);
|
|
398
|
+
// Drop a previous ZIP of this version so a failed build can't leave a stale one to upload.
|
|
399
|
+
const version = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).version || "0.0.0";
|
|
400
|
+
rmSync(resolve(root, `${basename(root)}-${version}.zip`), { force: true });
|
|
429
401
|
|
|
430
402
|
const { pluginRoot, distDir } = await buildPlugin({ root, minify: true });
|
|
431
403
|
|
|
432
|
-
const skippedArches = { wheels: [], ros2: [], cpp: [] };
|
|
433
|
-
|
|
434
404
|
// python / rclpy: lock + wheel vendor in Ubuntu 24.04 container
|
|
435
405
|
if (!noLock) {
|
|
436
406
|
const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime ?? {};
|
|
@@ -439,30 +409,12 @@ export async function runBuild(args) {
|
|
|
439
409
|
(manifest.type === "ros2" && manifest.rosClient === "rclpy");
|
|
440
410
|
if (needsLock) {
|
|
441
411
|
const { lockAndVendor } = await import("./python.mjs");
|
|
442
|
-
|
|
443
|
-
skippedArches.wheels = await lockAndVendor(pluginRoot, archs);
|
|
444
|
-
} catch (e) {
|
|
445
|
-
log.warn(`python lock/vendor skipped: ${e.message}`);
|
|
446
|
-
skippedArches.wheels = archs;
|
|
447
|
-
}
|
|
412
|
+
await lockAndVendor(pluginRoot, archs);
|
|
448
413
|
}
|
|
449
414
|
}
|
|
450
415
|
|
|
451
|
-
|
|
452
|
-
|
|
416
|
+
await exportRos2Build(root, archs);
|
|
417
|
+
await exportCppBuild(root, distDir, archs);
|
|
453
418
|
calcInstalledSize(pluginRoot);
|
|
454
419
|
if (!noZip) packZip(pluginRoot);
|
|
455
|
-
|
|
456
|
-
// Build summary
|
|
457
|
-
const allSkipped = [...new Set([...skippedArches.wheels, ...skippedArches.ros2, ...skippedArches.cpp])];
|
|
458
|
-
if (allSkipped.length > 0) {
|
|
459
|
-
const line = "─".repeat(58);
|
|
460
|
-
console.log(`\n${line}`);
|
|
461
|
-
log.warn(`Cross-arch builds skipped (QEMU not available): ${allSkipped.join(", ")}`);
|
|
462
|
-
if (skippedArches.wheels.length) log.warn(` wheels: ${skippedArches.wheels.join(", ")}`);
|
|
463
|
-
if (skippedArches.ros2.length) log.warn(` ros2: ${skippedArches.ros2.join(", ")}`);
|
|
464
|
-
if (skippedArches.cpp.length) log.warn(` cpp: ${skippedArches.cpp.join(", ")}`);
|
|
465
|
-
log.warn(` Enable: docker run --privileged --rm tonistiigi/binfmt --install all`);
|
|
466
|
-
console.log(line);
|
|
467
|
-
}
|
|
468
420
|
}
|
package/cli/src/cpp.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Used by dev:back (interactive dev loop) and build (export the binary into the ZIP).
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
|
-
import { canSkipImageBuild, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
6
|
+
import { canSkipImageBuild, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
7
7
|
|
|
8
8
|
export const CPP_DEV_IMAGE = "muscat-pk-cpp-dev";
|
|
9
9
|
|
|
@@ -15,15 +15,16 @@ export function assertDockerReady() {
|
|
|
15
15
|
|
|
16
16
|
/** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
|
|
17
17
|
export async function ensureImage(image = CPP_DEV_IMAGE, { platform = null } = {}) {
|
|
18
|
-
|
|
18
|
+
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
19
|
+
const sdkVer = process.env.MUSCAT_PK_RB_SDK_VERSION;
|
|
20
|
+
const key = imageKey(dockerfile, sdkVer ?? "");
|
|
21
|
+
if (canSkipImageBuild(image, key)) {
|
|
19
22
|
log.step(`${image} already built — skipping docker build`);
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
22
|
-
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
23
25
|
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
24
26
|
const platformFlags = platform ? ["--platform", platform] : [];
|
|
25
|
-
const
|
|
26
|
-
const buildArgs = sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : [];
|
|
27
|
+
const buildArgs = [...(sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : []), ...imageKeyLabel(key)];
|
|
27
28
|
const r = spawnSync(
|
|
28
29
|
"docker",
|
|
29
30
|
["build", ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
@@ -52,9 +53,12 @@ export function cmakeBuild(root, { image = CPP_DEV_IMAGE, buildType = "Debug", a
|
|
|
52
53
|
const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
|
|
53
54
|
// arch 빌드 시 arch별 이미지 사용 (로컬 이미지와 platform 불일치 방지)
|
|
54
55
|
const actualImage = arch ? `${CPP_DEV_IMAGE}-${arch}` : image;
|
|
56
|
+
// Export builds ship lib/ next to the binary: librb_sdk.so (robot has no SDK) + any .so the
|
|
57
|
+
// plugin copied there itself. Found via RPATH $ORIGIN/lib.
|
|
55
58
|
const script = [
|
|
56
|
-
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}
|
|
59
|
+
`cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}` + (arch ? ` -DCMAKE_BUILD_RPATH='$ORIGIN/lib'` : ""),
|
|
57
60
|
`cmake --build ${buildDir} -j`,
|
|
61
|
+
...(arch ? [`rm -f ${buildDir}/lib/librb_sdk.so.* && if readelf -d $(find ${buildDir} -maxdepth 1 -type f -executable) 2>/dev/null | grep -q librb_sdk; then mkdir -p ${buildDir}/lib && cp -a $(dpkg -L rainbow-rb-sdk | grep -E '/librb_sdk[.]so[.][0-9]') ${buildDir}/lib/; fi`] : []),
|
|
58
62
|
].join(" && ");
|
|
59
63
|
const label = arch ? `cmake build (${buildType}, ${arch}, in container)…` : `cmake build (${buildType}, in container)…`;
|
|
60
64
|
log.step(label);
|
package/cli/src/dev.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { resolve } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { runDevUi } from "./devUi.mjs";
|
|
8
8
|
import { runDevBack, stopDevBackContainers } from "./devBack.mjs";
|
|
9
|
-
import { startBridge, stopBridge, BRIDGE_CONTAINER, BRIDGE_PORT } from "./bridge.mjs";
|
|
9
|
+
import { localMuscatContainer, startBridge, stopBridge, BRIDGE_CONTAINER, BRIDGE_PORT } from "./bridge.mjs";
|
|
10
10
|
import { assertManifestPorts, assertManifestVariables } from "./manifest.mjs";
|
|
11
11
|
import { formatPortRanges } from "./ports.mjs";
|
|
12
12
|
import {
|
|
@@ -138,7 +138,11 @@ export async function runDev(args = []) {
|
|
|
138
138
|
pluginPorts: usesHostNetwork() ? [] : portRanges,
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
const muscat = bridgeStarted ? null : localMuscatContainer();
|
|
142
|
+
if (muscat) {
|
|
143
|
+
log.step(`local Muscat container '${muscat}' owns :${BRIDGE_PORT} — backend joins its network (gateway, MediaMTX)`);
|
|
144
|
+
process.env.MUSCAT_PK_MUSCAT_CONTAINER = muscat;
|
|
145
|
+
} else if (!bridgeStarted) {
|
|
142
146
|
log.warn(`port ${BRIDGE_PORT} is already in use — can't start Socket.IO bridge.`);
|
|
143
147
|
const answer = await prompt(" Proceed without Socket.IO bridge? [y/N]: ");
|
|
144
148
|
if (answer.toLowerCase() !== "y") return;
|
|
@@ -147,6 +151,7 @@ export async function runDev(args = []) {
|
|
|
147
151
|
// Tell dev:back which bridge container to share a netns with, so the backend
|
|
148
152
|
// reaches the bridge at localhost:10000 (no host.docker.internal).
|
|
149
153
|
process.env.MUSCAT_PK_BRIDGE_CONTAINER = BRIDGE_CONTAINER;
|
|
154
|
+
if (bridgeStarted.media) process.env.MUSCAT_PK_MEDIA = "1";
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
process.env.MUSCAT_PK_MANAGED_DEV = "1";
|
package/cli/src/devBack.mjs
CHANGED
|
@@ -52,6 +52,30 @@ export function stopDevBackContainers() {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Where the backend's 127.0.0.1 points. A local Muscat container (muscat-dev): join its
|
|
56
|
+
// netns so the gateway and MediaMTX resolve like on the robot. Under `dev`: the bridge's
|
|
57
|
+
// netns. Standalone dev:back: its own netns, gateway via the host.
|
|
58
|
+
function backendNetwork(portRanges) {
|
|
59
|
+
const muscat = process.env.MUSCAT_PK_MUSCAT_CONTAINER;
|
|
60
|
+
if (muscat) return { run: ["--network", `container:${muscat}`], env: [], shared: true };
|
|
61
|
+
if (usesHostNetwork()) return { run: ["--network", "host"], env: [], shared: true };
|
|
62
|
+
const bridge = process.env.MUSCAT_PK_BRIDGE_CONTAINER;
|
|
63
|
+
if (bridge) return { run: ["--network", `container:${bridge}`], env: [], shared: true };
|
|
64
|
+
return {
|
|
65
|
+
// host.docker.internal is automatic on Docker Desktop; this makes it resolve on Linux too.
|
|
66
|
+
run: [...publishPortFlags(portRanges), "--add-host", "host.docker.internal:host-gateway"],
|
|
67
|
+
env: ["-e", "RB_PLUGIN_GATEWAY_URL=http://host.docker.internal:10000"],
|
|
68
|
+
shared: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Our own MediaMTX, only when `dev` found its ports free (a local Muscat brings its own).
|
|
73
|
+
function startMediaServer(container) {
|
|
74
|
+
if (process.env.MUSCAT_PK_MEDIA !== "1") return;
|
|
75
|
+
spawnSync("docker", ["exec", "-d", container, "mediamtx", "/etc/mediamtx/mediamtx.yml"], { stdio: "ignore" });
|
|
76
|
+
log.step("MediaMTX: publish rtsp://127.0.0.1:8554/<path> · play http://localhost:8889/<path>");
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
// ── IDE tooling venv (best-effort, non-blocking) ────────────────────────────
|
|
56
80
|
// `muscat-pk new` already creates this up front (see new.mjs); this just
|
|
57
81
|
// refreshes it on every `dev`/`dev:back` in case requirements.txt changed.
|
|
@@ -95,39 +119,18 @@ async function runPython(root, portRanges) {
|
|
|
95
119
|
// container shares the host's network stack, so it reaches the bridge at
|
|
96
120
|
// localhost:10000 and the robot's Zenoh router / reverse-dial path directly
|
|
97
121
|
// — no netns sharing, port publishing, host.docker.internal, or byte-proxy.
|
|
98
|
-
const
|
|
99
|
-
// Share the bridge container's network namespace so localhost:10000 is the
|
|
100
|
-
// bridge (set by `muscat-pk dev`). Standalone dev:back has no bridge to join.
|
|
101
|
-
const bridgeContainer = process.env.MUSCAT_PK_BRIDGE_CONTAINER;
|
|
102
|
-
const netFlags = useHostNet
|
|
103
|
-
? ["--network", "host"]
|
|
104
|
-
: bridgeContainer ? ["--network", `container:${bridgeContainer}`] : [];
|
|
122
|
+
const net = backendNetwork(portRanges);
|
|
105
123
|
// Only the container's OWN reverse-dial path needs to publish a listen port.
|
|
106
|
-
// With
|
|
124
|
+
// With a shared netns the port is already reachable; with the byte-proxy
|
|
107
125
|
// the container just connects out — neither needs -p.
|
|
108
126
|
const standaloneZenohPortFlags =
|
|
109
|
-
!
|
|
110
|
-
? ["-p", `${zenohPort}:${zenohPort}`]
|
|
111
|
-
: [];
|
|
112
|
-
// Standalone dev:back owns its netns, so it publishes its own declared ports.
|
|
113
|
-
// Under `dev` the bridge owns the netns and published them already.
|
|
114
|
-
const pluginPortFlags =
|
|
115
|
-
!useHostNet && !bridgeContainer ? publishPortFlags(portRanges) : [];
|
|
116
|
-
const hostGatewayFlags = useHostNet || bridgeContainer
|
|
117
|
-
? []
|
|
118
|
-
: ["--add-host", "host.docker.internal:host-gateway"];
|
|
119
|
-
// With host networking or the bridge's netns, 127.0.0.1:10000 (the SDK's
|
|
120
|
-
// gateway default) already reaches the bridge. Otherwise the container's own
|
|
121
|
-
// 127.0.0.1 is itself — host.docker.internal reaches the host.
|
|
122
|
-
const gatewayFlags = useHostNet || bridgeContainer
|
|
123
|
-
? []
|
|
124
|
-
: ["-e", "RB_PLUGIN_GATEWAY_URL=http://host.docker.internal:10000"];
|
|
127
|
+
!net.shared && !proxyEndpoint && zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : [];
|
|
125
128
|
const envFlags = [
|
|
126
129
|
// docker exec below has no -t (no pseudo-TTY), so Python defaults to
|
|
127
130
|
// block-buffered stdout — print() output wouldn't show up until the
|
|
128
131
|
// buffer fills or the process exits. Force unbuffered.
|
|
129
132
|
"-e", "PYTHONUNBUFFERED=1",
|
|
130
|
-
...
|
|
133
|
+
...net.env,
|
|
131
134
|
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
132
135
|
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
133
136
|
...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
|
|
@@ -147,15 +150,13 @@ async function runPython(root, portRanges) {
|
|
|
147
150
|
docker(["rm", "-f", container], { stdio: "ignore" });
|
|
148
151
|
log.step("starting Python dev container…");
|
|
149
152
|
const up = docker(
|
|
150
|
-
["run", "-d", "--name", container, ...
|
|
151
|
-
// host.docker.internal is automatic on Docker Desktop (macOS/Windows);
|
|
152
|
-
// this makes it resolve on Linux too, for the no-bridge gateway fallback above.
|
|
153
|
-
...hostGatewayFlags,
|
|
153
|
+
["run", "-d", "--name", container, ...net.run, ...standaloneZenohPortFlags, ...realtimeFlags(),
|
|
154
154
|
"-e", "PIP_ROOT_USER_ACTION=ignore",
|
|
155
155
|
"-v", `${root}:/plugin`, "-w", "/plugin", PYTHON_DEV_IMAGE, "sleep", "infinity"],
|
|
156
156
|
{ stdio: "inherit" },
|
|
157
157
|
);
|
|
158
158
|
if (up.status !== 0) throw new Error("failed to start dev container.");
|
|
159
|
+
startMediaServer(container);
|
|
159
160
|
const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log, allow: [zenohPort] });
|
|
160
161
|
|
|
161
162
|
log.step("pip install requirements + watchfiles (in container)…");
|
|
@@ -373,7 +374,7 @@ runpy.run_path("/plugin/py/main.py", run_name="__main__")
|
|
|
373
374
|
PY`]);
|
|
374
375
|
|
|
375
376
|
if (pluginId) log.step(`RB_PLUGIN_ID=${pluginId}`);
|
|
376
|
-
if (
|
|
377
|
+
if (usesHostNetwork()) log.step("network: host (native Linux — direct robot/bridge access)");
|
|
377
378
|
if (proxyEndpoint) log.step(`Zenoh via host byte-proxy → ${proxyEndpoint}`);
|
|
378
379
|
else if (zenohAdvertiseHost && zenohPort) log.step(`Zenoh advertise → ${zenohAdvertiseHost}:${zenohPort}`);
|
|
379
380
|
log.step("watchfiles → py/main.py (reload on any .py change in py/)");
|
|
@@ -429,10 +430,12 @@ async function runRos2(root, portRanges) {
|
|
|
429
430
|
const container = `${ROS2_DEV_IMAGE}-run`;
|
|
430
431
|
const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
|
|
431
432
|
const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
|
|
433
|
+
const net = backendNetwork(portRanges);
|
|
432
434
|
const envFlags = [
|
|
433
435
|
// Same rationale as runPython(): no -t on docker exec below means
|
|
434
436
|
// rclpy's Python process stdout is block-buffered, not line-buffered.
|
|
435
437
|
"-e", "PYTHONUNBUFFERED=1",
|
|
438
|
+
...net.env,
|
|
436
439
|
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
437
440
|
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
438
441
|
...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
|
|
@@ -445,10 +448,11 @@ async function runRos2(root, portRanges) {
|
|
|
445
448
|
docker(["rm", "-f", container], { stdio: "ignore" });
|
|
446
449
|
log.step("starting ROS2 dev container…");
|
|
447
450
|
const up = docker(
|
|
448
|
-
["run", "-d", "--name", container, ...
|
|
451
|
+
["run", "-d", "--name", container, ...net.run, ...realtimeFlags(), "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", ROS2_DEV_IMAGE, "sleep", "infinity"],
|
|
449
452
|
{ stdio: "inherit" },
|
|
450
453
|
);
|
|
451
454
|
if (up.status !== 0) throw new Error("failed to start dev container.");
|
|
455
|
+
startMediaServer(container);
|
|
452
456
|
const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log, allow: [process.env.MUSCAT_ZENOH_PORT] });
|
|
453
457
|
|
|
454
458
|
// one-time deps + symlink build (edits to .py reflect without rebuilding)
|
|
@@ -459,11 +463,14 @@ async function runRos2(root, portRanges) {
|
|
|
459
463
|
let child = null;
|
|
460
464
|
const startNode = () => {
|
|
461
465
|
if (child) child.kill();
|
|
462
|
-
|
|
466
|
+
// Same command the robot runs: rclpy execs the console script, rclcpp uses `ros2 run`.
|
|
467
|
+
const launch = manifest.runtime?.launch || `${pkg}_node`;
|
|
468
|
+
const run = isRclcpp ? `ros2 run ${pkg} ${launch}` : `exec ${launch}`;
|
|
469
|
+
log.step(run);
|
|
463
470
|
child = spawn(
|
|
464
471
|
"docker",
|
|
465
472
|
["exec", ...envFlags, container, "bash", "-lc",
|
|
466
|
-
`cd ros2_ws && source install/setup.bash &&
|
|
473
|
+
`cd ros2_ws && source install/setup.bash && ${run}`],
|
|
467
474
|
{ cwd: root, stdio: "inherit" },
|
|
468
475
|
);
|
|
469
476
|
};
|
|
@@ -513,7 +520,9 @@ async function runCpp(root, portRanges) {
|
|
|
513
520
|
const container = `${CPP_DEV_IMAGE}-run`;
|
|
514
521
|
const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
|
|
515
522
|
const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
|
|
523
|
+
const net = backendNetwork(portRanges);
|
|
516
524
|
const envFlags = [
|
|
525
|
+
...net.env,
|
|
517
526
|
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
518
527
|
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
519
528
|
...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
|
|
@@ -526,10 +535,11 @@ async function runCpp(root, portRanges) {
|
|
|
526
535
|
docker(["rm", "-f", container], { stdio: "ignore" });
|
|
527
536
|
log.step("starting C++ dev container…");
|
|
528
537
|
const up = docker(
|
|
529
|
-
["run", "-d", "--name", container, ...
|
|
538
|
+
["run", "-d", "--name", container, ...net.run, ...realtimeFlags(), "-v", `${root}:/plugin`, "-w", "/plugin", CPP_DEV_IMAGE, "sleep", "infinity"],
|
|
530
539
|
{ stdio: "inherit" },
|
|
531
540
|
);
|
|
532
541
|
if (up.status !== 0) throw new Error("failed to start dev container.");
|
|
542
|
+
startMediaServer(container);
|
|
533
543
|
const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log, allow: [process.env.MUSCAT_ZENOH_PORT] });
|
|
534
544
|
|
|
535
545
|
const build = () => {
|