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

10 KiB
Raw Blame History

План реализации 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. Целевая структура

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

[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 на этапе передачи файлов.

Критерий приёмки: вывод визуально совпадает с примером 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.