diff --git a/.dockerignore b/.dockerignore index 679e11b25ef..0255e920e4f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,13 +10,13 @@ docker !docker/etc !docker/scripts +!docker/tools # Ignore a part of files under src src/**/.* src/**/*.asc src/**/*.gif src/**/*.md -src/**/*.pcd src/**/*.svg # Ignore generated files by colcon diff --git a/.github/actions/docker-build-and-push-tools/action.yaml b/.github/actions/docker-build-and-push-tools/action.yaml new file mode 100644 index 00000000000..c3aa33e1ed1 --- /dev/null +++ b/.github/actions/docker-build-and-push-tools/action.yaml @@ -0,0 +1,67 @@ +name: docker-build-and-push-tools +description: Composite action to build and push tools images to registry. + +inputs: + platform: + description: Target platform. + required: true + target-image: + description: Target docker image name in the registry. + required: true + build-args: + description: Additional build args. + required: false + +runs: + using: composite + steps: + - name: Install jq and vcstool + run: | + sudo apt-get -y update + sudo apt-get -y install jq python3-pip + pip install --no-cache-dir vcstool + shell: bash + + - name: Run vcs import + run: | + vcs import src < simulator.repos + shell: bash + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Get current date + id: date + run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT + shell: bash + + - name: Docker meta for autoware:visualizer + id: meta-visualizer + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/${{ inputs.target-image }} + tags: | + type=raw,value=visualizer-${{ inputs.platform }} + type=raw,value=visualizer-${{ steps.date.outputs.date }}-${{ inputs.platform }} + type=ref,event=tag,prefix=visualizer-,suffix=-${{ inputs.platform }} + bake-target: docker-metadata-action-visualizer + flavor: | + latest=false + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Build and Push to GitHub Container Registry + uses: docker/bake-action@v5 + with: + push: true + files: | + docker/docker-bake-tools.hcl + ${{ steps.meta-visualizer.outputs.bake-file }} + provenance: false + set: | + ${{ inputs.build-args }} diff --git a/.github/workflows/docker-build-and-push-arm64.yaml b/.github/workflows/docker-build-and-push-arm64.yaml index 0388d324e6f..4b6418509e6 100644 --- a/.github/workflows/docker-build-and-push-arm64.yaml +++ b/.github/workflows/docker-build-and-push-arm64.yaml @@ -72,6 +72,22 @@ jobs: *.cache-from=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:arm64-main *.cache-to=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:arm64-${{ github.ref_name }},mode=max + - name: Build 'autoware-tools' + uses: ./.github/actions/docker-build-and-push-tools + with: + platform: arm64 + target-image: autoware-tools + build-args: | + *.platform=linux/arm64 + *.args.ROS_DISTRO=${{ needs.load-env.outputs.rosdistro }} + *.args.BASE_IMAGE=${{ needs.load-env.outputs.base_image }} + *.args.AUTOWARE_BASE_IMAGE=${{ needs.load-env.outputs.autoware_base_image }} + *.args.AUTOWARE_BASE_CUDA_IMAGE=${{ needs.load-env.outputs.autoware_base_cuda_image }} + *.args.LIB_DIR=aarch64 + *.cache-from=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:arm64-${{ github.ref_name }} + *.cache-from=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:arm64-main + *.cache-to=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:arm64-${{ github.ref_name }},mode=max + - name: Show disk space if: always() run: | diff --git a/.github/workflows/docker-build-and-push.yaml b/.github/workflows/docker-build-and-push.yaml index 567678bde3e..478ba6a473a 100644 --- a/.github/workflows/docker-build-and-push.yaml +++ b/.github/workflows/docker-build-and-push.yaml @@ -62,6 +62,22 @@ jobs: *.cache-from=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:amd64-main *.cache-to=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:amd64-${{ github.ref_name }},mode=max + - name: Build 'autoware-tools' + uses: ./.github/actions/docker-build-and-push-tools + with: + platform: amd64 + target-image: autoware-tools + build-args: | + *.platform=linux/amd64 + *.args.ROS_DISTRO=${{ needs.load-env.outputs.rosdistro }} + *.args.BASE_IMAGE=${{ needs.load-env.outputs.base_image }} + *.args.AUTOWARE_BASE_IMAGE=${{ needs.load-env.outputs.autoware_base_image }} + *.args.AUTOWARE_BASE_CUDA_IMAGE=${{ needs.load-env.outputs.autoware_base_cuda_image }} + *.args.LIB_DIR=x86_64 + *.cache-from=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:amd64-${{ github.ref_name }} + *.cache-from=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:amd64-main + *.cache-to=type=registry,ref=ghcr.io/${{ github.repository }}-buildcache:amd64-${{ github.ref_name }},mode=max + - name: Show disk space if: always() run: | diff --git a/docker/docker-bake-tools.hcl b/docker/docker-bake-tools.hcl new file mode 100644 index 00000000000..99fbb6ed955 --- /dev/null +++ b/docker/docker-bake-tools.hcl @@ -0,0 +1,14 @@ +group "default" { + targets = [ + "visualizer" + ] +} + +// For docker/metadata-action +target "docker-metadata-action-visualizer" {} + +target "visualizer" { + inherits = ["docker-metadata-action-visualizer"] + dockerfile = "docker/tools/visualizer/Dockerfile.visualizer" + target = "visualizer" +} diff --git a/docker/tools/README.md b/docker/tools/README.md new file mode 100644 index 00000000000..a72209b2f91 --- /dev/null +++ b/docker/tools/README.md @@ -0,0 +1,23 @@ +# Open AD Kit Tools + +This directory offers tools for [Open AD Kit](https://autoware.org/open-ad-kit/) containers to make deployment and simulation easier. + +- `visualizer`: a container that can visualize the Autoware and scenario simulation on RViz. + +## Visualizer + +### Run + +```bash +docker run --rm --name openadkit-visualizer -p 6080:6080 -p 5900:5900 ghcr.io/autowarefoundation/autoware-tools:visualizer +``` + +### Settings + +The following environment variables can be configured for the visualizer container: + +| Variable | Default Value | Possible Values | Description | +| -------------- | ------------------------------ | --------------- | ----------------------------------------------------------------- | +| `RVIZ_CONFIG` | `/autoware/rviz/autoware.rviz` | Any valid path | The full path to the RViz configuration file inside the container | +| `WEB_ENABLED` | `false` | `true`, `false` | Enable visualization through a web browser | +| `WEB_PASSWORD` | - | Any string | Password for web visualization (required when `WEB_ENABLED=true`) | diff --git a/docker/tools/visualizer/Dockerfile.visualizer b/docker/tools/visualizer/Dockerfile.visualizer new file mode 100644 index 00000000000..29f20bb37a3 --- /dev/null +++ b/docker/tools/visualizer/Dockerfile.visualizer @@ -0,0 +1,87 @@ +# cspell:ignore openbox, VNC, tigervnc, novnc, websockify, newkey, xstartup, keyout + +### Builder +FROM ghcr.io/autowarefoundation/autoware:universe-visualization-devel AS builder +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +ENV CCACHE_DIR="/root/.ccache" +ARG ROS_DISTRO +WORKDIR /autoware +COPY src/simulator /autoware/src/simulator +COPY docker/scripts/resolve_rosdep_keys.sh /autoware/resolve_rosdep_keys.sh +RUN chmod +x /autoware/resolve_rosdep_keys.sh + +# Build simulator messages and rviz plugins ONLY +# hadolint ignore=SC1091 +RUN --mount=type=ssh \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update \ + && source /opt/ros/"$ROS_DISTRO"/setup.bash && source /opt/autoware/setup.bash \ + && rosdep update && rosdep install -y --from-paths src --ignore-src --rosdistro $ROS_DISTRO \ + && colcon build --cmake-args \ + "-Wno-dev" \ + "--no-warn-unused-cli" \ + --install-base /opt/autoware \ + --merge-install \ + --mixin release compile-commands ccache \ + --base-paths /autoware/src \ + --packages-up-to-regex ".*_msgs$" ".*rviz_plugin$" \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* "$HOME"/.cache + +# Extract rosdep dependencies for visualizer +# hadolint ignore=SC1091 +RUN source /opt/ros/"$ROS_DISTRO"/setup.bash && source /opt/autoware/setup.bash \ + && /autoware/resolve_rosdep_keys.sh /autoware/src ${ROS_DISTRO} --dependency-types=exec \ + > /rosdep-visualizer-depend-packages.txt \ + && cat /rosdep-visualizer-depend-packages.txt + +FROM ghcr.io/autowarefoundation/autoware:universe AS universe + +# Extract RViz configs for visualizer +# hadolint ignore=SC1091 +RUN source /opt/ros/"$ROS_DISTRO"/setup.bash && source /opt/autoware/setup.bash \ + && RVIZ_PATH="$(ros2 pkg prefix --share autoware_launch)/rviz" \ + && cp -r "$RVIZ_PATH" /autoware/rviz + +### visualizer +FROM ghcr.io/autowarefoundation/autoware:universe-visualization AS visualizer +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +ARG ROS_DISTRO +ARG LIB_DIR +WORKDIR /autoware + +# Get simulator messages, rviz plugins and dependencies +COPY --from=builder /opt/autoware /opt/autoware +COPY --from=builder /rosdep-visualizer-depend-packages.txt /tmp/rosdep-visualizer-depend-packages.txt +COPY --from=universe /autoware/rviz /autoware/rviz + +# Install openbox, VNC, and simulator dependencies +# hadolint ignore=SC2002 +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + curl unzip openbox tigervnc-standalone-server tigervnc-common \ + novnc websockify python3-numpy python3-xdg \ + # Remove xmlschema and yamale from rosdep packages since we install via pip + && sed -i '/\(xmlschema\|yamale\)/d' /tmp/rosdep-visualizer-depend-packages.txt \ + && pip install --no-cache-dir yamale xmlschema \ + && cat /tmp/rosdep-visualizer-depend-packages.txt | xargs apt-get install -y --no-install-recommends \ + && /autoware/cleanup_system.sh $LIB_DIR $ROS_DISTRO + +# Create SSL certificate for NoVNC +RUN openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout /etc/ssl/private/novnc.key \ + -out /etc/ssl/certs/novnc.crt \ + -days 365 \ + -subj "/O=Autoware-OpenADKit/CN=localhost" + +# Need to expose VNC and NoVNC ports when running the container +EXPOSE 5900 6080 + +# Add source commands to bash startup +RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> /root/.bashrc && \ + echo "source /opt/autoware/setup.bash" >> /root/.bashrc + +# Copy startup scripts +COPY docker/tools/visualizer/xstartup /root/.vnc/xstartup +COPY docker/tools/visualizer/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh && chmod +x /root/.vnc/xstartup +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/tools/visualizer/entrypoint.sh b/docker/tools/visualizer/entrypoint.sh new file mode 100644 index 00000000000..fe4562ef7a2 --- /dev/null +++ b/docker/tools/visualizer/entrypoint.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# cspell:ignore openbox, VNC, tigervnc, novnc, websockify, newkey, xstartup, pixelformat, AUTHTOKEN, authtoken, vncserver, autoconnect, vncpasswd +# shellcheck disable=SC1090,SC1091 + +# Check if RVIZ_CONFIG is provided +if [ -z "$RVIZ_CONFIG" ]; then + echo -e "\e[31mRVIZ_CONFIG is not set defaulting to /autoware/rviz/autoware.rviz\e[0m" + RVIZ_CONFIG="/autoware/rviz/autoware.rviz" + export RVIZ_CONFIG +fi + +configure_vnc() { + # Check if WEB_PASSWORD is provided + [ -z "$WEB_PASSWORD" ] && echo -e "\e[31mPassword is needed when WEB_ENABLED is true. Set WEB_PASSWORD environment variable\e[0m" && exit 1 + + # Create Openbox application configuration + mkdir -p /etc/xdg/openbox + cat >/etc/xdg/openbox/rc.xml <<'EOF' + + + + + yes + + center + center + + yes + 1 + + + +EOF + # Create rviz2 start script + cat >/usr/local/bin/start-rviz2.sh <<'EOF' +#!/bin/bash +source /opt/ros/humble/setup.bash +source /opt/autoware/setup.bash +exec rviz2 -d "$RVIZ_CONFIG" +EOF + chmod +x /usr/local/bin/start-rviz2.sh + echo "echo 'Autostart executed at $(date)' >> /tmp/autostart.log" >>/etc/xdg/openbox/autostart + echo "/usr/local/bin/start-rviz2.sh" >>/etc/xdg/openbox/autostart + + # Configure VNC password + mkdir -p ~/.vnc + echo "$WEB_PASSWORD" | vncpasswd -f >~/.vnc/passwd && chmod 600 ~/.vnc/passwd + + # Start VNC server with Openbox + echo "Starting VNC server with Openbox..." + vncserver :99 -geometry 1024x768 -depth 16 -pixelformat rgb565 + VNC_RESULT=$? + + if [ $VNC_RESULT -ne 0 ]; then + echo "Failed to start VNC server (exit code: $VNC_RESULT)" + exit $VNC_RESULT + fi + + # Set the DISPLAY variable to match VNC server + echo "Setting DISPLAY to :99" + echo "export DISPLAY=:99" >>~/.bashrc + sleep 2 + + # Start NoVNC + echo "Starting NoVNC..." + websockify --daemon --web=/usr/share/novnc/ --cert=/etc/ssl/certs/novnc.crt --key=/etc/ssl/private/novnc.key 6080 localhost:5999 + + # Print info + echo -e "\033[32m-------------------------------------------------------------------------\033[0m" + echo -e "\033[32mBrowser interface available at local address http://$(hostname -I | cut -d' ' -f1):6080/vnc.html?resize=scale&password=${WEB_PASSWORD}&autoconnect=true\033[0m" + if curl -s --head 1.1.1.1 >/dev/null 2>&1; then + echo -e "\033[32mIf you have a static public ip you can access it on WEB at http://$(curl -s ifconfig.me):6080/vnc.html?resize=scale&password=${WEB_PASSWORD}&autoconnect=true\033[0m" + else + echo -e "\033[31mNo internet connection available\033[0m" + fi + echo -e "\033[32m-------------------------------------------------------------------------\033[0m" +} + +# Source ROS and Autoware setup files +source "/opt/ros/$ROS_DISTRO/setup.bash" +source "/opt/autoware/setup.bash" + +# Execute passed command if provided, otherwise launch rviz2 +if [ "$WEB_ENABLED" == "true" ]; then + configure_vnc + [ $# -eq 0 ] && sleep infinity + exec "$@" +else + [ $# -eq 0 ] && rviz2 -d "$RVIZ_CONFIG" + exec "$@" +fi diff --git a/docker/tools/visualizer/xstartup b/docker/tools/visualizer/xstartup new file mode 100644 index 00000000000..d9b290c3d3a --- /dev/null +++ b/docker/tools/visualizer/xstartup @@ -0,0 +1,16 @@ +#!/bin/sh +# cspell:ignore openbox, VNC, xstartup, DBUS, Xresources, xrdb + +unset SESSION_MANAGER +unset DBUS_SESSION_BUS_ADDRESS +export DISPLAY=:99 + +[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup +[ -r "$HOME/.Xresources" ] && xrdb "$HOME/.Xresources" + +# Start Openbox window manager +echo "Starting Openbox window manager..." +openbox-session & + +# Keep the session alive +sleep infinity