diff --git a/.dockerignore b/.dockerignore index 679e11b25e..53eaf71ad0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,6 +10,7 @@ docker !docker/etc !docker/scripts +!docker/tools # Ignore a part of files under src src/**/.* 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 0000000000..1b2448b42f --- /dev/null +++ b/.github/actions/docker-build-and-push-tools/action.yaml @@ -0,0 +1,110 @@ +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: | + mkdir src + vcs import src < autoware.repos + shell: bash + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Restore ccache + uses: actions/cache/restore@v4 + with: + path: | + root-ccache + key: ccache-${{ inputs.platform }}-${{ hashFiles('src/**/*.cpp') }} + restore-keys: | + ccache-${{ inputs.platform }}- + + - name: Restore apt-get + uses: actions/cache/restore@v4 + with: + path: | + var-cache-apt + key: apt-get-${{ inputs.platform }}-${{ hashFiles('src/**/package.xml') }} + restore-keys: | + apt-get-${{ inputs.platform }}- + + - name: Inject cache into docker + uses: reproducible-containers/buildkit-cache-dance@v3.1.2 + with: + cache-map: | + { + "root-ccache": "/root/.ccache", + "var-cache-apt": "/var/cache/apt" + } + skip-extraction: true + + - 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: Docker meta for autoware:simulator + id: meta-simulator + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/${{ inputs.target-image }} + tags: | + type=raw,value=simulator-${{ inputs.platform }} + type=raw,value=simulator-${{ steps.date.outputs.date }}-${{ inputs.platform }} + type=ref,event=tag,prefix=simulator-,suffix=-${{ inputs.platform }} + bake-target: docker-metadata-action-simulator + 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-simulator.outputs.bake-file }} + ${{ steps.meta-visualizer.outputs.bake-file }} + provenance: false + set: | + ${{ inputs.build-args }} diff --git a/.github/actions/docker-build-and-push/action.yaml b/.github/actions/docker-build-and-push/action.yaml index 5feb1bcc41..d125786e97 100644 --- a/.github/actions/docker-build-and-push/action.yaml +++ b/.github/actions/docker-build-and-push/action.yaml @@ -181,6 +181,32 @@ runs: flavor: | latest=false + - 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: Docker meta for autoware:simulator + id: meta-simulator + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/${{ inputs.target-image }} + tags: | + type=raw,value=simulator-${{ inputs.platform }} + type=raw,value=simulator-${{ steps.date.outputs.date }}-${{ inputs.platform }} + type=ref,event=tag,prefix=simulator-,suffix=-${{ inputs.platform }} + bake-target: docker-metadata-action-simulator + flavor: | + latest=false + - name: Docker meta for autoware:universe-devel id: meta-universe-devel uses: docker/metadata-action@v5 @@ -220,6 +246,7 @@ runs: push: true files: | docker/docker-bake.hcl + docker/docker-bake-tools.hcl ${{ steps.meta-core-devel.outputs.bake-file }} ${{ steps.meta-universe-sensing-perception-devel.outputs.bake-file }} ${{ steps.meta-universe-sensing-perception.outputs.bake-file }} @@ -231,6 +258,8 @@ runs: ${{ steps.meta-universe-vehicle-system.outputs.bake-file }} ${{ steps.meta-universe-devel.outputs.bake-file }} ${{ steps.meta-universe.outputs.bake-file }} + ${{ steps.meta-simulator.outputs.bake-file }} + ${{ 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 167c1d7ebf..8ebb31ec59 100644 --- a/.github/workflows/docker-build-and-push-arm64.yaml +++ b/.github/workflows/docker-build-and-push-arm64.yaml @@ -67,6 +67,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 '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 567678bde3..361279f5ec 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 '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 0000000000..4a41a20adb --- /dev/null +++ b/docker/docker-bake-tools.hcl @@ -0,0 +1,22 @@ +group "default" { + targets = [ + "simulator", + "visualizer" + ] +} + +// For docker/metadata-action +target "docker-metadata-action-simulator" {} +target "docker-metadata-action-visualizer" {} + +target "simulator" { + inherits = ["docker-metadata-action-simulator"] + dockerfile = "docker/tools/Dockerfile" + target = "simulator" +} + +target "visualizer" { + inherits = ["docker-metadata-action-visualizer"] + dockerfile = "docker/tools/Dockerfile" + target = "visualizer" +} diff --git a/docker/tools/Dockerfile b/docker/tools/Dockerfile new file mode 100644 index 0000000000..1406993872 --- /dev/null +++ b/docker/tools/Dockerfile @@ -0,0 +1,104 @@ +ARG ROS_DISTRO + +### Builder +FROM ghcr.io/autowarefoundation/autoware:universe-devel AS builder +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +ENV CCACHE_DIR="/root/.ccache" +WORKDIR /autoware +COPY src /autoware/src +COPY simulator.repos /autoware/simulator.repos +COPY docker/scripts/resolve_rosdep_keys.sh /autoware/resolve_rosdep_keys.sh +RUN chmod +x /autoware/resolve_rosdep_keys.sh + +# Install dependencies and build the simulator +RUN --mount=type=ssh \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + vcs import src < simulator.repos \ + && apt-get update \ + && rosdep update && rosdep install -y --from-paths src --ignore-src --rosdistro $ROS_DISTRO \ + && source /opt/ros/"$ROS_DISTRO"/setup.bash && source /opt/autoware/setup.bash \ + && 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/simulator \ + && find /opt/autoware/lib -type f -name "*.py" -exec chmod +x {} \; \ + && find /opt/autoware/share -type f -name "*.py" -exec chmod +x {} \; \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* "$HOME"/.cache + +# Extract rosdep dependencies for simulator +RUN /autoware/resolve_rosdep_keys.sh /autoware/src/simulator ${ROS_DISTRO} \ + > /rosdep-simulator-depend-packages.txt \ + && cat /rosdep-simulator-depend-packages.txt + +### Simulator +FROM ghcr.io/autowarefoundation/autoware:universe AS simulator +WORKDIR /autoware +COPY --from=builder /opt/autoware /opt/autoware +COPY --from=builder /rosdep-simulator-depend-packages.txt /tmp/rosdep-simulator-depend-packages.txt + +RUN --mount=type=ssh \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update && apt-get install -y curl unzip \ + && source /opt/ros/"$ROS_DISTRO"/setup.bash && source /opt/autoware/setup.bash \ + && rosdep update \ + # Remove xmlschema and yamale from rosdep packages since we install via pip + && sed -i '/\(xmlschema\|yamale\)/d' /tmp/rosdep-simulator-depend-packages.txt \ + && pip install yamale xmlschema \ + && cat /tmp/rosdep-simulator-depend-packages.txt | xargs apt-get install -y --no-install-recommends \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* "$HOME"/.cache && \ + echo "source /opt/autoware/setup.bash" > /etc/bash.bashrc + +COPY docker/tools/etc/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] +CMD ["/bin/bash"] + +### Visualizer +FROM simulator AS visualizer +WORKDIR /autoware + +# Install openbox and VNC requirements +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 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# # Set up VNC password +# RUN mkdir -p ~/.vnc && \ +# echo "openadkit" | vncpasswd -f > ~/.vnc/passwd && \ +# chmod 600 ~/.vnc/passwd + +# 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" + +# Install ngrok for optional public access if no public ip available +RUN curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \ + | tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && \ + echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \ + | tee /etc/apt/sources.list.d/ngrok.list && \ + apt update && \ + apt install ngrok + +# 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/etc/xstartup /root/.vnc/xstartup +COPY docker/tools/etc/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh && chmod +x /root/.vnc/xstartup +ENTRYPOINT ["/entrypoint.sh"] +CMD ["/bin/bash"] diff --git a/docker/tools/etc/entrypoint.sh b/docker/tools/etc/entrypoint.sh new file mode 100644 index 0000000000..ba6f69f2ef --- /dev/null +++ b/docker/tools/etc/entrypoint.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +configure_vnc() { + # 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 +if [ -n "$RVIZ_CONFIG" ]; then + exec rviz2 -d "$RVIZ_CONFIG" +else + exec rviz2 +fi +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 + + # 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 + + # Configure ngrok if set + if [ -n "$NGROK_AUTHTOKEN" ]; then + ngrok config add-authtoken "$NGROK_AUTHTOKEN" + + if [ -n "$NGROK_URL" ]; then + ngrok http --url="$NGROK_URL" 6080 --log=stdout >ngrok.log & + else + ngrok http 6080 --log=stdout >ngrok.log & + sleep 2 + NGROK_URL=$(grep -oP 'url=\K[^\s]+' ngrok.log) + fi + fi + + # 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=openadkit&autoconnect=true\033[0m" + [ -z "$NGROK_AUTHTOKEN" ] && 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=openadkit&autoconnect=true\033[0m" + [ -n "$NGROK_AUTHTOKEN" ] && echo -e "\033[32mBrowser interface available at WEB address $NGROK_URL/vnc.html?resize=scale&password=openadkit&autoconnect=true\033[0m" + echo -e "\033[32m-------------------------------------------------------------------------\033[0m" +} + +# shellcheck disable=SC1090 +[ "$VNC_ENABLED" == "true" ] && configure_vnc +source "/opt/ros/$ROS_DISTRO/setup.bash" +source "/opt/autoware/setup.bash" +exec "$@" diff --git a/docker/tools/etc/xstartup b/docker/tools/etc/xstartup new file mode 100644 index 0000000000..ba1c8c814a --- /dev/null +++ b/docker/tools/etc/xstartup @@ -0,0 +1,15 @@ +#!/bin/sh + +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