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)
Установка
cargo install --path .
Бинарник называется xd. Требуется Rust 1.88+. Кроссплатформенно:
Windows (MSVC), Linux, macOS.
Команды
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 # статус: какой проект соответствует текущей папке
# (вне проекта — список проектов из конфига)
Глобальные флаги:
-c, --config <PATH> явный путь к конфигу (работает и внутри подкоманд)
-g, --global использовать только глобальный конфиг (конфликтует с -c)
--dry-run печать плана без выполнения; НЕ глобальный — ставится
до подкоманды: `xd --dry-run deploy name`
xd deploy без имени ищет проект, чей workdir совпадает с текущей папкой,
и падает с ошибкой, если такого нет. xd pick требует реальный терминал —
в скриптах используйте xd list.
Вывод деплоя:
[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.
Конфигурация
Файл ищется по цепочке:
-c/--config PATH, если указан явно (конфликтует с-g);./deploy.tomlв текущей папке — удобно держать рядом с проектом и в тестах;- глобальный
~/.config/xboct-deploy/deploy.toml— запуск «из любой папки».
Флаг -g/--global исключает шаг 2: используется только глобальный конфиг.
Деплой проекта текущей папки (xd deploy без имени) ищет конфиг по этой же
цепочке, так что временный локальный ./deploy.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
Три формы — выбирай по смыслу:
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 всё равно упадёт). Для очистки артефактов или вебхука с результатом:
[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 используйте литеральные строки '...':
workdir = 'C:\code\my-node-app'
или прямые слэши C:/code/my-node-app.
Аутентификация
Цепочка (первый успешный способ используется):
- ssh-agent: OpenSSH-агент (named pipe на Windows /
SSH_AUTH_SOCKна Unix), затем Pageant; перебираются все ключи агента; - файловый ключ
key_path→identity_file→~/.ssh/id_rsa(ключи с passphrase пока не поддерживаются); - иначе — понятная ошибка.
Проверка host key — TOFU поверх вашего ~/.ssh/known_hosts: неизвестный хост
доверяется и запоминается, смена ключа сервера — громкая ошибка (MITM-защита).
Переменные окружения (.env)
Если рядом с workdir проекта есть .env, его переменные (KEY=VALUE, #
комментарии, кавычки, префикс export) доступны всем локальным командам.
.env не копируется на сервер, если не указан явно в sync.
Приоритет источников: [env] из deploy.toml → .env → окружение ОС.
# .env
NODE_ENV=production
API_TOKEN="secret"
export DB_URL=postgres://localhost/app
Разработка
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.