Last active
June 11, 2026 05:38
-
-
Save wsxq2/b014023d89670853769a9306f405b1d6 to your computer and use it in GitHub Desktop.
a script that init ros2 humble on ubuntu 22.04 with docker. use `-h` to show usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| ROS_DISTRO="${ROS_DISTRO:-humble}" | |
| UBUNTU_CODENAME="${UBUNTU_CODENAME:-jammy}" | |
| INSTALL_DOCKER=false | |
| DOCKER_PROXY="${DOCKER_PROXY:-}" | |
| RUN_TESTS=false | |
| usage() { | |
| cat <<EOF | |
| Usage: $0 [OPTIONS] | |
| Install and configure ROS 2 Humble on Ubuntu Jammy. | |
| Options: | |
| --install-docker Also install Docker CE and enable services. | |
| --docker-proxy URL Configure Docker daemon proxy, for example http://127.0.0.1:7890. | |
| --run-tests Run quick smoke tests after installation. | |
| -h, --help Show this help. | |
| Environment: | |
| ROS_DISTRO ROS distribution to install. Default: humble. | |
| UBUNTU_CODENAME Ubuntu codename. Default: jammy. | |
| DOCKER_PROXY Same as --docker-proxy. | |
| EOF | |
| } | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --install-docker) | |
| INSTALL_DOCKER=true | |
| shift | |
| ;; | |
| --docker-proxy) | |
| [[ $# -ge 2 ]] || { echo "Missing value for --docker-proxy" >&2; exit 2; } | |
| DOCKER_PROXY="$2" | |
| shift 2 | |
| ;; | |
| --run-tests) | |
| RUN_TESTS=true | |
| shift | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| echo "Unknown option: $1" >&2 | |
| usage >&2 | |
| exit 2 | |
| ;; | |
| esac | |
| done | |
| log() { | |
| printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$*" | |
| } | |
| require_ubuntu() { | |
| if [[ ! -r /etc/os-release ]]; then | |
| echo "Cannot read /etc/os-release." >&2 | |
| exit 1 | |
| fi | |
| # shellcheck disable=SC1091 | |
| . /etc/os-release | |
| if [[ "${ID:-}" != "ubuntu" ]]; then | |
| echo "This script is intended for Ubuntu; detected ID=${ID:-unknown}." >&2 | |
| exit 1 | |
| fi | |
| if [[ "${VERSION_CODENAME:-}" != "$UBUNTU_CODENAME" ]]; then | |
| echo "This script targets Ubuntu ${UBUNTU_CODENAME}; detected ${VERSION_CODENAME:-unknown}." >&2 | |
| exit 1 | |
| fi | |
| } | |
| run_sudo() { | |
| if [[ "${EUID}" -eq 0 ]]; then | |
| "$@" | |
| else | |
| sudo "$@" | |
| fi | |
| } | |
| write_root_file_if_changed() { | |
| local path="$1" | |
| local tmp | |
| tmp="$(mktemp)" | |
| cat > "$tmp" | |
| if [[ -f "$path" ]] && cmp -s "$tmp" "$path"; then | |
| rm -f "$tmp" | |
| log "Unchanged: $path" | |
| return | |
| fi | |
| run_sudo install -D -m 0644 "$tmp" "$path" | |
| rm -f "$tmp" | |
| log "Updated: $path" | |
| } | |
| backup_once() { | |
| local path="$1" | |
| local backup="${path}.bak" | |
| if [[ -f "$path" && ! -e "$backup" ]]; then | |
| run_sudo cp -a "$path" "$backup" | |
| log "Backed up $path to $backup" | |
| fi | |
| } | |
| install_packages() { | |
| local missing=() | |
| local pkg | |
| for pkg in "$@"; do | |
| if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then | |
| missing+=("$pkg") | |
| fi | |
| done | |
| if [[ "${#missing[@]}" -eq 0 ]]; then | |
| log "Packages already installed: $*" | |
| return | |
| fi | |
| log "Installing packages: ${missing[*]}" | |
| run_sudo apt-get install -y "${missing[@]}" | |
| } | |
| apt_update() { | |
| log "Updating apt package indexes" | |
| run_sudo apt-get update | |
| } | |
| download_root_file() { | |
| local url="$1" | |
| local path="$2" | |
| if command -v curl >/dev/null 2>&1; then | |
| run_sudo curl -fsSL "$url" -o "$path" | |
| elif command -v wget >/dev/null 2>&1; then | |
| run_sudo wget -qO "$path" "$url" | |
| else | |
| echo "Need curl or wget to download $url before apt can be updated." >&2 | |
| exit 1 | |
| fi | |
| } | |
| ensure_docker_key_for_existing_source() { | |
| local docker_source="/etc/apt/sources.list.d/docker.list" | |
| if [[ ! -f "$docker_source" ]] || ! grep -q 'download.docker.com/linux/ubuntu' "$docker_source"; then | |
| return | |
| fi | |
| log "Repairing existing Docker apt source before apt update" | |
| run_sudo install -m 0755 -d /etc/apt/keyrings | |
| if [[ ! -s /etc/apt/keyrings/docker.asc ]]; then | |
| download_root_file https://download.docker.com/linux/ubuntu/gpg /etc/apt/keyrings/docker.asc | |
| run_sudo chmod a+r /etc/apt/keyrings/docker.asc | |
| fi | |
| write_root_file_if_changed "$docker_source" <<EOF | |
| deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME} stable | |
| EOF | |
| } | |
| configure_locale() { | |
| log "Configuring locale" | |
| install_packages locales | |
| if ! locale -a | grep -qi '^en_US\.utf8$'; then | |
| run_sudo locale-gen en_US en_US.UTF-8 | |
| fi | |
| if ! grep -q '^LANG=en_US.UTF-8$' /etc/default/locale 2>/dev/null; then | |
| run_sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 | |
| fi | |
| export LANG=en_US.UTF-8 | |
| } | |
| configure_ubuntu_sources() { | |
| log "Configuring Ubuntu apt mirror" | |
| backup_once /etc/apt/sources.list | |
| write_root_file_if_changed /etc/apt/sources.list <<EOF | |
| deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ ${UBUNTU_CODENAME} main restricted universe multiverse | |
| deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ ${UBUNTU_CODENAME}-updates main restricted universe multiverse | |
| deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ ${UBUNTU_CODENAME}-backports main restricted universe multiverse | |
| deb http://security.ubuntu.com/ubuntu/ ${UBUNTU_CODENAME}-security main restricted universe multiverse | |
| EOF | |
| } | |
| configure_ros_sources() { | |
| log "Configuring ROS 2 apt source" | |
| install_packages curl gnupg2 software-properties-common | |
| run_sudo add-apt-repository -y universe | |
| if [[ ! -s /usr/share/keyrings/ros-archive-keyring.gpg ]]; then | |
| download_root_file https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ | |
| /usr/share/keyrings/ros-archive-keyring.gpg | |
| fi | |
| write_root_file_if_changed /etc/apt/sources.list.d/ros2.list <<EOF | |
| deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu ${UBUNTU_CODENAME} main | |
| EOF | |
| } | |
| configure_rosdep_sources() { | |
| local rosdep_file="/etc/ros/rosdep/sources.list.d/20-default.list" | |
| log "Configuring rosdep source list" | |
| if [[ -s "$rosdep_file" ]]; then | |
| log "Unchanged: $rosdep_file" | |
| return | |
| fi | |
| run_sudo install -d -m 0755 /etc/ros/rosdep/sources.list.d | |
| run_sudo curl -L \ | |
| https://mirrors.tuna.tsinghua.edu.cn/github-raw/ros/rosdistro/master/rosdep/sources.list.d/20-default.list \ | |
| -o "$rosdep_file" | |
| } | |
| install_ros() { | |
| configure_ubuntu_sources | |
| ensure_docker_key_for_existing_source | |
| apt_update | |
| configure_locale | |
| configure_ros_sources | |
| configure_rosdep_sources | |
| apt_update | |
| install_packages "ros-${ROS_DISTRO}-desktop" ros-dev-tools | |
| } | |
| remove_conflicting_docker_packages() { | |
| local packages=( | |
| docker.io | |
| docker-doc | |
| docker-compose | |
| docker-compose-v2 | |
| podman-docker | |
| containerd | |
| runc | |
| ) | |
| local installed=() | |
| local pkg | |
| for pkg in "${packages[@]}"; do | |
| if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then | |
| installed+=("$pkg") | |
| fi | |
| done | |
| if [[ "${#installed[@]}" -gt 0 ]]; then | |
| log "Removing conflicting Docker packages: ${installed[*]}" | |
| run_sudo apt-get remove -y "${installed[@]}" | |
| else | |
| log "No conflicting Docker packages found" | |
| fi | |
| } | |
| configure_docker_sources() { | |
| log "Configuring Docker apt source" | |
| install_packages ca-certificates curl | |
| run_sudo install -m 0755 -d /etc/apt/keyrings | |
| if [[ ! -s /etc/apt/keyrings/docker.asc ]]; then | |
| download_root_file https://download.docker.com/linux/ubuntu/gpg /etc/apt/keyrings/docker.asc | |
| run_sudo chmod a+r /etc/apt/keyrings/docker.asc | |
| fi | |
| write_root_file_if_changed /etc/apt/sources.list.d/docker.list <<EOF | |
| deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME} stable | |
| EOF | |
| } | |
| configure_docker_daemon() { | |
| if [[ -z "$DOCKER_PROXY" ]]; then | |
| log "Skipping Docker daemon proxy configuration" | |
| return | |
| fi | |
| log "Configuring Docker daemon proxy" | |
| write_root_file_if_changed /etc/docker/daemon.json <<EOF | |
| { | |
| "proxies": { | |
| "http-proxy": "${DOCKER_PROXY}", | |
| "https-proxy": "${DOCKER_PROXY}", | |
| "no-proxy": "127.0.0.0/8,192.168.0.0/16" | |
| } | |
| } | |
| EOF | |
| } | |
| install_docker() { | |
| remove_conflicting_docker_packages | |
| configure_docker_sources | |
| apt_update | |
| install_packages docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin | |
| configure_docker_daemon | |
| if getent group docker >/dev/null; then | |
| log "Group already exists: docker" | |
| else | |
| run_sudo groupadd docker | |
| fi | |
| if id -nG "$USER" | tr ' ' '\n' | grep -qx docker; then | |
| log "User $USER is already in docker group" | |
| else | |
| run_sudo usermod -aG docker "$USER" | |
| log "Added $USER to docker group; log out and back in for this to affect new shells" | |
| fi | |
| run_sudo systemctl enable docker.service | |
| run_sudo systemctl enable containerd.service | |
| run_sudo systemctl restart docker | |
| } | |
| run_smoke_tests() { | |
| log "Running smoke tests" | |
| # shellcheck disable=SC1091 | |
| source "/opt/ros/${ROS_DISTRO}/setup.bash" | |
| ros2 --help >/dev/null | |
| if "$INSTALL_DOCKER"; then | |
| run_sudo docker run --rm hello-world | |
| fi | |
| } | |
| main() { | |
| require_ubuntu | |
| install_ros | |
| if "$INSTALL_DOCKER"; then | |
| install_docker | |
| else | |
| log "Skipping Docker installation. Pass --install-docker to enable it." | |
| fi | |
| if "$RUN_TESTS"; then | |
| run_smoke_tests | |
| fi | |
| log "Done" | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment