File size: 15,781 Bytes
700dd75 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 | #!/bin/bash
# Docker run script for decoupled_wbc with branch-based container isolation
#
# Usage:
# ./docker/run_docker.sh [OPTIONS] (run from inside decoupled_wbc/)
#
# Options:
# --build Build Docker image
# --clean Clean containers
# --deploy Run in deploy mode
# --install Pull prebuilt Docker image
# --push Push built image to Docker Hub
# --branch Use branch-specific container names
#
# Branch-based Container Isolation (when --branch flag is used):
# - Each git branch gets its own isolated containers
# - Container names include branch identifier (e.g., decoupled_wbc-deploy-user-main)
# - Works with git worktrees, separate clones, or nested repositories
# - Clean and build operations only affect the current branch
# Exit on error
set -e
# Default values
BUILD=false
CLEAN=false
DEPLOY=false
INSTALL=false
# Flag to push the built Docker image to Docker Hub
# This should be used when someone updates the Docker image dependencies
# because this image is used for CI/CD pipelines
# When true, the image will be tagged and pushed to docker.io/nvgear/gr00t_wbc:latest
DOCKER_HUB_PUSH=false
# Flag to build the docker with root user
# This could cause some of your local files to be owned by root
# If you get error like "PermissionError: [Errno 13] Permission denied:"
# You can run `sudo chown -R $USER:$USER .` in local machine to fix it
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"
# Parse command line arguments
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
;;
*)
# Collect all unknown arguments as extra args for the deployment script
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 to get branch name for container naming
function get_branch_id {
# Check if we're in a git repository
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
# Get current branch name (returns "HEAD" in detached state)
local branch_name=$(git rev-parse --abbrev-ref HEAD)
# Replace forward slashes with dashes for valid container names
echo "${branch_name//\//-}"
else
# Default: no branch identifier (not in git repo)
echo ""
fi
}
# Architecture detection helpers
is_arm64() { [ "$(dpkg --print-architecture)" = "arm64" ]; }
is_amd64() { [ "$(dpkg --print-architecture)" = "amd64" ]; }
# Get current user's username and UID
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
# Get input group ID for device access
INPUT_GID=$(getent group input | cut -d: -f3)
# Get script directory for path calculations
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Function to get the actual project directory (worktree-aware)
function get_project_dir {
# For worktrees, use the actual worktree root path
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
git rev-parse --show-toplevel
else
# Fallback to script-based detection (go up two levels: docker/ -> decoupled_wbc/ -> project root)
dirname "$(dirname "$SCRIPT_DIR")"
fi
}
# Get branch identifier
BRANCH_ID=$(get_branch_id)
# Set project directory (needs to be after branch detection)
PROJECT_DIR="$(get_project_dir)"
# Function to generate container name with optional branch support
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
}
# Set common variables used throughout the script
DEPLOY_CONTAINER=$(get_container_name "deploy")
BASH_CONTAINER=$(get_container_name "bash")
WORKTREE_NAME=$(basename "$PROJECT_DIR")
# Debug output for branch detection
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
# Get host's hostname and append -docker
HOSTNAME=$(hostname)-docker
function clean_container {
echo "Cleaning up Docker containers..."
# Stop containers
sudo docker stop $DEPLOY_CONTAINER 2>/dev/null || true
sudo docker stop $BASH_CONTAINER 2>/dev/null || true
# Remove containers
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 to install Docker Buildx if needed
function install_docker_buildx {
# Check if Docker Buildx is already installed
if sudo docker buildx version &> /dev/null; then
echo "Docker Buildx is already installed."
return 0
fi
echo "Installing Docker Buildx..."
# Create directories and detect architecture
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"
# Get version (with fallback)
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}
# Download and install for both user and root
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
# Create builder
sudo docker buildx create --use --name mybuilder || true
sudo docker buildx inspect --bootstrap
echo "Docker Buildx installation complete!"
}
# Function to install NVIDIA Container Toolkit if needed
function install_nvidia_toolkit {
# Check if NVIDIA Container Toolkit is already installed
if command -v nvidia-container-toolkit &> /dev/null; then
echo "NVIDIA Container Toolkit is already installed."
return 0
fi
echo "Installing NVIDIA Container Toolkit..."
# Add the package repositories
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
# Check if GPG key exists and remove it if it does
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
# Add new GPG key
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
# Add repository
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
# Install nvidia-container-toolkit and docker if needed
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
# Install docker if not already installed
if ! command -v docker &> /dev/null; then
sudo apt-get install -y docker.io
fi
# Configure Docker to use the NVIDIA runtime
sudo nvidia-ctk runtime configure --runtime=docker
# Restart the Docker daemon
sudo systemctl restart docker
echo "NVIDIA Container Toolkit installation complete!"
}
# Function to build Docker image for current branch
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"
# Tag for persistent cache
# sudo docker tag $DEPLOY_CONTAINER $CACHE_FROM
echo "Docker image build complete!"
}
# Build function
function build_with_cleanup {
echo "Building Docker image..."
echo "Removing existing containers and images..."
clean_container
# Tag for persistent cache before deleting the image
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!"
}
# Clean up if requested
if [ "$CLEAN" = true ]; then
clean_container
exit 0
fi
# Build if requested
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 display forwarding
setup_x11() {
# Set display if missing and X server available
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
# Enable X11 forwarding if possible
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
# Mount entire /dev directory for dynamic device access (including hidraw for joycon)
# This allows JoyCon controllers to be detected even when connected after container launch
sudo chmod g+r+w /dev/input/*
# Detect GPU setup and set appropriate environment variables
echo "Detecting GPU setup..."
GPU_ENV_VARS=""
# Check if we have both integrated and discrete GPUs (hybrid/Optimus setup)
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
# Set GPU runtime based on architecture
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
# Common Docker run parameters
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"
# Check if RL mode first, then handle container logic
if [ "$DEPLOY" = true ]; then
# Deploy mode - use decoupled_wbc-deploy-${USERNAME} container
# Always clean up old processes and create a new container
# Kill all decoupled_wbc processes across containers to prevent message passing conflicts
"$SCRIPT_DIR/kill_decoupled_wbc_processors.sh"
echo "Creating new deploy container..."
# Clean up old processes and create a fresh deploy container
# Remove existing deploy container if it exists
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
# Bash mode - use decoupled_wbc-bash-${USERNAME} container
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
# Cleanup X11 permissions
$X11_ENABLED && xhost -local:docker 2>/dev/null
|