Files
2026-09-07 17:22:34 +05:00

195 lines
9.5 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.
# 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 <PATH> явный путь к конфигу (работает и внутри подкоманд)
-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 -> <base_dir>/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.<name>]``~/.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`.