feat: add installation scripts for ROS2 Jazzy Desktop and ros-base with progress reporting

This commit is contained in:
Даниил Грабарь
2026-05-22 19:00:16 +03:00
parent 7cadd1d673
commit 2a70cdbe75
4 changed files with 206 additions and 440 deletions
+58 -436
View File
@@ -4,9 +4,6 @@ import argparse
import os import os
import shutil import shutil
import subprocess import subprocess
import tempfile
import threading
import urllib.request
from pathlib import Path from pathlib import Path
from typing import Callable, List, Optional from typing import Callable, List, Optional
@@ -23,42 +20,13 @@ _PROJECT_DIR = Path(__file__).parent.parent.parent
# Название дистрибутива ROS2, который устанавливает этот скрипт. # Название дистрибутива ROS2, который устанавливает этот скрипт.
_DISTRO = "jazzy" _DISTRO = "jazzy"
# Path where apt expects the ROS2 GPG signing key to be stored. # Webots simulator version targeted by this installer.
# Путь, по которому apt ожидает найти GPG-ключ подписи ROS2. # Версия симулятора Webots, устанавливаемая этим скриптом.
_ROS_KEYRING = Path("/usr/share/keyrings/ros-archive-keyring.gpg")
# Path to the apt sources file that points to the ROS2 package repository.
# Путь к файлу источников apt, указывающему на репозиторий пакетов ROS2.
_ROS_SOURCES = Path("/etc/apt/sources.list.d/ros2.list")
# Path created by "rosdep init" to mark that rosdep has already been initialized.
# Путь, создаваемый "rosdep init" для отметки того, что rosdep уже инициализирован.
_ROSDEP_SOURCES = Path("/etc/ros/rosdep/sources.list.d/20-default.list")
# Webots simulator version and the direct .deb download URL for amd64.
# Версия симулятора Webots и прямая ссылка для скачивания .deb для amd64.
_WEBOTS_VERSION = "2025a" _WEBOTS_VERSION = "2025a"
_WEBOTS_DEB_URL = (
f"https://github.com/cyberbotics/webots/releases/download/"
f"R{_WEBOTS_VERSION}/webots_{_WEBOTS_VERSION}_amd64.deb"
)
# Environment variables passed to apt to suppress interactive prompts (e.g. "restart services?"). # Directory that contains the shell scripts used by this command.
# Переменные окружения для apt, подавляющие интерактивные запросы (например, "перезапустить сервисы?"). # Директория с shell-скриптами, используемыми этой командой.
_APT_ENV = {**os.environ, "DEBIAN_FRONTEND": "noninteractive"} _SCRIPTS_DIR = _PROJECT_DIR / "scripts"
# Extra apt options: short per-connection and per-transfer timeouts plus retry count.
# Forces IPv4 because many VMs have broken IPv6 routing that causes silent hangs.
# Дополнительные опции apt: короткие таймауты на соединение и передачу данных, плюс число повторов.
# Принудительно используем IPv4, так как в VM часто сломана маршрутизация IPv6, что вызывает зависания.
_APT_OPTS = [
"-o", "Acquire::http::ConnectTimeout=15",
"-o", "Acquire::https::ConnectTimeout=15",
"-o", "Acquire::http::Timeout=30",
"-o", "Acquire::https::Timeout=30",
"-o", "Acquire::Retries=2",
"-o", "Acquire::ForceIPv4=true",
]
# Type alias for the callable used to write a line to the TUI log screen. # Type alias for the callable used to write a line to the TUI log screen.
# Псевдоним типа для функции записи строки в лог TUI. # Псевдоним типа для функции записи строки в лог TUI.
@@ -165,353 +133,57 @@ def _run_logged(
raise RuntimeError(f"Command failed: {cmd[0]}") raise RuntimeError(f"Command failed: {cmd[0]}")
def _run_apt_with_progress(
cmd: List[str],
write: Write,
on_progress: Callable[[float], None],
env: dict | None = None,
register_proc: Callable | None = None,
) -> None:
"""Run an apt command, stream its stdout to the log, and report download progress.
Uses APT::Status-Fd to receive machine-readable progress lines on a private
pipe. A background thread reads the pipe and calls on_progress(0-100) for
each percentage update. When a new URI starts downloading it is printed to
the log so the user can see what is being fetched.
Запускает команду apt, передаёт stdout в лог и показывает прогресс скачивания.
Использует APT::Status-Fd для получения машинночитаемых строк прогресса через
приватный канал. Фоновый поток читает канал и вызывает on_progress(0-100) при
каждом обновлении процента. При начале скачивания нового файла его URI
выводится в лог, чтобы пользователь видел что именно загружается.
"""
r_fd, w_fd = os.pipe()
try:
proc = subprocess.Popen(
cmd + ["-o", f"APT::Status-Fd={w_fd}"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env=env or os.environ,
pass_fds=(w_fd,),
)
finally:
# Close the write end in the parent so the reader thread gets EOF when apt exits.
# Закрываем пишущий конец в родителе, чтобы читающий поток получил EOF при выходе apt.
os.close(w_fd)
if register_proc:
register_proc(proc)
def _read_status() -> None:
"""Parse APT::Status-Fd lines and forward percentage and URI info.
Status-Fd line format: dlstatus:index:pct:message
or for package installs: pmstatus:pkg:pct:message
Разбирает строки APT::Status-Fd и передаёт процент и URI.
Формат строки: dlstatus:index:pct:message
или для установки пакетов: pmstatus:pkg:pct:message
"""
last_uri: str = ""
fetched = 0
with os.fdopen(r_fd, "r") as f:
for line in f:
parts = line.strip().split(":", 3)
if len(parts) < 3:
continue
kind, _, pct_str = parts[0], parts[1], parts[2]
msg = parts[3] if len(parts) == 4 else ""
try:
on_progress(float(pct_str))
except ValueError:
continue
if kind == "dlstatus" and msg:
# Print each new URI once as it starts downloading.
# Выводим каждый новый URI один раз при начале загрузки.
uri = msg.split()[0]
if uri != last_uri:
last_uri = uri
fetched += 1
write(f"[dim][{fetched}] {msg}[/dim]")
t = threading.Thread(target=_read_status, daemon=True)
t.start()
for line in proc.stdout:
s = line.rstrip()
if s:
write(s)
proc.wait()
t.join()
if proc.returncode not in (0, -9):
raise RuntimeError(f"Command failed: {cmd[0]}")
# Installation steps - each step maps to one visible phase in the log screen
# Шаги установки - каждый шаг соответствует одной видимой фазе в экране лога
def _step_prereqs(
write: Write,
on_progress: Callable[[float], None],
register_proc: Callable | None = None,
) -> None:
"""Step 1 - Refresh apt cache and install packages required for the ROS2 setup.
Installs: software-properties-common, curl, gnupg2, lsb-release, build-essential.
Also enables the Ubuntu universe repository which some ROS2 dependencies live in.
Шаг 1 - Обновляет кеш apt и устанавливает пакеты, необходимые для настройки ROS2.
Устанавливает: software-properties-common, curl, gnupg2, lsb-release, build-essential.
Также включает репозиторий Ubuntu universe, в котором находятся некоторые зависимости ROS2.
"""
write("[bold]Step 1 / 5 - Prerequisites[/bold]")
write("[cyan][*][/cyan] Updating package lists...")
_run_apt_with_progress(
["sudo", "apt-get", "update"] + _APT_OPTS,
write, on_progress, _APT_ENV, register_proc,
)
write("[cyan][*][/cyan] Installing prerequisites...")
_run_apt_with_progress(
[
"sudo", "apt-get", "install", "-y", "--no-install-recommends",
"software-properties-common", "curl", "gnupg2",
"lsb-release", "build-essential",
] + _APT_OPTS,
write, on_progress, _APT_ENV, register_proc,
)
write("[cyan][*][/cyan] Adding universe repository...")
# --no-update prevents add-apt-repository from running its own apt-get update,
# which would ignore our timeout options and could hang indefinitely.
# --no-update запрещает add-apt-repository запускать собственный apt-get update,
# который игнорирует наши таймауты и может зависнуть.
_run_logged(["sudo", "add-apt-repository", "-y", "--no-update", "universe"], write,
register_proc=register_proc)
write("[green][ok][/green] Prerequisites ready")
def _step_ros2_repo(
write: Write,
on_progress: Callable[[float], None],
register_proc: Callable | None = None,
) -> None:
"""Step 2 - Download the ROS2 signing key and register the ROS2 apt repository.
Removes any previous key and sources file first so re-runs always start clean.
Downloads the key from the official ros/rosdistro GitHub repository, writes it
to the system keyring, then creates /etc/apt/sources.list.d/ros2.list and
refreshes only that source to avoid updating all Ubuntu mirrors.
Шаг 2 - Скачивает ключ подписи ROS2 и регистрирует репозиторий apt ROS2.
Сначала удаляет предыдущие ключ и файл источников, чтобы повторные запуски
всегда начинались с чистого состояния. Скачивает ключ с официального GitHub
репозитория ros/rosdistro, записывает его в системный кейринг, затем создаёт
/etc/apt/sources.list.d/ros2.list и обновляет только этот источник.
"""
write("\n[bold]Step 2 / 5 - ROS2 repository[/bold]")
# Remove previous key and sources file to avoid stale config on re-runs.
# Удаляем предыдущие ключ и файл источников для чистого состояния при повторных запусках.
for path in (_ROS_KEYRING, _ROS_SOURCES):
if path.exists():
subprocess.run(["sudo", "rm", "-f", str(path)], check=False)
write(f"[dim]Removed {path}[/dim]")
write("[cyan][*][/cyan] Downloading ROS2 signing key...")
key_url = "https://raw.githubusercontent.com/ros/rosdistro/master/ros.key"
with tempfile.NamedTemporaryFile(delete=False, suffix=".gpg") as tmp:
tmp_path = tmp.name
try:
with urllib.request.urlopen(key_url, timeout=30) as resp:
Path(tmp_path).write_bytes(resp.read())
subprocess.run(
["sudo", "install", "-m", "644", tmp_path, str(_ROS_KEYRING)],
check=True,
)
finally:
Path(tmp_path).unlink(missing_ok=True)
write("[green][ok][/green] Signing key installed")
# Detect machine architecture and Ubuntu codename to build the sources.list line.
# Определяем архитектуру машины и кодовое имя Ubuntu для строки sources.list.
arch = subprocess.check_output(["dpkg", "--print-architecture"], text=True).strip()
codename = subprocess.check_output(
["bash", "-c", ". /etc/os-release && echo ${UBUNTU_CODENAME:-${VERSION_CODENAME}}"],
text=True,
).strip()
sources_line = (
f"deb [arch={arch} signed-by={_ROS_KEYRING}] "
f"http://packages.ros.org/ros2/ubuntu {codename} main\n"
)
result = subprocess.run(
["sudo", "tee", str(_ROS_SOURCES)],
input=sources_line, capture_output=True, text=True,
)
if result.returncode != 0:
raise RuntimeError(f"Failed to write {_ROS_SOURCES}")
write(f"[dim]{_ROS_SOURCES}[/dim]")
# Update only the ROS2 source - avoids downloading all Ubuntu mirror metadata.
# Обновляем только источник ROS2 - избегаем скачивания метаданных всех зеркал Ubuntu.
write("[cyan][*][/cyan] Updating ROS2 package list...")
_run_apt_with_progress(
[
"sudo", "apt-get", "update",
"-o", f"Dir::Etc::sourcelist={_ROS_SOURCES}",
"-o", "Dir::Etc::sourceparts=-",
"-o", "APT::Get::List-Cleanup=0",
] + _APT_OPTS,
write, on_progress, _APT_ENV, register_proc,
)
write("[green][ok][/green] ROS2 repository ready")
def _step_install_ros2(
write: Write,
on_progress: Callable[[float], None],
pkg: str,
register_proc: Callable | None = None,
) -> None:
"""Step 3 - Install the chosen ROS2 Jazzy package via apt.
pkg is either "desktop" (full install with GUI tools) or "ros-base"
(minimal headless install). The full package name becomes ros-jazzy-{pkg}.
Шаг 3 - Устанавливает выбранный пакет ROS2 Jazzy через apt.
pkg - это либо "desktop" (полная установка с GUI), либо "ros-base"
(минимальная установка без GUI). Полное имя пакета: ros-jazzy-{pkg}.
"""
write(f"\n[bold]Step 3 / 5 - ros-{_DISTRO}-{pkg}[/bold]")
write(f"[cyan][*][/cyan] Installing ros-{_DISTRO}-{pkg}...")
_run_apt_with_progress(
["sudo", "apt-get", "install", "-y", f"ros-{_DISTRO}-{pkg}"] + _APT_OPTS,
write, on_progress, _APT_ENV, register_proc,
)
write(f"[green][ok][/green] ros-{_DISTRO}-{pkg} installed")
def _step_dev_tools(
write: Write,
on_progress: Callable[[float], None],
register_proc: Callable | None = None,
) -> None:
"""Step 4 - Install colcon, rosdep, vcstool and initialize rosdep.
Installs the Python packages needed to build and manage ROS2 workspaces.
Runs "rosdep init" only if it has not been run before, then always runs
"rosdep update" to fetch the latest package index.
Шаг 4 - Устанавливает colcon, rosdep, vcstool и инициализирует rosdep.
Устанавливает Python-пакеты, необходимые для сборки и управления рабочими
пространствами ROS2. Запускает "rosdep init" только если он ещё не запускался,
затем всегда запускает "rosdep update" для получения свежего индекса пакетов.
"""
write("\n[bold]Step 4 / 5 - Dev tools[/bold]")
write("[cyan][*][/cyan] Installing colcon, rosdep, vcstool...")
_run_apt_with_progress(
[
"sudo", "apt-get", "install", "-y",
"python3-argcomplete",
"python3-colcon-clean",
"python3-colcon-common-extensions",
"python3-rosdep",
"python3-vcstool",
] + _APT_OPTS,
write, on_progress, _APT_ENV, register_proc,
)
if not _ROSDEP_SOURCES.exists():
write("[cyan][*][/cyan] Initializing rosdep...")
_run_logged(["sudo", "rosdep", "init"], write, register_proc=register_proc)
else:
write("[green][ok][/green] rosdep already initialized")
write("[cyan][*][/cyan] Updating rosdep...")
_run_logged(["rosdep", "update", "--rosdistro", _DISTRO], write,
register_proc=register_proc)
write("[green][ok][/green] Dev tools ready")
def _step_shell_setup(write: Write) -> None:
"""Step 5 - Append ROS2 environment setup lines to the user shell rc file.
Adds "source /opt/ros/jazzy/setup.bash" so ROS2 commands are available in
every new terminal. Also adds a commented-out ROS_AUTOMATIC_DISCOVERY_RANGE
line as a reminder for multi-machine setups. Both lines are added only once.
Шаг 5 - Добавляет строки настройки окружения ROS2 в rc-файл оболочки пользователя.
Добавляет "source /opt/ros/jazzy/setup.bash" чтобы команды ROS2 были доступны
в каждом новом терминале. Также добавляет закомментированную строку
ROS_AUTOMATIC_DISCOVERY_RANGE как напоминание для многомашинных настроек.
Обе строки добавляются только один раз.
"""
write("\n[bold]Step 5 / 5 - Shell configuration[/bold]")
shell_name = Path(os.environ.get("SHELL", "/bin/bash")).name
rc = Path.home() / (".zshrc" if shell_name == "zsh" else ".bashrc")
rc_text = rc.read_text() if rc.exists() else ""
source_line = f"source /opt/ros/{_DISTRO}/setup.bash"
discovery_comment = "# export ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST"
additions = []
if source_line not in rc_text:
additions.append(source_line)
if discovery_comment not in rc_text:
additions.append(discovery_comment)
if additions:
with rc.open("a") as f:
f.write(f"\n# ROS2 {_DISTRO}\n")
for line in additions:
f.write(line + "\n")
write(f"[green][ok][/green] Added ROS2 setup to ~/{rc.name}")
else:
write(f"[green][ok][/green] ROS2 setup already in ~/{rc.name}")
# Tasks - long-running functions executed inside a LogScreen background thread # Tasks - long-running functions executed inside a LogScreen background thread
# Задачи - долгие функции, выполняемые в фоновом потоке внутри LogScreen # Задачи - долгие функции, выполняемые в фоновом потоке внутри LogScreen
def _run_script(script: Path, screen: LogScreen) -> None:
"""Run a shell script, stream its output to the TUI log, and parse
PROGRESS:<pct>:<label> markers to update the progress bar.
Raises RuntimeError if the script exits with a non-zero code.
Запускает shell-скрипт, транслирует вывод в лог TUI и разбирает маркеры
PROGRESS:<pct>:<метка> для обновления прогресс-бара.
Выбрасывает RuntimeError если скрипт завершился с ненулевым кодом.
"""
proc = subprocess.Popen(
["bash", str(script)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, cwd=_PROJECT_DIR,
)
screen.set_proc(proc)
for line in proc.stdout:
s = line.rstrip()
if s.startswith("PROGRESS:"):
# Format emitted by scripts: PROGRESS:<pct>:<label>
# Формат, выводимый скриптами: PROGRESS:<pct>:<метка>
parts = s.split(":", 2)
try:
screen.set_progress(float(parts[1]), parts[2] if len(parts) > 2 else "")
except (ValueError, IndexError):
pass
elif s:
screen.write(s)
proc.wait()
if proc.returncode not in (0, -9):
raise RuntimeError(f"Script failed (exit {proc.returncode}): {script.name}")
def _task_install(screen: LogScreen, pkg: str) -> None: def _task_install(screen: LogScreen, pkg: str) -> None:
"""Full ROS2 Jazzy installation task, split into 5 sequential steps. """Run the ROS2 Jazzy installation shell script for the chosen variant (desktop / ros-base).
The script emits PROGRESS: markers so the bar advances during installation.
Maps each step to a sub-range of the 0-100% progress bar so the bar Запускает shell-скрипт установки ROS2 Jazzy для выбранного варианта (desktop / ros-base).
advances smoothly through prerequisites, repo setup, ROS2 install, Скрипт выводит маркеры PROGRESS:, чтобы прогресс-бар обновлялся во время установки.
dev tools and shell configuration.
Полная задача установки ROS2 Jazzy, разбитая на 5 последовательных шагов.
Каждый шаг отображается в своём диапазоне прогресс-бара 0-100%, так что
бар плавно движется через prerequisites, настройку репозитория, установку
ROS2, инструменты разработчика и настройку оболочки.
""" """
try: try:
def prog(lo: float, hi: float) -> Callable[[float], None]: # "desktop" -> setup_ros2_desktop.sh, "ros-base" -> setup_ros2_ros_base.sh
"""Map a 0-100 apt percentage into the [lo, hi] sub-range of the progress bar. script = _SCRIPTS_DIR / f"setup_ros2_{pkg.replace('-', '_')}.sh"
Отображает 0-100% apt в поддиапазон [lo, hi] прогресс-бара. if not script.exists():
""" screen.write(f"[red]Script not found:[/red] {script}")
return lambda p: screen.set_progress(lo + p / 100.0 * (hi - lo)) screen.finish(False)
screen.set_progress(0, "Preparing...")
_step_prereqs(screen.write, prog(0, 15), register_proc=screen.set_proc)
if screen.is_stopped():
return return
screen.set_progress(0, "Starting installation...")
screen.set_progress(15, "Setting up ROS2 repository...") _run_script(script, screen)
_step_ros2_repo(screen.write, prog(15, 30), register_proc=screen.set_proc) if not screen.is_stopped():
if screen.is_stopped():
return
screen.set_progress(30, f"Installing ros-{_DISTRO}-{pkg}...")
_step_install_ros2(screen.write, prog(30, 75), pkg, register_proc=screen.set_proc)
if screen.is_stopped():
return
screen.set_progress(75, "Installing dev tools...")
_step_dev_tools(screen.write, prog(75, 95), register_proc=screen.set_proc)
if screen.is_stopped():
return
screen.set_progress(95, "Configuring shell...")
_step_shell_setup(screen.write)
screen.set_progress(100, "Done") screen.set_progress(100, "Done")
screen.write(f"\n[green]ROS2 {_DISTRO} ({pkg}) installed successfully.[/green]")
screen.write(f"\n[green]ROS2 {_DISTRO} installed successfully.[/green]")
screen.finish(True) screen.finish(True)
except Exception as exc: except Exception as exc:
if not screen.is_stopped(): if not screen.is_stopped():
@@ -601,71 +273,21 @@ def _task_build(screen: LogScreen) -> None:
def _task_install_webots(screen: LogScreen) -> None: def _task_install_webots(screen: LogScreen) -> None:
"""Download the Webots .deb from GitHub and install it with apt. """Run the Webots installation shell script, streaming output and progress to the TUI.
Progress is split into two phases: Запускает shell-скрипт установки Webots, транслируя вывод и прогресс в TUI.
- 0-65%: downloading the .deb file (streamed in 64 KB chunks).
- 65-100%: running apt-get install on the downloaded file.
Скачивает .deb Webots с GitHub и устанавливает его через apt.
Прогресс разделён на две фазы:
- 0-65%: скачивание .deb файла (потоковое, кусками по 64 КБ).
- 65-100%: запуск apt-get install для скачанного файла.
""" """
try: try:
screen.write(f"[bold]Installing Webots {_WEBOTS_VERSION}[/bold]\n") script = _SCRIPTS_DIR / "install_webots.sh"
screen.write(f"[dim]{_WEBOTS_DEB_URL}[/dim]\n") if not script.exists():
screen.set_progress(0, "Downloading Webots...") screen.write(f"[red]Script not found:[/red] {script}")
with tempfile.TemporaryDirectory() as tmp:
deb_path = Path(tmp) / f"webots_{_WEBOTS_VERSION}_amd64.deb"
with urllib.request.urlopen(_WEBOTS_DEB_URL, timeout=30) as resp:
total = int(resp.headers.get("Content-Length", 0))
downloaded = 0
with open(deb_path, "wb") as f:
while True:
if screen.is_stopped():
return
chunk = resp.read(65536)
if not chunk:
break
f.write(chunk)
downloaded += len(chunk)
if total > 0:
pct = min(downloaded / total * 65, 65)
mb = downloaded / 1_048_576
total_mb = total / 1_048_576
screen.set_progress(pct, f"Downloading... {mb:.0f} / {total_mb:.0f} MB")
if screen.is_stopped():
return
screen.write("[green]Download complete.[/green]")
screen.set_progress(65, "Installing package...")
proc = subprocess.Popen(
["sudo", "apt-get", "install", "-y", str(deb_path)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True,
)
screen.set_proc(proc)
for line in proc.stdout:
s = line.rstrip()
if s:
screen.write(s)
proc.wait()
if proc.returncode not in (0, -9):
screen.write("\n[red]Webots installation failed.[/red]")
screen.finish(False) screen.finish(False)
return return
screen.set_progress(0, "Starting Webots installation...")
if screen.is_stopped(): _run_script(script, screen)
return if not screen.is_stopped():
screen.set_progress(100, "Done") screen.set_progress(100, "Done")
screen.write("\n[green]Webots installed successfully.[/green]") screen.write(f"\n[green]Webots {_WEBOTS_VERSION} installed successfully.[/green]")
screen.finish(True) screen.finish(True)
except Exception as exc: except Exception as exc:
if not screen.is_stopped(): if not screen.is_stopped():
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Download and install the Webots simulator from the Cyberbotics GitHub release.
# Emits PROGRESS:<pct>:<label> lines so the Python caller can update its progress bar.
# Скачивает и устанавливает симулятор Webots из релизов GitHub Cyberbotics.
# Выводит строки PROGRESS:<pct>:<метка> для обновления прогресс-бара в Python.
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
PROGRESS() { echo "PROGRESS:$1:$2"; }
WEBOTS_VERSION="2025a"
DEB_URL="https://github.com/cyberbotics/webots/releases/download/R${WEBOTS_VERSION}/webots_${WEBOTS_VERSION}_amd64.deb"
TMP_DIR="$(mktemp -d)"
DEB_PATH="${TMP_DIR}/webots_${WEBOTS_VERSION}_amd64.deb"
cleanup() { rm -rf "$TMP_DIR"; }
trap cleanup EXIT
PROGRESS 5 "Downloading Webots ${WEBOTS_VERSION}..."
echo "Downloading Webots ${WEBOTS_VERSION}..."
echo "URL: ${DEB_URL}"
# wget writes download progress to stderr; redirect to stdout so it appears in the TUI log.
wget --progress=dot:mega -O "$DEB_PATH" "$DEB_URL" 2>&1
PROGRESS 70 "Installing Webots package..."
echo "Download complete. Installing..."
sudo apt-get install -y "$DEB_PATH"
PROGRESS 100 "Done"
echo "Webots ${WEBOTS_VERSION} installed successfully."
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
# Install ROS2 Jazzy Desktop on Ubuntu 24.04.
# Emits PROGRESS:<pct>:<label> lines so the Python caller can update its progress bar.
# Устанавливает ROS2 Jazzy Desktop на Ubuntu 24.04.
# Выводит строки PROGRESS:<pct>:<метка> для обновления прогресс-бара в Python.
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
PROGRESS() { echo "PROGRESS:$1:$2"; }
PROGRESS 0 "Setting up locale..."
echo "Setting up locale..."
sudo apt-get update -q
sudo apt-get install -y -q locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
PROGRESS 8 "Adding universe repository..."
echo "Adding universe repository..."
sudo apt-get install -y -q software-properties-common
sudo add-apt-repository -y universe
PROGRESS 16 "Adding ROS2 GPG key..."
echo "Adding ROS2 GPG key..."
sudo apt-get update -q
sudo apt-get install -y -q curl
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg
PROGRESS 22 "Configuring ROS2 apt repository..."
echo "Configuring ROS2 apt repository..."
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo "$UBUNTU_CODENAME") main" \
| sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
PROGRESS 28 "Updating package lists..."
echo "Updating package lists..."
sudo apt-get update -q
sudo apt-get upgrade -y -q
PROGRESS 35 "Installing ros-jazzy-desktop (this may take a while)..."
echo "Installing ros-jazzy-desktop..."
sudo apt-get install -y ros-jazzy-desktop
PROGRESS 75 "Installing ROS2 dev tools..."
echo "Installing ROS2 dev tools..."
sudo apt-get install -y ros-dev-tools
PROGRESS 88 "Initializing rosdep..."
echo "Initializing rosdep..."
sudo rosdep init 2>/dev/null || true
rosdep update
PROGRESS 100 "Done"
echo "ROS2 Jazzy Desktop installed successfully."
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
# Install ROS2 Jazzy (ros-base) on Ubuntu 24.04.
# Emits PROGRESS:<pct>:<label> lines so the Python caller can update its progress bar.
# Устанавливает ROS2 Jazzy (ros-base) на Ubuntu 24.04.
# Выводит строки PROGRESS:<pct>:<метка> для обновления прогресс-бара в Python.
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
PROGRESS() { echo "PROGRESS:$1:$2"; }
PROGRESS 0 "Setting up locale..."
echo "Setting up locale..."
sudo apt-get update -q
sudo apt-get install -y -q locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
PROGRESS 8 "Adding universe repository..."
echo "Adding universe repository..."
sudo apt-get install -y -q software-properties-common
sudo add-apt-repository -y universe
PROGRESS 16 "Adding ROS2 GPG key..."
echo "Adding ROS2 GPG key..."
sudo apt-get update -q
sudo apt-get install -y -q curl
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg
PROGRESS 22 "Configuring ROS2 apt repository..."
echo "Configuring ROS2 apt repository..."
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo "$UBUNTU_CODENAME") main" \
| sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
PROGRESS 28 "Updating package lists..."
echo "Updating package lists..."
sudo apt-get update -q
sudo apt-get upgrade -y -q
PROGRESS 35 "Installing ros-jazzy-ros-base (this may take a while)..."
echo "Installing ros-jazzy-ros-base..."
sudo apt-get install -y ros-jazzy-ros-base
PROGRESS 75 "Installing ROS2 dev tools..."
echo "Installing ROS2 dev tools..."
sudo apt-get install -y ros-dev-tools
PROGRESS 88 "Initializing rosdep..."
echo "Initializing rosdep..."
sudo rosdep init 2>/dev/null || true
rosdep update
PROGRESS 100 "Done"
echo "ROS2 Jazzy (ros-base) installed successfully."