Files
xboct-deploy/docs/archive/implementation-plan.md

177 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План реализации 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.