Для AI-агентов: markdown этой страницы — /docs-content/infra/deploy.md индекс документации — /llms.txt
Deploy API
Развёртывание приложений на BLACKHOLE-серверах без SSH. Все операции идут через агент туннеля: команды выполняются на сервере, файлы загружаются через туннель, логи читаются из системного журнала (утилита journalctl). AI-агенты должны использовать эту группу эндпоинтов для полного цикла «взять код из git → установить зависимости → запустить сервис → читать логи».
Требования: сервер в режиме BLACKHOLE, статус running, blackholeStatus: CONNECTED. В OPEN-режиме Deploy API вернёт NOT_BLACKHOLE.
Спящую отдельную виртуальную машину поднимать заранее не нужно: выкладка, команда, загрузка файла и чтение журнала будят сервер в статусе sleeping сами и ждут готовности в том же запросе — до 6.5 минут. Не уложился — приходит 503 WAKE_TIMEOUT, сервер возвращается в sleeping, повторный запрос безопасен. Разбор — Полный деплой приложения, раздел «Известные особенности».
Это контракт для Black Hole VM (
kind: "STANDALONE"). Для galaxy-приложения (kind: "GALAXY_APP") требованиеCONNECTEDне действует — деплой сам собирает контейнер. Полная модель и контракт деплоя galaxy-приложения — Galaxy-приложение.
OOM galaxy-приложения → выпуск на выделенный сервер. Если деплой galaxy-приложения падает с
502 GALAXY_APP_START_FAILEDименно по причине OOM (приложение переросло лимит памяти контейнера 512 МБ), ответ содержит структурную подсказкуerror.hintсrecoveryAction: "graduate-to-dedicated-vm". Рекомендованное действие — пересоздать приложение на отдельной виртуальной машине:POST /v1/infra/serversсplacement: "dedicated"иgraduateFrom(идентификатор упавшего galaxy-приложения, который будет удалён после создания сервера), затемPOST /v1/infra/servers/:id/deployс тем же исходным кодом. Защита от повторного создания через заголовокIdempotency-Keyна этот выпуск не распространяется — вместе сgraduateFromон вернёт400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION. Допустимо, когда политикаserverCreationпортала разрешает создание серверов и вы в пределах квоты, выделенная виртуальная машина тарифицируется (засыпает при простое). Подсказка добавляется только при причине OOM, а не при обычном краше.
Формат ответов: все эндпоинты по умолчанию отдают JSON (201/200 со {"success": true, ...}). Для /deploy и /exec доступен потоковый режим через ?stream=true — возвращается поток SSE (Server-Sent Events) с построчным прогрессом. Для AI-агентов и MCP-клиентов всегда используйте JSON (не добавляйте ?stream=true) — они не умеют разбирать SSE.
Ограничения частоты:
| Ограничение | Значение |
|---|---|
| Операций в минуту на сервер | 10 |
Одновременных exec/deploy |
1 на сервер |
Таймаут exec |
1–600 секунд (по умолчанию 300) |
Размер встроенного тела (base64 в /upload, /deploy source.content) |
96 МБ тела, около 72 МБ архива. Сверх потолка — 413 INLINE_SOURCE_TOO_LARGE |
Размер файла через URL (/upload url, /deploy source.url) |
500 МБ |
Размер архива сохранённой версии (/deploy source.versionId) |
500 МБ |
Размер multipart-архива в /deploy |
500 МБ, пока архив уходит в хранилище потоком. Иначе около 72 МБ — условия |
Скоуп: vibe:infra
Выполнить команду
POST /v1/infra/servers/:id/exec
Выполняет shell-команду на BLACKHOLE-сервере через агент туннеля. Полный стандартный вывод (stdout), поток ошибок (stderr) и код возврата передаются клиенту. По умолчанию ответ — JSON после завершения команды (рекомендуется для AI-агентов и скриптов). Если нужен построчный прогресс — передайте ?stream=true и читайте SSE-события stdout/stderr/exit. Отказ при выполнении команды приходит в потоковом режиме событием error.
Для AI-агентов и MCP-клиентов обязательно используйте JSON-режим (без ?stream=true) — SSE они разбирать не умеют. Официальный MCP-клиент платформы Вайбкод (инструмент manage_server_deploy) снимает этот параметр сам и всегда работает в JSON-режиме.
Потоковый режим даёт живой прогресс, но не увеличивает предельное время выполнения: оно одинаково в обоих режимах и равно timeout + 30 секунд, а сам timeout ограничен 600 секундами. Для работы, которая заведомо в это не укладывается, запускайте фоновую задачу — см. «Фоновые задачи» ниже.
На каких серверах работает
Тип сервера приходит в поле kind ответов GET /v1/infra/servers и GET /v1/infra/servers/:id.
kind |
Команда выполняется | Что учесть |
|---|---|---|
STANDALONE — отдельная виртуальная машина |
да | Контракт страницы действует целиком, включая workdir и env |
GALAXY_APP — приложение в галактике |
да, внутри контейнера приложения | Поля workdir и env не поддерживаются — 400 GALAXY_EXEC_NO_WORKDIR_ENV. Вместо них добавьте приставку к самой команде: cd /opt/app; FOO=bar node script.js. Ошибки во время выполнения приходят со статусом 502, а не 200 — кроме EXEC_BUSY, который приходит как 409 (см. ниже) |
GALAXY — машина, несущая контейнеры приложений |
нет — 403 GALAXY_HOST_EXEC_FORBIDDEN |
На машине стоят контейнеры разных ключей одного аккаунта Битрикс24, поэтому целью команд она не является. Выполняйте команду на приложении, по его собственному id (kind равен GALAXY_APP). Свободное место на машине смотрите в кабинете, на карточке галактики — см. Galaxy-приложение |
Параметры
| Параметр | В | Тип | Обяз. | По умолч. | Описание |
|---|---|---|---|---|---|
id |
path | string (UUID) | да | — | ID BLACKHOLE-сервера, status: running, blackholeStatus: CONNECTED |
stream |
query | string | нет | — | true — потоковый режим SSE. Для JSON-ответа параметр не передавайте: JSON возвращается по умолчанию |
Поля запроса (body)
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
command |
string | да | Shell-команда. 1–10 000 символов |
timeout |
number | нет | Таймаут выполнения в секундах: 1–600. По умолчанию 300 |
workdir |
string | нет | Рабочая директория. До 500 символов |
env |
object | нет | Переменные окружения: { "KEY": "value" }. Только строковые значения |
Примеры
curl — личный ключ
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/exec" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"command": "ls -la /opt/app", "timeout": 30}'
curl — OAuth-приложение
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/exec" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command": "npm ci --production", "workdir": "/opt/app", "timeout": 180}'
JavaScript — личный ключ
const res = await fetch(
`https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/exec`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: 'node -v',
timeout: 10,
}),
}
)
const { data } = await res.json()
console.log(`exit ${data.exitCode} за ${data.duration}s:\n${data.stdout}`)
JavaScript — OAuth-приложение
// Команда с переменными окружения и рабочей директорией
const res = await fetch(
`https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/exec`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: 'npx tsx scripts/seed.ts',
workdir: '/opt/app',
env: { DATABASE_URL: 'postgresql://localhost/mydb' },
timeout: 60,
}),
}
)
Поля ответа
| Поле | Тип | Описание |
|---|---|---|
success |
boolean | true если команда выполнилась (включая ненулевой exitCode) |
data.exitCode |
number | Код возврата процесса |
data.stdout |
string | Стандартный вывод команды |
data.stderr |
string | Поток ошибок команды |
data.duration |
number | Время выполнения в секундах |
data.truncated |
boolean | true если stdout/stderr обрезаны по размеру (5 МБ на поток) |
Пример ответа
{
"success": true,
"data": {
"exitCode": 0,
"stdout": "Linux epd65hdv07p8g89c0c06 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux\n",
"stderr": "",
"duration": 5,
"truncated": false
}
}
Пример ответа при ошибке
409 — на сервере уже выполняется другая операция. Это отказ до запуска команды, поэтому HTTP-статус отражает результат. С этим же статусом у galaxy-приложения приходит занятость общего exec-канала хоста — она возникает уже во время выполнения, но по смыслу это тоже «занято, повторите»:
{
"success": false,
"error": {
"code": "EXEC_BUSY",
"message": "Another operation is running on this server",
"retryable": true,
"retryAfter": 10
}
}
Ответ также несёт HTTP-заголовок Retry-After (в секундах, совпадает с полем retryAfter) — это короткий интервал опроса: повторяйте вызов с ним, пока блокировка не снимется. Полный верхний предел «когда лок снимется сам» лежит в error.hint.autoExpiresInSeconds.
Ошибки
Ошибки эндпоинта делятся на две группы, и отдаются они по-разному. Отказ до запуска команды приходит с настоящим HTTP-статусом. Отказ во время выполнения приходит в теле ответа — с одним исключением: занятость exec-канала (EXEC_BUSY) у galaxy-приложения приходит настоящим 409, потому что это отказ «занято», а не сбой.
Ошибки до запуска команды
| HTTP | Код | Описание |
|---|---|---|
| 400 | VALIDATION_ERROR |
Нарушена схема запроса (пустая команда, неверный таймаут, workdir длиннее 500 символов) |
| 400 | NOT_BLACKHOLE |
Сервер в режиме OPEN — Deploy API недоступен |
| 400 | COMMAND_TOO_LONG |
Команда длиннее 10 000 символов. В ответе hint: большие данные и скрипты передавайте через /upload, затем bash /путь/скрипт.sh |
| 400 | GALAXY_EXEC_NO_WORKDIR_ENV |
В команде для galaxy-приложения (kind: "GALAXY_APP") переданы workdir или env. Контейнерный запуск их не принимает — добавьте приставку к самой команде: cd /opt/app; FOO=bar node script.js |
| 401 | MISSING_API_KEY |
Не передан заголовок X-Api-Key |
| 401 | INVALID_API_KEY |
Неверный или просроченный API-ключ |
| 402 | ACCOUNT_FROZEN |
Баланс Вайбкод заморожен. Запрос отбивается до операции, спящий сервер при этом не будится — пополните баланс и повторите |
| 403 | WRONG_KEY |
Сервер существует, но вызывающему не принадлежит. Операции над содержимым приложения — выкладка, команда, загрузка файла и чтение логов — открыты по любому из трёх оснований: управляющий ключ сервера, ключ, приложение которого привязано к этому серверу (Application.serverId), либо членство в команде разработки этого сервера (обе роли, «Разработчик» и «Администратор»). Токены доступа и загрузка значка требуют именно управляющего ключа. Управление самой машиной открыто ещё и роли «Администратор» команды. В ответе — hint с восстановлением из двух шагов. Подробнее — Восстановление доступа к серверу |
| 403 | SERVER_WAKE_BLOCKED |
Сервер спит, а пробуждение запрещено платформой — завершённый пробный период или административная блокировка. Команда такой сервер не поднимает, повтор не поможет, пока запрет не снят. Разбор запрета — Разбудить сервер |
| 403 | GALAXY_HOST_EXEC_FORBIDDEN |
Команда пришла на id машины-галактики (kind: "GALAXY"). Выполняйте её на приложении, по его собственному id — см. «На каких серверах работает» выше |
| 403 | INFRA_FORBIDDEN_FOR_COWORK_KEY |
Вызов сделан ключом Cowork/Code — такой ключ работает только с данными, изменяющие операции ему закрыты. Что делать — Проектный ключ для деплоя |
| 404 | NOT_FOUND |
Сервера с таким id нет или он удалён. Сервер, который существует, но вам не принадлежит, отвечает 403 WRONG_KEY — код различает «нет такого сервера» и «нет прав на него» |
| 409 | EXEC_BUSY |
На сервере уже выполняется другая /exec или /deploy. Ответ несёт заголовок Retry-After и поля retryable: true / retryAfter (секунды) — повторяйте с этим интервалом. Либо снимите лок через /lock. Если EXEC_BUSY сохраняется после снятия лока — освободите канал принудительно через POST /v1/infra/servers/:id/unstick. У galaxy-хоста и galaxy-приложения принудительное освобождение владельцу недоступно (409 GALAXY_UNSTICK_UNSUPPORTED): exec-канал общий для всех приложений хоста — повторите через 30-60 с, при устойчивом отказе обратитесь в поддержку |
| 409 | SERVER_NOT_READY |
Сервер не готов к операции: не запущен или туннель не подключён. В ответе — поле hint с причиной и следующим шагом. Спящий сервер этот код не описывает — его платформа будит сама. Запустите остановленный сервер вызовом /start либо восстановите туннель вызовом /repair и повторите запрос. Случай «числится подключённым, но у Gateway нет живого туннеля» на этом маршруте приходит как 502 TUNNEL_NOT_FOUND (см. ниже) |
| 409 | WAKE_IN_PROGRESS |
Спящий сервер уже будит другой запрос. Дождитесь его завершения и повторите |
| 422 | VM_MISSING |
У записи сервера нет виртуальной машины у облачного провайдера, поэтому будить нечего. Удалите сервер и создайте новый |
| 502 | WAKE_FAILED |
Во время пробуждения спящего сервера он перешёл в неожиданное состояние. Повторите запрос |
| 502 | PROVIDER_ERROR |
Облачный провайдер вернул ошибку при запуске виртуальной машины спящего сервера. Туннель здесь ни при чём — вызов /repair не поможет |
| 409 | GALAXY_APP_NOT_READY |
Только у galaxy-приложения (kind: "GALAXY_APP"): платформа не определила имя его контейнера на хосте и команду не запустила. Два условия. Имя контейнера не прошло проверку — в message приходит invalid on-host name. Либо у приложения нет субдомена или связи с galaxy-хостом — тогда в message приходит Galaxy app is missing its subdomain or host link. Текущее состояние приложения — GET /v1/infra/servers/:id |
| 429 | RATE_LIMITED |
Превышен лимит 10 операций в минуту на сервер |
| 503 | WAKE_TIMEOUT |
Спящий сервер не поднялся за отведённые 6.5 минуты — машина не запустилась или туннель не подключился. Сервер возвращается в sleeping, поэтому повторный запрос безопасен. См. «Известные особенности» |
Ошибки во время выполнения
HTTP-статус не отражает результат команды. На отдельной виртуальной машине (
kind: "STANDALONE") соединение удерживается на всё время выполнения, а статус200отправляется до его начала — поэтому и успешная, и провалившаяся команда приходят с кодом200. Признак отказа — полеsuccess: falseи объектerrorв теле ответа. Проверяйтеsuccessв теле, а не HTTP-статус: клиент, который ветвится по статусу, посчитает провалившуюся команду успешной. У galaxy-приложения (kind: "GALAXY_APP") та же ошибка приходит со статусом502.
| Код | Описание |
|---|---|
EXEC_TIMEOUT |
Превышен timeout (или стандартные 300 секунд). Агент завершает процесс-группу принудительно (SIGKILL, без grace-паузы). В ответе — объект hint, его состав разобран ниже |
EXEC_FAILED |
Ошибка выполнения на агенте |
EXEC_NO_EXIT |
Поток завершился, не прислав статус выхода. Что успела сделать команда на сервере — неизвестно, поэтому это не успех: агент мог перестать отвечать, туннель — оборваться в середине команды, а очень большой вывод — переполнить канал уже после того, как команда отработала. В data возвращается накопленный к моменту обрыва вывод (stdout, stderr). Полей exitCode, duration и truncated нет — их значения платформе неизвестны. В ответе hint с путём восстановления. Только на отдельной виртуальной машине (kind: "STANDALONE"). У galaxy-приложения этот случай пока приходит как success: true с exitCode: -1 |
EXEC_BUSY |
Занят собственный exec-мьютекс агента — он уже выполняет другую команду. Отличается от платформенного лока (409 до старта): приходит уже во время выполнения, в теле как success: false (на STANDALONE — с HTTP 200, на GALAXY_APP — с 409, вместе с заголовком Retry-After и полями retryable: true / retryAfter). В ответе hint с путём восстановления: на STANDALONE — через /unstick, на GALAXY_APP — повтор и обращение в поддержку, потому что общий канал хоста владелец приложения освободить не может |
CONTAINER_NOT_READY |
Хост галактики ответил, но контейнера приложения, в котором должна была выполниться команда, на нём ещё нет: слот заведён и ни разу не выложен, выкладка идёт прямо сейчас или контейнер перезапускается. Приходит со статусом 502. В ответе hint, который ведёт к выкладке приложения, а не к хосту — хост исправен, будить и чинить его незачем. Только у galaxy-приложения (kind: "GALAXY_APP") |
Состав `hint` у `EXEC_TIMEOUT`
| Поле | Тип | Описание |
|---|---|---|
hint.reason |
string | Что произошло: команда не уложилась в отведённое время, и вся процесс-группа завершена немедленно, без grace-паузы |
hint.recovery |
string | Что делать: верхняя граница timeout — 600 секунд, для более длинных операций запускайте фоновую задачу (systemd-run --unit=<name>) и следите за ней через GET /v1/infra/servers/:id/logs с параметром ?service=<имя>, а для прогресса в пределах таймаута передайте ?stream=true |
hint.recoveryAction |
string | Указатель на документацию: docs: /docs/infra/deploy/exec |
Подсказка приходит и в JSON-ответе, и в SSE-событии error потокового режима. Такой же по составу объект hint несёт агентский EXEC_BUSY — на отдельной виртуальной машине (kind: "STANDALONE") его recoveryAction указывает на POST /v1/infra/servers/:id/unstick. У galaxy-приложения этот путь владельцу закрыт, поэтому там recoveryAction описывает повтор и обращение в поддержку.
Отказ связи с туннелем приходит в том же виде — со своим кодом в поле error.code (например TUNNEL_NOT_FOUND или GATEWAY_TIMEOUT: …). Часть gateway-кодов несёт детали после двоеточия (GATEWAY_UNREACHABLE: …, GATEWAY_TIMEOUT: …), поэтому сопоставляйте error.code по префиксу, а не по точному совпадению. В потоковом режиме (?stream=true) эти же ошибки приходят SSE-событием error. Данные события несут code и message, а у EXEC_TIMEOUT, GATEWAY_TIMEOUT и агентского EXEC_BUSY — ещё и hint. Подсказка у GATEWAY_TIMEOUT говорит главное: код возврата не получен, поэтому исход команды неизвестен и она может продолжать выполняться на сервере — не запускайте её повторно вслепую, сначала проверьте состояние. Поля success в них нет — признаком отказа служит само событие error.
Полный список общих ошибок API — Ошибки.
Известные особенности
- Спящую отдельную виртуальную машину команда будит сама. Если сервер (
kind: "STANDALONE") в статусеsleeping, платформа запускает пробуждение и ждёт его в том же запросе — до 6.5 минут, и только потом выполняет команду. Отдельный вызов/wakeне нужен, но клиентский таймаут запроса это время обязан пережить. Если сервер не поднялся, приходит503 WAKE_TIMEOUTи сервер возвращается вsleeping— команда не выполнялась, повторный запрос безопасен. - Команда исполняется через
/bin/sh. Полеcommandзапускается интерпретатором/bin/sh— это минимальный POSIX-шелл (dash), неbash. Конструкции, специфичные дляbash(set -o pipefail,[[ … ]], массивы), в нём недоступны и завершаются ошибкой. Запускайте их явно — черезcommand: "bash -c 'set -o pipefail; …'"либо файлом-скриптомcommand: "/bin/bash /opt/app/script.sh". - Блокировка на уровне сервера. Пока идёт
/execили/deploy, второй такой вызов вернёт 409EXEC_BUSY. Если предыдущий/deployбыл прерван (например, упал на шагеhealthcheckили клиент оборвал соединение по таймауту), серверный лок продолжает держаться до автоматического истечения — для/deployэто до 15 минут, для/exec— пока не истечётtimeoutкоманды плюс 90 секунд. Снять лок сразу можно вызовомDELETE /v1/infra/servers/:id/lock, после чего/execснова работает. Пересоздавать сервер не нужно. - Максимальный размер
stdout/stderr— 5 МБ на поток. Если вывод команды больше, приходитtruncated: trueи хвост обрезается. Команда при этом отработала,exitCodeкорректный. Для большого вывода перенаправляйте в файл:command: "my-cmd > /opt/app/output.log 2>&1"и потом читайте через/exec cat /opt/app/output.log. .envиз/deployне загружается автоматически. Systemd подгрузит.envпри старте сервиса, но не для разовых команд через/exec. Если нужныDATABASE_URL/ API-ключи — передавайте в полеenv:{ env: { DATABASE_URL: "..." } }.- Два таймаута в сумме дают
timeout + 30секунд. Агент убивает процесс ровно поtimeout. Gateway ждёт ещё 30 секунд на финальные события и только потом отдаётEXEC_TIMEOUT. - Поддержание соединения в JSON-режиме. Если команда выполняется дольше 15 секунд, сервер периодически шлёт пробелы в тело ответа — это предотвращает таймауты nginx и промежуточных прокси. Пробелы идут внутри уже открытого JSON-объекта, поэтому тело с первого байта выглядит как JSON (
{), а не начинается с пробелов: и строгие клиенты, и обычные парсеры читают его одинаково, снимать пробелы вручную не нужно. - Клиентский таймаут: убедитесь, что он межбайтовый, а не общий. Keepalive-пробелы сбрасывают таймауты «на тишину», поэтому классические сокет-таймауты Python (
urlopen(..., timeout=300), read-таймаутrequests) сами по себе долгую команду в JSON-режиме не оборвут. Обрывают две другие вещи. Первая — клиенты и обёртки с настоящим wall-clock-дедлайном на весь запрос (например,aiohttpсClientTimeout(total=…)или собственный дедлайн агент-фреймворка): keepalive против них бессилен. Вторая — серверный параметрtimeoutсамой команды (по умолчанию 300 секунд): по его истечении агент завершает процесс и приходитEXEC_TIMEOUTнезависимо от клиентских настроек — не путайте с обрывом на своей стороне. Практика: в Python задавайте раздельные таймауты —requests.post(url, json=body, headers=headers, timeout=(10, 60))— 10 секунд на connect и 60 на паузу между байтами. Если клиент всё же оборвал соединение, лок на сервере держится ещё доtimeout + 90секунд (EXEC_BUSYдля повторных вызовов). Команды дольше нескольких минут надёжнее запускать фоновой задачей — см. «Фоновые задачи» ниже. На спящем сервере это правило вступает в силу не сразу: пробелы идут только после пробуждения, поэтому до первого байта ответа проходит до 6.5 минут полной тишины, и рецептtimeout=(10, 60)оборвёт запрос ещё до запуска команды. Либо поднимите межбайтовый таймаут выше этого окна, либо разбудите машину заранее вызовом/wakeсwait=trueи вызывайте/execуже на готовой.
Принудительное освобождение канала
Если после DELETE /v1/infra/servers/:id/lock вызов /exec всё ещё возвращает EXEC_BUSY (а /deploy — падает с кодом DEPLOY_FAILED и сообщением «Another command is running»), значит завис exec-мьютекс на стороне агента: серверный лок снят, а канал остаётся занятым фоновым процессом. Этот случай лечит POST /v1/infra/servers/:id/unstick — там разобраны параметры, ответ и все коды отказа.
Как выбрать режим
Три режима вызова и когда каждый уместен:
| Когда | Как | Мониторинг |
|---|---|---|
| Команда укладывается в 600 секунд, прогресс не нужен | { "command": "...", "timeout": 600 } без ?stream=true — блокирующий JSON-ответ |
exitCode, stdout, stderr в теле ответа |
Команда укладывается в timeout, нужен живой прогресс |
тот же запрос с ?stream=true — SSE-события stdout / stderr / exit |
читать события по мере поступления |
| Команда дольше 600 секунд или канал не должен быть занят на всё её время | фоновая задача systemd-run --unit=<name> — /exec возвращается сразу |
GET /v1/infra/servers/:id/logs?service=<name> |
Общий дедлайн запроса на стороне клиента не ставьте меньше значения timeout команды — иначе клиент оборвёт соединение раньше, чем сервер успеет ответить. Спящий сервер добавляет к этому дедлайну до 6.5 минут на пробуждение — пример ниже рассчитан на уже запущенную машину. Пример раздельных таймаутов для Python:
import requests
r = requests.post(
f"{VIBE_URL}/v1/infra/servers/{SERVER_ID}/exec",
headers={"X-Api-Key": VIBE_API_KEY},
json={"command": "npm ci --production", "workdir": "/opt/app", "timeout": 600},
timeout=(10, 60), # 10 секунд на соединение, 60 на паузу между байтами
)
Поднять timeout у /exec шагу install не поможет — у шагов деплоя свой предел в 300 секунд, отдельный от 600 у /exec.
Важно: умолчание timeout — 300 секунд, а не 600. Потолок 600 существует, но чтобы им воспользоваться, поле надо передать явно. Команда, рассчитанная на девять минут и запущенная без timeout, будет снята на пятой.
Фоновые задачи
timeout у /exec ограничен 600 секундами, а по его истечении агент завершает всю процесс-группу принудительно (SIGKILL, без grace-паузы). Операции длиннее — установка пакетов, восстановление дампа БД, тяжёлая сборка — запускайте фоновой задачей: /exec вернётся мгновенно, канал освободится (второй вызов не упрётся в EXEC_BUSY), а процесс продолжит работать независимо от HTTP-соединения.
Рекомендуемый способ — транзиентный systemd-юнит
{ "command": "systemctl reset-failed restore-db 2>/dev/null; systemd-run --unit=restore-db /bin/bash /opt/data/restore.sh", "timeout": 30 }
Задача получает собственный cgroup и живёт независимо от exec-сессии. Управление — стандартными вызовами:
- статус:
{ "command": "systemctl is-active restore-db" }—active(идёт),inactive(завершилась успешно),failed(упала). - логи: journald подхватывает вывод юнита автоматически — читайте через
GET /v1/infra/servers/:id/logs?service=restore-db. systemctl reset-failed <имя>перед повторным запуском — иначе systemd откажет в создании юнита с именем упавшей задачи.
Лёгкий вариант — фон с редиректом вывода
{ "command": "(cd /opt/app && npm run build > /tmp/build.log 2>&1 &)", "timeout": 10 }
Редирект обоих потоков в файл обязателен — он освобождает пайпы exec-сессии. Прогресс: { "command": "tail -20 /tmp/build.log" }.
Анти-паттерн — голый `nohup`
nohup cmd & без редиректа не работает: в пайпе (а не терминале) nohup не перенаправляет вывод, фоновый процесс наследует пайп exec-сессии, агент ждёт его закрытия до самого timeout — и затем принудительно завершает всю процесс-группу, включая ваш «фоновый» процесс. Всегда добавляйте > файл 2>&1.
Секреты (пароли БД и т. п.) передавайте через поле env, а не внутри command — команды журналируются агентом (первые 200 символов). Помните также, что шаги install/preStart у /deploy имеют собственный таймаут 300 секунд — тяжёлые установки выносите в разовый /exec или фоновую задачу, а не в install-скрипт.