| #!/bin/bash |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| set -e |
|
|
| |
| BUILD=false |
| CLEAN=false |
| DEPLOY=false |
| INSTALL=false |
| |
| |
| |
| |
| DOCKER_HUB_PUSH=false |
| |
| |
| |
| |
| ROOT=false |
| BRANCH_MODE=false |
| EXTRA_ARGS=() |
| PROJECT_NAME="decoupled_wbc" |
| PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]') |
| REMOTE_IMAGE="nvgear/gr00t_wbc:latest" |
|
|
| |
| while [[ $# -gt 0 ]]; do |
| case $1 in |
| --build) |
| BUILD=true |
| shift |
| ;; |
| --clean) |
| CLEAN=true |
| shift |
| ;; |
| --deploy) |
| DEPLOY=true |
| shift |
| ;; |
| --install) |
| INSTALL=true |
| shift |
| ;; |
| --push) |
| DOCKER_HUB_PUSH=true |
| shift |
| ;; |
| --root) |
| ROOT=true |
| shift |
| ;; |
| --branch) |
| BRANCH_MODE=true |
| shift |
| ;; |
| *) |
| |
| EXTRA_ARGS+=("$1") |
| shift |
| ;; |
| esac |
| done |
|
|
| if [ "$INSTALL" = true ] && [ "$BUILD" = true ]; then |
| echo "Cannot use --install and --build together. Choose one." |
| exit 1 |
| fi |
|
|
|
|
| |
| function get_branch_id { |
| |
| if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then |
| |
| local branch_name=$(git rev-parse --abbrev-ref HEAD) |
| |
| echo "${branch_name//\//-}" |
| else |
| |
| echo "" |
| fi |
| } |
|
|
| |
| is_arm64() { [ "$(dpkg --print-architecture)" = "arm64" ]; } |
| is_amd64() { [ "$(dpkg --print-architecture)" = "amd64" ]; } |
|
|
| |
| if [ "$ROOT" = true ]; then |
| USERNAME=root |
| USERID=0 |
| DOCKER_HOME_DIR=/root |
| CACHE_FROM=${PROJECT_SLUG}-deploy-cache-root |
| else |
| USERNAME=$(whoami) |
| USERID=$(id -u) |
| DOCKER_HOME_DIR=/home/${USERNAME} |
| CACHE_FROM=${PROJECT_SLUG}-deploy-cache |
| fi |
| |
| INPUT_GID=$(getent group input | cut -d: -f3) |
|
|
| |
| SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
|
|
| |
| function get_project_dir { |
| |
| if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then |
| git rev-parse --show-toplevel |
| else |
| |
| dirname "$(dirname "$SCRIPT_DIR")" |
| fi |
| } |
|
|
| |
| BRANCH_ID=$(get_branch_id) |
|
|
| |
| PROJECT_DIR="$(get_project_dir)" |
|
|
| |
| function get_container_name { |
| local container_type="$1" |
| if [[ -n "$BRANCH_ID" ]] && [[ "$BRANCH_MODE" = true ]]; then |
| echo "${PROJECT_SLUG}-${container_type}-${USERNAME}-${BRANCH_ID}" |
| else |
| echo "${PROJECT_SLUG}-${container_type}-${USERNAME}" |
| fi |
| } |
|
|
| |
| DEPLOY_CONTAINER=$(get_container_name "deploy") |
| BASH_CONTAINER=$(get_container_name "bash") |
| WORKTREE_NAME=$(basename "$PROJECT_DIR") |
|
|
| |
| if [[ -n "$BRANCH_ID" ]] && [[ "$BRANCH_MODE" = true ]]; then |
| echo "Branch mode enabled - using branch: $BRANCH_ID" |
| echo "Project directory: $PROJECT_DIR" |
| elif [[ -n "$BRANCH_ID" ]]; then |
| echo "Branch mode disabled - using default containers" |
| echo "Project directory: $PROJECT_DIR" |
| else |
| echo "Running outside git repository" |
| echo "Project directory: $PROJECT_DIR" |
| fi |
|
|
| |
| HOSTNAME=$(hostname)-docker |
|
|
| function clean_container { |
| echo "Cleaning up Docker containers..." |
| |
| |
| sudo docker stop $DEPLOY_CONTAINER 2>/dev/null || true |
| sudo docker stop $BASH_CONTAINER 2>/dev/null || true |
| |
| echo "Removing containers..." |
| sudo docker rm $DEPLOY_CONTAINER 2>/dev/null || true |
| sudo docker rm $BASH_CONTAINER 2>/dev/null || true |
| echo "Containers cleaned!" |
| } |
|
|
|
|
| |
| function install_docker_buildx { |
| |
| if sudo docker buildx version &> /dev/null; then |
| echo "Docker Buildx is already installed." |
| return 0 |
| fi |
| |
| echo "Installing Docker Buildx..." |
| |
| |
| mkdir -p ~/.docker/cli-plugins/ && sudo mkdir -p /root/.docker/cli-plugins/ |
| ARCH=$(dpkg --print-architecture) |
| [[ "$ARCH" == "arm64" ]] && BUILDX_ARCH="linux-arm64" || BUILDX_ARCH="linux-amd64" |
| |
| |
| BUILDX_VERSION=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | grep tag_name | cut -d '"' -f 4) |
| BUILDX_VERSION=${BUILDX_VERSION:-v0.13.1} |
| |
| |
| curl -L "https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.${BUILDX_ARCH}" -o ~/.docker/cli-plugins/docker-buildx |
| sudo cp ~/.docker/cli-plugins/docker-buildx /root/.docker/cli-plugins/docker-buildx |
| chmod +x ~/.docker/cli-plugins/docker-buildx && sudo chmod +x /root/.docker/cli-plugins/docker-buildx |
| |
| |
| sudo docker buildx create --use --name mybuilder || true |
| sudo docker buildx inspect --bootstrap |
| |
| echo "Docker Buildx installation complete!" |
| } |
|
|
| |
| function install_nvidia_toolkit { |
| |
| if command -v nvidia-container-toolkit &> /dev/null; then |
| echo "NVIDIA Container Toolkit is already installed." |
| return 0 |
| fi |
| |
| echo "Installing NVIDIA Container Toolkit..." |
|
|
| |
| distribution=$(. /etc/os-release;echo $ID$VERSION_ID) |
|
|
| |
| if [ -f "/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg" ]; then |
| echo "Removing existing NVIDIA GPG key..." |
| sudo rm /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg |
| fi |
|
|
| |
| curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg |
|
|
| |
| curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | \ |
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ |
| sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list |
|
|
| |
| sudo apt-get update |
| sudo apt-get install -y nvidia-container-toolkit |
| |
| |
| if ! command -v docker &> /dev/null; then |
| sudo apt-get install -y docker.io |
| fi |
|
|
| |
| sudo nvidia-ctk runtime configure --runtime=docker |
|
|
| |
| sudo systemctl restart docker |
|
|
| echo "NVIDIA Container Toolkit installation complete!" |
| } |
|
|
|
|
| |
| function build_docker_image { |
| echo "Building Docker image: $DEPLOY_CONTAINER" |
|
|
| sudo docker buildx build \ |
| --build-arg USERNAME=$USERNAME \ |
| --build-arg USERID=$USERID \ |
| --build-arg HOME_DIR=$DOCKER_HOME_DIR \ |
| --build-arg WORKTREE_NAME=$WORKTREE_NAME \ |
| --cache-from $CACHE_FROM \ |
| -t $DEPLOY_CONTAINER \ |
| -f "$SCRIPT_DIR/Dockerfile.deploy" \ |
| --load \ |
| "$PROJECT_DIR" |
|
|
| |
| |
| echo "Docker image build complete!" |
| } |
|
|
| |
| function build_with_cleanup { |
| echo "Building Docker image..." |
| echo "Removing existing containers and images..." |
| clean_container |
| |
| sudo docker tag $DEPLOY_CONTAINER $CACHE_FROM 2>/dev/null || true |
| sudo docker rmi $DEPLOY_CONTAINER 2>/dev/null || true |
| echo "Images cleaned!" |
| |
| install_docker_buildx |
| install_nvidia_toolkit |
| build_docker_image |
| } |
|
|
| function install_remote_image { |
| echo "Installing Docker image from remote registry: $REMOTE_IMAGE" |
| echo "Removing existing containers to ensure a clean install..." |
| clean_container |
| sudo docker pull "$REMOTE_IMAGE" |
| sudo docker tag "$REMOTE_IMAGE" "$DEPLOY_CONTAINER" |
| sudo docker tag "$REMOTE_IMAGE" "$CACHE_FROM" 2>/dev/null || true |
| echo "Docker image install complete!" |
| } |
|
|
| |
| if [ "$CLEAN" = true ]; then |
| clean_container |
| exit 0 |
| fi |
|
|
| |
| if [ "$BUILD" = true ]; then |
| build_with_cleanup |
| fi |
|
|
| if [ "$INSTALL" = true ]; then |
| install_remote_image |
| fi |
|
|
| if [ "$DOCKER_HUB_PUSH" = true ]; then |
| echo "Pushing Docker image to Docker Hub: docker.io/${REMOTE_IMAGE}" |
| sudo docker tag $DEPLOY_CONTAINER docker.io/${REMOTE_IMAGE} |
| sudo docker push docker.io/${REMOTE_IMAGE} |
| echo "Docker image pushed to Docker Hub!" |
| exit 0 |
| fi |
|
|
| |
| setup_x11() { |
| |
| if [ -z "$DISPLAY" ] && command -v xset >/dev/null 2>&1 && xset q >/dev/null 2>&1; then |
| export DISPLAY=:1 |
| echo "No DISPLAY set, using :1" |
| fi |
| |
| |
| if [ -n "$DISPLAY" ] && command -v xhost >/dev/null 2>&1 && xhost +local:docker 2>/dev/null; then |
| echo "X11 forwarding enabled" |
| return 0 |
| else |
| echo "Headless environment - X11 disabled" |
| export DISPLAY="" |
| return 1 |
| fi |
| } |
|
|
| X11_ENABLED=false |
| setup_x11 && X11_ENABLED=true |
|
|
| |
| |
| sudo chmod g+r+w /dev/input/* |
|
|
| |
| echo "Detecting GPU setup..." |
| GPU_ENV_VARS="" |
|
|
| |
| HAS_AMD_GPU=$(lspci | grep -i "vga\|3d\|display" | grep -i amd | wc -l) |
| HAS_INTEL_GPU=$(lspci | grep -i "vga\|3d\|display" | grep -i intel | wc -l) |
| HAS_NVIDIA_GPU=$(lspci | grep -i "vga\|3d\|display" | grep -i nvidia | wc -l) |
|
|
| if [[ "$HAS_INTEL_GPU" -gt 0 ]] || [[ "$HAS_AMD_GPU" -gt 0 ]] && [[ "$HAS_NVIDIA_GPU" -gt 0 ]]; then |
| echo "Detected hybrid GPU setup (Intel/AMD integrated + NVIDIA discrete)" |
| echo "Setting NVIDIA Optimus environment variables for proper rendering offload..." |
| GPU_ENV_VARS="-e __NV_PRIME_RENDER_OFFLOAD=1 \ |
| -e __VK_LAYER_NV_optimus=NVIDIA_only" |
| else |
| GPU_ENV_VARS="" |
| fi |
|
|
| |
| if is_arm64; then |
| echo "Detected ARM64 architecture (Jetson Orin), using device access instead of nvidia runtime..." |
| GPU_RUNTIME_ARGS="--device /dev/nvidia0 --device /dev/nvidiactl --device /dev/nvidia-modeset --device /dev/nvidia-uvm --device /dev/nvidia-uvm-tools" |
| else |
| GPU_RUNTIME_ARGS="--gpus all --runtime=nvidia" |
| fi |
|
|
| |
| DOCKER_RUN_ARGS="--hostname $HOSTNAME \ |
| --user $USERNAME \ |
| --group-add $INPUT_GID \ |
| $GPU_RUNTIME_ARGS \ |
| --ipc=host \ |
| --network=host \ |
| --privileged \ |
| --device=/dev \ |
| $GPU_ENV_VARS \ |
| -p 5678:5678 \ |
| -e DISPLAY=$DISPLAY \ |
| -e NVIDIA_VISIBLE_DEVICES=all \ |
| -e NVIDIA_DRIVER_CAPABILITIES=graphics,compute,utility \ |
| -e __GLX_VENDOR_LIBRARY_NAME=nvidia \ |
| -e USERNAME=$USERNAME \ |
| -e DECOUPLED_WBC_DIR="$DOCKER_HOME_DIR/Projects/$WORKTREE_NAME" \ |
| -e PYTHONPATH="$DOCKER_HOME_DIR/Projects/$WORKTREE_NAME" \ |
| -v /dev/bus/usb:/dev/bus/usb \ |
| -v /tmp/.X11-unix:/tmp/.X11-unix \ |
| -v $HOME/.ssh:$DOCKER_HOME_DIR/.ssh \ |
| -v $HOME/.gear:$DOCKER_HOME_DIR/.gear \ |
| -v $HOME/.Xauthority:$DOCKER_HOME_DIR/.Xauthority \ |
| -v $PROJECT_DIR:$DOCKER_HOME_DIR/Projects/$(basename "$PROJECT_DIR") |
| --device /dev/snd \ |
| --group-add audio \ |
| -e PULSE_SERVER=unix:/run/user/$(id -u)/pulse/native \ |
| -v /run/user/$(id -u)/pulse/native:/run/user/$(id -u)/pulse/native \ |
| -v $HOME/.config/pulse/cookie:/home/$USERNAME/.config/pulse/cookie" |
|
|
| |
| if [ "$DEPLOY" = true ]; then |
| |
| |
| |
| |
| "$SCRIPT_DIR/kill_decoupled_wbc_processors.sh" |
| echo "Creating new deploy container..." |
| |
| |
| |
| if sudo docker ps -a --format '{{.Names}}' | grep -q "^$DEPLOY_CONTAINER$"; then |
| echo "Removing existing deploy container..." |
| sudo docker rm -f $DEPLOY_CONTAINER |
| fi |
| sudo docker run -it --rm $DOCKER_RUN_ARGS \ |
| -w $DOCKER_HOME_DIR/Projects/$WORKTREE_NAME \ |
| --name $DEPLOY_CONTAINER \ |
| $DEPLOY_CONTAINER \ |
| /bin/bash -ic 'exec "$0" "$@"' \ |
| "${DOCKER_HOME_DIR}/Projects/${WORKTREE_NAME}/decoupled_wbc/docker/entrypoint/deploy.sh" \ |
| "${EXTRA_ARGS[@]}" |
| else |
| |
| if sudo docker ps -a --format '{{.Names}}' | grep -q "^$BASH_CONTAINER$"; then |
| echo "Bash container exists, starting it..." |
| sudo docker start $BASH_CONTAINER > /dev/null |
| sudo docker exec -it $BASH_CONTAINER /bin/bash |
| else |
| echo "Creating new bash container with auto-install decoupled_wbc..." |
| sudo docker run -it $DOCKER_RUN_ARGS \ |
| -w $DOCKER_HOME_DIR/Projects/$WORKTREE_NAME \ |
| --name $BASH_CONTAINER \ |
| $DEPLOY_CONTAINER \ |
| /bin/bash -ic 'exec "$0"' \ |
| "${DOCKER_HOME_DIR}/Projects/${WORKTREE_NAME}/decoupled_wbc/docker/entrypoint/bash.sh" |
| fi |
| fi |
|
|
| |
| $X11_ENABLED && xhost -local:docker 2>/dev/null |
|
|