diff --git a/cobot/__init__.py b/cobot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cobot/cli.py b/cobot/cli.py new file mode 100644 index 0000000..291bfa5 --- /dev/null +++ b/cobot/cli.py @@ -0,0 +1,24 @@ +import argparse +import importlib +import sys + +from cobot.commands import setup as cmd_setup + + +def main(): + parser = argparse.ArgumentParser( + prog="cobot", + description="Lightweight Cobot - Cobot ROS2 Control Framework", + ) + subparsers = parser.add_subparsers(dest="command", metavar="") + subparsers.required = True + + # Регистрируем подкоманды — каждый модуль в cobot/commands/ описывает свою + _register_commands(subparsers) + + args = parser.parse_args() + args.func(args) + + +def _register_commands(subparsers): + cmd_setup.register(subparsers) diff --git a/cobot/commands/__init__.py b/cobot/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cobot/commands/setup.py b/cobot/commands/setup.py new file mode 100644 index 0000000..a9dee40 --- /dev/null +++ b/cobot/commands/setup.py @@ -0,0 +1,10 @@ +import argparse + + +def register(subparsers): + p = subparsers.add_parser("setup", help="First-time project setup") + p.set_defaults(func=run) + + +def run(args: argparse.Namespace) -> None: + print("cobot setup — TODO: implement setup logic here") diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..c070c44 --- /dev/null +++ b/install.sh @@ -0,0 +1,247 @@ +#!/bin/bash + +set -e + +export UV_NO_CONFIG=1 + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +CYAN='\033[0;36m' +NC='\033[0m' +BOLD='\033[1m' + +PYTHON_VERSION="3.11" +REPO_URL="https://gitverse.ru/daniel-robotics/lightweight-cobot.git" +INSTALL_DIR="${COBOT_INSTALL_DIR:-$HOME/.lwc/ros2_iiwa7}" + +# Определяем интерактивный режим: при запуске через curl | bash stdin не является терминалом +if [ -t 0 ]; then IS_INTERACTIVE=true; else IS_INTERACTIVE=false; fi + +log_info() { echo -e "${CYAN}[*]${NC} $1"; } +log_success() { echo -e "${GREEN}[ok]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[!]${NC} $1"; } +log_error() { echo -e "${RED}[err]${NC} $1"; exit 1; } + +print_banner() { + echo "" + echo -e "${BOLD}" + echo "┌─────────────────────────────────────────────────────────┐" + echo "│ Lightweight Cobot installer │" + echo "│ KUKA iiwa7 ROS2 Control Framework │" + echo "└─────────────────────────────────────────────────────────┘" + echo -e "${NC}" +} + +detect_os() { + case "$(uname -s)" in + Linux*) + OS="linux" + # Определяем пакетный менеджер для установки зависимостей + if command -v apt-get &>/dev/null; then PKG_MANAGER="apt" + elif command -v dnf &>/dev/null; then PKG_MANAGER="dnf" + elif command -v pacman &>/dev/null; then PKG_MANAGER="pacman" + else PKG_MANAGER="unknown" + fi + ;; + Darwin*) OS="macos" ;; + *) log_error "Unsupported OS: $(uname -s)" ;; + esac + log_info "OS: $OS" +} + +# Устанавливает системные пакеты через найденный пакетный менеджер +pkg_install() { + case "$PKG_MANAGER" in + apt) sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends "$@" ;; + dnf) sudo dnf install -y "$@" ;; + pacman) sudo pacman -S --noconfirm "$@" ;; + *) log_error "Cannot auto-install $* — unknown package manager" ;; + esac +} + +check_git() { + log_info "Checking git..." + if command -v git &>/dev/null; then + log_success "git $(git --version | awk '{print $3}') found" + return + fi + log_info "Installing git..." + case "$OS" in + linux) pkg_install git ;; + macos) xcode-select --install 2>/dev/null || brew install git ;; + esac + command -v git &>/dev/null || log_error "Failed to install git" + log_success "git $(git --version | awk '{print $3}') installed" +} + +check_docker() { + log_info "Checking Docker..." + if command -v docker &>/dev/null; then + log_success "Docker $(docker --version | awk '{print $3}' | tr -d ',') found" + return + fi + log_info "Installing Docker via get.docker.com..." + # Скачиваем установщик во временный файл, а не запускаем через pipe — + # так видны ошибки сети отдельно от ошибок самого установщика + local _installer + _installer="$(mktemp /tmp/lwc-docker.XXXXXX.sh)" + if ! curl -fsSL https://get.docker.com -o "$_installer"; then + rm -f "$_installer" + log_error "Failed to download Docker installer" + fi + sh "$_installer" + rm -f "$_installer" + command -v docker &>/dev/null || log_error "Docker installation failed" + log_success "Docker $(docker --version | awk '{print $3}' | tr -d ',') installed" + # Добавляем пользователя в группу docker, чтобы не требовался sudo + if [ "$(id -u)" -ne 0 ] && command -v usermod &>/dev/null; then + sudo usermod -aG docker "$USER" + log_warn "Added $USER to docker group — re-login to apply" + fi +} + +install_uv() { + log_info "Checking uv..." + # uv может быть установлен в ~/.local/bin или ~/.cargo/bin, проверяем оба + UV_CMD="" + for candidate in "uv" "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv"; do + if command -v "$candidate" &>/dev/null 2>&1; then + UV_CMD="$candidate" + break + fi + done + if [ -n "$UV_CMD" ]; then + log_success "uv $($UV_CMD --version | awk '{print $2}') found" + return + fi + log_info "Installing uv..." + # Два отдельных файла: лог и установщик — чтобы различать ошибки скачивания и установки + local _log _installer + _log="$(mktemp /tmp/lwc-uv.XXXXXX.log)" + _installer="$(mktemp /tmp/lwc-uv-installer.XXXXXX.sh)" + if ! curl -LsSf https://astral.sh/uv/install.sh -o "$_installer" 2>"$_log"; then + cat "$_log" >&2; rm -f "$_log" "$_installer" + log_error "Failed to download uv installer" + fi + if sh "$_installer" >>"$_log" 2>&1; then + rm -f "$_installer" + for candidate in "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv"; do + [ -x "$candidate" ] && UV_CMD="$candidate" && break + done + if [ -z "$UV_CMD" ]; then + cat "$_log" >&2; rm -f "$_log" + log_error "uv installed but binary not found" + fi + rm -f "$_log" + log_success "uv $($UV_CMD --version | awk '{print $2}') installed" + else + cat "$_log" >&2; rm -f "$_log" "$_installer" + log_error "Failed to install uv" + fi +} + +check_python() { + log_info "Checking Python $PYTHON_VERSION..." + if "$UV_CMD" python find "$PYTHON_VERSION" &>/dev/null; then + local ver + ver="$("$UV_CMD" python find "$PYTHON_VERSION" | xargs -I{} {} --version 2>&1)" + log_success "$ver found" + return + fi + # uv умеет скачивать и изолировать нужную версию Python без sudo + log_info "Installing Python $PYTHON_VERSION via uv..." + "$UV_CMD" python install "$PYTHON_VERSION" || log_error "Failed to install Python $PYTHON_VERSION" + log_success "Python $PYTHON_VERSION installed" +} + +resolve_install_dir() { + local script_dir + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || pwd)" + # Если рядом со скриптом есть setup.py — значит мы уже внутри репозитория + if [ -f "$script_dir/setup.py" ]; then + INSTALL_DIR="$script_dir" + log_info "Repo: $INSTALL_DIR" + else + # Иначе клонируем в ~/.lwc/ros2_iiwa7 + log_info "Cloning into $INSTALL_DIR..." + mkdir -p "$(dirname "$INSTALL_DIR")" + + # TODO: Убрать ветку dev и юзать main после мержа + git clone --branch dev "$REPO_URL" "$INSTALL_DIR" + log_success "Repo cloned" + fi +} + +install_cobot() { + log_info "Installing cobot CLI..." + cd "$INSTALL_DIR" + # uv tool install создаёт изолированное окружение и кладёт бинарник cobot в ~/.local/bin + "$UV_CMD" tool install --python "$PYTHON_VERSION" --editable . \ + || log_error "Failed to install cobot" + log_success "cobot installed" +} + +setup_path() { + local bin_dir="$HOME/.local/bin" + local shell_rc + case "$SHELL" in + */zsh) shell_rc="$HOME/.zshrc" ;; + */fish) shell_rc="$HOME/.config/fish/config.fish" ;; + *) shell_rc="$HOME/.bashrc" ;; + esac + # Добавляем ~/.local/bin в PATH, если его там ещё нет + if [[ ":$PATH:" != *":$bin_dir:"* ]]; then + echo "" >> "$shell_rc" + echo "export PATH=\"$bin_dir:\$PATH\"" >> "$shell_rc" + export PATH="$bin_dir:$PATH" + log_warn "Run 'source $shell_rc' or open a new terminal to use 'cobot'" + fi + command -v cobot &>/dev/null && log_success "cobot -> $(command -v cobot)" +} + +print_success() { + echo "" + echo -e "${GREEN}${BOLD}Done!${NC}" + echo "" + echo " cobot setup - first-time setup" + echo " cobot --help - list all commands" + echo "" +} + +ask_run_setup() { + # В неинтерактивном режиме (curl | bash) пропускаем вопрос + if [ "$IS_INTERACTIVE" = false ]; then + log_info "Run 'cobot setup' to configure the framework" + return + fi + + printf "Run setup now? [y/N] " + read -r answer + case "$answer" in + [yY]|[yY][eE][sS]) + cobot setup + ;; + *) + echo "" + echo "To configure the framework, run: cobot setup" + echo "" + ;; + esac +} + +main() { + print_banner + detect_os + check_git + check_docker + install_uv + check_python + resolve_install_dir + install_cobot + setup_path + print_success + ask_run_setup +} + +main diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f55c8be --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup, find_packages + +setup( + name="lightweight-cobot", + version="0.1.0", + description="Lightweight Cobot — KUKA iiwa7 ROS2 Control Framework", + packages=find_packages(), + python_requires=">=3.11", + entry_points={ + "console_scripts": [ + "cobot=cobot.cli:main", + ], + }, +)