docs: update docs, archive old
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# План реализации 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 <PROJECT> [--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<String, Project> }`. Валидация: 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 <cmd>`, Unix — `sh -c <cmd>`
|
||||
(иначе 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-сессии
|
||||
(subsystem sfp через russh + крейт russh-sftp, см. ADR-006).
|
||||
2. Одиночный файл: remote create/truncate -> запись -> shutdown; если target —
|
||||
существующий каталог, файл кладётся внутрь с локальным именем.
|
||||
3. Рекурсивная папка: walkdir по source; для каждой подпапки mkdir если её ещё нет
|
||||
(проверка metadata, аналог 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 решено не добавлять: статистика файлов/байт и тайминги
|
||||
уже дают достаточно информации, а прогресс-бар усложнил бы вывод при
|
||||
параллельных логах команд (ADR-002, минимум зависимостей).
|
||||
|
||||
Критерий приёмки: вывод визуально совпадает с примером 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.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 📑 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) на первом этапе не реализуется.
|
||||
- Обоснование: Для хобби-проекта проверка состояния системы перед каждым шагом (идемпотентность) избыточна и сильно усложнит архитектуру. Если команда упала, пользователь просто фиксит ошибку и запускает деплой заново.
|
||||
|
||||
## ADR-006: Замена ssh2 (libssh2) на russh в качестве SSH-транспорта
|
||||
|
||||
- Решение: Вместо крейта ssh2 (обёртка над libssh2) используется чисто Rust-овый russh (0.62.x, crypto-backend ring). Парсер ~/.ssh/config остаётся на ssh2-config (он не зависит от libssh2). Публичный API утилиты синхронный (ADR-003); tokio-runtime живёт внутри модуля ssh и наружу не торчит.
|
||||
- Обоснование: libssh2 не поддерживает шифр `chacha20-poly1305@openssh.com`, а свежие OpenSSH-серверы (9.x/10.x, в т.ч. дефолтные Ubuntu) часто предлагают только его — подключение падает с Session(-5). Утилита обязана работать с любым стоковым сервером без правки sshd_config. russh умеет chacha20-poly1305, aes-gcm и современные KEX (включая post-quantum гибриды).
|
||||
- Следствия: проверка host key реализована через TOFU поверх ~/.ssh/known_hosts средствами russh; ssh-agent (этап 6) доступен через встроенные agent-возможности russh; сборка не требует NASM/CMake (в отличие от aws-lc-rs дефолта russh 0.63, поэтому зафиксирован backend ring).
|
||||
|
||||
---
|
||||
|
||||
## 📈 План развития проекта (Roadmap)
|
||||
|
||||
- MVP: Парсинг TOML, последовательный запуск локальных std::process::Command, SSH-коннект по ключу, запуск удаленных команд.
|
||||
- Files: Реализация копирования одиночных файлов и рекурсивного копирования папок по SFTP.
|
||||
- UX: Добавление красивого вывода (библиотека indicatif для progress-bar или colored для цветных логов).
|
||||
- Advanced: Поддержка переменных окружения (.env), интеграция с системным ssh-agent.
|
||||
Reference in New Issue
Block a user