Files
xboct-deploy/README.md
T
2026-09-07 17:22:34 +05:00

9.5 KiB
Raw Blame History

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.

Конфигурация

Файл ищется по цепочке:

  1. -c/--config PATH, если указан явно (конфликтует с -g);
  2. ./deploy.toml в текущей папке — удобно держать рядом с проектом и в тестах;
  3. глобальный ~/.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_RESULTsuccess либо failed;
  • XD_ERROR — текст ошибки (только при failed).

Эти же переменные доступны в --dry-run, где показываются в плане без выполнения.

Windows-нюанс TOML

В basic-строках "..." бэкслеши — escape-символы, путь C:\Users сломает парсинг. Для путей Windows используйте литеральные строки '...':

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_pathidentity_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
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.