# xboct-deploy Легковесная CLI-утилита на Rust для централизованного деплоя хобби-проектов (Node.js, Rust) с локального ПК на слабую VPS по SSH. - сборка, тесты и линтеры выполняются **локально** — CPU VPS не тратится - артефакты передаются по **SFTP** (без rsync, работает на Windows/Linux/macOS) - сервисные команды (`pm2 restart`, `systemctl restart`) запускаются **по SSH** - fail-fast: упавший шаг немедленно останавливает деплой (ADR-005) ## Установка ```sh cargo install --path . ``` Бинарник называется **`xd`**. Требуется Rust 1.88+. Кроссплатформенно: Windows (MSVC), Linux, macOS. ## Команды ```sh xd list # показать проекты из конфига xd list-servers # показать серверы из конфига xd pick # интерактивно выбрать проект (нечёткий поиск) xd deploy my-node-app # задеплоить проект my-node-app xd deploy # задеплоить проект текущей папки (workdir == cwd) xd --dry-run deploy my-node-app # напечатать план деплоя без выполнения xd edit-config # открыть глобальный конфиг в $EDITOR xd # статус: какой проект соответствует текущей папке # (вне проекта — список проектов из конфига) ``` Глобальные флаги: ```txt -c, --config явный путь к конфигу (работает и внутри подкоманд) -g, --global использовать только глобальный конфиг (конфликтует с -c) --dry-run печать плана без выполнения; НЕ глобальный — ставится до подкоманды: `xd --dry-run deploy name` ``` `xd deploy` без имени ищет проект, чей `workdir` совпадает с текущей папкой, и падает с ошибкой, если такого нет. `xd pick` требует реальный терминал — в скриптах используйте `xd list`. Вывод деплоя: ```txt [Git] on branch 'main' ✓ deploying 'my-node-app' [Local] running: npm run build... Success! (1.2s) connecting... [SFTP] transferring dist to /var/www/pages/my-node-app... Done! (14 file(s), 231.5 KiB) (829ms) [Remote] running: pm2 restart pages... Success! (102ms) [LocalAfter] running: curl -s -X POST https://example.com/hook... Success! (45ms) Deploy successful! (total 2.3s) ``` Строка `[Git]` появляется только при заданном `branch`, `connecting...` — перед первым SSH-шагом (sync/remote), `[LocalAfter]` — шаги `local_after`. ## Конфигурация Файл ищется по цепочке: 1. `-c/--config PATH`, если указан явно (конфликтует с `-g`); 2. `./deploy.toml` в текущей папке — удобно держать рядом с проектом и в тестах; 3. глобальный `~/.config/xboct-deploy/deploy.toml` — запуск «из любой папки». Флаг `-g/--global` исключает шаг 2: используется только глобальный конфиг. Деплой проекта текущей папки (`xd deploy` без имени) ищет конфиг по этой же цепочке, так что временный локальный `./deploy.toml` переопределяет глобальный — создали файл, задеплоились, удалили. Схема — **один проект = один блок**, все параметры точечными ключами: ```toml [servers.my-vps] # профиль сервера: описывается один раз host = "195.0.2.10" # алиас из ~/.ssh/config или IP port = 2222 user = "deploy" key_path = "~/.ssh/id_ed25519" base_dir = "/var/www/pages" # корень для относительных sync target [projects.my-node-app] server = "my-vps" # ссылка на профиль SSH workdir = 'C:\code\my-node-app' # где выполнять локальные команды (~ разворачивается) branch = "main" # опционально: защита от деплоя чужой ветки env.PUBLIC_BASE_PATH = "/my-node-app" # переменные для локальной сборки local.commands = ["npm ci", "npm run build"] # шаги сборки на вашей машине sync = "dist" # залить dist -> /my-node-app remote.commands = ["pm2 restart pages"] # команды управления по SSH ``` ### Поле `sync` Три формы — выбирай по смыслу: ```toml sync = "dist" # один источник; target = имя проекта sync = ["dist", "static"] # несколько источников; target = имя проекта sync = [{ source = "web/build", target = "/opt/static" }] # полные правила ``` Во всех формах `target` без `/` на конце резолвится через `base_dir` сервера (`"my-node-app"` → `/var/www/pages/my-node-app`). Абсолютный target (`"/opt/static"`) используется как есть. Отдельных блоков `[[...sync]]`, `[projects.x.env]`, `[projects.x.local]`, `[projects.x.remote]` больше нет — это не нужно помнить и копировать, всё в блоке проекта. Проект может задать параметры подключения и напрямую (`host`, `user`, `port`, `key_path` вместо `server`) — тогда они имеют приоритет над профилем. Приоритет параметров SSH: проект → `[servers.]` → `~/.ssh/config` → дефолт. ### Локальные команды после деплоя (`local_after`) Выполняются на вашей машине **после** sync + remote и **всегда** — и при успехе, и после любого упавшего шага (deploy всё равно упадёт). Для очистки артефактов или вебхука с результатом: ```toml [projects.my-node-app] server = "my-vps" workdir = 'C:\code\my-node-app' local.commands = ["npm run build"] sync = "dist" remote.commands = ["pm2 restart pages"] [projects.my-node-app.local_after] commands = [ 'del /q dist', 'curl -s -X POST https://example.com/hook -d "result=%XD_DEPLOY_RESULT%"', ] ``` Внутри доступны переменные (в cmd — `%VAR%`, в sh — `$VAR`): - `XD_DEPLOY_RESULT` — `success` либо `failed`; - `XD_ERROR` — текст ошибки (только при `failed`). Эти же переменные доступны в `--dry-run`, где показываются в плане без выполнения. ### Windows-нюанс TOML В basic-строках `"..."` бэкслеши — escape-символы, путь `C:\Users` сломает парсинг. Для путей Windows используйте литеральные строки `'...'`: ```toml workdir = 'C:\code\my-node-app' ``` или прямые слэши `C:/code/my-node-app`. ## Аутентификация Цепочка (первый успешный способ используется): 1. **ssh-agent**: OpenSSH-агент (named pipe на Windows / `SSH_AUTH_SOCK` на Unix), затем Pageant; перебираются все ключи агента; 2. **файловый ключ** `key_path` → `identity_file` → `~/.ssh/id_rsa` (ключи с passphrase пока не поддерживаются); 3. иначе — понятная ошибка. Проверка host key — TOFU поверх вашего `~/.ssh/known_hosts`: неизвестный хост доверяется и запоминается, смена ключа сервера — громкая ошибка (MITM-защита). ## Переменные окружения (.env) Если рядом с workdir проекта есть `.env`, его переменные (KEY=VALUE, `#` комментарии, кавычки, префикс `export`) доступны всем локальным командам. `.env` не копируется на сервер, если не указан явно в `sync`. Приоритет источников: `[env]` из deploy.toml → `.env` → окружение ОС. ```env # .env NODE_ENV=production API_TOKEN="secret" export DB_URL=postgres://localhost/app ``` ## Разработка ```sh just test # fmt + clippy -D warnings + cargo test just build # cargo build just install # cargo install --path . (бинарник xd) just run # деплой проекта github-tracker из test/deploy.toml (в git не входит) ``` Схема деплоя и планы — в `docs/`, текущие задачи — в `backlog.md`.