commit b2edfb975d8ef461c67e7b69af22877512073813 Author: Ku6epXBOCTuK Date: Wed Aug 5 13:14:38 2026 +0500 docs: initial prd diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md new file mode 100644 index 0000000..7bb4bee --- /dev/null +++ b/docs/implementation-plan.md @@ -0,0 +1,172 @@ +# План реализации xboctploy + +Источник требований: `docs/pdr-adr.md` (PRD + ADR-001..005). +Статус: утверждено — конфиг локальный с фолбэком на глобальный, scope полный +(включая .env + ssh-agent), cargo-проект в корне репо. + +--- + +## 1. Зафиксированные решения + +| Вопрос | Решение | +| ------------------------ | ------------------------------------------------------------------------------------------------------- | +| Расположение deploy.toml | Сначала `./deploy.toml` из текущей папки; если не найден — глобальный `~/.config/xboctploy/deploy.toml` | +| Объём работ | Полный roadmap включительно (.env + ssh-agent) | +| Размещение проекта | Cargo-проект в корне репозитория xboctploy/ | +| Имя бинарника | `xboctploy` | +| Модель выполнения | Синхронная (ADR-003), без tokio | + +Локальный конфиг приоритетен — так тулза тестируется рядом с проектом, +а глобальный остаётся для запуска «из любой папки». + +## 2. Целевая структура + +```txt +Cargo.toml +src/ + main.rs # вход, anyhow-ошибки, коды выхода (0 успех / 1 fail-fast) + cli.rs # clap derive: xboctploy [--config PATH] [--dry-run] [--list] + config.rs # парсинг и валидация deploy.toml (serde + toml) + local.rs # локальные команды, fail-fast + ssh.rs # ssh2-config (~/.ssh/config), auth по ключу, exec удалённых команд + sync.rs # SFTP: файлы + рекурсивная загрузка папок + deploy.rs # оркестрация Local -> Sync -> Remote +docs/ + pdr-adr.md + implementation-plan.md +``` + +## 3. Схема deploy.toml + +```toml +[projects.my-node-app] +workdir = "~/code/my-node-app" # где выполняются локальные команды +host = "my-vps" # алиас из ~/.ssh/config или IP +user = "deploy" +port = 22 +key_path = "~/.ssh/id_rsa" # опционально, дефолт ~/.ssh/id_rsa + +[projects.my-node-app.local] +commands = ["npm ci", "npm run build"] + +[[projects.my-node-app.sync]] +source = "dist" # файл или папка (относительно workdir) +target = "/var/www/node-app" # абсолютный путь на VPS + +[projects.my-node-app.remote] +commands = ["pm2 restart node-app"] +``` + +В Rust: `Config { projects: HashMap }`. Валидация: host обязателен, +commands не пустые для секций, которые присутствуют; неизвестные поля — ошибка +(serde deny_unknown_fields), чтобы опечатки ловились сразу. + +## 4. Этапы реализации + +### Этап 1 — Каркас + конфиг + +1. `cargo init`, прописать зависимости (без фиксации версий): + - clap (feature derive) + - serde (feature derive), toml + - anyhow + - ssh2, ssh2-config + - colored, walkdir, home +2. src/cli.rs: подкоманда по умолчанию — имя проекта; флаги --config, --dry-run, --list. +3. src/config.rs: структуры + резолв конфига по цепочке: + `--config PATH` -> `./deploy.toml` -> `~/.config/xboctploy/deploy.toml`; + если ничего не найдено — понятная ошибка. Развёртывание `~` через крейт home. +4. `--list`: печатает имена проектов из конфига. +5. Юнит-тесты: валидный TOML, битый TOML, отсутствие файла, развёртывание путей, + приоритет локального конфига над глобальным. + +Критерий приёмки: `xboctploy --list` показывает проекты из тестового конфига; +битый конфиг даёт понятную ошибку с именем поля. + +### Этап 2 — Локальные команды + +1. src/local.rs: последовательный запуск commands в workdir. +2. Кроссплатформенность спавна: Windows — `cmd /C `, Unix — `sh -c ` + (иначе npm/npx не резолвятся в .cmd на Windows). +3. stdout/stderr наследуются в терминал; ненулевой exit code -> + немедленный Err с текстом упавшей команды (ADR-005, fail-fast). +4. --dry-run: печатает список команд без запуска. + +Критерий приёмки: на тестовом проекте `npm run build` выполняется; +падающий шаг останавливает деплой с красной ошибкой и exit code 1. + +### Этап 3 — SSH + удалённые команды + +1. src/ssh.rs: резолв хоста через ssh2-config (алиасы ~/.ssh/config: + hostname, user, port, identityfile), затем явные значения из deploy.toml + поверх конфига ssh. +2. Подключение: TCP -> handshake -> userauth_pubkey_file по ключу + (key_path или ~/.ssh/id_rsa). +3. exec удалённой команды через ChannelSession: стриминг stdout/stderr, + проверка channel.exit_status(); != 0 -> Err со stderr (fail-fast). + +Критерий приёмки: `pm2 restart node-app` (или echo) выполняется на реальной VPS +по ключу; неверный ключ/хост даёт читаемую ошибку, а не панику. + +### Этап 4 — SFTP-синхронизация + +1. src/sync.rs: открытие SFTP-сесси поверх установленной SSH-сессии. +2. Одиночный файл: sftp.open/sendfile -> запись в target. +3. Рекурсивная папка: walkdir по source; для каждой подпапки удалённый mkdir + с игнорированием ошибки "already exists" (аналог mkdir -p); файлы поверх. +4. Лог: `[SFTP] transferring dist/ -> /var/www/node-app ... Done!`, + счётчик файлов/байт. + +Критерий приёмки: dist/ целиком появляется на VPS, повторный деплой +не падает на существующих папках. + +### Этап 5 — Оркестрация + UX + +1. src/deploy.rs: пайплайн local -> sync -> remote, fail-fast между фазами. +2. Цветной вывод (colored) в формате PRD: + - зелёный [Local] ... Success! + - синий [SFTP] ... Done! + - жёлтый [Remote] ... Success! + - `Deploy successful!` / красный блок ошибки с stderr. +3. Тайминги каждого шага (Instant::now). +4. Опционально: indicatif progress-bar на этапе передачи файлов. + +Критерий приёмки: вывод визуально совпадает с примером UX из PRD; +--dry-run печатает весь план всех трёх фаз без единого действия. + +### Этап 6 — Продвинутый уровень + +1. `.env` из workdir: ручной парсер (~20 строк, KEY=VALUE, игнор комментариев), + переменные инжектятся в env локальных дочерних процессов; .env не заливается + на VPS, если не указан явно в sync. +2. ssh-agent: ssh2 agent_connect -> agent_list_identities -> userauth_agent. +3. Цепочка аутентификации: ssh-agent -> ключевой файл -> понятная ошибка. +4. Флаг переопределения конфига --config уже есть с этапа 1. + +Критерий приёмки: агент в цепочке используется первым; .env-переменные +видны внутри локальных команд сборки. + +### Этап 7 — Финализация + +1. `cargo fmt`, `cargo clippy -- -D warnings`, `cargo test`. +2. `cargo build --release`. +3. README: установка (cargo install --path .), схема конфига, примеры. +4. Smoke-тест на реальной VPS вручную (владелец): node-проект end-to-end. + +## 5. Отклонения от ADR-002 (минимальные, оправданные) + +| Крейт | Причина | +| ----------------- | ---------------------------------------------- | +| walkdir | рекурсивное копирование папок по SFTP (этап 4) | +| colored | цветные логи требуются PRD раздел 3 (этап 5) | +| home | развёртывание `~` в путях конфига | +| indicatif (опция) | progress-bar, только если понадобится | + +Всё остальное — строго по ADR-002. Асинхронность не вводим (ADR-003). + +## 6. Стратегия верификации + +- Юнит-тесты: config (парсинг, валидация, пути, приоритет локального конфига) + — этап 1; парсер .env — этап 6. +- Интеграционный smoke-тест против реальной VPS — вручную владельцем (этап 7); + при желании позже добавляется test с гейтом XBOCTPLOY_TEST_HOST. +- Линтеры на каждом этапе: cargo clippy -D warnings + cargo fmt --check. diff --git a/docs/pdr-adr.md b/docs/pdr-adr.md new file mode 100644 index 0000000..c5546b6 --- /dev/null +++ b/docs/pdr-adr.md @@ -0,0 +1,70 @@ +# 📑 PRODUCT REQUIREMENTS DOCUMENT (PRD)## 1. Цели проекта + +- Основная цель: Создать легковесную CLI-утилиту на Rust для централизованного деплоя хобби-проектов (Node.js, Rust) с локального ПК на слабую VPS по SSH. +- Зачем это нужно: Избежать дублирования скриптов деплоя в репозиториях, полностью разгрузить CPU слабой VPS (сборка только на ПК) и получить удовольствие от разработки пет-проекта. + +## 2. Ключевые функции (Scope) + +- Центральный конфиг: Все настройки всех проектов хранятся в одном локальном файле (deploy.toml). +- Локальное выполнение: Запуск команд сборки, тестов или линтеров на машине разработчика. +- Синхронизация файлов: Передача скомпилированных артефактов (папок dist, бинарников) на VPS. +- Удаленное выполнение: Запуск команд управления сервисами (pm2 restart, systemctl restart) на VPS через SSH. +- Безопасность: Использование существующего SSH-агента или приватного ключа (~/.ssh/id_rsa), никаких паролей в конфигах. +- Аварийная остановка (Fail-Fast): Если любой шаг (локальный или удаленный) падает с ошибкой, деплой немедленно прекращается. + +## 3. Пользовательский сценарий (UX) + +1. Разработчик находится в любой папке терминала. +2. Вводит команду: xboctploy my-node-app. +3. Утилита красиво (с логами и цветом) показывает прогресс: + +- 🟢 [Local] running: npm run build... Success! + - 🔵 [SFTP] transferring dist/ to /var/www/node-app... Done! + - 🟡 [Remote] running: pm2 restart node-app... Success! + - 🎉 Deploy successful! + +### Разрешение конфигурации + +Утилита ищет deploy.toml по цепочке: сначала `./deploy.toml` в текущей папке, +если не найден — глобальный `~/.config/xboctploy/deploy.toml`. Локальный файл +приоритетен для простоты тестирования на конкретном проекте. + +--- + +## 🏗️ ARCHITECTURE DECISION RECORD (ADR)## ADR-001: Выбор языка и формата конфигурации + +- Решение: Разработка на Rust, формат конфигурации — TOML. +- Обоснование: Rust обеспечивает максимальную скорость запуска CLI и безопасность работы с памятью. TOML выбран как «родной» для экосистемы Rust формат (аналогично Cargo.toml), который проще читать и писать вручную, чем YAML, и он строго типизирован в отличие от JSON. + +## ADR-002: Управление зависимостями (Крейты) + +Для минимизации кодовой базы и сохранения легковесности утверждается следующий стек библиотек: + +- clap (с feature-флагом derive) — для парсинга аргументов командной строки. +- serde + toml — для парсинга файла конфигурации в структуры Rust. +- ssh2-config + ssh2 (или async-ssh2-lite, если потребуется асинхронность) — для чтения системных настроек SSH (~/.ssh/config) и создания SSH/SFTP сессий. +- anyhow — для простой и понятной обработки ошибок без написания кастомных перечислений (enums). + +## ADR-003: Модель выполнения (Синхронная vs Асинхронная) + +- Решение: Использовать синхронную (блокирующую) модель выполнения. +- Обоснование: Деплой одного проекта происходит строго последовательно (Шаг 1 -> Шаг 2 -> Шаг 3). Асинхронность (tokio) усложнит код, увеличит размер бинарника и не даст преимуществ, так как параллельное выполнение шагов внутри одного деплоя не требуется. + +## ADR-004: Механизм передачи файлов + +- Решение: Использование протокола SFTP/SCP через встроенные возможности крейта ssh2. +- Обоснование: Отказ от вызова системного rsync через std::process::Command делает утилиту самодостаточной и независимой от того, установлен ли rsync на конкретной ОС (например, на Windows утилита отработает так же успешно, как на Linux). + +## ADR-005: Стратегия обработки ошибок и идемпотентность + +- Решение: Реализовать стратегию Fail-Fast с выводом stderr упавшей команды. Идемпотентность (как в Ansible) на первом этапе не реализуется. +- Обоснование: Для хобби-проекта проверка состояния системы перед каждым шагом (идемпотентность) избыточна и сильно усложнит архитектуру. Если команда упала, пользователь просто фиксит ошибку и запускает деплой заново. + +--- + +## 📈 План развития проекта (Roadmap) + +- MVP: Парсинг TOML, последовательный запуск локальных std::process::Command, SSH-коннект по ключу, запуск удаленных команд. +- Files: Реализация копирования одиночных файлов и рекурсивного копирования папок по SFTP. +- UX: Добавление красивого вывода (библиотека indicatif для progress-bar или colored для цветных логов). +- Advanced: Поддержка переменных окружения (.env), интеграция с системным ssh-agent.