167 lines
7.6 KiB
Markdown
167 lines
7.6 KiB
Markdown
# 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 .
|
||
```
|
||
|
||
Требуется Rust 1.88+. Кроссплатформенно: Windows (MSVC), Linux, macOS.
|
||
|
||
## Быстрый старт
|
||
|
||
```sh
|
||
xboct-deploy --list # показать проекты из конфига
|
||
xboct-deploy --list-servers # показать серверы из конфига
|
||
xboct-deploy --pick # интерактивно выбрать проект (нечёткий поиск)
|
||
xboct-deploy --dry-run demo # напечатать план деплоя без выполнения
|
||
xboct-deploy demo # задеплоить проект demo
|
||
xboct-deploy --this # задеплоить проект текущей папки (глобальный конфиг)
|
||
```
|
||
|
||
Вывод:
|
||
|
||
```txt
|
||
deploying 'demo'
|
||
[Local] running: npm run build... Success! (1.2s)
|
||
[SFTP] transferring dist to /var/www/pages... Done! (14 file(s), 231.5 KiB) (829ms)
|
||
[Remote] running: pm2 restart pages... Success! (102ms)
|
||
Deploy successful! (total 2.3s)
|
||
```
|
||
|
||
## Конфигурация
|
||
|
||
Файл ищется по цепочке:
|
||
|
||
1. `--config PATH`, если указан явно;
|
||
2. `./deploy.toml` в текущей папке — удобно держать рядом с проектом и в тестах;
|
||
3. глобальный `~/.config/xboct-deploy/deploy.toml` — запуск «из любой папки».
|
||
|
||
`--this` деплоит проект, чей `workdir` совпадает с текущей папкой; конфиг
|
||
ищется по обычной цепочке, так что временный локальный `./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=%XBP_DEPLOY_RESULT%"',
|
||
]
|
||
```
|
||
|
||
Внутри доступны переменные (в cmd — `%VAR%`, в sh — `$VAR`):
|
||
|
||
- `XBP_DEPLOY_RESULT` — `success` либо `failed`;
|
||
- `XBP_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 run # деплой из test/deploy.toml (в git не входит)
|
||
```
|